1. 程式人生 > 其它 >Spring---Spring的AOP(三)

Spring---Spring的AOP(三)

1、Spring的AOP簡介

1.1 什麼是AOP

 

AOP為Aspect Oriented Programming的縮寫,是一種面向切面的程式設計,是基於動態代理的,對原有程式碼毫無入侵性,把和主業務無關的事情放到程式碼外面去做。所以當你下次發現某一行程式碼經常在你的Controller例出現時,就要考慮用AOP來精簡程式碼了。

 

簡而言之:在執行時,動態地將程式碼切入到類的指定方法、指定位置上的程式設計思想,就是面向切面的程式設計。

 

AOP是OOP的延續,是軟體開發中的熱點,也是Spring框架中的一個重要內容,是函數語言程式設計的一種衍生泛型。

 

1.2 AOP 的作用及優勢

AOP的作用:

利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高開發的效率。

 

AOP的優勢:

減少重複程式碼,提高開發效率,維護方便

 

1.3 AOP 的底層實現

AOP的底層原理是通過Spring 提供的動態代理技術實現的。在執行期間,Spring通過動態代理技術動態的生成代理物件,代理物件方法執行時增進功能的介入,再去呼叫目標物件的方法,從而增強功能。

 

1.4 AOP 的動態代理技術

Spring的AOP的底層實現用到了兩種代理機制:

JDK的動態代理:基於介面的動態代理技術

cglib的動態代理:基於父類的動態代理技術

 

什麼是動態代理:

代理是一種設計模式,提供了目標對目標物件的訪問方式,即是通過代理物件訪問目標物件,可以在目標物件的基礎上,增加額外的功能,拓展目標物件的功能。

 

1.4.1 JDK 的動態代理

1、目標類介面及目標類

public interface TargetInterface {
    public void save();
}
public class Target implements TargetInterface{

    @Override
    public void save() {
        System.out.println("Save Running.....");
    }
}

 

2、物件增強程式碼

 public class Enhance {
     public void before(){
         System.out.println("前置增強....");
     }
 ​
     public void after(){
         System.out.println("後置增強....");
     }
 }

 

3、動態代理程式碼

當呼叫介面的任何方法時,代理物件的程式碼都不需要修改

 

 1  public class proxyTest {
 2      public static void main(String[] args) {
 3  4          final Target target = new Target(); //建立目標物件
 5          final Enhance enhance = new Enhance(); // 獲得增強物件
 6  7          //返回值就是動態生成的代理物件
 8          TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(
 9                  //目標物件載入器
10                  target.getClass().getClassLoader(),
11                  target.getClass().getInterfaces(),
12                  new InvocationHandler() {
13                      //呼叫代理物件的任何方法,實際上執行的都是invoke方法
14                      @Override
15                      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
16                          enhance.before(); //前置增強
17                          //執行目標方法
18                          Object invoke = method.invoke(target, args);
19                          enhance.after(); // 後置增強
20                          return invoke;
21                      }
22                  }
23 24          );
25 26          //呼叫代理物件方法
27          proxy.save();
28 29      }
30  }

 

 

1.4.2 cglib 的動態代理

1、目標類

 public class Target{
 ​
     public void save() {
         System.out.println("Save Running.....");
     }
 }

 

2、物件增強程式碼

 public class Enhance {
     public void before(){
         System.out.println("前置增強....");
     }
 ​
     public void after(){
         System.out.println("後置增強....");
     }
 }

 

3、動態代理程式碼

 1  public class proxyTest {
 2      public static void main(String[] args) {
 3  4          final Target target = new Target(); //建立目標物件
 5          final Enhance enhance = new Enhance(); // 獲得增強物件
 6  7          //返回值就是動態生成的代理物件  基於cglib
 8          /*
 9          * 1. 建立增強器
10          * 2。設定目標類(父類)
11          * 3. 設定回撥
12          * 4. 建立代理物件
13          * */
14 15          Enhancer enhancer = new Enhancer();
16          enhancer.setSuperclass(Target.class);
17          //設定回撥
18          enhancer.setCallback(new MethodInterceptor() {
19              @Override
20              public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
21                  //執行前置
22                  enhance.before();
23                  Object invoke = method.invoke(target, args); //執行目標物件
24                  //執行後置
25                  enhance.after();
26                  return invoke;
27              }
28          });
29          // 建立代理物件
30          Target proxy = (Target) enhancer.create();
31 32          proxy.save();
33      }
34  }

 

