《Spring》,十二 ---- AOP 实现机制 《疯狂Java讲义》

动态代理

  JDK动态代理(参考《疯狂Java讲义》(三十六)---- 类加载机制与反射

  JDK动态代理机制可以在运行期间,为相应的接口动态生成对应的代理对象。所以,可以将横切关注点逻辑封装到动态代理的InvacationHandler中,然后在系统运行期间,根据横切关注点需要植入的模块位置,将横切逻辑织入到相应的代理类中。

  这种方式实现的唯一缺点是,所有需要织入横切关注点逻辑的模块类都得实现相应的接口,因为动态代理只针对接口有效。

示例:

  ForumService.class

package com.ivy.aop;

public interface ForumService {

    void removeTopic(int topicId);
    void removeForum(int forumId);
}

  ForumServiceImpl.class

package com.ivy.aop;

public class ForumServiceImpl implements ForumService {

    @Override
    public void removeTopic(int topicId) {
        // TODO Auto-generated method stub
        System.out.println("remove topic record : " + topicId);
        try {
            Thread.currentThread().sleep(20);
        } catch (Exception ex) {
            throw new RuntimeException();
        }
    }

    @Override
    public void removeForum(int forumId) {
        System.out.println("remove forum record : " + forumId);
        try {
            Thread.currentThread().sleep(40);
        } catch (Exception ex) {
            throw new RuntimeException();
        }

    }

}

  PerformanceMonitor.class

package com.ivy.aop;

public class PerformanceMonitor {

    private static ThreadLocal<MethodPerformance> performanceRecord = new ThreadLocal<MethodPerformance>();
    
    public static void begin(String method) {
        System.out.println("begin monitor...");
        MethodPerformance mp = new MethodPerformance(method);
        performanceRecord.set(mp);
    }
    
    public static void end() {
        System.out.println("end monitor...");
        MethodPerformance mp = performanceRecord.get();
        mp.printPerformance();
    }
}

  MethodPerformance.class

package com.ivy.aop;

public class MethodPerformance {

    private long begin;
    private long end;
    private String serviceMethod;
    public MethodPerformance(String serviceMethod) {
        this.serviceMethod = serviceMethod;
        this.begin = System.currentTimeMillis();
    }
    
    public void printPerformance() {
        end = System.currentTimeMillis();
        long elapse = end - begin;
        
        System.out.println(serviceMethod + " cost " + elapse + "ms.");
    }
}

  

  PerformanceHandler.class

package com.ivy.aop;

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

public class PerformanceHandler implements InvocationHandler{

    private Object target;
    public PerformanceHandler(Object target) {
        this.target = target;
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
        PerformanceMonitor.begin(target.getClass().getName() + "." + method.getName());
        Object object = method.invoke(target, args);
        PerformanceMonitor.end();
        return object;
    }
}

  TestForumServiceByJDKProxy.class

package com.ivy.aop;

import java.lang.reflect.Proxy;

public class TestForumServiceByJDKProxy {

    public static void main(String[] args) {
        ForumService target = new ForumServiceImpl();
        PerformanceHandler handler = new PerformanceHandler(target);
// 只能对接口进行代理 ForumService proxy = (ForumService)Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler); proxy.removeForum(10); proxy.removeTopic(1012); } }

  运行结果:

begin monitor...
remove forum record : 10
end monitor...
com.ivy.aop.ForumServiceImpl.removeForum cost 41ms.
begin monitor...
remove topic record : 1012
end monitor...
com.ivy.aop.ForumServiceImpl.removeTopic cost 20ms.

  

动态字节码增强 -- CGLib动态代理

  java虚拟机加载的class文件都是符合一定规范的,所以,只要交给虚拟机运行的文件符合Java Class规范,程序的运行就么有问题。通常的class文件都是从Java源代码文件使用Javac编译器编译而成的,但只要符合Java Class规范,我们也可以使用CGLib等Java工具类,在程序运行期间,动态构建字节码的class文件。在这样的前提下,我们可以为需要织入横切逻辑的模块类在运行期间,通过动态字节码增强技术,为这些系统模块类生成相应的子类,而将横切逻辑加到这些子类中,让应用程序在执行期间使用的是这些动态生成的子类,从而达到将横切逻辑织入系统的目的。使用动态字节码增强技术,即使模块类没有实现相应的接口,我们依然可以对其进行扩展,而不用像动态代理那样受限于接口。不过这种实现的不足是,如果需要扩展的类以及类中的实例方法等声名为final,则无法对其进行子类化扩展。

  Spring AOP在无法使用动态代理机制进行AOP扩展的时候,会使用CGLIB库德动态字节码增强来实现AOP的功能扩展。

  

  使用JDK创建代理有一个限制,它只能够为接口创建代理实例,对于没有通过接口定义业务方法的类,就需要CGLib动态创建代理了。

  CGLib采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截的技术拦截所有弗雷方法的调用,并顺势织入横切逻辑。

  CglibProxy.class

package com.ivy.aop;

import java.lang.reflect.Method;

import org.springframework.aop.interceptor.PerformanceMonitorInterceptor;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CglibProxy implements MethodInterceptor{

    private Enhancer enhancer = new Enhancer();
    public Object getProxy(Class clazz) {
        // 设置需要创建子类的类
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        // 通过字节码结束动态创建子类实例
        return enhancer.create();
    }
    @Override
    // 拦截父类所有方法的调用
    public Object intercept(Object obj, Method method, Object[] args,
            MethodProxy proxy) throws Throwable {
        // TODO Auto-generated method stub
        PerformanceMonitor.begin(obj.getClass().getName() + "." + method.getName());
        // 通过代理类调用父类中的方法
        Object result = proxy.invokeSuper(obj, args);
        PerformanceMonitor.end();
        return result;
    }

}

  TestForumService.class

package com.ivy.aop;

public class TestForumService {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        CglibProxy proxy = new CglibProxy();
// 可以对实现类进行代理 ForumServiceImpl forumService = (ForumServiceImpl)proxy.getProxy(ForumServiceImpl.class); forumService.removeForum(10); forumService.removeTopic(1023); } }

  运行结果:

begin monitor...
remove forum record : 10
end monitor...
com.ivy.aop.ForumServiceImpl$$EnhancerByCGLIB$$2d2c5cf3.removeForum cost 41ms.
begin monitor...
remove topic record : 1023
end monitor...
com.ivy.aop.ForumServiceImpl$$EnhancerByCGLIB$$2d2c5cf3.removeTopic cost 20ms.