【Spring】AOP的基本原理

Spring为Java大地带来了一阵春风,它作为一个优秀的轻量级企业应用开发框架,能够大大简化企业应用开发的复杂性。
Spring以控制反转(IOC)(从另个角度也叫:依赖注入(DI)AOP这两样先进的设计理念为基础,统一了应用对象的查找、配置和生命周期的管理,分离了业务与基础服务中的不同关注
点,开发人员可以基于简单Java对象轻松的实现与EJB同样强大的功能。

AOP经常被定义为一种编程技术,用来在系统中提升业务的分离,系统有很多组件组成,每一组件负责一部分功能。然而,这些组件也经常带有一些除了核心功能之外的附带功能。系统服务如日志、事务管理和安全经常融入到一些其他功能模块中。这些系统服务通常叫做交叉业务,这是因为,它们总是分布在系统的很多组件中,通过将这些业务分布在多个组件中,你给你的代码引入了双重复杂性。


什么是AOP

AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

 

而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。

 

使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”

 

实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。

AOP使用场景

AOP用来封装横切关注点,具体可以在下面的场景中使用:

 

Authentication 权限

Caching 缓存

Context passing 内容传递

Error handling 错误处理

Lazy loading 懒加载

Debugging  调试

logging, tracing, profiling and monitoring 记录跟踪 优化 校准

Performance optimization 性能优化

Persistence  持久化

Resource pooling 资源池

Synchronization 同步

Transactions 事务

AOP各种的实现

AOP就是面向切面编程,我们可以从几个层面来实现AOP。

《【Spring】AOP的基本原理》

在编译器修改源代码,在运行期字节码加载前修改字节码或字节码加载后动态创建代理类的字节码,以下是各种实现机制的比较。 

 

类别

机制

原理

优点

缺点

静态AOP

静态织入

在编译期,切面直接以字节码的形式编译到目标字节码文件中。

对系统无性能影响。

灵活性不够。

动态AOP

jdk动态代理

在运行期,目标类加载后,为接口动态生成代理类,将切面植入到代理类中。

相对于静态AOP更加灵活。

切入的关注点需要实现接口。对系统有一点性能影响。

动态字节码生成(Cglib

在运行期,目标类加载后,动态构建字节码文件生成目标类的子类,将切面逻辑加入到子类中。

没有接口也可以织入。

扩展类的实例方法为final时,则无法进行织入。

自定义类加载器

在运行期,目标加载前,将切面逻辑加到目标字节码里。

可以对绝大部分类进行织入。

代码中如果使用了其他类加载器,则这些类将不会被织入。

字节码转换

在运行期,所有类加载器加载字节码前,前进行拦截。

可以对所有类进行织入。

 

AOP相关概念

方面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。方面用Spring的 Advisor或拦截器实现。

 

连接点(Joinpoint): 程序执行过程中明确的点,如方法的调用或特定的异常被抛出。

 

通知(Advice): 在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。Spring中定义了四个advice: BeforeAdvice, AfterAdvice, ThrowAdvice和DynamicIntroductionAdvice

 

切入点(Pointcut): 指定一个通知将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点:例如,使用正则表达式。 Spring定义了Pointcut接口,用来组合MethodMatcher和ClassFilter,可以通过名字很清楚的理解, MethodMatcher是用来检查目标类的方法是否可以被应用此通知,而ClassFilter是用来检查Pointcut是否应该应用到目标类上

 

引入(Introduction): 添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现 IsModified接口,来简化缓存。Spring中要使用Introduction, 可有通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口

 

目标对象(Target Object): 包含连接点的对象。也被称作被通知或被代理对象。POJO

 

AOP代理(AOP Proxy): AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。

 

织入(Weaving): 组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。

Spring AOP组件

下面这种类图列出了Spring中主要的AOP组件

《【Spring】AOP的基本原理》

如何使用Spring AOP

可以通过配置文件或者编程的方式来使用Spring AOP。

 

配置可以通过xml文件来进行,大概有四种方式:

1.        配置ProxyFactoryBean,显式地设置advisors, advice, target等

2.        配置AutoProxyCreator,这种方式下,还是如以前一样使用定义的bean,但是从容器中获得的其实已经是代理对象

3.        通过<aop:config>来配置

4.        通过<aop: aspectj-autoproxy>来配置,使用AspectJ的注解来标识通知及切入点

 

也可以直接使用ProxyFactory来以编程的方式使用Spring AOP,通过ProxyFactory提供的方法可以设置target对象, advisor等相关配置,最终通过 getProxy()方法来获取代理对象

 

具体使用的示例可以google. 这里略去

Spring AOP代理对象的生成

Spring提供了两种方式来生成代理对象: JDKProxy和Cglib,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理。下面我们来研究一下Spring如何使用JDK来生成代理对象,具体的生成代码放在JdkDynamicAopProxy这个类中,直接上相关代码:

[java] 
view plain
copy

  1. /** 
  2.     * <ol> 
  3.     * <li>获取代理类要实现的接口,除了Advised对象中配置的,还会加上SpringProxy, Advised(opaque=false) 
  4.     * <li>检查上面得到的接口中有没有定义 equals或者hashcode的接口 
  5.     * <li>调用Proxy.newProxyInstance创建代理对象 
  6.     * </ol> 
  7.     */  
  8.    public Object getProxy(ClassLoader classLoader) {  
  9.        if (logger.isDebugEnabled()) {  
  10.            logger.debug(“Creating JDK dynamic proxy: target source is “ +this.advised.getTargetSource());  
  11.        }  
  12.        Class[] proxiedInterfaces =AopProxyUtils.completeProxiedInterfaces(this.advised);  
  13.        findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);  
  14.        return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);  
  15. }  

 