1.5 AOP相關術語

Spring的AOP實現底層就是對上面的動態代理的程式碼進行了封裝,封裝之後只需要對需要關注的部分進行程式碼編寫,並通過配置的方式完成指定的目標方法增強。

 

我們必須理解的常用的AOP相關術語:

 

Target(目標物件) 代理的目標物件
Proxy(代理) 一個類被AOP織入增強後,就產生一個結果代理類
Joinpoint(連線點) 所謂連線點就是指那些被攔截的點。在Spring中,這些點指的是方法 因為Spring只支援方法型別的連線點
Pointcut(切入點) 所謂切入點是指我們對那些Joinpoint進行攔截的定義
Advice(通知/增強) 通知是指攔截到Joinpoint 之後所要做的事情
Aspect(切面) 是切入點和通知的結合
Weaving(織入) 是指把增強應用到目標物件來建立的代理物件的過程。 Spring採用動態代理織入,而Aspect採用編譯期織入和類裝載期織入

 

1.6 AOP開發明確的事項

1、需要編寫的內容

  • 編寫核心業務程式碼(目標類的目標方法)

  • 編寫切面類,切面類中有通知(增強功能方法)

  • 在配置檔案中,配置織入關係,即將那些通知與連線點進行結合

 

2、AOP技術實現的內容

Spring框架監控切入點方法的執行。一旦監控到切入點方法被執行,使用代理機制,動態建立目標物件的代理物件,根據通知類別,在代理物件的對應位置,將通知對應的功能織入,完成完整的程式碼邏輯。

 

3、AOP底層使用哪種代理方式

在Spring中,框架會根據目標類是否實現介面來決定採用哪種動態代理方式。

 

1.7 知識要點總結

  • AOP:面向切面程式設計

  • AOP底層實現:基於JDK的動態代理和基於Cglib的動態代理

  • AOP的重要概念:

    Pointcut(切入點): 被增強方法

    Advice(通知/增強):封裝增強業務邏輯的方法

    Aspect(切面):切點+通知

    Weaving(織入):將切點與通知結合的過程

  • 開發明確事項:

    誰是切點(切點表示式配置)

    誰是通知(切面類中的增強方法)

    將切點和通知進行織入配置

 

2. 基於xml的AOP開發

2.1 快速入門

1、匯入AOP相關座標

      <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.16</version>
        </dependency>
<!--        aspectj的織入-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.13</version>
        </dependency>

2、建立目標介面和目標類(內部有切點)

 public interface TargetInterface {
     public void save();
 }
 public class Target implements TargetInterface{
 ​
     @Override
     public void save() {
         System.out.println("Save Running.....");
     }
 }

 

3、建立切面類(內部有增強方法)

 public class MyAspect {
 ​
     public void before(){
         System.out.println("前置增強....");
     }
 }
 

 

4、將目標類和切面類的物件建立權交給Spring

 <!--    目標物件-->
     <bean id="target" class="com.ntect.aop.Target"></bean><!--    切面-->
     <bean id="myAspect" class="com.ntect.aop.MyAspect"></bean>

 

5、在applicationContext.xml中配置織入關係

 

先匯入aop名稱空間

 

配置織入通知

 <!--    配置織入:告訴Spring哪些方法(切點)需要進行增強(前置、後置...)-->
     <aop:config>
 <!--        宣告切面-->
         <aop:aspect ref="myAspect">
 <!--            切面:切點+通知 -->
             <aop:before method="before" pointcut="execution(public void com.ntect.aop.Target.save())"/>
         </aop:aspect>
     </aop:config>

 

6、測試程式碼

@RunWith(SpringJUnit4ClassRunner.class)
 @ContextConfiguration("classpath:applicationContext.xml")
 public class AopTest {
     @Autowired
     private TargetInterface target;
 ​
     @Test
     public void testAop(){
         target.save();
     }
 ​
 ​
 }

 

7、測試結果

 

2.2 XML配置AOP詳解

1、切點表示式的寫法

