Spring註解(三)——————SpringAOP原理

關注微信公衆號【行走在代碼行的尋路人】獲取Java相關資料,分享項目經驗及知識乾貨。

以下測試的源碼地址:https://github.com/877148107/Spring-Annotation

  • SpringAOP

AOP面向切面編程【動態代理】指在程序運行期間動態的將某段代碼切入到指定方法指定位置進行運行的編程方式

AOP案例如下:

在業務邏輯除法運行的時候打印相關日誌信息

通知方法:
 *             前置通知(@Before):logStart:在目標方法(div)運行之前運行
 *             後置通知(@After):logEnd:在目標方法(div)運行結束之後運行(無論方法正常結束還是異常結束)
 *             返回通知(@AfterReturning):logReturn:在目標方法(div)正常返回之後運行
 *             異常通知(@AfterThrowing):logException:在目標方法(div)出現異常以後運行
 *             環繞通知(@Around):動態代理,手動推進目標方法運行(joinPoint.procced())

AOP實現的三步:

*      1)、將業務邏輯組件和切面類都加入到容器中;告訴Spring哪個是切面類(@Aspect)
 *     2)、在切面類上的每一個通知方法上標註通知註解,告訴Spring何時何地運行(切入點表達式)
 *     3)、開啓基於註解的aop模式;@EnableAspectJAutoProxy

/**
 * @ClassName: LogAspects
 * =================================================
 * @Description: AOP切面類
 *  @Aspect告訴Spring這是一個切面類
 * =================================================
 * CreateInfo:
 * @Author: William.Wangmy
 * @CreateDate: 2019/11/24 22:20
 * @Version: V1.0
 */
@Aspect
public class LogAspects {

    /**
     * 抽取公共的切入點
     * 如果在本類引入的話直接寫pointCut()即可
     */
    @Pointcut("execution(public int com.spring.annotation.aop.MyCalculate.*(..))")
    public void pointCut(){

    }

    /**
     * MyCalculate.division運行之前的監控
     * 前置通知
     */
    @Before("pointCut()")
    public void startLog(JoinPoint joinPoint){
        Signature signature = joinPoint.getSignature();
        Object[] args = joinPoint.getArgs();
        System.out.println(signature.getName()+"開始。。。。。。參數:"+ Arrays.asList(args));
    }

    /**
     * MyCalculate.division運行結束的監控
     * 後置通知
     */
    @After("pointCut()")
    public void endLog(JoinPoint joinPoint){
        Signature signature = joinPoint.getSignature();
        System.out.println(signature.getName()+"結束。。。。。。");
    }

    /**
     * MyCalculate.division運行返回監控
     * 返回通知
     */
    @AfterReturning(value = "pointCut()",returning = "reslut")
    public void returnLog(JoinPoint joinPoint,Object reslut){
        Signature signature = joinPoint.getSignature();
        System.out.println(signature.getName()+"MyCalculate.division返回結果。。。。。。"+reslut);
    }

    /**
     * 運行異常的監控
     * 異常通知
     */
    @AfterThrowing(value = "pointCut()",throwing = "exception")
    public void exceptionLog(JoinPoint joinPoint,Exception exception){
        Signature signature = joinPoint.getSignature();
        System.out.println(signature.getName()+"異常。。。。。。"+exception);
    }
}

/**
 * @ClassName: MyAopConfiguration
 * =================================================
 * @Description: AOP的一個配置類
 * AOP面向切面編程【動態代理】
 *              指在程序運行期間動態的將某段代碼切入到指定方法指定位置進行運行的編程方式
 *  AOP的通知方法:前置通知startLog(註解方式:@Before)
 *               後置通知endLog(註解方式:@After)
 *               返回通知returnLog(註解方式:@AfterReturning)
 *               異常通知exceptionLog(註解方式:@AfterThrowing)
 *               環繞通知:動態代理,手動推進目標方法運行(joinPoint.procced())(註解方式:@Around)
 * @Aspect:告訴Spring這是一個切面類,而不是普通的類
 * @EnableAspectJAutoProxy:開啓基於註解的APO模式,在Spring中還有許多@Enablexxxx開啓某一項功能
 *
 *
 * =================================================
 * CreateInfo:
 * @Author: William.Wangmy
 * @CreateDate: 2019/11/22 23:22
 * @Version: V1.0
 */