那这个其实很明了,注释上我也已经写清楚了,不再赘述。

 

下面的问题是,代理对象生成了,那切面是如何织入的?

我们知道InvocationHandler是JDK动态代理的核心,生成的代理对象的方法调用都会委托到InvocationHandler.invoke()方法。而通过JdkDynamicAopProxy的签名我们可以看到这个类其实也实现了InvocationHandler,下面我们就通过分析这个类中实现的invoke()方法来具体看下Spring AOP是如何织入切面的。

 

[java] 
view plain
copy

  1. publicObject invoke(Object proxy, Method method, Object[] args) throwsThrowable {  
  2.        MethodInvocation invocation = null;  
  3.        Object oldProxy = null;  
  4.        boolean setProxyContext = false;  
  5.    
  6.        TargetSource targetSource = this.advised.targetSource;  
  7.        Class targetClass = null;  
  8.        Object target = null;  
  9.    
  10.        try {  
  11.            //eqauls()方法,具目标对象未实现此方法  
  12.            if (!this.equalsDefined && AopUtils.isEqualsMethod(method)){  
  13.                 return (equals(args[0])? Boolean.TRUE : Boolean.FALSE);  
  14.            }  
  15.    
  16.            //hashCode()方法,具目标对象未实现此方法  
  17.            if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)){  
  18.                 return newInteger(hashCode());  
  19.            }  
  20.    
  21.            //Advised接口或者其父接口中定义的方法,直接反射调用,不应用通知  
  22.            if (!this.advised.opaque &&method.getDeclaringClass().isInterface()  
  23.                     &&method.getDeclaringClass().isAssignableFrom(Advised.class)) {  
  24.                 // Service invocations onProxyConfig with the proxy config…  
  25.                 return AopUtils.invokeJoinpointUsingReflection(this.advised,method, args);  
  26.            }  
  27.    
  28.            Object retVal = null;  
  29.    
  30.            if (this.advised.exposeProxy) {  
  31.                 // Make invocation available ifnecessary.  
  32.                 oldProxy = AopContext.setCurrentProxy(proxy);  
  33.                 setProxyContext = true;  
  34.            }  
  35.    
  36.            //获得目标对象的类  
  37.            target = targetSource.getTarget();  
  38.            if (target != null) {  
  39.                 targetClass = target.getClass();  
  40.            }  
  41.    
  42.            //获取可以应用到此方法上的Interceptor列表  
  43.            List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method,targetClass);  
  44.    
  45.            //如果没有可以应用到此方法的通知(Interceptor),此直接反射调用 method.invoke(target, args)  
  46.            if (chain.isEmpty()) {  
  47.                 retVal = AopUtils.invokeJoinpointUsingReflection(target,method, args);  
  48.            } else {  
  49.                 //创建MethodInvocation  
  50.                 invocation = newReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);  
  51.                 retVal = invocation.proceed();  
  52.            }  
  53.    
  54.            // Massage return value if necessary.  
  55.            if (retVal != null && retVal == target &&method.getReturnType().isInstance(proxy)  
  56.                     &&!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {  
  57.                 // Special case: it returned”this” and the return type of the method  
  58.                 // is type-compatible. Notethat we can’t help if the target sets  
  59.                 // a reference to itself inanother returned object.  
  60.                 retVal = proxy;  
  61.            }  
  62.            return retVal;  
  63.        } finally {  
  64.            if (target != null && !targetSource.isStatic()) {  
  65.                 // Must have come fromTargetSource.  
  66.                targetSource.releaseTarget(target);  
  67.            }  
  68.            if (setProxyContext) {  
  69.                 // Restore old proxy.  
  70.                 AopContext.setCurrentProxy(oldProxy);  
  71.            }  
  72.        }  
  73.     }  

 

