前言 傻逼guide,就是把代码一贴然后屁话没有,我自己来讲
为什么该项目要用代理 动态代理可以帮助屏蔽复杂的网络传输细节
透明化网络通信: 动态代理可以将网络通信的细节封装在代理对象中,使得客户端和服务端的交互对于使用者来说是透明的。客户端只需要调用代理对象的方法,而无需关心底层的网络通信细节。
隐藏远程调用细节: 当使用动态代理时,调用代理对象的方法实际上会触发代理对象的invoke方法,在这个方法中可以进行远程调用的具体实现。这使得客户端可以像调用本地方法一样调用远程服务,而不必关心远程调用的底层实现。
处理网络传输过程: 代理对象可以负责将方法调用的参数序列化为网络传输的格式,并将远程调用的结果反序列化为客户端能够理解的形式。这使得客户端和服务端可以在不同的物理机器上运行,通过网络传输数据,而客户端代码无需关心数据在网络上传输的具体细节。
动态生成代理类: 动态代理允许在运行时动态生成代理类,根据接口或类的定义创建代理实例。这样就可以在不预先知道具体实现类的情况下,动态生成代理来处理实际的网络通信。这种灵活性使得动态代理适用于服务发现、负载均衡等场景。
总体而言,动态代理通过隐藏底层网络通信的复杂性,使得客户端和服务端的交互变得简单,让使用者更专注于业务逻辑而不是网络通信的细节。这样的设计有助于提高代码的可维护性和可扩展性。
回顾动态代理步骤
定义一个接口及其实现类;
自定义 InvocationHandler 并重写invoke方法,在 invoke 方法中我们会调用原生方法?其实也可以不调用原生方法(被代理类的方法)并自定义一些处理逻辑 ;代理对象会实现一个接口,并且该接口的每个方法调用都会被重定向到 invoke 方法
通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象;
本项目中的动态代理 静态变量 private static final String INTERFACE_NAME = "interfaceName"; /** * Used to send requests to the server.And there are two implementations: socket and netty */ private final RpcRequestTransport rpcRequestTransport;//用来发送request的接口,里面只有一个send方法 private final RpcServiceConfig rpcServiceConfig;//配置类 public RpcClientProxy(RpcRequestTransport rpcRequestTransport, RpcServiceConfig rpcServiceConfig) { this.rpcRequestTransport = rpcRequestTransport; this.rpcServiceConfig = rpcServiceConfig; } public RpcClientProxy(RpcRequestTransport rpcRequestTransport) { this.rpcRequestTransport = rpcRequestTransport; this.rpcServiceConfig = new RpcServiceConfig(); }
rpcServiceConfig 在config包内,就是什么类的什么方法
public class RpcServiceConfig { /** * service version */ private String version = ""; /** * when the interface has multiple implementation classes, distinguish by group */ private String group = ""; /** * target service */ private Object service; public String getRpcServiceName() { return this.getServiceName() + this.getGroup() + this.getVersion(); } public String getServiceName() { return this.service.getClass().getInterfaces()[0].getCanonicalName(); } }
接口及其实现类以及实现InvocantionHandler 本项目合在一起了
@Slf4j public class RpcClientProxy implements InvocationHandler { }
没有什么接口,就是这个类,然后实现了InvocationHandler
得到代理对象 public <T> T getProxy(Class<T> clazz) { return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class<?>[]{clazz}, this); }
通过这个工厂方法获得代理类实例
这个方法的原型:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { ...... }
这个方法一共有 3 个参数:
loader :类加载器,用于加载代理对象。
interfaces : 被代理类实现的一些接口;
h : 实现了 InvocationHandler 接口的对象;
重写invoke方法 当通过代理类调用一切方法时,实际上调用的是这个invoke
@Override public Object invoke(Object proxy, Method method, Object[] args) { log.info("invoked method: [{}]", method.getName()); RpcRequest rpcRequest = RpcRequest.builder().methodName(method.getName()) .parameters(args)//要传递给方法的参数数组 .interfaceName(method.getDeclaringClass().getName())//接口 .paramTypes(method.getParameterTypes())//与参数对应的参数类型数组 .requestId(UUID.randomUUID().toString())//唯一的请求id .group(rpcServiceConfig.getGroup())////用于处理一个接口有多个类实现的情况。 .version(rpcServiceConfig.getVersion())//版本 .build();//构建一个请求 RpcResponse<Object> rpcResponse = null; //本项目支持netty和socket两种通信方式 if (rpcRequestTransport instanceof NettyRpcClient) { //所以这部分你看,我们的代理类并没有执行原生类的方法,也只是把原来的method的信息传入到rpcRequest里,然后作为一个参数去发送请求给服务端而已 CompletableFuture<RpcResponse<Object>> completableFuture = (CompletableFuture<RpcResponse<Object>>) rpcRequestTransport.sendRpcRequest(rpcRequest); rpcResponse = completableFuture.get(); } if (rpcRequestTransport instanceof SocketRpcClient) { rpcResponse = (RpcResponse<Object>) rpcRequestTransport.sendRpcRequest(rpcRequest); } this.check(rpcResponse, rpcRequest); return rpcResponse.getData(); }
详细说说这个:
if (rpcRequestTransport instanceof NettyRpcClient) { CompletableFuture<RpcResponse<Object>> completableFuture = (CompletableFuture<RpcResponse<Object>>) rpcRequestTransport.sendRpcRequest(rpcRequest); rpcResponse = completableFuture.get(); }
使用 CompletableFuture 来异步发送 RPC 请求,然后通过 get() 方法等待异步结果返回,最终得到 rpcResponse。
但是此处用异步没有意义,因为他下面的代码还是check,还是要等response的结果
check 就是检查格式对不对,内容啥的
private void check(RpcResponse<Object> rpcResponse, RpcRequest rpcRequest) { if (rpcResponse == null) {//检查是否为空 throw new RpcException(RpcErrorMessageEnum.SERVICE_INVOCATION_FAILURE, INTERFACE_NAME + ":" + rpcRequest.getInterfaceName()); } if (!rpcRequest.getRequestId().equals(rpcResponse.getRequestId())) {//是否是对应请求的响应 throw new RpcException(RpcErrorMessageEnum.REQUEST_NOT_MATCH_RESPONSE, INTERFACE_NAME + ":" + rpcRequest.getInterfaceName()); } if (rpcResponse.getCode() == null || !rpcResponse.getCode().equals(RpcResponseCodeEnum.SUCCESS.getCode())) {//是否是成功请求 throw new RpcException(RpcErrorMessageEnum.SERVICE_INVOCATION_FAILURE, INTERFACE_NAME + ":" + rpcRequest.getInterfaceName()); } }
本项目中实际的使用 1. SocketClientMain public class SocketClientMain { public static void main(String[] args) { RpcRequestTransport rpcRequestTransport = new SocketRpcClient(); RpcServiceConfig rpcServiceConfig = new RpcServiceConfig(); RpcClientProxy rpcClientProxy = new RpcClientProxy(rpcRequestTransport, rpcServiceConfig); HelloService helloService = rpcClientProxy.getProxy(HelloService.class); String hello = helloService.hello(new Hello("111", "222")); System.out.println(hello); } }
String hello = helloService.hello(new Hello("111", "222"));:通过代理对象调用了 HelloService 接口的 hello 方法,实际上是通过动态代理发送了 RPC 请求。
理论上来说,这里把RpcRequestTransport rpcRequestTransport = new SocketRpcClient();换成new NettyRpcClient也是有用的,但我没试
2.又和注入有关了 来看 postProcessAfterInitialization 这段代码
@Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { Class<?> targetClass = bean.getClass(); Field[] declaredFields = targetClass.getDeclaredFields(); for (Field declaredField : declaredFields) { RpcReference rpcReference = declaredField.getAnnotation(RpcReference.class); if (rpcReference != null) { RpcServiceConfig rpcServiceConfig = RpcServiceConfig.builder() .group(rpcReference.group()) .version(rpcReference.version()).build(); RpcClientProxy rpcClientProxy = new RpcClientProxy(rpcClient, rpcServiceConfig); Object clientProxy = rpcClientProxy.getProxy(declaredField.getType()); declaredField.setAccessible(true); try { declaredField.set(bean, clientProxy); } catch (IllegalAccessException e) { e.printStackTrace(); } } } return bean; }
看这里
RpcClientProxy rpcClientProxy = new RpcClientProxy(rpcClient, rpcServiceConfig); Object clientProxy = rpcClientProxy.getProxy(declaredField.getType()); declaredField.setAccessible(true); try { declaredField.set(bean, clientProxy);
这部分代码的意思呢,就是给带有RpcReference注解的类,自动注入一个其参数的代理对象
比如我们的HelloController
@Component public class HelloController { @RpcReference(version = "version1", group = "test1") private HelloService helloService; public void test() throws InterruptedException { String hello = this.helloService.hello(new Hello("111", "222")); //如需使用 assert 断言,需要在 VM options 添加参数:-ea assert "Hello description is 222".equals(hello); Thread.sleep(12000); for (int i = 0; i < 10; i++) { System.out.println(helloService.hello(new Hello("111", "222"))); } } }
流程是什么呢?
在实例化 bean 之后,postProcessAfterInitialization 方法被调用。
对于每个 bean 类的字段,检查是否标记了 @RpcReference 注解。
如果发现标记了 @RpcReference 注解的字段,根据注解中的信息创建相应的 RpcServiceConfig 对象。
通过 RpcClientProxy 创建了一个代理对象 clientProxy,这个代理对象实现了 declaredField.getType() 所表示的接口,这个接口可能就是 HelloService 接口或其它接口,取决于 helloService 字段的类型。
这个代理对象 clientProxy 会在方法调用时委托给 RpcClientProxy 的 invoke 方法,该方法负责处理远程调用的逻辑。
通过反射设置 clientProxy 到 HelloController 类的 helloService 字段上,替换了原来的字段值。
这下懂了吧