java动态代理

121 阅读4分钟

前言

动态代理:程序运行过程中动态地创建代理类和对象的技术。通过动态代理,我们可以在不修改源代码的情况下,在方法执行前后加入一些附加操作,如:日志记录、性能统计、事务管理等。

Java中,动态代理两种实现:

  1. 基于接口动态代理(JDK动态代理):使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来创建代理对象。
  2. 基于继承动态代理(CGLIB动态代理)。

基于接口的动态代理(JDK动态代理)

基于Java反射机制。

// 接口
public interface Calculator {
    int add(int a,int b);
}
// 实现接口
public class CalculatorImpl implements Calculator{
    @Override
    public int add(int a, int b) {
        return a+b;
    }
}
// 实现InvocationHandler接口
public class LoggingInvocationHandler implements InvocationHandler {
    private Object target;

    public LoggingInvocationHandler(Object target){
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(target, args);
        System.out.println("method:"+method.getName());
        return result;
    }
}

测试调用:

// 实现类
val calculator = CalculatorImpl()
// 代理类:传入classLoader,interfaces,具体代理类
val proxy = Proxy.newProxyInstance(
    calculator.javaClass.classLoader,
    calculator.javaClass.interfaces,
    LoggingInvocationHandler(calculator)
) as Calculator
// 通过代理类调用实现类的方法
val result = proxy.add(2, 3)
println("result:$result")

CGLIB

基于字节码生成库。

  1. 引入CGLIB库
implementation 'cglib:cglib:3.3.0'
  1. 创建目标类:创建一个普通的Java类作为被代理的目标类。
  2. 创建拦截器(Interceptor)类:实现对目标类方法拦截和增强逻辑。
  3. 使用Enhancer生成代理对象

net.sf.cglib.proxy.Enhancer:增强类,通过字节码动态创建委托类的子类实现。不能对final类进行代理操作。Hibernate不能持久化final class的原因。

// 创建被代理类
public class SayHello {
    public void say(){
        System.out.println("hello");
    }
}

/**
 *代理类 
 */
public class ProxyCglib implements MethodInterceptor{
     private Enhancer enhancer = new Enhancer();  
     public Object getProxy(Class clazz){  
          //设置需要创建子类的类  
          enhancer.setSuperclass(clazz);  
          enhancer.setCallback(this);  
          //通过字节码技术动态创建子类实例  
          return enhancer.create();  
     }  

     //实现MethodInterceptor接口方法  
     public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {  
          System.out.println("可以在调用实际方法前做一些事情");  
          //通过代理类调用父类中的方法  
          Object result = proxy.invokeSuper(obj, args);  
          System.out.println("可以在调用实际方法后做一些事情");  
          return result;  
     } 
}
// 测试
public class Mytest {

    public static void main(String[] args) {
          ProxyCglib proxy = new ProxyCglib();  
          //通过生成子类的方式创建代理类  
          SayHello proxyImp = (SayHello)proxy.getProxy(SayHello.class);  
          proxyImp.say();  
    }
}

JDK和CGLib动态代理对比

JDK动态代理实现了被代理对象所实现的接口,CGLib是继承了被代理对象。JDK和CGLib都是在运行期生成字节码,JDK是直接写Class字节码,CGLib使用ASM框架写Class字节码,CGlib代理实现更复杂,生成代理类的效率比JDK代理低。

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

原理区别: java动态代理利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。核心实现InvocationHandler接口,使用invoke()方法进行面向切面的处理。

CGLib动态代理利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。核心是实现MethodInterceptor接口,使用intercept()方法进行面向切面的处理。

  1. 如果目标对象实现了接口,默认情况下会采用JDK动态代理实现AOP
  2. 如果目标对象实现了接口,可以强制使用CGLib实现AOP
  3. 如果目标对象没有实现接口,必须采用CGLib库,spring会自动在JDK动态代理和CGLib之间切换。
类型机制回调方式适用场景效率
JDK动态代理委托机制,代理类和目标实现了同样的接口,InvocationHandler持有目标类,代理类委托,InvocationHandler去调用目标类的原始方法反射目标类是接口类反射调用稍慢
CGLib动态代理继承机制,代理类继承了目标类并重写了目标方法,通过回调函数MethodInterceptor调用父类方法执行原始逻辑通过FastClass方法索引调用非接口类、非final类,非final方法第一次调用因为哟啊生成多个Class对象,比JDK方式慢。多次调用方法索引比反射快,如果方法过多,switch case过多效率需要测试

静态代理和动态代理区别

  • 静态代理只通过手动完成代理操作,如:被代理类增加新方法,代理类需要同步新增,违背开闭原则
  • 动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则
  • 若动态代理要对目标类的增强逻辑扩展,结合策略模式,只需要新增策略类便可完成,无需修改代理类的代码。