主流程可以简述为:获取可以应用到此方法上的通知链(Interceptor Chain),如果有,则应用通知,并执行joinpoint; 如果没有,则直接反射执行joinpoint。而这里的关键是通知链是如何获取的以及它又是如何执行的,下面逐一分析下。

 

首先,从上面的代码可以看到,通知链是通过Advised.getInterceptorsAndDynamicInterceptionAdvice()这个方法来获取的,我们来看下这个方法的实现:

[java] 
view plain
copy

  1. public List<Object>getInterceptorsAndDynamicInterceptionAdvice(Method method, Class targetClass) {  
  2.                    MethodCacheKeycacheKey = new MethodCacheKey(method);  
  3.                    List<Object>cached = this.methodCache.get(cacheKey);  
  4.                    if(cached == null) {  
  5.                             cached= this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(  
  6.                                                this,method, targetClass);  
  7.                             this.methodCache.put(cacheKey,cached);  
  8.                    }  
  9.                    returncached;  
  10.          }  

 

可以看到实际的获取工作其实是由AdvisorChainFactory. getInterceptorsAndDynamicInterceptionAdvice()这个方法来完成的,获取到的结果会被缓存。

下面来分析下这个方法的实现:

 

[java] 
view plain
copy

  1. /** 
  2.     * 从提供的配置实例config中获取advisor列表,遍历处理这些advisor.如果是IntroductionAdvisor, 
  3.     * 则判断此Advisor能否应用到目标类targetClass上.如果是PointcutAdvisor,则判断 
  4.     * 此Advisor能否应用到目标方法method上.将满足条件的Advisor通过AdvisorAdaptor转化成Interceptor列表返回. 
  5.     */  
  6.     publicList getInterceptorsAndDynamicInterceptionAdvice(Advised config, Methodmethod, Class targetClass) {  
  7.        // This is somewhat tricky… we have to process introductions first,  
  8.        // but we need to preserve order in the ultimate list.  
  9.        List interceptorList = new ArrayList(config.getAdvisors().length);  
  10.    
  11.        //查看是否包含IntroductionAdvisor  
  12.        boolean hasIntroductions = hasMatchingIntroductions(config,targetClass);  
  13.    
  14.        //这里实际上注册一系列AdvisorAdapter,用于将Advisor转化成MethodInterceptor  
  15.        AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();  
  16.    
  17.        Advisor[] advisors = config.getAdvisors();  
  18.         for (int i = 0; i <advisors.length; i++) {  
  19.            Advisor advisor = advisors[i];  
  20.            if (advisor instanceof PointcutAdvisor) {  
  21.                 // Add it conditionally.  
  22.                 PointcutAdvisor pointcutAdvisor= (PointcutAdvisor) advisor;  
  23.                 if(config.isPreFiltered() ||pointcutAdvisor.getPointcut().getClassFilter().matches(targetClass)) {  
  24.                     //TODO: 这个地方这两个方法的位置可以互换下  
  25.                     //将Advisor转化成Interceptor  
  26.                     MethodInterceptor[]interceptors = registry.getInterceptors(advisor);  
  27.    
  28.                     //检查当前advisor的pointcut是否可以匹配当前方法  
  29.                     MethodMatcher mm =pointcutAdvisor.getPointcut().getMethodMatcher();  
  30.    
  31.                     if (MethodMatchers.matches(mm,method, targetClass, hasIntroductions)) {  
  32.                         if(mm.isRuntime()) {  
  33.                             // Creating a newobject instance in the getInterceptors() method  
  34.                             // isn’t a problemas we normally cache created chains.  
  35.                             for (intj = 0; j < interceptors.length; j++) {  
  36.                                interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptors[j],mm));  
  37.                             }  
  38.                         } else {  
  39.                             interceptorList.addAll(Arrays.asList(interceptors));  
  40.                         }  
  41.                     }  
  42.                 }  
  43.            } else if (advisor instanceof IntroductionAdvisor){  
  44.                 IntroductionAdvisor ia =(IntroductionAdvisor) advisor;  
  45.                 if(config.isPreFiltered() || ia.getClassFilter().matches(targetClass)) {  
  46.                     Interceptor[] interceptors= registry.getInterceptors(advisor);  
  47.                     interceptorList.addAll(Arrays.asList(interceptors));  
  48.                 }  
  49.            } else {  
  50.                 Interceptor[] interceptors =registry.getInterceptors(advisor);  
  51.                 interceptorList.addAll(Arrays.asList(interceptors));  
  52.            }  
  53.        }  
  54.        return interceptorList;  
  55. }  

 

这个方法执行完成后,Advised中配置能够应用到连接点或者目标类的Advisor全部被转化成了MethodInterceptor.

 

接下来我们再看下得到的拦截器链是怎么起作用的。

 