@EnableAspectJAutoProxy
@Configuration
public class MyAopConfiguration {

    @Bean
    public MyCalculate myCalculate(){
        return new MyCalculate();
    }

    @Bean
    public LogAspects logAspects(){
        return new LogAspects();
    }
}
/**
 * @ClassName: MyCalculate
 * =================================================
 * @Description: AOP業務邏輯測試類
 * =================================================
 * CreateInfo:
 * @Author: William.Wangmy
 * @CreateDate: 2019/11/24 22:18
 * @Version: V1.0
 */
public class MyCalculate {

    public int division(int i,int j){
        return i/j;
    }
}
  • SpringAOP的原理

主要是看給容器中註冊了什麼組件,這個組件什麼時候工作,這個組件的功能是什麼?

主要入口@EnableAspectJAutoProxy,引入@Import(AspectJAutoProxyRegistrar.class) 利用AspectJAutoProxyRegistrar自定義給容器中註冊Bean:beanDefinition,bean名稱:internalAutoProxyCreator,bean類:org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator:註解切面的自動代理創建器

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

	/**
	 * Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
	 * to standard Java interface-based proxies. The default is {@code false}.
	 */
	boolean proxyTargetClass() default false;

	/**
	 * Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal}
	 * for retrieval via the {@link org.springframework.aop.framework.AopContext} class.
	 * Off by default, i.e. no guarantees that {@code AopContext} access will work.
	 * @since 4.3.1
	 */
	boolean exposeProxy() default false;

}

  • AnnotationAwareAspectJAutoProxyCreator的功能分析

*          父類=>AnnotationAwareAspectJAutoProxyCreator
 *              父類=>AspectJAwareAdvisorAutoProxyCreator
 *                  父類=>AbstractAdvisorAutoProxyCreator
 *                      父類=>AbstractAutoProxyCreator 實現了implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware
 *                          父類=>ProxyProcessorSupport 實現了implements Ordered, BeanClassLoaderAware, AopInfrastructureBean
 *          這個類主要實現了一個bean的後置處理器和一個BeanFactoryAware
 *          SmartInstantiationAwareBeanPostProcessor:bean的後置處理器
 *          BeanFactoryAware:可以將bean工廠傳進來,自動裝備beanfactory

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
		implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
        .......
}
  •  分析AbstractAutoProxyCreator關於後置處理器 裝配bean前後的方法,和自動裝備beanfactory的方法

 * AbstractAutoProxyCreator:
 *      AbstractAutoProxyCreator=>setBeanFactory()
 *      AbstractAutoProxyCreator=>postProcessBeforeInstantiation()
 *      AbstractAutoProxyCreator=>postProcessAfterInitialization()
 * AbstractAdvisorAutoProxyCreator:
 *      AbstractAdvisorAutoProxyCreator=>setBeanFactory()重寫了父類的方法,並且調用了initBeanFactory()
 * AnnotationAwareAspectJAutoProxyCreator:
 *      AnnotationAwareAspectJAutoProxyCreator=>initBeanFactory()重寫了父類的方法

  • AOP-BeanPostProcessors流程

 *      1.傳入配置類創建IOC容器
 *      2.註冊配置類,然後調用刷新方法刷新容器


 *      3.registerBeanPostProcessors(beanFactory);註冊bean的後置處理器來方便攔截bean的創建;


 *          1).先獲取ioc容器已經定義了的需要創建對象的所有BeanPostProcessor


 *          2).給容器中加別的BeanPostProcessor


 *          3).優先註冊實現了PriorityOrdered接口的BeanPostProcessor,


 *          4).再給容器中註冊實現了Ordered接口的BeanPostProcessor,AbstractAutoProxyCreator就實現Ordered接口
 *              org.springframework.aop.config.internalAutoProxyCreator


 *          5).註冊沒實現優先級接口的BeanPostProcessor;


 *          6).註冊BeanPostProcessor,實際上就是創建BeanPostProcessor對象,保存在容器中
 *             1.創建internalAutoProxyCreator的Processor(AnnotationAwareAspectJAutoProxyCreator)


 *             2.populateBean;給bean的各種屬性賦值
 *             3.initializeBean:初始化bean,跟前面beanPostProcessor初始化一樣


 *                 invokeAwareMethods():處理Aware接口的方法回調
 *                 applyBeanPostProcessorsBeforeInitialization():應用後置處理器的postProcessBeforeInitialization()
 *                 invokeInitMethods();執行自定義的初始化方法
 *                 applyBeanPostProcessorsAfterInitialization();執行後置處理器的postProcessAfterInitialization();


 *             4.調用了AnnotationAwareAspectJAutoProxyCreator.initBeanFactory將BeanPostProcessor(AnnotationAwareAspectJAutoProxyCreator)創建成功
 *                並且得到了aspectJAdvisorFactory通知工廠


 *          7).把BeanPostProcessor註冊到BeanFactory中;beanFactory.addBeanPostProcessor(postProcessor)

