浅析Cglib动态代理

介绍

我们知道JDK动态代理只能代理实现了接口的类,没有实现接口的类是无法通过JDK来代理的。

Cglib是针对类来实现代理的,它的原理是对指定的目标类生成一个子类,这个子类覆盖目标类的方法并在其中实现方法的增强。因为采用了继承和重写,因此不能对final的类、final方法进行代理。

Cglib底层其实是借助了ASM这个非常强大的Java字节码生成框架,它可以在运行时对字节码进行修改或动态生成。

实现

目标对象
1
2
3
4
5
public class CglibTest {
public String say (String s) {
return "你好:" + s;
}
}
MethodInterceptor的实现类
1
2
3
4
5
6
7
8
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("调用方法前.....");
methodProxy.invokeSuper(o, objects); //调用目标对象的方法
System.out.println("调用方法后.....");
return o;
}

intercept() 方法中有4个参数:

  • o:表示目标对象
  • method:目标方法
  • objects:参数列表
  • methodProxy:表示方法的代理。invokeSuper()表示调用目标对象的方法。

实现intercept()方法有点类似于实现JDK动态代理中InvocationHandler接口中的invoke()方法,都是在该方法中添加需要被代理的逻辑。

生成代理对象
1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy(); //MethodInterceptor的实现类
Enhancer enhancer = new Enhancer(); //借助Enhancer对象
enhancer.setSuperclass(CglibTarget.class); //目标对象字节码
enhancer.setCallback(proxy); //设置回调即MethodInterceptor的实现类
CglibTarget cglibTarget = (CglibTarget) enhancer.create(); //生成代理对象
cglibTarget.say("lebron"); //调用目标方法
}

输出:

1
2
3
调用方法前.....
你好:lebron
调用方法后.....

简单描述Cglib代理过程:

通过Cglib的Enhancer来指定要代理的目标对象(传入的是目标对象class对象)、实际处理代理逻辑的对象(MethodInterceptor实现类),最终通过调用create()方法得到代理对象。

当代理对象调用目标方法时,都会转发给MethodInterceptor.intercept()方法,在intercept()方法里我们可以加入任何代理逻辑,比如修改方法参数,加入日志功能、安全检查功能等;在intercept()方法中,通过调用MethodProxy.invokeSuper()方法,我们将调用转发给原始目标对象,在本例中也就是CglibTarget的具体方法。

问题:如何针对不同的方法使用不同的代理逻辑?

这里只要增加相应方法的MethodInterceptor 的实现类以及实现一个回调过滤器CallbackFilter即可完成。

在目标对象中增加一个 record() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
public class CglibTarget {
public void say (String s) {
System.out.println( "你好:" + s);
}
public void record (String s) {
System.out.println( "功能:" + s);
}
}

MethodInterceptor实现类:针对调用 record() 方法的拦截,目的是记录方法调用前后的时间

1
2
3
4
5
6
7
8
9
10
11
12
public class CglibProxy2 implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("当前时间: " + System.currentTimeMillis());
methodProxy.invokeSuper(o, objects); //调用目标对象的方法
System.out.println("运行结束时间: " + System.currentTimeMillis());
return o;
}
}

拦截过滤器CallbackFilter:CallbackFilter的accept()方法返回的数值表示的是Callback[]数组的索引,Callback[]数组中的元素就是定义好的MethodInterceptor实现类。

1
2
3
4
5
6
7
8
9
10
11
12
public class ProxyFilter implements CallbackFilter {
@Override
public int accept(Method method) {
if ("say".equals(method.getName())) {
return 0;
}
return 1;
}
}

如果调用的是 say()方法,那么返回0,表示采用Callback[0]也就是第一个MethodInterceptor实现类来拦截 say()方法;

main方法测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy(); //MethodInterceptor的实现类1
CglibProxy2 proxy2 = new CglibProxy2(); //MethodInterceptor的实现类2
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(CglibTarget.class); //目标对象字节码
enhancer.setCallbacks(new Callback[]{proxy, proxy2}); //设置回调即MethodInterceptor的实现类
enhancer.setCallbackFilter(new ProxyFilter()); //设置方法顺序?
CglibTarget cglibTarget = (CglibTarget) enhancer.create(); //生成代理对象
cglibTarget.say("lebron");
cglibTarget.record("记录时间");
}

输出:

1
2
3
4
5
6
调用方法前.....
你好:lebron
调用方法后.....
当前时间: 1532182370942
功能:记录时间
运行结束时间: 1532182370942

可见,针对say() 和 record() 这两个不同的方法实现了不同的代理逻辑。

针对不同的方法,可以定义不同的MethodInterceptor,来实现不同的代理逻辑。

Cglib动态代理的缺点

  1. 对于final类、static、private、final方法,无法进行代理。
  2. 由于Cglib的大部分类是直接对Java字节码进行操作,这样生成的类会在Java的永久代中。如果动态代理操作过多,容易造成永久代满,触发OOM异常。

Cglib与JDK动态代理的区别

  1. JDK动态代理只能针对接口(接口中的方法)进行代理,不能对普通的类进行代理(因为生成的代理类的父类为Proxy,Java不允许多重继承);Cglib能够代理普通类(除final类final方法),无论有没有实现接口(如果传入的是接口,那么就实现接口);
  2. JDK动态代理生成的代理类是实现目标接口的类;Cglib动态代理生成的代理类是目标类的子类
  3. JDK动态代理采用反射机制调用目标类的方法;Cglib采用类似索引的方式直接调用目标类方法(Cglib的FastClass机制:FastClass对Class对象进行特别的处理,通过数组保存method的引用,每次调用方法的时候都是通过一个index下标来保持对方法的引用。),执行效率较高;
  4. Cglib代理需要引入额外的包;