[java] 
view plain
copy

  1. if (chain.isEmpty()) {  
  2.                 retVal = AopUtils.invokeJoinpointUsingReflection(target,method, args);  
  3.             } else {  
  4.                 //创建MethodInvocation  
  5.                 invocation = newReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);  
  6.                 retVal = invocation.proceed();  
  7.             }  

        

         从这段代码可以看出,如果得到的拦截器链为空,则直接反射调用目标方法,否则创建MethodInvocation,调用其proceed方法,触发拦截器链的执行,来看下具体代码

[java] 
view plain
copy

  1. public Object proceed() throws Throwable {  
  2.        //  We start with an index of -1and increment early.  
  3.        if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size()- 1) {  
  4.            //如果Interceptor执行完了,则执行joinPoint  
  5.            return invokeJoinpoint();  
  6.        }  
  7.    
  8.        Object interceptorOrInterceptionAdvice =  
  9.            this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);  
  10.          
  11.        //如果要动态匹配joinPoint  
  12.        if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher){  
  13.            // Evaluate dynamic method matcher here: static part will already have  
  14.            // been evaluated and found to match.  
  15.            InterceptorAndDynamicMethodMatcher dm =  
  16.                 (InterceptorAndDynamicMethodMatcher)interceptorOrInterceptionAdvice;  
  17.            //动态匹配:运行时参数是否满足匹配条件  
  18.            if (dm.methodMatcher.matches(this.method, this.targetClass,this.arguments)) {  
  19.                 //执行当前Intercetpor  
  20.                 returndm.interceptor.invoke(this);  
  21.            }  
  22.            else {  
  23.                 //动态匹配失败时,略过当前Intercetpor,调用下一个Interceptor  
  24.                 return proceed();  
  25.            }  
  26.        }  
  27.        else {  
  28.            // It’s an interceptor, so we just invoke it: The pointcutwill have  
  29.            // been evaluated statically before this object was constructed.  
  30.            //执行当前Intercetpor  
  31.            return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);  
  32.        }  
  33. }  

=======================================================================================

3 AOP的实现机制 
  本章节将详细介绍AOP有各种实现机制。

3.1 动态代理 
  Java在JDK1.3后引入的动态代理机制,使我们可以在运行期动态的创建代理类。使用动态代理实现AOP需要有四个角色:被代理的类,被代理类的接口,织入器,和InvocationHandler,而织入器使用接口反射机制生成一个代理类,然后在这个代理类中织入代码。被代理的类是AOP里所说的目标,InvocationHandler是切面,它包含了Advice和Pointcut。 
《【Spring】AOP的基本原理》

3.1.1 使用动态代理 
  那如何使用动态代理来实现AOP。下面的例子演示在方法执行前织入一段记录日志的代码,其中Business是代理类,LogInvocationHandler是记录日志的切面,IBusiness, IBusiness2是代理类的接口,Proxy.newProxyInstance是织入器。 
清单一:动态代理的演示

Java代码  

  1. public static void main(String[] args) {   
  2.     //需要代理的接口,被代理类实现的多个接口都必须在这里定义   
  3.     Class[] proxyInterface = new Class[] { IBusiness.class, IBusiness2.class };   
  4.     //构建AOP的Advice,这里需要传入业务类的实例   
  5.     LogInvocationHandler handler = new LogInvocationHandler(new Business());   
  6.     //生成代理类的字节码加载器   
  7.     ClassLoader classLoader = DynamicProxyDemo.class.getClassLoader();   
  8.     //织入器,织入代码并生成代理类   
  9.     IBusiness2 proxyBusiness = (IBusiness2) Proxy.newProxyInstance(classLoader, proxyInterface, handler);   
  10.     //使用代理类的实例来调用方法。   
  11.     proxyBusiness.doSomeThing2();   
  12.     ((IBusiness) proxyBusiness).doSomeThing();   
  13. }   
  14.   
  15. /**  
  16. * 打印日志的切面  
  17. */   
  18. public static class LogInvocationHandler implements InvocationHandler {   
  19.   
  20.     private Object target; //目标对象   
  21.   
  22.     LogInvocationHandler(Object target) {   
  23.         this.target = target;   
  24.     }   
  25.   
  26.     @Override   
  27.     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {   
  28.         //执行原有逻辑   
  29.         Object rev = method.invoke(target, args);   
  30.         //执行织入的日志,你可以控制哪些方法执行切入逻辑   
  31.         if (method.getName().equals(“doSomeThing2”)) {   
  32.             System.out.println(“记录日志”);   
  33.         }   
  34.         return rev;   
  35.     }   
  36. }   
  37.   
  38. 接口IBusiness和IBusiness2定义省略。   

 

   业务类,需要代理的类。