表示式語法:

 execution[修飾符]返回值型別 包名.類名.方法名(引數)
     /*
      *1. 訪問修飾符可以省略
      *2. 返回值型別、包名、類名、方法名可以使用 * 代表任意
      *3. 包名與類名之間一個點. 代表當前包下的類,兩個點 .. 表示當前包及其子包下的類
      *4. 引數列表可以使用兩個點 .. 表示任意個數,任意型別的引數列表
     */

 

例如:

 execution(public void com.ntect.aop.Target.method())
 execution(public void com.ntect.aop.Target.*(..))
 execution(public void com.ntect.aop.*.*(..))  //常用形式
 execution(public void com.ntect.aop..*.*(..))
 execution(* *..*.*(..))

 

2、通知的型別

通知的配置語法:

<aop:通知型別 method="切面類中方法名" pointcut="切點表示式"></aop:通知型別>
名稱 標籤 說明
前置通知 <aop:before> 用於配置前置通知,指定增強方法在切入點方法之前執行
後置通知 <aop:after-returnning> 用於配置後置通知,指定增強的方法在切入點方法之後執行
環繞通知 <aop:around> 用於配置環繞通知,指定增強的方法在切入點 方法之之後都執行
異常丟擲通知 <aop:throwing> 用於配置異常丟擲通知,指定增強的方法在出現異常時執行
最終通知 <aop:after> 用於配置最終通知,無論增強方式執行是否異常都會執行

 

 1  public class MyAspect {
 2  3      public void before(){
 4          System.out.println("前置增強....");
 5      }
 6  7      public void afterReturning(){
 8          System.out.println("後置增強.....");
 9      }
10 11      //Proceeding JoinPoint : 正在執行的連線點====切點
12      public Object around(ProceedingJoinPoint pjp) throws Throwable {
13          System.out.println("環繞前增強....");
14          Object proceeding = pjp.proceed(); // 切點方法
15          System.out.println("環繞後增強....");
16          return proceeding;
17      }
18 19      public void afterThrowing(){
20          System.out.println("異常丟擲增強....");
21      }
22 23      public void after(){
24          System.out.println("最終增強....");
25      }
26  }
<aop:config>
<!--        宣告切面-->
        <aop:aspect ref="myAspect">
<!--            切面:切點+通知 -->
            <aop:before method="before" pointcut="execution(public void com.ntect.aop.Target.save())"/>
            <aop:after-returning method="afterReturning" pointcut="execution(* com.ntect.aop.*.*(..))"/>
            <aop:around method="around" pointcut="execution(* com.ntect.aop.*.*(..))"/>
            <aop:after-throwing method="afterThrowing" pointcut="execution(* com.ntect.aop.*.*(..))"/>
            <aop:after method="after" pointcut="execution(* com.ntect.aop.*.*(..))"/>
        </aop:aspect>
    </aop:config>

 

 

3、切點表示式的抽取

當多個增強的切點表示式相同時,可以將切點表示式進行抽取,在增強中使用pointcut-ref屬性代替pointcut屬性來引用抽取後的切點表示式。

 <!--    抽取切點表示式-->
     <aop:config>
 <!--        引用MyAspect的Bean為切面物件-->
         <aop:aspect ref="myAspect">
             <aop:pointcut id="myPointcut" expression="execution(* com.ntect.aop.*.*(..))"/>
             <aop:before method="before" pointcut-ref="myPointcut"/>
             <aop:around method="around" pointcut-ref="myPointcut"/>
             <aop:after method="after" pointcut-ref="myPointcut"/>
         </aop:aspect>
     </aop:config>

 

 

2.3 知識要點

  • aop織入的配置

 <aop:config>
         <aop:aspect ref="切面類">
             <aop:after method="通知方法名稱" pointcut="切點表示式"/>
         </aop:aspect>
     </aop:config>
  • 通知的型別:前置通知、後置通知、環繞通知、異常丟擲通知、最終通知

  • 切點表示式的寫法:

execution([修飾符] 返回值型別 包名.類名.方法名(引數))

 

3. 基於註解的AOP開發

3.1 快速入門

開發步驟:

1、建立目標介面和目標類(內部有切點)

2、建立切面類(內部有增強方法)

