前言
傻逼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
字段上,替换了原来的字段值。
这下懂了吧