Java代码  

  1. public class Business implements IBusiness, IBusiness2 {   
  2.   
  3.     @Override   
  4.     public boolean doSomeThing() {   
  5.         System.out.println(“执行业务逻辑”);   
  6.         return true;   
  7.     }   
  8.   
  9.     @Override   
  10.     public void doSomeThing2() {   
  11.         System.out.println(“执行业务逻辑2”);   
  12.     }   
  13.   
  14. }   

 

   输出

Java代码  

  1. 执行业务逻辑2   
  2. 记录日志   
  3. 执行业务逻辑   

 

  可以看到“记录日志”的逻辑切入到Business类的doSomeThing方法前了。

 

3.1.2 动态代理原理 
    本节将结合动态代理的源代码讲解其实现原理。动态代理的核心其实就是代理对象的生成,即Proxy.newProxyInstance(classLoader, proxyInterface, handler)。让我们进入newProxyInstance方法观摩下,核心代码其实就三行。 
清单二:生成代理类

Java代码  

  1. //获取代理类   
  2. Class cl = getProxyClass(loader, interfaces);   
  3. //获取带有InvocationHandler参数的构造方法   
  4. Constructor cons = cl.getConstructor(constructorParams);   
  5. //把handler传入构造方法生成实例   
  6. return (Object) cons.newInstance(new Object[] { h });     

 

    其中getProxyClass(loader, interfaces)方法用于获取代理类,它主要做了三件事情:在当前类加载器的缓存里搜索是否有代理类,没有则生成代理类并缓存在本地JVM里。清单三:查找代理类。

Java代码  

  1.  // 缓存的key使用接口名称生成的List   
  2. Object key = Arrays.asList(interfaceNames);   
  3. synchronized (cache) {   
  4.     do {   
  5. Object value = cache.get(key);   
  6.          // 缓存里保存了代理类的引用   
  7. if (value instanceof Reference) {   
  8.     proxyClass = (Class) ((Reference) value).get();   
  9. }   
  10. if (proxyClass != null) {   
  11. // 代理类已经存在则返回   
  12.     return proxyClass;   
  13. else if (value == pendingGenerationMarker) {   
  14.     // 如果代理类正在产生,则等待   
  15.     try {   
  16. cache.wait();   
  17.     } catch (InterruptedException e) {   
  18.     }   
  19.     continue;   
  20. else {   
  21.     //没有代理类,则标记代理准备生成   
  22.     cache.put(key, pendingGenerationMarker);   
  23.     break;   
  24. }   
  25.     } while (true);   
  26. }   

  

代理类的生成主要是以下这两行代码。 清单四:生成并加载代理类

 

Java代码  

  1. //生成代理类的字节码文件并保存到硬盘中(默认不保存到硬盘)   
  2. proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);   
  3. //使用类加载器将字节码加载到内存中   
  4. proxyClass = defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);   

 

  ProxyGenerator.generateProxyClass()方法属于sun.misc包下,Oracle并没有提供源代码,但是我们可以使用JD-GUI这样的反编译软件打开jre\lib\rt.jar来一探究竟,以下是其核心代码的分析。 
清单五:代理类的生成过程

Java代码  

  1. //添加接口中定义的方法,此时方法体为空   
  2. for (int i = 0; i < this.interfaces.length; i++) {   
  3.   localObject1 = this.interfaces[i].getMethods();   
  4.   for (int k = 0; k < localObject1.length; k++) {   
  5.      addProxyMethod(localObject1[k], this.interfaces[i]);   
  6.   }   
  7. }   
  8.   
  9. //添加一个带有InvocationHandler的构造方法   
  10. MethodInfo localMethodInfo = new MethodInfo(“<init>”“(Ljava/lang/reflect/InvocationHandler;)V”1);   
  11.   
  12. //循环生成方法体代码(省略)   
  13. //方法体里生成调用InvocationHandler的invoke方法代码。(此处有所省略)   
  14. this.cp.getInterfaceMethodRef(“InvocationHandler”“invoke”“Object; Method; Object;”)   
  15.   
  16. //将生成的字节码,写入硬盘,前面有个if判断,默认情况下不保存到硬盘。   
  17. localFileOutputStream = new FileOutputStream(ProxyGenerator.access$000(this.val$name) + “.class”);   
  18. localFileOutputStream.write(this.val$classFile);   

 

  那么通过以上分析,我们可以推出动态代理为我们生成了一个这样的代理类。把方法doSomeThing的方法体修改为调用LogInvocationHandler的invoke方法。 
清单六:生成的代理类源码

 

