动态代理

动态代理主要有两种方式:

  1. jdk动态代理
  2. CGLIB动态代理

JDK动态代理主要涉及java.lang.reflect包下边的两个类:Proxy和InvocationHandler。其中,InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑生成代理处理器handler,再利用Proxy类的newProxyInstance创建代理类对象,最后通过代理类对象调用业务方法完成动态代理,JDK动态代理只能代理实现了接口的类。
JDK代理是不需要第三方库支持的,只需要JDK环境就可以进行代理,使用条件:

  1. 被代理的对象必须要实现业务接口;
  2. 必须实现 InvocationHandler接口;
  3. 使用 Proxy.newProxyInstance产生代理对象;

下面通过一个实例来实现jdk动态代理+织入切面

  1. 创建CarService接口与其实现类CarServiceImpl;
// CarService接口中有move()方法
public interface CarService {
    void move();
}
// CarServiceImpl.java CarService接口的实现类,实现具体业务方法move()
public class CarServiceImpl implements CarService {
    @Override
    public void move() {
        System.out.println("目标方法,小车正在移动");
    }
}
  1. 创建切面接口Advice与其实现类MyLogger;
//Advice接口,声明前置通知方法beforeMethod与后置通知方法afterMethod
public interface Advice {
    void beforeMethod(Method method);
    void afterMethod(Method method);
}

}
// MyLogger.java Advice接口的实现类,实现具体前置通知方法beforeMethod与后置通知方法afterMethod
public class MyLogger implements Advice {
    @Override
    public void beforeMethod(Method method) {
        System.out.println("执行前置通知,快上车");
    }

    @Override
    public void afterMethod(Method method) {
        System.out.println("执行后置通知,小车到站了");
    }
}
  1. 创建代理处理器类Hander并实现InvocationHandler接口;
// Handler.java InvocationHandler接口的实现类,创建代理类处理器,插入切面逻辑代码与业务方法完成组合
import com.xiong.aspect.Advice;
import com.xiong.service.CarService;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class Handler implements InvocationHandler {

    private CarService target;
    private Advice aspect;

    public Handler() {
    }

    public Handler(CarService target, Advice aspect) {
        this.target = target;
        this.aspect = aspect;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    	//加入增强方法
        aspect.beforeMethod(method);
        //业务方法
        method.invoke(target,args);
        //System.out.println(method);
        //加入增强方法
        aspect.afterMethod(method);
        return null;
    }
}
  1. 创建测试类ProxyTest,利用Proxy类的newProxyInstance(ClassLoader,Interfaces,handler)方法获取代理对象proxy
// ProxyTest.java 测试JDK动态代理
package com.xiong.proxy;

import com.xiong.aspect.impl.MyLogger;
import com.xiong.service.CarService;
import com.xiong.service.impl.CarServiceImpl;
import java.lang.reflect.Proxy;

//测试

public class ProxyTest {
    public static void main(String[] args) {
        //创建业务接口实现类对象
        CarService target = new CarServiceImpl();
        //创建代理类处理器handler,传入业务接口实现类对象与切面类对象
        Handler handler = new Handler(target,new MyLogger());
        //通过Proxy类的newProxyInstance方法获取代理对象
        CarService proxy = (CarService) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),handler);
        System.out.println(proxy.getClass());
        //调用业务方法
        proxy.move();
    }
}

结果:

class com.sun.proxy.$Proxy0
执行前置通知,快上车
目标方法,小车正在移动
执行后置通知,小车到站了

Process finished with exit code 0

打印proxy对象显示正是用的jdk动态代理。

JDK和CGLib动态代理区别

1、JDK动态代理具体实现步骤:

  1. 通过实现InvocationHandler接口来自定义自己的InvocationHandler处理器;
  2. 通过Proxy.getProxyClass获得动态代理类;
  3. 通过反射机制获得代理类的构造方法,方法签名为getConstructor(InvocationHandler.class)
  4. 通过构造函数获得代理对象并将自定义的InvocationHandler实例对象作为参数传入
  5. 通过代理对象调用目标方法

其中2-4步封装到了newProxyInstance方法中。

JDK动态代理是面向接口的代理模式,如果被代理目标没有接口那么Spring也无能为力,Spring通过Java的反射机制生产被代理接口的新的匿名实现类,重写了其中AOP的增强方法。

2、CGLib动态代理:

利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。CGLIB通过继承方式实现代理,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势织入横切逻辑。

3、两者对比:

JDK动态代理是面向接口的。
CGLib动态代理是通过字节码底层继承要代理类来实现,因此如果被代理类被final关键字所修饰,会失败。

4、使用注意:

如果要被代理的对象是个实现类,那么Spring会使用JDK动态代理来完成操作(Spirng默认采用JDK动态代理实现机制);
如果要被代理的对象不是个实现类那么,Spring会强制使用CGLib来实现动态代理。

JDK和CGLib动态代理性能对比

关于两者之间的性能的话,网上有人对于不通版本的jdk进行测试,经过多次试验,测试结果大致是这样的,在1.6和1.7的时候,JDK动态代理的速度要比CGLib动态代理的速度要慢,但是并没有教科书上的10倍差距,在JDK1.8的时候,JDK动态代理的速度已经比CGLib动态代理的速度快很多了,但是JDK动态代理和CGLIB动态代理的适用场景还是不一样的!

参考链接: https://blog.csdn.net/shallynever/article/details/103351299.