Spring AOP之动态代理

Spring AOP中包含两种AOP代理方式,分别是JDK动态代理和CGlib动态代理

首先看一下JDK动态代理


JDK动态代理主要设计到java.lang.reflect包中的两个类:Proxy和InvocationHandler.其中InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编织在一起。

Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。

如下,通过模拟论坛删除Topic和删除Forum记录来举例,这个例子是通过操作执行的时间来监控性能的。在ForumServiceImpl中没有性能监视的横切代码:

ForumServiceImpl.java

public class ForumServiceImpl implements ForumService {

    public void removeTopic(int topicId) {
//      PerformanceMonitor.begin("com.baobaotao.proxy.ForumServiceImpl.removeTopic");
        System.out.println("模拟删除Topic记录:"+topicId);
        try {
            Thread.currentThread().sleep(20);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }       
//      PerformanceMonitor.end();
    }

    public void removeForum(int forumId) {
//      PerformanceMonitor.begin("com.baobaotao.proxy.ForumServiceImpl.removeForum");
        System.out.println("模拟删除Forum记录:"+forumId);
        try {
            Thread.currentThread().sleep(40);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }       
//      PerformanceMonitor.end();
    }
}

注释的部分就是性能监视的代码,我们将性能监视的代码整理如下,PerformanceHandler实现了InvocationHandler

PerformaceHandler.java

public class PerformaceHandler implements InvocationHandler {
    private Object target;
    public PerformaceHandler(Object target){
        this.target = target;
    }
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        PerformanceMonitor.begin(target.getClass().getName()+"."+ method.getName()); //①
        Object obj = method.invoke(target, args); //②
        PerformanceMonitor.end(); //③
        return obj;
    }
}

上面的代码中invoke()方法中①和的③部分就是性能监视的横切代码,②处的语句通过java的反射机制间接调用了目标对象的方法,这样就可以将横切逻辑代码和业务逻辑代码编织到一起。

上面的代码中,我们实现了InvocationHandler接口,该接口定义了一个invoke(Object proxy, Method method, Object[] args)方法,参数定义如下:

@param proxy the proxy instance that the method was invoked on

@param method the {@code Method} instance corresponding to the interface method invoked on the proxy instance. The declaring class of the {@code Method} object will be the interface that the method was declared in, which may be a superinterface of the proxy interface that the proxy class inherits the method through.

@param args an array of objects containing the values of the arguments passed in the method invocation on the proxy instance, or {@code null} if interface method takes no arguments. Arguments of primitive types are wrapped in instances of the appropriate primitive wrapper class, such as {@code java.lang.Integer} or {@code java.lang.Boolean}.

proxy是最终生成的代理实例;method是被代理目标实例的某个具体方法,通过它可以发起目标实例方法的反射调用,args是传入实例某一个方法的入参数组,在方法反射调用时使用。

在上面的构造函数中通过target传入希望被代理的目标对象,在执行method.invoke()方法时调用目标实例的方法。

PerformanceMonitor.java

public class PerformanceMonitor {
    private static ThreadLocal performaceRecord = new ThreadLocal();
    public static void begin(String method) {
        System.out.println("begin monitor...");
        MethodPerformace mp = performaceRecord.get();
        if(mp == null){
            mp = new MethodPerformace(method);
            performaceRecord.set(mp);
        }else{
            mp.reset(method);   
        }
    }
    public static void end() {
        System.out.println("end monitor...");
        MethodPerformace mp = performaceRecord.get();
        mp.printPerformace();
    }
}

MethodPerformace.java

public class MethodPerformace {
    private int random;
    private long begin;
    private long end;
    private String serviceMethod;
    public MethodPerformace(String serviceMethod){
        reset(serviceMethod);
    }
    public void printPerformace(){
        end = System.currentTimeMillis();
        long elapse = end - begin;
        System.out.println(serviceMethod+"花费"+elapse+"毫秒。");
        System.out.printf(this.toString());
    }
    public void reset(String serviceMethod){
//        this.random = (int) Math.random();
        Random rd = new Random();
        this.random = rd.nextInt(10);
        this.serviceMethod = serviceMethod;
        this.begin = System.currentTimeMillis();
    }

    @Override
    public String toString() {
        return "MethodPerformace{" +
                "random=" + random +
                ", begin=" + begin +
                ", end=" + end +
                ", serviceMethod='" + serviceMethod + '\'' +
                '}';
    }
}

MethodPerformace是对每个方法执行时间进行记录和性能整理的类,使用ThreadLocal是因为ThreadLocal为每个使用该变量的线程提供独立的变量副本,这里考虑到PerformanceMonitor被当做工具类使用,有可能被多个线程调用,有关ThreadLocal的使用可以参考其他资料,这里也可以直接将performaceRecord变量声明为一个静态变量,此处仅为测试,所以只有一个线程来调用该类,修改如下:

