动态代理番外篇

2017-07-16
OPEN SOURCE

在上一篇文章中说到动态代理的实现除了JDK还有第三方的实现。现在就来瞧瞧JDK之外的动态代理的实现。

Cglib

这个库在Github上有1390颗星星,下面是对Cglib的描述。

cglib is a powerful, high performance and quality Code Generation Library, It is used to extend JAVA classes and implements interfaces at runtime.

说到使用这个库的框架,其中大名鼎鼎的Hibernate就用到了,当然还有Spring。 Spring的AOP默认使用JDKProxy,如果被代理的类没有实现接口就使用Cglib去生成动态代理类。

它的出现是为了弥补JDK动态代理中不能对未实现接口的类进行代理。其原理也可想而知了,无非就是动态生成一个子类,这个子类继承了要代理的类,然后去重写父类(需要代理的类)的方法。这样的话,代理类就一定不能是final类型了,需要代理的方法也不能是final。因为Java不允许继承final类,不允许重写final方法。
下面通过一个实例来介绍Cglib实现动态代理。

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 需要被代理的目标类
*/
public class TargetClass {
public String hello(String name){
return "hello ,"+ name;
}
@Override
public String toString() {
return "TargetClass{}";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 目标对象拦截器,实现MethodInterceptor
*/
public class TargetIncerceptor implements MethodInterceptor {
/**
* 重写方法拦截在方法前和方法后加入业务
* @param o 为目标对象
* @param method 为目标方法
* @param objects 为参数
* @param methodProxy CGlib方法代理对象
* @return
* @throws Throwable
*/
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("调用前");
Object result = methodProxy.invokeSuper(o, objects);
System.out.println(" 调用后"+result);
return result;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class CglibCase {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TargetClass.class);
enhancer.setCallback(new TargetIncerceptor());
TargetClass targetClass = (TargetClass) enhancer.create();
String hello = targetClass.hello("dw");
System.out.println(hello);
}
}

从表面上来看和JDK的使用还是有类似的地方的。Cglib使用Enhancer来去装载父类,将方法拦截器植入到生成的子类字节码中,最后创建对象。Cgilb依赖ASM字节码框架去动态生成字节码,具体生成出来的字节码文件和JDK动态代理生成出来的差别很大,区别就在于Cglib生成的代理类没有使用反射去调用要被代理的方法。从这里就可以看出Cglib在执行效率上要比JDK动态代理要高,毕竟反射效率是很低的。

Byte Buddy

这个类库是最近看一个RPC框架代码的时候发现的。起因是自己想尝试着写一个简单的远程方法调用的样例程序,在Github上找到了一个简单的基于Netty的实现,其中在处理动态代理的时候除了使用JDK的实现还有一个额外的实现,那就是Byte Buddy。然后稍微研究了一番,发现使用这个类库写动态代理确实很方便。

废话不多讲,先简单的看代码实现。

1
2
3
4
5
6
//定义接口
public interface Hello {
String sayHi(String name);
String sayHi(String name,int time);
void sayHi(int i);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//实现类
public class HelloImpl implements Hello {
public String sayHi(String name) {
return "Hello "+name;
}
public String sayHi(String name, int time) {
return time+" Hello "+name ;
}
public void sayHi(int i) {
System.out.println("nothing"+i);
}
}
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
26
27
28
29
30
31
32
public class ByteBuddyProxy {
//创建代理对象
public <T> T getInstance(Class<T> clazz,Object handler){
Class<? extends T> cls = new ByteBuddy()
.subclass(clazz)
.method(ElementMatchers.isDeclaredBy(clazz))
.intercept(MethodDelegation.to(handler))
.make()
.load(clazz.getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
.getLoaded();
T t = null;
try {
t = cls.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return t;
}
public static void main(String[] args) {
ByteBuddyProxy proxy = new ByteBuddyProxy();
Hello hello = proxy.getInstance(Hello.class, new Invoker(HelloImpl.class));
hello.sayHi(1);
String sayHi = hello.sayHi("dongwei",9);
System.out.println(sayHi);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
//暂时理解为和JDK Proxy中的InvocationHandler一样的东西吧
public class Invoker{
private Class<?> clazz;
public Invoker(Class<?> clazz){
this.clazz = clazz;
}
@RuntimeType
public Object invoke(@Origin Method method, @AllArguments @RuntimeType Object[] args) throws Throwable {
Object result = method.invoke(clazz.newInstance(),args);
return result;
}
}

这只是一个很简单的小栗子,还有很多复杂的特性没有深入研究。我只能对写这个库的作者表示很崇高的敬佩!

小结

当然除了文章中所介绍的生成动态代理的工具还有很多没有提及,比方说javaassit等。其原理大致类似,无非就是在JVM加载字节码的时候将字节码给替换了或者改掉了,很多都依赖于ASM库,如果对class字节码规范很熟悉的话自己也可以使用ASM来写一个字节码生成工具。
从阅读别人写的RPC框架的源码中发现了很多有意思的东西,从其中的动态代理可以了解到字节码生成加载。慢慢才发现写代码真的不是想象中那么简单的,变化的东西太多了,也只有时刻保持着学习的态度也才可能跟得上节奏,才不会被淘汰。


留言: