实现一个自己的RPC框架1.0——知识储备

前言


在实现一个自己的RPC框架之前,需要先了解动态代理和反射,反射可以参考这篇文章 java反射。动态代理和反射可以说是RPC的精髓所在,正是通过它们,才能使远程调用“本地化”。代理模式是基本的设计模式之一,其中动态代理更为重要。代理模式的目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。

静态代理模式

1、基本设计思想:一个静态的代理模式包括几个部分:抽象类、委托类,代理类,其中委托类和代理类都需要实现抽象类(比如下面的HelloService类)的接口。在main函数里面,我们调用的是代理类的方法。

使用静态代理模式有以下优点:

  • 1、代理对象存在的价值主要用于拦截对真实业务对象的访问;
  • 2、代理对象具有和目标对象(真实业务对象)实现共同的接口或继承于同一个类;
  • 3、代理对象是对目标对象的增强,以便对消息进行预处理和后处理。

2、一个实现的demo

抽象类HelloService

1
2
3
4
public interface HelloService {
String hello(String name);
String hi(String msg);
}

委托类HelloServiceImpl

1
2
3
4
5
6
7
8
9
10
public class HelloServiceImpl implements HelloService{
@Override
public String hello(String name) {
return "Hello" + name;
}
@Override
public String hi(String msg) {
return "Hi" + msg;
}
}

代理类HelloServiceProxy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class HelloServiceProxy implements HelloService {
public HelloService helloService;

public HelloServiceProxy(HelloService helloService) {
this.helloService = helloService;
}

@Override
public String hello(String name) {
System.out.println("预处理...");
String result = helloService.hello(name);
System.out.println(result);
System.out.println("处理后...");
return result;
}

@Override
public String hi(String msg) {
System.out.println("预处理...");
String result = helloService.hi(msg);
System.out.println(result);
System.out.println("处理后...");
return result;
}
}

主函数

1
2
3
4
5
6
public static void main(String[] args) {
HelloService helloService = new HelloServiceImpl();
HelloServiceProxy helloServiceProxy = new HelloServiceProxy(helloService);
helloServiceProxy.hello("abc");
helloServiceProxy.hi("cba");
}

通过调用代理类的方法,相当于对委托类进行了一个前后的消息预处理,理解起来也比较容易。但是静态代理模式有一个很大的弊端,我们会发现有一个接口需要被代理,就需要有一个代理类,十分麻烦,这时候就需要动态代理的出现。

动态代理模式

动态代理模式主要有两种方式,分别是:JDK动态代理和CGlib动态代理,这边由于我后面RPC的实现是用JDK代理的,先来介绍一下。

JDK动态代理

JDK代理通过实现InvocationHandler接口,重写invoke方法得以实现。通过该方法实现对委托类的代理的访问,是代理类完整逻辑的集中体现,包括要切入的增强逻辑和进行反射执行的真实业务逻辑。其中抽象类和委托类和之前相同。

实现InvocationHandler接口,并重写invoke方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MyInvocationHandler implements InvocationHandler {
// 真实业务对象
private Object target;

public MyInvocationHandler(Object target) {
this.target = target;
}
/**
* 该方法代理类完整逻辑的集中体现。第一个参数既是代理类实例,第二个参数是被调用的方法对象,
* 第三个方法是调用参数。通常通过反射完成对具体角色业务逻辑的调用,并对其进行增强。
* */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 增强逻辑
System.out.println("PROXY : " + proxy.getClass().getName());
// 反射调用,目标方法
Object result = method.invoke(target, args);
// 增强逻辑
System.out.println(method.getName() + " : " + result);
return result;
}
}

main函数实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
HelloService helloService = new HelloServiceImpl();

// 生成代理类的class对象 该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
Class<?> clazz = Proxy.getProxyClass(helloService.getClass().getClassLoader(), helloService
.getClass().getInterfaces());
// 创建InvocationHandler
InvocationHandler myInvocationHandler = new MyInvocationHandler(helloService);
// 获取代理类的构造器对象
Constructor constructor = clazz.getConstructor(new Class[] {InvocationHandler.class});
// 反射创建代理对象 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
HelloService proxy = (HelloService)constructor.newInstance(myInvocationHandler);

/***
* 上面那部分内容也可以简写成以下部分
*/
// HelloService proxy = (HelloService)Proxy.newProxyInstance(HelloService.class.getClassLoader(),
// helloService.getClass().getInterfaces(), new MyInvocationHandler(helloService));

proxy.hello("abc");
proxy.hi("cba");
}

在调用proxy的hello方法的时候实际上调用的是代理类的方法,也就是重写的invoke,其中invoke中的method这一参数就是委托类中的hello方法。

CGLIB动态代理

CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。

首先是一个目标类,它并不需要实现接口

1
2
3
4
5
6
7
8
9
public class Dog{
final public void run(String name) {
System.out.println("狗"+name+"----run");
}

public void eat() {
System.out.println("狗----eat");
}
}

接着需要通过实现MethodInterceptor接口,并重写intercept方法来实现代理类

1
2
3
4
5
6
7
8
9
public class MyMethodInterceptor implements MethodInterceptor{
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("这里是对目标类进行增强!!!");
//注意这里的方法调用,不是用反射哦!!!
Object object = proxy.invokeSuper(obj, args);
return object;
}
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class CgLibProxy {
public static void main(String[] args) {
//在指定目录下生成动态代理类,我们可以反编译看一下里面到底是一些什么东西
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\java\\java_workapace");

//创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
Enhancer enhancer = new Enhancer();
//设置目标类的字节码文件
enhancer.setSuperclass(Dog.class);
//设置回调函数
enhancer.setCallback(new MyMethodInterceptor());

//这里的creat方法就是正式创建代理类
Dog proxyDog = (Dog)enhancer.create();
//调用代理类的eat方法
proxyDog.eat();
}
}

最后再来简单总结一下CGLib的具体实现步骤:

1.首先实例化出一个Enhance对象,并设置所需的参数,然后再enhancer.create() 创建出代理对象。

2.调用代理对象的方法,比如说有一个eat()方法,这样会进入到方法拦截器的intercept()方法中,在这个方法中会调用一个proxy.invokeSuper()方法。

3.invokeSuper中是通过FastClass机制来调用目标类的方法。FastClass机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法

最后我们总结一下JDK动态代理和Gglib动态代理的区别:

1.JDK动态代理是实现了被代理对象的接口,Cglib是继承了被代理对象。

2.JDK和Cglib都是在运行期生成字节码,JDK是直接写Class字节码,Cglib使用ASM框架写Class字节码,Cglib代理实现更复杂,生成代理类比JDK效率低。

3.JDK调用代理方法,是通过反射机制调用,Cglib是通过FastClass机制直接调用方法,Cglib执行效率更高。