***********************上面是創建和註冊AnnotationAwareAspectJAutoProxyCreator的過程************
*AnnotationAwareAspectJAutoProxyCreator是InstantiationAwareBeanPostProcessor這種類型的後置處理器


4.finishBeanFactoryInitialization:完成BeanFactory初始化工作,創建剩下的單實例bean
*          1).遍歷獲取容器中所有的Bean,依次創建對象getBean(beanName)


*              getBean(beanName)->doGetBean()->getSingleton()


2).創建bean:AnnotationAwareAspectJAutoProxyCreator在所有bean創建之前會有一個攔截
*                因爲繼承了這個後置處理器InstantiationAwareBeanPostProcessor,會調用postProcessBeforeInstantiation()
*              【BeanPostProcessor是在Bean對象創建完成初始化前後調用的】
*              【InstantiationAwareBeanPostProcessor是在創建Bean實例之前先嚐試用後置處理器返回對象的】

*              1.先從緩存中獲取當前bean,如果能獲取到說明bean在之前被創建過,然後就直接使用,否則再創建。創建好的bean都會放入到緩存中
*                  後置處理器先去嘗試返回一個對象:
*                  bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
*                  拿到所有的後置處理器,如果是InstantiationAwareBeanPostProcessor,就執行postProcessBeforeInstantiation
*                 if (bean != null) {
*                    bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
*                   }

2.創建bean:createBean()AnnotationAwareAspectJAutoProxyCreator這個會在任何bean創建之前返回bean實例
*                  Object bean = resolveBeforeInstantiation(beanName, mbdToUse)解析BeforeInstantiation,後置處理器在此能夠返回一個代理對象;
*                  如果能返回就是使用,如果不能返回就調用doCreateBean(beanName, mbdToUse, args)去創建一個bean實例和上面創建internalAutoProxyCreator的Processor的流程一樣。


AnnotationAwareAspectJAutoProxyCreator是InstantiationAwareBeanPostProcessor這種類型的後置處理器,其作用:


* 關注:MyCalculate和LogAspects的bean創建
*        1.在每個bean創建之前,調用postProcessBeforeInstantiation()


2.判斷當前bean是否在advisedBeans中(保存了所有需要增強bean:就是myCalculate的業務邏輯,需要切面進行切的。不能按照之前簡單的bean需要增強)


3.判斷當前bean是不是基礎類型的或者是不是切面
*            基礎類型:Advice、Pointcut、Advisor、AopInfrastructureBean
*            切面:是不是@Aspect註解


*        4.是否需要跳過
*            獲取候選的增強器(切面裏面的通知方法)
*            每一個封裝的通知方法的增強器是 InstantiationModelAwarePointcutAdvisor;
*            判斷每一個增強器是否是 AspectJPointcutAdvisor 類型的;返回true,
*        5.永遠返回false


*  MyCalculate創建完後:會調用postProcessAfterInitialization()
*        return wrapIfNecessary(bean, beanName, cacheKey);如果需要的情況下進行包裝


