RPC项目升级笔记(一):反射、动态代理

262 阅读3分钟

Request和Response的封装这里就不写啦🤨

先说说反射

  1. 反射即Reflection,Java的反射是指程序在运行期间可以拿到一个对象的所有信息。 为什么说是运行期间呢,因为正常情况,如果我们要想运行一个对象的方法,那我们就需要传入这个对象的实例,不然无法编译成功
  2. 反射是为了解决在运行期,对某个实例一无所知的情况下,如何调用其方法

反射在RPCServer的应用

核心代码如下:

// 反射调用对应方法
Method method = userService.getClass().getMethod(request.getMethodName(), request.getParamsTypes());
Object invoke = method.invoke(userService, request.getParams());
  1. JVM为每个加载的classinterface创建了对应的Class实例来保存classinterface的所有信息。获取一个class对应的Class实例后,就可以获取该class的所有信息。通过Class实例获取class信息的方法称为反射(Reflection)。

  2. userService是一个实例,使用getClass()方法获取对应的Class实例。再通过getMethod(name, Class...):获取某个publicMethod,而参数就是我们从服务器发来request中封装的方法名,以及参数类型。

  3. 然后对Method实例调用invoke就相当于调用该方法,invoke的第一个参数是对象实例(在这里就是userService),后面的可变参数要与方法参数一致(在这里我们使用服务器封装的request获取方法参数)。

动态代理在RPCCLient的应用

RPCCLient代码如下:

public class RPCClient {
    public static void main(String[] args) {

        ClientProxy clientProxy = new ClientProxy("127.0.0.1", 8899);
        UserService proxy = clientProxy.getProxy(UserService.class);

        // 服务的方法1
        User userByUserId = proxy.getUserByUserId(10);
        System.out.println("从服务端得到的user为:" + userByUserId);
        // 服务的方法2
        User user = User.builder().userName("张三").id(100).sex(true).build();
        Integer integer = proxy.insertUserId(user);
        System.out.println("向服务端插入数据:"+integer);
    }
}
  1. 使用Java标准库提供的动态代理功能,我们在运行期动态创建了UserService这一接口的实例。
  2. 动态代理是通过Proxy创建代理对象,即使用方法getProxy(UserService.class)获取接口UserService的代理对象。
  3. 然后将接口方法“代理”给InvocationHandler完成的。
  • 在运行期动态创建一个interface实例的方法如下:
  1. 定义一个InvocationHandler实例,即文中clientProxy,它负责实现接口的方法调用;

ClientProxy代码如下:

@AllArgsConstructor
public class ClientProxy implements InvocationHandler {
    // 传入参数Service接口的class对象,反射封装成一个request
    private String host;
    private int port;


    // jdk 动态代理, 每一次代理对象调用方法,会经过此方法增强(反射获取request对象,socket发送至客户端)
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // request的构建,使用了lombok中的builder,代码简洁
        RPCRequest request = RPCRequest.builder().interfaceName(method.getDeclaringClass().getName())
                .methodName(method.getName())
                .params(args).paramsTypes(method.getParameterTypes()).build();
        // 数据传输
        RPCResponse response = IOClient.sendRequest(host, port, request);
        //System.out.println(response);
        return response.getData();
    }
    <T>T getProxy(Class<T> clazz){
        Object o = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, this);
        return (T)o;
    }
}
  1. 通过Proxy.newProxyInstance()创建interface实例,在这里我用了getProxy()封装,它需要3个参数:

    1. 使用的ClassLoader,通常就是接口类的ClassLoader
    2. 需要实现的接口数组(在这里我只放了一个接口,即UserService.class),至少需要传入一个接口进去;
    3. 用来处理接口方法调用的InvocationHandler实例,在这里由于方法写在InvocationHandler实例内部,那便是this
<T>T getProxy(Class<T> clazz){ 
   Object o = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, this);
   return (T)o; 
}

  1. 将返回的Object强制转型为接口。
UserService userService = clientProxy.getProxy(UserService.class);

动态代理实际上是JVM在运行期动态创建class字节码并加载的过程, 感觉到很神奇是因为JVM帮我们自动编写了一个实现类(不需要源码,可以直接生成字节码),并不存在可以直接实例化接口的黑魔法!!😁 大概如下

public class UserServiceDynamicProxy implements UserService {
    InvocationHandler handler;
    public UserServiceDynamicProxy(InvocationHandler handler) {
        this.handler = handler;
    }
    public void getUserById(Integer id) {
        handler.invoke(
           this,
           UserService.class.getMethod("getUserById", Integer.class),
           new Object[] { name });
    }
}

总之,Java标准库提供了动态代理功能,允许在运行期动态创建一个接口的实例,动态代理是通过Proxy创建代理对象,然后将接口方法“代理”给InvocationHandler完成的。

  • 参考资料

www.liaoxuefeng.com/wiki/125259…