public class PerformanceMonitor {
//  private static ThreadLocal performaceRecord = new ThreadLocal();
    private static MethodPerformace performaceRecord = null;
    public static void begin(String method) {
        System.out.println("begin monitor...");
//      MethodPerformace mp = performaceRecord.get();
        MethodPerformace mp = performaceRecord;
        if(mp == null){
            mp = new MethodPerformace(method);
            performaceRecord = mp;
//          performaceRecord.set(mp);
        }else{
            mp.reset(method);   
        }
    }
    public static void end() {
        System.out.println("end monitor...");
//      MethodPerformace mp = performaceRecord.get();
        MethodPerformace mp = performaceRecord;
        mp.printPerformace();
    }
}

测试JDK代理的代码如下:

public class TestForumService {
    public static void main(String[] args) {
//       业务类正常编码的测试
        ForumService forumService = new ForumServiceImpl();
        forumService.removeForum(10);
        forumService.removeTopic(1012);

//      使用JDK动态代理
        ForumService target = new ForumServiceImpl();//①
        PerformaceHandler handler = new PerformaceHandler(target);//②
        ForumService proxy = (ForumService) Proxy.newProxyInstance(target
                        .getClass().getClassLoader(),
                target.getClass().getInterfaces(), handler);//③
        proxy.removeForum(10);//④
        proxy.removeTopic(1012);//⑤
    }
}

上面①处是希望被代理的目标业务类,②处将目标业务类和横切代码编织到一起,在③处根据目标业务类逻辑和性能监视横切逻辑的InvocationHandler实例创建代理实例,最后④⑤处调用代理实例。

③处使用了Proxy的newProxyInstance()静态方法为编织了业务逻辑和性能监视横切逻辑的handler创建了一个符合ForumServic接口的代理实例。该方法的第一个入参为类加载器,第二个为创建代理实例所需要实现的一组接口,第三个为整合业务逻辑和横切逻辑的编织器对象。

执行结果如下:
Spring AOP之动态代理_第1张图片

如下图,是通过创建代理对象进行业务方法调用的整体逻辑时序图:

Spring AOP之动态代理_第2张图片

接下来看一下CGlib动态代理


通过上面的JDK代理实现了横切逻辑和业务逻辑的分离,但是它有一个显示,即它只能为接口创建代理实例,Proxy接口的newProxyInstance方法的第二个参数就是需要代理实例实现的接口列表。对于没有通过接口定义业务方法的类,CGLib就可以完成动态代理。

CGLib是通过创建子类的方式实现的,并在子类中拦截所有父类方法的调用,顺势织入横切逻辑。

CglibProxy.java

public class CglibProxy implements MethodInterceptor {
    private Enhancer enhancer = new Enhancer();

    public Object getProxy(Class clazz) {
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }
    public Object intercept(Object obj, Method method, Object[] args,
            MethodProxy proxy) throws Throwable {
        PerformanceMonitor.begin(obj.getClass().getName()+"."+method.getName());
        Object result=proxy.invokeSuper(obj, args);
        PerformanceMonitor.end();
        return result;
    }
}

上面代码中,用户可以通过getProxy(Class clazz)为一个类创建动态代理对象,该代理对象通过扩展(子类的方式)clazz创建代理对象。在对象中织入性能监视的横切逻辑。

Enhancer是用来通过字节码技术动态创建子类实例的。

其中,intercept(Object var1, Method var2, Object[] var3, MethodProxy var4)是CGlib定义的接口方法,它拦截所有的目标类方法调用,var1为目标类实例,var2为目标类方法的反射对象,var3为方法的入参,var4为代理类实例,下面通过CGLib创建代理类:

public class TestForumService {
    public static void main(String[] args) {
//      使用CGLib动态代理
        CglibProxy proxy = new CglibProxy();
        ForumService forumService = (ForumService)proxy.getProxy(ForumServiceImpl.class);
        forumService.removeForum(10);
        forumService.removeTopic(1023);
    }
}

上面两种方式各有优缺点,首先JDK动态代理不能创建没有接口的类的代理,其次,其次两者的性能是有一定的区别的。CGlib创建的对象的性能JDK高,但是CGLib创建对象的时间话费却比JDK动态代理多,所以对于singleton的代理对象或者具有实例池的代理,由于不需要频繁的创建代理对象,所以比较适合采用CGlib动态代理技术,反之则JDK适合。

你可能感兴趣的:(Spring,spring,aop)