Java代码  

  1. public class ProxyBusiness implements IBusiness, IBusiness2 {   
  2.   
  3. private LogInvocationHandler h;   
  4.   
  5. @Override   
  6. public void doSomeThing2() {   
  7.     try {   
  8.         Method m = (h.target).getClass().getMethod(“doSomeThing”null);   
  9.         h.invoke(this, m, null);   
  10.     } catch (Throwable e) {   
  11.         // 异常处理(略)   
  12.     }   
  13. }   
  14.   
  15. @Override   
  16. public boolean doSomeThing() {   
  17.     try {   
  18.        Method m = (h.target).getClass().getMethod(“doSomeThing2”null);   
  19.        return (Boolean) h.invoke(this, m, null);   
  20.     } catch (Throwable e) {   
  21.         // 异常处理(略)   
  22.     }   
  23.     return false;   
  24. }   
  25.   
  26. public ProxyBusiness(LogInvocationHandler h) {   
  27.     this.h = h;   
  28. }   
  29.   
  30. //测试用   
  31. public static void main(String[] args) {   
  32.     //构建AOP的Advice   
  33.     LogInvocationHandler handler = new LogInvocationHandler(new Business());   
  34.     new ProxyBusiness(handler).doSomeThing();   
  35.     new ProxyBusiness(handler).doSomeThing2();   
  36. }   
  37. }   

 

3.1.3 小结 
    从前两节的分析我们可以看出,动态代理在运行期通过接口动态生成代理类,这为其带来了一定的灵活性,但这个灵活性却带来了两个问题,第一代理类必须实现一个接口,如果没实现接口会抛出一个异常。第二性能影响,因为动态代理使用反射的机制实现的,首先反射肯定比直接调用要慢,经过测试大概每个代理类比静态代理多出10几毫秒的消耗。其次使用反射大量生成类文件可能引起Full GC造成性能影响,因为字节码文件加载后会存放在JVM运行时区的方法区(或者叫持久代)中,当方法区满的时候,会引起Full GC,所以当你大量使用动态代理时,可以将持久代设置大一些,减少Full GC次数。 

3.2 动态字节码生成 
   使用动态字节码生成技术实现AOP原理是在运行期间目标字节码加载后,生成目标类的子类,将切面逻辑加入到子类中,所以使用Cglib实现AOP不需要基于接口。

《【Spring】AOP的基本原理》

    本节介绍如何使用Cglib来实现动态字节码技术。Cglib是一个强大的,高性能的Code生成类库,它可以在运行期间扩展Java类和实现Java接口,它封装了Asm,所以使用Cglib前需要引入Asm的jar。 清单七:使用CGLib实现AOP

Java代码  

  1. public static void main(String[] args) {   
  2.         byteCodeGe();   
  3.     }   
  4.   
  5.     public static void byteCodeGe() {   
  6.         //创建一个织入器   
  7.         Enhancer enhancer = new Enhancer();   
  8.         //设置父类   
  9.         enhancer.setSuperclass(Business.class);   
  10.         //设置需要织入的逻辑   
  11.         enhancer.setCallback(new LogIntercept());   
  12.         //使用织入器创建子类   
  13.         IBusiness2 newBusiness = (IBusiness2) enhancer.create();   
  14.         newBusiness.doSomeThing2();   
  15.     }   
  16.   
  17.     /**  
  18.      * 记录日志  
  19.      */   
  20.     public static class LogIntercept implements MethodInterceptor {   
  21.   
  22.         @Override   
  23.         public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable {   
  24.             //执行原有逻辑,注意这里是invokeSuper   
  25.             Object rev = proxy.invokeSuper(target, args);   
  26.             //执行织入的日志   
  27.             if (method.getName().equals(“doSomeThing2”)) {   
  28.                 System.out.println(“记录日志”);   
  29.             }   
  30.             return rev;   
  31.         }   
  32.     }   

 

 

3.3 自定义类加载器 
   如果我们实现了一个自定义类加载器,在类加载到JVM之前直接修改某些类的方法,并将切入逻辑织入到这个方法里,然后将修改后的字节码文件交给虚拟机运行,那岂不是更直接。

 

《【Spring】AOP的基本原理》

Javassist是一个编辑字节码的框架,可以让你很简单地操作字节码。它可以在运行期定义或修改Class。使用Javassist实现AOP的原理是在字节码加载前直接修改需要切入的方法。这比使用Cglib实现AOP更加高效,并且没太多限制,实现原理如下图: 

《【Spring】AOP的基本原理》

    我们使用系统类加载器启动我们自定义的类加载器,在这个类加载器里加一个类加载监听器,监听器发现目标类被加载时就织入切入逻辑,咱们再看看使用Javassist实现AOP的代码: 
清单八:启动自定义的类加载器

Java代码  

  1. //获取存放CtClass的容器ClassPool   
  2. ClassPool cp = ClassPool.getDefault();   
  3. //创建一个类加载器   
  4. Loader cl = new Loader();   
  5. //增加一个转换器   
  6. cl.addTranslator(cp, new MyTranslator());   
  7. //启动MyTranslator的main函数   
  8. cl.run(“jsvassist.JavassistAopDemo$MyTranslator”, args);   

 清单九:类加载监听器