3、將目標類和切面類的物件建立權交給Spring

 @Component("target")
 public class Target implements TargetInterface {
 ​
     public void save() {
         System.out.println("Save Running.....");
     }
 }
 @Component("myAspect")
 public class MyAspect {
     public void before(){
         System.out.println("前置增強....");
     }

 

4、在切面類中使用註解配置織入關係

 @Component("myAspect")
 @Aspect  //標註當前MyAspeect是一個切面類
 public class MyAspect {
 ​
     //配置前置增強
     @Before("execution(* com.ntect.anno.*.*(..))")
     public void before(){
         System.out.println("前置增強....");
     }

 

5、在配置檔案中開啟元件掃描和AOP的自動代理

 <!--    開啟元件掃描-->
     <context:component-scan base-package="com.ntect.anno"/><!--    aop自動代理-->
     <aop:aspectj-autoproxy/>

 

6、測試程式碼

 @RunWith(SpringJUnit4ClassRunner.class)
 @ContextConfiguration("classpath:applicationContext-anno.xml")
 public class annoTest {
     @Autowired
     private TargetInterface target;
 ​
     @Test
     public void testAop(){
         target.save();
     }
 ​
 }

 

7、測試結果

 

3.2 註解配置AOP

1、註解通知的型別

通知的配置語法:@通知註解(“切點表示式”)

名稱 註解 說明
前置通知 @Before 用於配置前置通知。指定增強的方法在切入點方法之前執行
後置通知 @AfterReturning 用於配置後置通知。指定增強的方法在切入點方法之後執行
環繞通知 @Around 用於配置環繞通知。指定的增強方法在切入點方法之前和之後執行
異常丟擲通知 @AfterThrowing 用於配置異常丟擲通知。指定增強的方法在出現異常時執行
最終通知 @After 用於配置最終通知。無論增強方式執行是否有異常都會執行


 1  @Component("myAspect")
 2  @Aspect  //標註當前MyAspeect是一個切面類
 3  public class MyAspect {
 4  5      //配置前置增強
 6      @Before("execution(* com.ntect.anno.*.*(..))")
 7      public void before(){
 8          System.out.println("前置增強....");
 9      }
10 11      @AfterReturning("execution(* com.ntect.anno.*.*(..))")
12      public void afterReturning(){
13          System.out.println("後置增強.....");
14      }
15 16      @Around("execution(* com.ntect.anno.*.*(..))")
17      //Proceeding JoinPoint : 正在執行的連線點====切點
18      public Object around(ProceedingJoinPoint pjp) throws Throwable {
19          System.out.println("環繞前增強....");
20          Object proceeding = pjp.proceed(); // 切點方法
21          System.out.println("環繞後增強....");
22          return proceeding;
23      }
24 25      @AfterThrowing("execution(* com.ntect.anno.*.*(..))")
26      public void afterThrowing(){
27          System.out.println("異常丟擲增強....");
28      }
29 30      @After("execution(* com.ntect.anno.*.*(..))")
31      public void after(){
32          System.out.println("最終增強....");
33      }
34  }

 

2、切點表示式的抽取

同 xml配置aop一樣時,我們可以將切點表示式抽取,具體如下:

 1 @Component("myAspect")
 2  @Aspect  //標註當前MyAspeect是一個切面類
 3  public class MyAspect {
 4  5      //定義切點表示式
 6      @Pointcut("execution(* com.ntect.anno.*.*(..))")
 7      public void  pointCut(){}
 8      //配置前置增強
 9      @Before("MyAspect.pointCut()")
10      public void before(){
11          System.out.println("前置增強....");
12      }
13 14      @AfterReturning("MyAspect.pointCut()")
15      public void afterReturning(){
16          System.out.println("後置增強.....");
17      }
18 19      @Around("MyAspect.pointCut()")
20      //Proceeding JoinPoint : 正在執行的連線點====切點
21      public Object around(ProceedingJoinPoint pjp) throws Throwable {
22          System.out.println("環繞前增強....");
23          Object proceeding = pjp.proceed(); // 切點方法
24          System.out.println("環繞後增強....");
25          return proceeding;
26      }
27 28      @AfterThrowing("MyAspect.pointCut()")
29      public void afterThrowing(){
30          System.out.println("異常丟擲增強....");
31      }
32 33      @After("MyAspect.pointCut()")
34      public void after(){
35          System.out.println("最終增強....");
36      }
37  }

 

測試結果:

 

3、總結

  • 註解開發步驟:

    1、使用@Aspect標註切面

    2、使用@通知註解標註通知方法

    3、在配置檔案中配置AOP自動代理<aop:aspectj-autoproxy/>