1.獲取當前bean的所有增強器(通知方法)
*          1).找到候選的所有的增強器(找哪些通知方法是需要切入當前bean方法的)
*          2).根據List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);找到能在當前bean使用並返回能用的增強器


3).給增強器排序


2.獲取到所有的增強器Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(),並將當前bean保存到緩存中advisedBeans.put(cacheKey, Boolean.TRUE)


1).創建代理對象


 2).保存到代理工廠proxyFactory中


 3).創建代理對象:Spring自動決定
*              JdkDynamicAopProxy(config);jdk動態代理;
*              ObjenesisCglibAopProxy(config);cglib的動態代理;


4)、給容器中返回當前組件使用cglib增強了的代理對象;
5)、以後容器中獲取到的就是這個組件的代理對象,執行目標方法的時候,代理對象就會執行通知方法的流程;

如果要執行MyCalculate的業務邏輯,就是使用cglib增強了的代理對象,調用提交保存的那些通知方法

  • 目標方法MyCalculate.division的執行:

容器中保存了組件的代理對象:CGLIB增強後的對象,這個對象裏面包含了增強器、目標對象等詳細信息
 *      1.CglibAopProxy.intercept();攔截目標方法的執行


 *      2.根據ProxyFactory對象獲取將要執行目標方法的攔截器鏈
 *          攔截器鏈:把每一個通知方法包裝成攔截器,利用MethodInterceptor機制
 *          List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);


 *          1).List<Object> interceptorList保存所有的攔截器,list長度爲5:一個默認的和四個增強器


 *          2).遍歷所有的增強器並轉換成Interceptor
 *              registry.getInterceptors(advisor)


 *          3).將增強器轉換成List<MethodInterceptor>
 *                  如果是MethodInterceptor,直接加入到集合中
 *                  如果不是,使用AdvisorAdapter將增強器轉爲MethodInterceptor;
 *                  轉換完成返回MethodInterceptor數組;


 *                  AspectJAfterThrowingAdvice和AspectJAfterAdvice是經過轉換成爲了攔截器,其他三個本身就是攔截器
 *      3.如果沒有攔截鏈直接執行目標方法
 *      4.如果有攔截器鏈,把需要執行的目標對象,目標方法,攔截器鏈等詳細傳入創建一個CglibMethodInvocation對象並調用。
 *      retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();


 *      5.攔截器鏈觸發的過程:
 *          1).如果沒有攔截器執行執行目標方法,或者攔截器的索引和攔截器數組-1大小一樣(指定到了最後一個攔截器)執行目標方法;


 *          2)、鏈式獲取每一個攔截器,攔截器執行invoke方法,每一個攔截器等待下一個攔截器執行完成返回以後再來執行;
 *                  攔截器鏈的機制,保證通知方法與目標方法的執行順序;
 *              MethodBeforeAdviceInterceptor:


 *              AspectJAfterAdvice:


 *              AspectJAfterThrowingAdvice:


 *              AfterReturningAdviceInterceptor:

  • AOP通知方法執行流程

  • AOP總結

1.@EnableAspectJAutoProxy開啓AOP功能
2.@EnableAspectJAutoProxy中導入組件@Import(AspectJAutoProxyRegistrar.class)註冊Bean,AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
3.bean名稱:org.springframework.aop.config.internalAutoProxyCreator,bean類:class org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator
4.AnnotationAwareAspectJAutoProxyCreator是一個後置處理器。將這個後置處理器註冊到容器中
5.註冊後置處理器到bean工廠BeanFactory,實際上就是創建一個BeanProcessor對象放在容器中
    5.1 獲取所有的後置處理,判斷是否實現了相應的接口分別裝載到不同的list裏面
    5.2 AnnotationAwareAspectJAutoProxyCreator首先從BeanFactory中獲取,如果能獲取到就加入對應的list中,如果不能獲取到就去實例化這個bean
        5.2.1 populateBean(beanName, mbd, instanceWrapper);給bean賦值
        5.2.2 initializeBean(beanName, exposedObject, mbd);初始化bean
        5.2.3 invokeAwareMethods(beanName, bean);處理Aware接口的方法回調
        5.2.4 applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);調用後置處理器初始化之前的方法
        5.2.5 invokeInitMethods(beanName, wrappedBean, mbd);調用後置處理器初始化的方法
        5.2.6 applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);調用後置處理器初始化之後的方法
        5.2.7 返回初始化的後置處理器BeanProcessor
    5.3 beanFactory.addBeanPostProcessor(postProcessor);把BeanPostProcessor註冊到BeanFactory中
