• spring源码分析(二)Aop


    创建日期:2016.08.19

    修改日期:2016.08.20-2016.08.21

    交流QQ:992591601

    参考资料:《spring源码深度解析》、《spring技术内幕》、传值播客spring教学视频

                    http://www.cnblogs.com/xing901022/p/4264334.html

                    http://www.cnblogs.com/digdeep/p/4528353.html

    一,动态代理、java InvocationHandler实现、Cglib实现

            要了解动态代理,可阅读设计模式相关书籍,不难理解。这篇博文我简单解释,动态代理就是与静态代理相对应的,在程序运行过程中动态创建的代理类。代理类可以简单理解为一种对目标类的增强,之后我们要使用目标类的话,只需用代理类就可以了。

            实现动态代理有两种方式,其一是java自带的动态代理功能,另外就是使用Cglib库实现。前者的使用有一个必须的条件,那就是目标类必须实现接口。而后者的使用则是对前者的一种补充。

            假设我们有这样两个bean,其一为AlienFishServiceBean,不实现任何接口。

                                                        其一为FishServiceImpl,实现FishService接口。

            对于前者,我们需要使用Cglib来实现动态代理功能(示例代码,实现功能:当bean的fishName字段不为空时,才能调用该bean的方法):

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. package cn;  
    2.   
    3. import java.lang.reflect.Method;  
    4.   
    5. import cn.aop.service.AlienFishServiceBean;  
    6.   
    7. import net.sf.cglib.proxy.Enhancer;  
    8. import net.sf.cglib.proxy.MethodInterceptor;  
    9. import net.sf.cglib.proxy.MethodProxy;  
    10.   
    11. /** 
    12. * @ClassName: CGlibProxyFactory  
    13. * @Description: CGlibProxyFactory 
    14. * @author 无名 
    15. * @date 2016-8-14 17:31:48  
    16. * @version 1.0 
    17.  */  
    18. public class CGlibProxyFactory implements MethodInterceptor {  
    19.     private Object targetObject;  
    20.       
    21.     public Object createProxyIntance(Object targetObject) {  
    22.         this.targetObject = targetObject;  
    23.         Enhancer enhancer = new Enhancer();  
    24.         enhancer.setSuperclass(this.targetObject.getClass());  
    25.         enhancer.setCallback(this);  
    26.         return enhancer.create();  
    27.     }  
    28.   
    29.     public Object intercept(Object proxy, Method method, Object[] args,  
    30.             MethodProxy  methodProxy) throws Throwable {  
    31.         AlienFishServiceBean bean = (AlienFishServiceBean) this.targetObject;  
    32.         Object result = null;  
    33.         if(bean.getFishName()!=null) {  
    34.             result = methodProxy.invoke(targetObject, args);  
    35.         }  
    36.         return result;  
    37.     }  
    38. }  

              对于后者,我们可以使用java自带的动态代理功能(示例代码,实现功能:当bean的fishName字段不为空时,才能调用该bean的方法):

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. package cn;  
    2.   
    3. import java.lang.reflect.InvocationHandler;  
    4. import java.lang.reflect.Method;  
    5. import java.lang.reflect.Proxy;  
    6.   
    7. import cn.aop.service.FishService;  
    8. import cn.aop.service.FishServiceImpl;  
    9.   
    10. /** 
    11. * @ClassName: JDKProxyFactory  
    12. * @Description: JDKProxyFactory 
    13. * @author 无名 
    14. * @date 2016-8-13 下午11:55:31  
    15. * @version 1.0 
    16.  */  
    17. public class JDKProxyFactory implements InvocationHandler {  
    18.       
    19.     private Object targetObject;  
    20.       
    21.     public Object createInstance(Object targetObject) {  
    22.         this.targetObject = targetObject;  
    23.         return Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(),   
    24.                 this.targetObject.getClass().getInterfaces(), this);  
    25.     }  
    26.   
    27.     @Override  
    28.     public Object invoke(Object proxy, Method method, Object[] args)  
    29.             throws Throwable {  
    30.         FishService fs = (FishServiceImpl)targetObject;  
    31.         Object result = null;  
    32.         if(fs.getFishName() != null) {  
    33.             result = method.invoke(targetObject, args);  
    34.         }  
    35.         return result;  
    36.     }  
    37. }  


           上述两个代理方式使用时,先用代理类工厂创建对应代理类,然后使用代理类即可(此代理类是对目标类的代理,‘增强了’目标类的一些方面)。

    二,Spring Aop



           spring aop需要的jar包:

      org.springframework.aop-xxx.jar(spring),aopalliance-1.0.jar,aspectjrt.jar, aspectjweaver.jar,cglib.jar

           spring aop是在动态代理这种设计模式的基础之上的。  

           这里我说下aop的几个基本概念,都是基于我自己的理解,简单粗暴:

                  aspect:面,可以理解为一个事务,对该事务做对应处理。

                  pointcut:切入点,对应于面具体的切入的地方。

                  advice:spring定义了四个advice, BeforeAdvice, AfterAdvice, ThrowAdvice和DynamicIntroductionAdvice。在触及面和点的时候,根据配置,执行对应通知。

            

            spring aop的实现由两种,其一是配置文件方式,另外是注解方式。

            我们首先,用配置文件方式:

            目标类,接口和实现类:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. package cn.service;  
    2.   
    3. public interface FishService {  
    4.     public void say00();  
    5.     public void say01();  
    6. }  
    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. package cn.service.impl;  
    2.   
    3. import cn.service.FishService;  
    4.   
    5. public class FishServiceBean implements FishService {  
    6.   
    7.     public void say00() {  
    8.         System.out.println("I am fish 00");  
    9.     }  
    10.   
    11.     public void say01() {  
    12.         System.out.println("I am fish 01");  
    13.     }  
    14.   
    15. }  

             下面的类提供对应的advice

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. package cn.service;  
    2.   
    3. import org.aspectj.lang.ProceedingJoinPoint;  
    4. /** 
    5.  * 切面 
    6.  * 
    7.  */  
    8. public class MyInterceptor {      
    9.     public void doBefore() {  
    10.         System.out.println("before");  
    11.     }  
    12.   
    13.     public void doAfter() {  
    14.         System.out.println("after");  
    15.     }  
    16.       
    17.     public void doFinal() {  
    18.         System.out.println("final");  
    19.     }  
    20.       
    21.     public void doThrowing() {  
    22.         System.out.println("throwing");  
    23.     }  
    24.       
    25.     public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {  
    26.         System.out.println("进入方法");  
    27.         Object result = pjp.proceed();  
    28.         System.out.println("退出方法");  
    29.         return result;  
    30.     }  
    31.       
    32. }  


                配置文件(aop:config内设置aop,切面里设置切入点,及几种advice):

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. <?xml version="1.0" encoding="UTF-8"?>  
    2. <beans xmlns="http://www.springframework.org/schema/beans"  
    3.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
    4.        xmlns:context="http://www.springframework.org/schema/context"   
    5.        xmlns:aop="http://www.springframework.org/schema/aop"        
    6.        xsi:schemaLocation="http://www.springframework.org/schema/beans  
    7.            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd  
    8.            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd  
    9.            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">  
    10.         <aop:aspectj-autoproxy/>   
    11.         <bean id="fishService" class="cn.service.impl.FishServiceBean"></bean>  
    12.         <bean id="aspetbean" class="cn.service.MyInterceptor"/>  
    13.         <aop:config>  
    14.             <aop:aspect id="asp" ref="aspetbean">  
    15.                 <aop:pointcut id="mycut" expression="execution(* cn.service..*.*(..))"/>  
    16.                 <aop:before pointcut-ref="mycut" method="doBefore"/>  
    17.                 <aop:after-returning pointcut-ref="mycut" method="doFinal"/>  
    18.             <aop:after-throwing pointcut-ref="mycut" method="doThrowing"/>  
    19.             <aop:after pointcut-ref="mycut" method="doAfter"/>  
    20.             <aop:around pointcut-ref="mycut" method="doBasicProfiling"/>  
    21.             </aop:aspect>  
    22.         </aop:config>  
    23. </beans>  


              测试类:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. package junit.test;  
    2.   
    3. import org.junit.BeforeClass;  
    4. import org.junit.Test;  
    5. import org.springframework.context.ApplicationContext;  
    6. import org.springframework.context.support.ClassPathXmlApplicationContext;  
    7.   
    8. import cn.service.FishService;  
    9.   
    10. public class SpringAOPTest {  
    11.   
    12.     @BeforeClass  
    13.     public static void setUpBeforeClass() throws Exception {  
    14.     }  
    15.   
    16.     @Test public void interceptorTest(){  
    17.         ApplicationContext cxt = new ClassPathXmlApplicationContext("beans.xml");  
    18.         FishService fishService = (FishService)cxt.getBean("fishService");  
    19.         fishService.say00();  
    20.         fishService.say01();  
    21.     }  
    22. }  


               运行结果:



      

              与配置文件方式相对应的便是注解的方式

               注解方式只需在spring的xml文件里保留  <aop:aspectj-autoproxy/> 就可以了。

              而上面对应的MyInterceptor类需要多写相对的注解(著名切入点、advice等)。

    三,Spring源码分析

           首先自己设想一下spring实现aop的思路,大概是:1,获取、寻找所有bean,如果为AspectJ注解的类,则进行对应处理。

                                                                                            2,对标记为AspectJ注解的类进行增强器的提取(前文提到动态代理是对目标类的一种增强)

                                                                                            3,创建动态代理。

           首先看一个类AopNamespaceHandler,这里是aop注解对应的解析器:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. public class AopNamespaceHandler extends NamespaceHandlerSupport {  
    2.   
    3.     public void init() {  
    4.         // In 2.0 XSD as well as in 2.1 XSD.  
    5.         registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());  
    6.         registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());  
    7.         registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());  
    8.   
    9.         // Only in 2.0 XSD: moved to context namespace as of 2.1  
    10.         registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());  
    11.     }  
    12.   
    13. }  


            我们看registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());这一句,可知其内涵是向NamespaceHandlerSupport这一类的

             private final Map<String, BeanDefinitionParser> parsers =
    new HashMap<String, BeanDefinitionParser>();    这一map数据结构中注册对应解析器。

          此后 一旦遇到aspectj-autoproxy注解,便自然会从map中取到对应解析器,并使用解析器AspectJAutoProxyBeanDefinitionParser解析。AspectJAutoProxyBeanDefinitionParser是继承了BeanDefinitionParser接口的。

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. public BeanDefinition parse(Element element, ParserContext parserContext) {  
    2.     AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);  
    3.     extendBeanDefinition(element, parserContext);  
    4.     return null;  
    5. }  
    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(  
    2.         ParserContext parserContext, Element sourceElement) {  
    3.   
    4.     BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(  
    5.             parserContext.getRegistry(), parserContext.extractSource(sourceElement));  
    6.     useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);  
    7.     registerComponentIfNecessary(beanDefinition, parserContext);  
    8. }  

           首先看下进入该函数时参数的状态:

            sourceElement的name为aop:aspectj-autoproxy。这个函数就是专门用于注册aop:aspectj注解的。

            这个方法三句代码,各有其作用,分别是:1注册AnnotationAwareAspectJAutoProxyCreator(AOP的实现都靠这个),返回的BeanDefinition设置对应的AnnotationAwareAspectJAutoProxyCreator

                                                                                 2处理proxy-target-class(CgLib或jdk)以及expose-proxy属性

                                                                                 3注册组件并通知



           从spring的 DefaultAopProxyFactory类入手,该类继承自AopProxyFactory接口。

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. package org.springframework.aop.framework;  
    2.   
    3. /** 
    4.  * Interface to be implemented by factories that are able to create 
    5.  * AOP proxies based on {@link AdvisedSupport} configuration objects. 
    6.  * 
    7.  * <p>Proxies should observe the following contract: 
    8.  * <ul> 
    9.  * <li>They should implement all interfaces that the configuration 
    10.  * indicates should be proxied. 
    11.  * <li>They should implement the {@link Advised} interface. 
    12.  * <li>They should implement the equals method to compare proxied 
    13.  * interfaces, advice, and target. 
    14.  * <li>They should be serializable if all advisors and target 
    15.  * are serializable. 
    16.  * <li>They should be thread-safe if advisors and target 
    17.  * are thread-safe. 
    18.  * </ul> 
    19.  * 
    20.  * <p>Proxies may or may not allow advice changes to be made. 
    21.  * If they do not permit advice changes (for example, because 
    22.  * the configuration was frozen) a proxy should throw an  
    23.  * {@link AopConfigException} on an attempted advice change. 
    24.  * 
    25.  * @author Rod Johnson 
    26.  * @author Juergen Hoeller 
    27.  */  
    28. public interface AopProxyFactory {  
    29.   
    30.     /** 
    31.      * Create an {@link AopProxy} for the given AOP configuration. 
    32.      * @param config the AOP configuration in the form of an 
    33.      * AdvisedSupport object 
    34.      * @return the corresponding AOP proxy 
    35.      * @throws AopConfigException if the configuration is invalid 
    36.      */  
    37.     AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException;  
    38.   
    39. }  


              我们看最核心的createAopProxy方法:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {  
    2.     if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {  
    3.         Class targetClass = config.getTargetClass();  
    4.         if (targetClass == null) {  
    5.             throw new AopConfigException("TargetSource cannot determine target class: " +  
    6.                     "Either an interface or a target is required for proxy creation.");  
    7.         }  
    8.         if (targetClass.isInterface()) {  
    9.             return new JdkDynamicAopProxy(config);  
    10.         }  
    11.         if (!cglibAvailable) {  
    12.             throw new AopConfigException(  
    13.                     "Cannot proxy target class because CGLIB2 is not available. " +  
    14.                     "Add CGLIB to the class path or specify proxy interfaces.");  
    15.         }  
    16.         return CglibProxyFactory.createCglibProxy(config);  
    17.     }  
    18.     else {  
    19.         return new JdkDynamicAopProxy(config);  
    20.     }  
    21. }  

               这段代码逻辑很清晰,判断如果目标类实现接口则使用jdk创建代理,否则使用cglib。

               再仔细看这段代码,发现无论是jdk还是cglib,都要使用AdvisedSupport config这个参数。

               设置断点,查看该变量,发现果然信息量很大,对应的AdvisedSupport类代码我就不贴了,看下面截图也可以想象个大概了。

    具体看下advice的内容,我们配置文件里配置的内容都在里面了:

                 至于这个config是怎样生成的,其实很好想象,就是前面讲的,对应注解的解析,注册。

                 new JdkDynamicAopProxy(config)

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. /** 
    2.  * Construct a new JdkDynamicAopProxy for the given AOP configuration. 
    3.  * @param config the AOP configuration as AdvisedSupport object 
    4.  * @throws AopConfigException if the config is invalid. We try to throw an informative 
    5.  * exception in this case, rather than let a mysterious failure happen later. 
    6.  */  
    7. public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {  
    8.     Assert.notNull(config, "AdvisedSupport must not be null");  
    9.     if (config.getAdvisors().length == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {  
    10.         throw new AopConfigException("No advisors and no TargetSource specified");  
    11.     }  
    12.     this.advised = config;  
    13. }  

                 CglibProxyFactory.createCglibProxy(config)

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. /** 
    2.  * Create a new Cglib2AopProxy for the given AOP configuration. 
    3.  * @param config the AOP configuration as AdvisedSupport object 
    4.  * @throws AopConfigException if the config is invalid. We try to throw an informative 
    5.  * exception in this case, rather than let a mysterious failure happen later. 
    6.  */  
    7. public Cglib2AopProxy(AdvisedSupport config) throws AopConfigException {  
    8.     Assert.notNull(config, "AdvisedSupport must not be null");  
    9.     if (config.getAdvisors().length == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {  
    10.         throw new AopConfigException("No advisors and no TargetSource specified");  
    11.     }  
    12.     this.advised = config;  
    13.     this.advisedDispatcher = new AdvisedDispatcher(this.advised);  
    14. }  

            都是用config设置对应的advise属性。

            小小总结:最后我又单步调试一通,结合上一章节内容《spring源码分析(一)IoC、DI》,发现AOP代理类的创建时CreateBean这一阶段开始的,其实这时候就是对应目标类用代理类取代了。

            这是断点运行时,观察createBean返回的结果,可见创建的fishService bean实质上是JdkDynamicAopProxy代理类。在那之后我们使用fishService实际上就是使用对应的JdkDynamicAopProxy代理类了,之前注册的那些advice也就生效了:

            (在这一过程中值得一提的还有AbstractAutoProxyCreator的postProcessAfterInitialization方法,正如注释所说,作用是Create a proxy with the configured interceptors if   the bean is  identified as one to proxy by the subclass.

                                                               AnnotationAwareAspectJAutoProxyCreator的findCandidateAdvisors方法,作用是获取增强器)

  • 相关阅读:
    maven安装以及eclipse配置maven
    jquery 图片文件转base64 显示
    Java 解析Excel文件为JSON
    Dropwizard框架入门
    使用Spring Security和OAuth2实现RESTful服务安全认证
    SQL语句大小写是否区分的问题,批量修改整个数据库所有表所有字段大小写
    13个可实现超棒数据可视化效果的Javascript框架
    C#创建数字证书并导出为pfx,并使用pfx进行非对称加解密
    C#使用RSA证书文件加密和解密示例
    C# 中使用 RSA加解密算法
  • 原文地址:https://www.cnblogs.com/rixiang/p/5792317.html
Copyright © 2020-2023  润新知