<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Spring AOP 再回首

          共 21361字,需瀏覽 43分鐘

           ·

          2021-07-31 00:56

          點擊藍色“JavaKeeper”關(guān)注我喲
          加個“星標”,一起成長,做牛逼閃閃的技術(shù)人

          Keeper導(dǎo)讀:最近在做業(yè)務(wù)操作日志相關(guān)的項目,剛好用的是AOP相關(guān)內(nèi)容,就又整理了下之前的筆記,看某免費教學(xué)視頻記錄下的內(nèi)容,入門篇~

          一、AOP 前奏

          假設(shè)我們原本有個計算器的程序,現(xiàn)在有這樣的需求:

          1. 在程序執(zhí)行期間記錄日志
          2. 希望計算器只處理正數(shù)運算

          啥也不說了,悶頭干~

          public interface ArithmeticCalculator {
              void add(int i,int j);
              void sub(int i,int j);
          }
          public void add(int i, int j) {
                  if(i < 0 || j < 0){
                      throw new IllegalArgumentException("Positive numbers only");
                  }
                  System.out.println("The method add begins with " + i + "," + j);
                  int result =  i + j;
                  System.out.println("result: "+ result);
                  System.out.println("The method add ends with " + i + "," + j);
              }

              public void sub(int i, int j) {
                  if(i < 0 || j < 0){
                      throw new IllegalArgumentException("Positive numbers only");
                  }
                  System.out.println("The method sub begins with " + i + "," + j);
                  int result =  i - j;
                  System.out.println("result: "+ result);
                  System.out.println("The method sub ends with " + i + "," + j);
              }

          問題

          這么一通干,存在的問題:

          • 代碼混亂:越來越多的非業(yè)務(wù)需求(日志和驗證等)加入后,原有的業(yè)務(wù)方法急劇膨脹。每個方法在處理核心邏輯的同時還必須兼顧其他多個關(guān)注點
          • 代碼分散:以日志需求為例,只是為了滿足這個單一需求,就不得不在多個模塊(方法)里多次重復(fù)相同的日志代碼。如果日志需求發(fā)生變化,必須修改所有模塊

          解決

          這里就可以用動態(tài)代理來解決。可以參考之前的文章: 《面試官問 Spring AOP 中兩種代理模式的區(qū)別,我懵逼了》

          代理設(shè)計模式的原理:使用一個代理將對象包裝起來,然后用該代理對象取代原始對象。任何對原始對象的調(diào)用都要通過代理。代理對象決定是否以及何時將方法調(diào)用轉(zhuǎn)到原始對象上。

          1、我們用 JDK 動態(tài)代理來實現(xiàn)日志功能

          JDK 動態(tài)代理主要涉及到 java.lang.reflect 包中的兩個類:Proxy 和 InvocationHandler。

          InvocationHandler 是一個接口,通過實現(xiàn)該接口定義橫切邏輯,并通過反射機制調(diào)用目標類的代碼,動態(tài)將橫切邏輯和業(yè)務(wù)邏輯編制在一起。

          Proxy 利用 InvocationHandler 動態(tài)創(chuàng)建一個符合某一接口的實例,生成目標類的代理對象。

          原理是使用反射機制。

          public class LoggingHandler implements InvocationHandler {

              /**
               * 被代理的目標對象
               */

              private Object proxyObj;

              public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                  System.out.println("The method " + method.getName() + " begins with " + Arrays.toString(args));
                  Object result = method.invoke(proxyObj,args);
                  System.out.println("The method " + method.getName() + " ends with " + Arrays.toString(args));
                  return result;
              }

              public Object createProxy(Object proxyObj){
                  this.proxyObj = proxyObj;
                  //返回一個代理對象
                  return Proxy.newProxyInstance(proxyObj.getClass().getClassLoader(),
                          proxyObj.getClass().getInterfaces(),this);
              }
          }

          2、再用 CGLib 動態(tài)代理實現(xiàn)校驗功能

          CGLIB 全稱為 Code Generation Library,是一個強大的高性能,高質(zhì)量的代碼生成類庫,可以在運行期擴展 Java 類與實現(xiàn) Java 接口,CGLib 封裝了 asm,可以再運行期動態(tài)生成新 的 class。

          和 JDK 動態(tài)代理相比較:JDK 創(chuàng)建代理有一個限制,就是只能為接口創(chuàng)建代理實例, 而對于沒有通過接口定義業(yè)務(wù)方法的類,則可以通過 CGLIB 創(chuàng)建動態(tài)代理。

          需要導(dǎo)入 cglib-nodep-***.jar

          public class ValidationHandler implements MethodInterceptor {

              /**
               * 被代理的目標對象
               */

              private Object targetObject;


              public Object createProxy(Object targetObject){
                  this.targetObject = targetObject;
                  Enhancer enhancer = new Enhancer();
                  //設(shè)置代理目標
                  enhancer.setSuperclass(targetObject.getClass());
                  //設(shè)置回調(diào)
                  enhancer.setCallback(this);
                  return enhancer.create();
              }

              /**
               * 在代理實例上處理方法調(diào)用并返回結(jié)果
               * @param o : 代理類
               * @param method :被代理的方法
               * @param objects :該方法的參數(shù)數(shù)組
               * @param methodProxy
               */

              public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                  Object result = null;

                  for (Object object : objects) {
                      if(Integer.parseInt(object.toString()) < 0){
                          throw new IllegalArgumentException("Positive numbers only");
                      }
                  }
                  //執(zhí)行目標對象的方法
                  result = methodProxy.invoke(targetObject,objects);
                  return result;
              }
          }

          3、使用

          public static void main(String[] args) {

               ValidationHandler validationHandler = new ValidationHandler();
               LoggingHandler loggingHandler = new LoggingHandler();

               //cglib 要求目標對象是個單獨的對象
               ArithmeticCalculatorImpl calculatorImpl = new ArithmeticCalculatorImpl();
               validationHandler.createProxy(calculatorImpl);

               //JDK 動態(tài)代理要求目標對象是接口
               ArithmeticCalculator calculator = new ArithmeticCalculatorImpl();
               ArithmeticCalculator loggingProxy = (ArithmeticCalculator) loggingHandler.createProxy(calculator);
               loggingProxy.add(1,2);

               ArithmeticCalculatorImpl validationProxy = (ArithmeticCalculatorImpl) validationHandler.createProxy(calculatorImpl);
               validationProxy.sub(-1,2);
             }

          二、AOP

          • AOP(Aspect-Oriented Programming,面向切面編程):是一種新的方法論,是對傳統(tǒng) OOP(Object-Oriented Programming,面向?qū)ο缶幊? 的補充

          • AOP 的主要編程對象是切面(aspect),而切面模塊化橫切關(guān)注點

          • 在應(yīng)用 AOP 編程時,仍然需要定義公共功能,但可以明確的定義這個功能在哪里,以什么方式應(yīng)用,并且不必修改受影響的類。這樣一來橫切關(guān)注點就被模塊化到特殊的對象(切面)里

          • AOP 的好處:

            • 每個事物邏輯位于一個位置,代碼不分散,便于維護和升級

            • 業(yè)務(wù)模塊更簡潔,只包含核心業(yè)務(wù)代

          AOP 核心概念

          • 切面(aspect):類是對物體特征的抽象,切面就是對橫切關(guān)注點的抽象

          • 橫切關(guān)注點:對哪些方法進行攔截,攔截后怎么處理,這些關(guān)注點稱之為橫切關(guān)注點

          • 連接點(joinpoint):程序執(zhí)行的某個特定位置:如類某個方法調(diào)用前、調(diào)用后、方法拋出異常后等。連接點由兩個信息確定:方法表示的程序執(zhí)行點;相對點表示的方位。例如 ArithmethicCalculator#add() 方法執(zhí)行前的連接點,執(zhí)行點為 ArithmethicCalculator#add();方位為該方法執(zhí)行前的位置。

            被攔截到的點,因為 Spring 只支持方法類型的連接點,所以在 Spring 中連接點指的就是被攔截到的方法,實際上連接點還可以是字段或者構(gòu)造器

          • 切入點(pointcut):每個類都擁有多個連接點,例如 ArithmethicCalculator 的所有方法實際上都是連接點,即連接點是程序類中客觀存在的事務(wù)。AOP 通過切點定位到特定的連接點。類比:連接點相當于數(shù)據(jù)庫中的記錄,切點相當于查詢條件。切點和連接點不是一對一的關(guān)系,一個切點匹配多個連接點,切點通過 org.springframework.aop.Pointcut 接口進行描述,它使用類和方法作為連接點的查詢條件。

          • 通知(advice):所謂通知指的就是指攔截到連接點之后要執(zhí)行的代碼,通知分為前置、后置、 異常、最終、環(huán)繞通知五類

          • 目標對象(Target):代理的目標對象

          • 代理(Proxy):向目標對象應(yīng)用通知之后創(chuàng)建的對象

          • 織入(weave):將切面應(yīng)用到目標對象并導(dǎo)致代理對象創(chuàng)建的過程

          • 引入(introduction):在不修改代碼的前提下,引入可以在運行期為類動態(tài)地添加一些方法或字段。

          "橫切"的技術(shù),剖解開封裝的對象內(nèi)部,并將那些影響了多個類的公共行為封裝到一個可重用模塊, 并將其命名為"Aspect",即切面。所謂"切面",簡單說就是那些與業(yè)務(wù)無關(guān),卻為業(yè)務(wù)模塊所共同調(diào)用的邏輯或責(zé)任封裝起來,便于減少系統(tǒng)的重復(fù)代碼,降低模塊之間的耦合度,并有利于未來的可操作性和可維護性。

          使用"橫切"技術(shù),AOP 把軟件系統(tǒng)分為兩個部分:核心關(guān)注點橫切關(guān)注點。業(yè)務(wù)處理的主要流程是核心關(guān)注點,與之關(guān)系不大的部分是橫切關(guān)注點。

          橫切關(guān)注點的一個特點是,他們經(jīng)常發(fā)生在核心關(guān)注點的多處,而各處基本相似,比如權(quán)限認證、日志、事物。

          AOP 的作用在于分離系統(tǒng)中的各種關(guān)注點,將核心關(guān)注點和橫切關(guān)注點分離開來。

          AOP 主要應(yīng)用場景有:

          • Authentication 權(quán)限
          • Caching 緩存
          • Context passing 內(nèi)容傳遞
          • Error handling 錯誤處理
          • Lazy loading 懶加載
          • Debugging 調(diào)試
          • logging, tracing, profiling and monitoring 記錄跟蹤、優(yōu)化、校準
          • Performance optimization 性能優(yōu)化
          • Persistence 持久化
          • Resource pooling 資源池
          • Synchronization 同步
          • Transactions 事務(wù)

          三、Spring AOP

          • AspectJ:Java 社區(qū)里最完整最流行的 AOP 框架
          • 在 Spring2.0 以上版本中,可以使用基于 AspectJ 注解或基于 XML 配置的 AOP

          在 Spring 中啟用 AspectJ 注解支持

          • 要在 Spring 應(yīng)用中使用 AspectJ 注解,必須在 classpath 下包含 AspectJ 類庫:

            aopalliance.jar、aspectj.weaver.jar 和 spring-aspects.jar

          • 將 aop Schema 添加到 <beans> 根元素中

          • 要在 Spring IOC 容器中啟用 AspectJ 注解支持,只要在 Bean 配置文件中定義一個空的 XML 元素 <aop:aspectj-autoproxy>

          • 當 Spring IOC 容器偵測到 Bean 配置文件中的 <aop:aspectj-autoproxy> 元素時,會自動為與 AspectJ 切面匹配的 Bean 創(chuàng)建代理

          3.1 用 AspectJ 注解聲明切面

          • 要在 Spring 中聲明 AspectJ 切面,只需要在 IOC 容器中將切面聲明為 Bean 實例。當在 Spring IOC 容器中初始化 AspectJ 切面之后,Spring IOC 容器就會為那些與 AspectJ 切面相匹配的 Bean 創(chuàng)建代理

          • 在 AspectJ 注解中,切面只是一個帶有 @Aspect 注解的 Java 類

          • 通知是標注有某種注解的簡單的 Java 方法

          • AspectJ 支持 5 種類型的通知注解:

            • @Before:前置通知,在方法執(zhí)行之前執(zhí)行
            • @After:后置通知,在方法執(zhí)行之后執(zhí)行
            • @AfterRunning:返回通知,在方法返回結(jié)果之后執(zhí)行
            • @AfterThrowing:異常通知,在方法拋出異常之后
            • @Around:環(huán)繞通知,圍繞著方法執(zhí)行

          1、前置通知

          前置通知:在方法執(zhí)行之前執(zhí)行的通知

          前置通知使用 @Before 注解,并將切入點表達式的值作為注解值

          • @Aspect:標識這個類是一個切面

          • @Before:標識這個方法是個前置通知

            • execution 代表切點表達式,表示執(zhí)行 ArithmeticCalculator 接口的 add() 方法
            • * 代表匹配任意修飾符及任意返回值
            • 參數(shù)列表中的 .. 代表匹配任意數(shù)量的參數(shù)
          @Aspect
          @Component
          public class LogAspect {

              private Logger log = LoggerFactory.getLogger(this.getClass());

              /**
               * 前置通知,目標方法調(diào)用前被調(diào)用
               */

              @Before("execution(* priv.starfish.aop.aspect.Calculator.add(..))")
              public void beforeAdvice(){
                  log.info("The method add begins");
              }
           
          }
          利用方法簽名編寫 AspectJ 切入點表達式

          最典型的切入點表達式是根據(jù)方法的簽名來匹配各種方法:

          • execution *priv.starfish.aop.ArithmeticCalculator.*(..):匹配 ArithmeticCalculator 中聲明的所有方法,第一個 *

            代表任意修飾符及任意返回值,第二個 * 代表任意方法,.. 代表匹配任意數(shù)量的參數(shù)。若目標類與接口與該切面在同一個包中,可以省略包名

          • execution public * ArithmeticCalculator.*(..):匹配 ArithmeticCalculator 接口的所有公有方法

          • execution public double ArithmeticCalculator.*(..):匹配 ArithmeticCalculator 中返回 double 類型數(shù)值的方法

          • execution public double ArithmeticCalculator.*(double, ..):匹配第一個參數(shù)為 double 類型的方法, .. 匹配任意數(shù)量任意類型的參數(shù)

          • execution public double ArithmeticCalculator.*(double, double):匹配參數(shù)類型為 double, double 的方法

          合并切入點表達式
          • AspectJ 可以使用 且(&&)、或(||)、非(!)來組合切入點表達式
          • 在 Schema風(fēng)格下,由于在 XML 中使用“&&”需要使用轉(zhuǎn)義字符“&amp;&amp”來代替,所以很不方便,因此 Spring AOP 提供了and、or、not 來代替 &&、||、!。
          @Pointcut("execution(* *.add(int,int)) || execution(* *.sub(int,int))")
          public void loggingOperation(){};
          讓通知訪問當前連接點的細節(jié)

          可以在通知方法中聲明一個類型為 JoinPoint 的參數(shù),然后就能訪問連接細節(jié)了,比如方法名稱和參數(shù)值等

          @Before("execution(* priv.starfish.aop.aspect.Calculator.add(..))")
          public void beforeAdvice(JoinPoint joinPoint) {
            log.info("The method " + joinPoint.getSignature().getName()
                     + "() begins with " + Arrays.toString(joinPoint.getArgs()));
          }

          2、后置通知

          • 后置通知是在連接點完成之后執(zhí)行的,即連接點返回結(jié)果或者拋出異常的時候,下面的后置通知記錄了方法的終止
          • 一個切面可以包括一個或者多個通知
          @Aspect
          @Component
          public class LogAspect {
          private Logger log = LoggerFactory.getLogger(this.getClass());

            /**
             * 前置通知,目標方法調(diào)用前被調(diào)用
             */

            @Before("execution(* priv.starfish.aop.aspect.Calculator.add(..))")
            public void beforeAdvice(){
                log.info("The method add begins");
            }
            
            /**
             * 后置通知,目標方法執(zhí)行完執(zhí)行
             */

            @After("execution( * *.*(..))")
            public void afterAdvice(JoinPoint joinPoint){
              log.info("The method " + joinPoint.getSignature().getName() + "() ends");
            }
            
          }

          3、返回通知

          • 無論連接點是正常返回還是拋出異常,后置通知都會執(zhí)行。如果只想在連接點返回的時候記錄日志,應(yīng)該使用返回通知代替后置通知

            在返回通知中訪問連接點的返回值

            • 在返回通知中,只要將 returning 屬性添加到 @AfterReturning 注解中,就可以訪問連接點的返回值。該屬性的值即為用來傳入返回值的參數(shù)名稱
            • 必須在通知方法的簽名中添加一個同名參數(shù),在運行時,Spring AOP 會通過這個參數(shù)傳遞返回值
            • 原始的切點表達式需要出現(xiàn)在 pointcut 屬性中
          /**
           * 后置返回通知
           * 如果參數(shù)中的第一個參數(shù)為JoinPoint,則第二個參數(shù)為返回值的信息
           * 如果參數(shù)中的第一個參數(shù)不為JoinPoint,則第一個參數(shù)為returning中對應(yīng)的參數(shù)
           * returning 只有目標方法返回值與通知方法相應(yīng)參數(shù)類型時才能執(zhí)行后置返回通知,否則不執(zhí)行
           */

          @AfterReturning(value = "loggingOperation()",returning = "res")
          public void afterReturningAdvice(JoinPoint joinPoint, Object res){
              System.out.println("后置返回通知 返回值:"+res);
          }

          4、后置異常通知

          • 只在連接點拋出異常時才執(zhí)行異常通知
          • 將 throwing 屬性添加到 @AfterThrowing 注解中,也可以訪問連接點拋出的異常。Throwable 是所有錯誤和異常類的超類,所以在異常通知方法可以捕獲到任何錯誤和異常
          • 如果只對某種特殊的異常類型感興趣,可以將參數(shù)聲明為其他異常的參數(shù)類型。然后通知就只在拋出這個類型及其子類的異常時才被執(zhí)行
          /**
           * 后置異常通知
           *  定義一個名字,該名字用于匹配通知實現(xiàn)方法的一個參數(shù)名,當目標方法拋出異常返回后,將把目標方法拋出的異常傳給通知方法;
           *  throwing 只有目標方法拋出的異常與通知方法相應(yīng)參數(shù)異常類型時才能執(zhí)行后置異常通知,否則不執(zhí)行,
           */

          @AfterThrowing(value = "loggingOperation()",throwing = "exception")
          public void afterThrowingAdvice(JoinPoint joinPoint,ArithmeticException exception){
              log.error(joinPoint.getSignature().getName() + "has throw an exception" + exception);
          }

          5、環(huán)繞通知

          • 環(huán)繞通知是所有通知類型中功能最為強大的,能夠全面地控制連接點。甚至可以控制是否執(zhí)行連接點
          • 對于環(huán)繞通知來說,連接點的參數(shù)類型必須是 ProceedingJoinPoint。它是 JoinPoint 的子接口,允許控制何時執(zhí)行,是否執(zhí)行連接點
          • 在環(huán)繞通知中需要明確調(diào)用 ProceedingJoinPoint 的 proceed() 方法來執(zhí)行被代理的方法,如果忘記這樣做就會導(dǎo)致通知被執(zhí)行了,但目標方法沒有被執(zhí)行
          • 注意:環(huán)繞通知的方法需要返回目標方法執(zhí)行之后的結(jié)果,即調(diào)用 joinPoint.proceed(); 的返回值,否則會出現(xiàn)空指針異常
          /**
           * 環(huán)繞通知:
           * 環(huán)繞通知非常強大,可以決定目標方法是否執(zhí)行,什么時候執(zhí)行,執(zhí)行時是否需要替換方法參數(shù),執(zhí)行完畢是否需要替換返回值。
           * 環(huán)繞通知第一個參數(shù)必須是org.aspectj.lang.ProceedingJoinPoint類型
           */

          @Around("loggingOperation()")
          public Object aroundAdvice(ProceedingJoinPoint joinPoint){
              System.out.println("- - - - - 環(huán)繞前置通知 - - - -");
              try {
                  //調(diào)用執(zhí)行目標方法
                  Object obj = joinPoint.proceed();
                  System.out.println("- - - - - 環(huán)繞后置返回通知 - - - -");
                  return obj;
              } catch (Throwable throwable) {
                  throwable.printStackTrace();
                  System.out.println("- - - - - 環(huán)繞異常通知 - - - -");
              }finally {
                  System.out.println("- - - - - 環(huán)繞后置通知 - - - -");
              }
              return null;
          }

          指定切面的優(yōu)先級

          • 在同一個連接點上應(yīng)用不止一個切面時,除非明確指定,否則它們的優(yōu)先級是不確定的
          • 切面的優(yōu)先級可以通過實現(xiàn) Ordered 接口或利用 @Order 注解指定
          • 實現(xiàn) Ordered 接口,getOrder() 方法的返回值越小,優(yōu)先級越高
          • 若使用 @Order 注解,序號出現(xiàn)在注解中
          @Aspect
          @Order(0)
          public class ValidationAspect {
          }

          @Aspect
          @Order(1)
          public class LogAspect {

          重用切入點定義

          • 在編寫 AspectJ 切面時,可以直接在通知注解中書寫切入點表達式。但同一個切點表達式可能會在多個通知中重復(fù)出現(xiàn)
          • 在 AspectJ 切面中,可以通過 @Pointcut 注解將一個切入點聲明成簡單的方法。切入點的方法體通常是空的,因為將切入點定義與應(yīng)用程序邏輯混在一起是不合理的
          • 切入點方法的訪問控制符同時也控制著這個切入點的可見性。如果切入點要在多個切面中共用,最好將它們集中在一個公共的類中。在這種情況下,它們必須被聲明為 public。在引入這個切入點時,必須將類名也包括在內(nèi)。如果類沒有與這個切面放在同一個包中,還必須包含包名。
          • 其他通知可以通過方法名稱引入該切入點
          /**
           * 切入點
           */

          @Pointcut("execution(public int priv.starfish.aop.aspect.CalculatorImpl.*(int,int))")
          public void executePackage(){};

          /**
           * 這里直接寫成 value= 調(diào)用了切入點 excution 表達式
           */

          @AfterReturning(value = "executePackage()",returning = "res")
          public void afterReturningAdvice(JoinPoint joinPoint, Object res){
            System.out.println("- - - - - 后置返回通知- - - - -");
            System.out.println("后置返回通知 返回值:"+res);
          }

          引入通知

          • 引入通知是一種特殊的通知類型。它通過為接口提供實現(xiàn)類,允許對象動態(tài)地實現(xiàn)接口,就像對象已經(jīng)在運行時擴展了實現(xiàn)類一樣

          • 引入通知可以使用兩個實現(xiàn)類 MaxCalculatorImpl 和 MinCalculatorImpl,讓 ArithmeticCalculatorImpl 動態(tài)地實現(xiàn) MaxCalculator和 MinCalculator接口。而這與從 MaxCalculatorImpl 和 MinCalculatorImpl 中實現(xiàn)多繼承的效果相同。但卻不需要修改 ArithmeticCalculatorImpl 的源代碼

          • 引入通知也必須在切面中聲明

          • 在切面中,通過為任意字段添加**@DeclareParents**注解來引入聲明

          • 注解類型的 value 屬性表示哪些類是當前引入通知的目標。value 屬性值也可以是一個 AspectJ 類型的表達式,可以將一個接口引入到多個類中。defaultImpl屬性中指定這個接口使用的實現(xiàn)類

          代碼在 starfish-learn-spring 上

          3.2 用基于 XML 的配置聲明切面

          • 除了使用 AspectJ 注解聲明切面,Spring 也支持在 Bean 配置文件中聲明切面。這種聲明是通過 aop schema 中的 XML 元素完成的

          • 正常情況下,基于注解的聲明要優(yōu)先于基于 XML 的聲明。通過 AspectJ注解,切面可以與 AspectJ 兼容,而基于 XML 的配置則是 Spring 專有的。由于 AspectJ 得到越來越多的 AOP 框架支持,所以以注解風(fēng)格編寫的切面將會有更多重用的機會

          • 當使用 XML 聲明切面時,需要在 <beans> 根元素中導(dǎo)入 aop Schema

          • 在 Bean 配置文件中,所有的 Spring AOP 配置都必須定義在 <aop:config> 元素內(nèi)部。對于每個切面而言,都要創(chuàng)建一個 <aop:aspect>

            元素來為具體的切面實現(xiàn)引用后端 Bean 實例

          • 切面 Bean 必須有一個標識符,供 <aop:aspect> 元素引用

          <?xml version="1.0" encoding="UTF-8"?>
          <beans xmlns="http://www.springframework.org/schema/beans"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xmlns:aop="http://www.springframework.org/schema/aop"
                 xmlns:tx="http://www.springframework.org/schema/tx"
                 xsi:schemaLocation="http://www.springframework.org/schema/beans
                  http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
                  http://www.springframework.org/schema/aop
                  http://www.springframework.org/schema/aop/spring-aop-4.2.xsd"
          >


              <bean id="calculator" class="priv.starfish.aop.xml.CalculatorImpl" />
              <bean id="timeHandler" class="priv.starfish.aop.xml.TimeHandler" />

              <aop:config>
                  <aop:aspect id="time" ref="timeHandler">
                      <aop:pointcut id="addTime" expression="execution(* priv.starfish.aop.xml.*.*(..))"/>
                      <aop:before method="printTime" pointcut-ref="addTime" />
                      <aop:after method="printTime" pointcut-ref="addTime" />
                  </aop:aspect>
              </aop:config>
          </beans>
          public class TimeHandler {

              public void printTime() {
                  System.out.println("CurrentTime = " + System.currentTimeMillis());
              }
          }
          public static void main(String[] args) {
              ApplicationContext ctx =
                      new ClassPathXmlApplicationContext("applicationContext.xml");

              Calculator calculator = (Calculator)ctx.getBean("calculator");
              calculator.add(2,3);
          }

          3.2 AOP 兩種代理方式

          Spring 中 AOP 代理由 Spring 的 IOC 容器負責(zé)生成、管理,其依賴關(guān)系也由 IOC 容器負責(zé)管理。因此,AOP 代理可以直接使用容器中

          的其它 bean 實例作為目標,這種關(guān)系可由 IOC 容器的依賴注入提供。Spring創(chuàng)建代理的規(guī)則為:

          1. 默認使用 Java 動態(tài)代理來創(chuàng)建 AOP 代理,這樣就可以為任何接口實例創(chuàng)建代理了

          2. 當需要代理的類不是代理接口的時候,Spring會切換為使用CGLIB代理,也可強制使用 CGLIB

          Spring 提供了兩種方式來生成代理對象:JDKProxyCglib,具體使用哪種方式生成由 AopProxyFactory 根據(jù) AdvisedSupport 對象

          的配置來決定。默認的策略是如果目標類是接口, 則使用 JDK 動態(tài)代理技術(shù),否則使用 Cglib 來生成代理。

          注:JDK 動態(tài)代理要比 cglib 代理執(zhí)行速度快,但性能不如 cglib 好。所以在選擇用哪種代理還是要看具體情況,一般單例模式用 cglib 比較好。


          爛了大街的 Spring 循環(huán)依賴問題,你以為自己就真會了嗎


          內(nèi)容編輯 | Keeper
          文章來源 | 尚硅谷視頻學(xué)習(xí)筆記

          瀏覽 30
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  99re在线视频免费播放 | 成人91av视频在线看 | 国产精品高清无码 | 色拍拍综合网 | 亚洲性老太-V888AV |