6.完成BeanFactory初始化工作,創建剩下的單實例bean。關注業務處理bean和切面類的bean
    6.1 Object sharedInstance = getSingleton(beanName);檢查緩存中是否存在myCalculate實例bean,存在就直接獲取,不存在就實例化這個bean,跟5.2邏輯一樣,
    6.2 MyCalculate創建完後:會調用postProcessAfterInitialization()
        6.2.1 Object cacheKey = getCacheKey(beanClass, beanName);先從緩存中獲取bean
        6.2.2 this.advisedBeans.containsKey(cacheKey);判斷當前bean是否在advisedBeans中(保存了所有需要增強bean:就是myCalculate的業務邏輯,需要切面進行切的。不能按照之前簡單的bean需要增強)
        6.2.3 isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName);判斷當前bean是不是基礎類型的或者是不是切面,基礎類型:Advice、Pointcut、Advisor、AopInfrastructureBean,切面:是不是@Aspect註解
        6.2.4 獲取所有的增強器
            1). 找到候選的所有的增強器(找哪些通知方法是需要切入當前bean方法的)
            2). 根據List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);找到能在當前bean使用並返回能用的增強器
            3). 給增強器(通知方法)排序,後面執行增強器是會有順序執行
            4). 獲取到所有的增強器Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(),並將當前bean保存到緩存中advisedBeans.put(cacheKey, Boolean.TRUE)
            5). 創建一個代理對象,保存到代理工廠proxyFactory中:代理對象Spring自動決定;JdkDynamicAopProxy(config);jdk動態代理;ObjenesisCglibAopProxy(config);cglib的動態代理;
        
    6.3 logAspects創建
        6.7.1 跟上面6.2創建一樣,但是這個類是標記了@Aspect註解是一個切面類,將這個切面類放入緩存中
        6.7.1 調用postProcessAfterInitialization()是首先從緩存中獲取,上面將這個切面類放入了緩存中所以這裏獲取到了直接放回
7.以上步驟利用AnnotationAwareAspectJAutoProxyCreator後置處理將myCalculate和logAspects單實例bean創建完成並且闖將了代理對象放入緩存中
8.目標業務方法執行:
    8.1 CglibAopProxy.intercept();攔截目標方法的執行
    8.2 根據ProxyFactory對象獲取將要執行目標方法的攔截器鏈.攔截器鏈:把每一個通知方法包裝成攔截器,利用MethodInterceptor機制
           List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        8.2.1 獲取五個增強器。遍歷所有的增強器並轉換成Interceptor。registry.getInterceptors(advisor)
        8.2.2 這裏 AspectJAfterThrowingAdvice和AspectJAfterAdvice是經過轉換成爲了攔截器,其他三個本身就是攔截器
        8.2.3 如果有攔截器鏈,把需要執行的目標對象,目標方法,攔截器鏈等詳細傳入創建一個CglibMethodInvocation對象並調用。
                   retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
        8.2.4 攔截器鏈觸發的過程:
 *          1).如果沒有攔截器執行執行目標方法,或者攔截器的索引和攔截器數組-1大小一樣(指定到了最後一個攔截器)執行目標方法;
            2).這裏6.2.4中第三點的時候給增強器進行了排序這裏觸發會按照上面排序進行觸發
            MethodBeforeAdviceInterceptor:分別調用切面類對應註解的方法startLog
            AspectJAfterAdvice:分別調用切面類對應註解的方法endLog
            AspectJAfterThrowingAdvice:分別調用切面類對應註解的方法exceptionLog(
            AfterReturningAdviceInterceptor:分別調用切面類對應註解的方法returnLog
    8.3 詳細執行流程見如上圖AOP通知方法執行流程

    

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章