AOP(AspectOrientProgramming),俗称面向切面编程,作为面向对象的补充,用于处理分布在系统各个模块中的横切关注点,如事务管理、日志记录、缓存等等。AOP实现的关键在于AOP框架自动创建的AOP代理。AOP代理主要分为静态代理和动态代理。静态代理以AspectJ为代表;动态代理由SpringAOP表示。静态代理在编译时实现,而动态代理在运行时实现。可以想象,前者的性能更好。本文主要介绍SpringAOP的两种代理实现机制,JDK动态代理和CGLIB动态代理。静态代理是在编译阶段生成的AOP代理类,也就是说将生成的字节码编织成一个增强的AOP对象;动态代理不修改字节码,而是在内存中临时生成一个AOP对象。AOP对象包含了目标对象的所有方法,并在特定切点处进行了增强,回调了原对象的方法。SpringAOP中动态代理的方式主要有两种,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射接收被代理类,并要求被代理类实现接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。如果目标类没有实现接口,那么SpringAOP会选择使用CGLIB来动态代理目标类。CGLIB(CodeGenerationLibrary)是一个用于代码生成的类库,可以在运行时动态生成一个类的子类。注意CGLIB是通过继承做的动态代理,所以如果一个类被标记为final,那么它就不能使用CGLIB作为动态代理,也不能使用private等方法作为切面。我们分别通过实例研究AOP的具体实现。直接使用SpringAOP,首先要定义需要切入的接口和实现,为简单起见,定义一个Speakable接口和一个具体的实现类,只有sayHi()和sayBye()两个方法。publicinterfaceSpeakable{voidsayHi();voidsayBye();}@ServicepublicclassPersonSpringimplementsSpeakable{@OverridepublicvoidsayHi(){try{Thread.currentThread().sleep(30);}catch(Exceptione){thrownewRuntimeException(e);}System.out.println(“嗨!!”);}@OverridepublicvoidsayBye(){try{Thread.currentThread().sleep(10);}catch(Exceptione){thrownewRuntimeException(e);}System.out.println(“再见!!”);}}接下来我们希望实现一个记录sayHi()和sayBye()执行时间的函数。定义一个MethodMonitor类,记录Method执行时间}publicvoidlog(){longelapsedTime=System.currentTimeMillis()-start;System.out.println("endmonitor..");System.out.println("Method:"+method+",executiontime:"+elapsedTime+"毫秒.");}}仅仅有这个类是不够的,我希望有一个静态方法使用起来更顺畅,像这样MonitorSession.begin();doWork();MonitorSession.end();照做,定义一个MonitorSessionpublicclassMonitorSession{privatestaticThreadLocalmonitorThreadLocal=newThreadLocal<>();publicstaticvoidbegin(Stringmethod){MethodMonitorlogger=newMethodMonitor(method);monitorThreadLocal.set(logger);}publicstaticvoidend(){MethodMonitorlogger=monitorThreadLocal.get();logger.log()}}万事俱备,接下来我们只需要做好切面编码,@Aspect@ComponentpublicclassMonitorAdvice{@Pointcut("execution(*com.deanwangpro.aop.service.Speakable.*(..))")publicvoidpointcut(){}@Around("pointcut()")publicvoidaround(ProceedingJoinPointpjp)throwsThrowable{MonitorSession.begin(pjp.getSignature().getName());pjp.proceed();MonitorSession.end();}}怎么用?我用的是springboot,写个启动函数@SpringBootApplicationpublicclassApplication{@AutowiredprivateSpeakablepersonSpring;publicstaticvoidmain(String[]args){SpringApplication.run(Application.class,args);}@BeanpublicCommandLineRunnercommandLineRunner(ApplicationContextctx){returnargs->{//springaopSystem.out.println("********springaop********");personSpring.sayHi();personSpring.sayBye();System.exit(0);};}}运行后输出:jdkdynamicproxybeginmonitor..Hi!!endmonitor..Method:sayHi,executiontime:32milliseconds.beginmonitor..Bye!!endmonitor..Method:sayBye,executiontime:22milliseconds.刚才的JDK动态代理例子其实就是JDK动态代理的内部实现机制,因为Person实现了一个in界面。为了不和第一个例子冲突,我们再定义一个Person来实现Speakable,这个实现是没有Spring注解的,所以不会被Spring管理。publicclassPersonImplimplementsSpeakable{@OverridepublicvoidsayHi(){try{Thread.currentThread().sleep(30);}catch(Exceptione){thrownewRuntimeException(e);}System.out.println("Hi!!");}@OverridepublicvoidsayBye(){try{Thread.currentThread().sleep(10);}catch(Exceptione){thrownewRuntimeException(e);}System.out.println("Bye!!");}}重头戏来了,我们需要使用InvocationHandler实现一个代理,让它包含Person对象。然后rerun期实际执行代理方法,然后代理执行真正的方法。所以我们可以在执行真正的方法之前和之后做一些技巧。JDK动态代理是使用反射实现的,直接看代码。publicclassDynamicProxyimplementsInvocationHandler{privateObjecttarget;publicDynamicProxy(Objectobject){this.target=object;}@OverridepublicObjectinvoke(Objectarg0,Methodarg1,Object[]arg2)throwsThrowable{MonitorSession.begin(arget1.getName());Objectobj=arg1.invoke();MonitorSession.end();returnobj;}@SuppressWarnings("unchecked")publicTgetProxy(){return(T)Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);}}可以通过getProxy获取这个代理对象,invoke是具体的执行方法。可以看到我们在每个真正的方法执行前后都添加了Monitor。我实现了一个工厂类来获取Person代理对象********jdkdynamicproxy********");SpeakablejdkProxy=PersonProxyFactory.newJdkProxy();jdkProxy.sayHi();jdkProxy.sayBye();输出结果:********jdkdynamicproxy********beginmonitor..Hi!!endmonitor..Method:sayHi,executiontime:32milliseconds.beginmonitor..Bye!!endmonitor..Method:sayBye,executiontime:22milliseconds.CGLib动态代理我们创建一个又是newPerson,这次没有实现任何接口。publicclassPerson{publicvoidsayHi(){try{Thread.currentThread().sleep(30);}catch(Exceptione){thrownewRuntimeException(e);}System.out.println("Hi!!");}publicvoidsayBye(){try{Thread.currentThread().sleep(10);}catch(Exceptione){thrownewRuntimeException(e);}System.out.println("Bye!!");}}如果Spring识别出代理类没有实现Interface,那么就会使用CGLib创建动态代理,原理其实就是代理类的子类。publicclassCGLibProxyimplementsMethodInterceptor{privatestaticCGLibProxyinstance=newCGLibProxy();privateCGLibProxy(){}publicstaticCGLibProxygetInstance(){returninstance;}privateEnhancerenhancer=newEnhancer();@SuppressWarnings("unchecked")clazz);enhancer.setCallback(this);return(T)enhancer.create();}@OverridepublicObjectintercept(Objectarg0,Methodarg1,Object[]arg2,MethodProxyarg3)throwsThrowable{MonitorSession.begin(arg1.getName());Objectobj=arg3.invokeSuper(arg0,arg2);MonitorSession.end();returnobj;}}类似这个代理对象可以通过getProxy获取,intercept就是具体的执行方法,可以看到我们在执行每一个真正的方法前后都添加了Monitor。在工厂类中添加获取Person代理类的方法,publicstaticPersonnewCglibProxy(){CGLibProxycglibProxy=CGLibProxy.getInstance();Personproxy=cglibProxy.getProxy(Person.class);returnproxy;}具体使用//cglibdynamicproxySystem.out.println("**********cglibproxy********");PersoncglibProxy=PersonProxyFactory.newCglibProxy();cglibProxy.sayHi();cglibProxy.sayBye();输出:beginmonitor..嗨!!结束监控。.Method:sayHi,executiontime:53milliseconds.beginmonitor..Bye!!endmonitor..Method:sayBye,executiontime:14milliseconds.总结对比JDK动态代理和CGLib代理,在实际使用中,发现CGLib创建代理对象的时间比JDK动态代理要长。实测数据方法:newJdkProxy,执行时间:5毫秒。方法:newCglibProxy,执行时间:18毫秒。因此,CGLib更适合不需要频繁实例化的代理。具体方法执行效率上,应该是不通过反射的CGlib更快,但测试结果并非如此,还需要高手指点。JDK方法:sayHi,执行时间:32毫秒。CGLibMethod:sayHi,执行时间:53毫秒。