Java代码  

  1. public static class MyTranslator implements Translator {   
  2.   
  3.         public void start(ClassPool pool) throws NotFoundException, CannotCompileException {   
  4.         }   
  5.   
  6.         /* *  
  7.          * 类装载到JVM前进行代码织入  
  8.          */   
  9.         public void onLoad(ClassPool pool, String classname) {   
  10.             if (!“model$Business”.equals(classname)) {   
  11.                 return;   
  12.             }   
  13.             //通过获取类文件   
  14.             try {   
  15.                 CtClass  cc = pool.get(classname);   
  16.                 //获得指定方法名的方法   
  17.                 CtMethod m = cc.getDeclaredMethod(“doSomeThing”);   
  18.                 //在方法执行前插入代码   
  19.                 m.insertBefore(“{ System.out.println(\”记录日志\”); }”);   
  20.             } catch (NotFoundException e) {   
  21.             } catch (CannotCompileException e) {   
  22.             }   
  23.         }   
  24.   
  25.         public static void main(String[] args) {   
  26.             Business b = new Business();   
  27.             b.doSomeThing2();   
  28.             b.doSomeThing();   
  29.         }   
  30.     }   

 输出: 

Java代码  

  1. 执行业务逻辑2   
  2. 记录日志   
  3. 执行业务逻辑  

  
    其中Bussiness类在本文的清单一中定义。看起来是不是特别简单,CtClass是一个class文件的抽象描述。咱们也可以使用insertAfter()在方法的末尾插入代码,使用insertAt()在指定行插入代码。 

3.3.1 小结 
    从本节中可知,使用自定义的类加载器实现AOP在性能上要优于动态代理和Cglib,因为它不会产生新类,但是它仍然存在一个问题,就是如果其他的类加载器来加载类的话,这些类将不会被拦截。 

3.4 字节码转换 
    自定义的类加载器实现AOP只能拦截自己加载的字节码,那么有没有一种方式能够监控所有类加载器加载字节码呢?有,使用Instrumentation,它是 Java 5 提供的新特性,使用 Instrumentation,开发者可以构建一个字节码转换器,在字节码加载前进行转换。本节使用Instrumentation和javassist来实现AOP。 

3.4.1 构建字节码转换器 
    首先需要创建字节码转换器,该转换器负责拦截Business类,并在Business类的doSomeThing方法前使用javassist加入记录日志的代码。

Java代码  

  1. public class MyClassFileTransformer implements ClassFileTransformer {   
  2.   
  3.     /**  
  4.      * 字节码加载到虚拟机前会进入这个方法  
  5.      */   
  6.     @Override   
  7.     public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,   
  8.                             ProtectionDomain protectionDomain, byte[] classfileBuffer)   
  9.             throws IllegalClassFormatException {   
  10.         System.out.println(className);   
  11.         //如果加载Business类才拦截   
  12.         if (!“model/Business”.equals(className)) {   
  13.             return null;   
  14.         }   
  15.   
  16.         //javassist的包名是用点分割的,需要转换下   
  17.         if (className.indexOf(“/”) != –1) {   
  18.             className = className.replaceAll(“/”“.”);   
  19.         }   
  20.         try {   
  21.             //通过包名获取类文件   
  22.             CtClass cc = ClassPool.getDefault().get(className);   
  23.             //获得指定方法名的方法   
  24.             CtMethod m = cc.getDeclaredMethod(“doSomeThing”);   
  25.             //在方法执行前插入代码   
  26.             m.insertBefore(“{ System.out.println(\”记录日志\”); }”);   
  27.             return cc.toBytecode();   
  28.         } catch (NotFoundException e) {   
  29.         } catch (CannotCompileException e) {   
  30.         } catch (IOException e) {   
  31.             //忽略异常处理   
  32.         }   
  33.         return null;   
  34. }   

 

3.4.2 注册转换器 
    使用premain函数注册字节码转换器,该方法在main函数之前执行。

Java代码  

  1. public class MyClassFileTransformer implements ClassFileTransformer {   
  2.     public static void premain(String options, Instrumentation ins) {   
  3.         //注册我自己的字节码转换器   
  4.         ins.addTransformer(new MyClassFileTransformer());   
  5. }   
  6. }   

 

3.4.3 配置和执行 
    需要告诉JVM在启动main函数之前,需要先执行premain函数。首先需要将premain函数所在的类打成jar包。并修改该jar包里的META-INF\MANIFEST.MF 文件。 

Java代码  

  1. Manifest-Version: 1.0   
  2. Premain-Class: bci. MyClassFileTransformer  

     然后在JVM的启动参数里加上。-javaagent:D:\java\projects\opencometProject\Aop\lib\aop.jar 

             3.4.4 输出

    执行main函数,你会发现切入的代码无侵入性的织入进去了。

Java代码  

  1. public static void main(String[] args) {   
  2.    new Business().doSomeThing();   
  3.    new Business().doSomeThing2();   
  4. }   
  5.    

   输出

Java代码  

  1. model/Business   
  2. sun/misc/Cleaner   
  3. java/lang/Enum   
  4. model/IBusiness   
  5. model/IBusiness2   
  6. 记录日志   
  7. 执行业务逻辑   
  8. 执行业务逻辑2   
  9. java/lang/Shutdown   
  10. java/lang/Shutdown$Lock   

  

 从输出中可以看到系统类加载器加载的类也经过了这里。

 

4 AOP实战 
说了这么多理论,那AOP到底能做什么呢? AOP能做的事情非常多。

  • 性能监控,在方法调用前后记录调用时间,方法执行太长或超时报警。
  • 缓存代理,缓存某方法的返回值,下次执行该方法时,直接从缓存里获取。
  • 软件破解,使用AOP修改软件的验证类的判断逻辑。
  • 记录日志,在方法执行前后记录系统日志。
  • 工作流系统,工作流系统需要将业务代码和流程引擎代码混合在一起执行,那么我们可以使用AOP将其分离,并动态挂接业务。
  • 权限验证,方法执行前验证是否有权限执行当前方法,没有则抛出没有权限执行异常,由业务代码捕捉。 

4.1 Spring的AOP 
   Spring默认采取的动态代理机制实现AOP,当动态代理不可用时(代理类无接口)会使用CGlib机制。但Spring的AOP有一定的缺点,第一个只能对方法进行切入,不能对接口,字段,静态代码块进行切入(切入接口的某个方法,则该接口下所有实现类的该方法将被切入)。第二个同类中的互相调用方法将不会使用代理类。因为要使用代理类必须从Spring容器中获取Bean。第三个性能不是最好的,从3.3章节我们得知使用自定义类加载器,性能要优于动态代理和CGlib。 
可以获取代理类

Java代码  

  1. public IMsgFilterService getThis()   
  2. {   
  3.         return (IMsgFilterService) AopContext.currentProxy();   
  4. }   
  5.   
  6. public boolean evaluateMsg () {   
  7.    // 执行此方法将织入切入逻辑   
  8. return getThis().evaluateMsg(String message);   
  9. }   
  10.   
  11. @MethodInvokeTimesMonitor(“KEY_FILTER_NUM”)   
  12. public boolean evaluateMsg(String message) {   

 不能获取代理类

Java代码  

  1. public boolean evaluateMsg () {   
  2.    // 执行此方法将不会织入切入逻辑   
  3. return evaluateMsg(String message);   
  4. }   
  5.   
  6. @MethodInvokeTimesMonitor(“KEY_FILTER_NUM”)   
  7. public boolean evaluateMsg(String message) {   

 

总结

一个典型的动态代理创建对象过程可分为以下四个步骤:
1、通过实现InvocationHandler接口创建自己的调用处理器 IvocationHandler handler = new InvocationHandlerImpl(…);
2、通过为Proxy类指定ClassLoader对象和一组interface创建动态代理类
Class clazz = Proxy.getProxyClass(classLoader,new Class[]{…});
3、通过反射机制获取动态代理类的构造函数,其参数类型是调用处理器接口类型
Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});
4、通过构造函数创建代理类实例,此时需将调用处理器对象作为参数被传入
Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));
为了简化对象创建过程,Proxy类中的newInstance方法封装了2~4,只需两步即可完成代理对象的创建。
生成的ProxySubject继承Proxy类实现Subject接口,实现的Subject的方法实际调用处理器的invoke方法,而invoke方法利用反射调用的是被代理对象的的方法(Object result=method.invoke(proxied,args))

美中不足

诚然,Proxy已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持interface代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫Proxy。Java的继承机制注定了这些动态代理类们无法实现对class的动态代理,原因是多继承在Java中本质上就行不通。有很多条理由,人们可以否定对 class代理的必要性,但是同样有一些理由,相信支持class动态代理会更美好。接口和类的划分,本就不是很明显,只是到了Java中才变得如此的细化。如果只从方法的声明及是否被定义来考量,有一种两者的混合体,它的名字叫抽象类。实现对抽象类的动态代理,相信也有其内在的价值。此外,还有一些历史遗留的类,它们将因为没有实现任何接口而从此与动态代理永世无缘。如此种种,不得不说是一个小小的遗憾。但是,不完美并不等于不伟大,伟大是一种本质,Java动态代理就是佐例。

    原文作者:AOP
    原文地址: https://blog.csdn.net/cangchen/article/details/44944597
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