1. 程式人生 > 其它 >Spring基礎知識(9)- Spring 整合 AspectJ

Spring基礎知識(9)- Spring 整合 AspectJ

Spring AOP 是一個簡化版的 AOP 實現,並沒有提供完整版的 AOP 功能。通常情況下,Spring AOP 是能夠滿足我們日常開發過程中的大多數場景的,但在某些情況下,我們可能需要使用 Spring AOP 範圍外的某些 AOP 功能。

Spring AOP 僅支援執行公共(public)非靜態方法的呼叫作為連線點,如果我們需要向受保護的(protected)或私有的(private)的方法進行增強,此時就需要使用功能更加全面的 AOP 框架來實現,其中使用最多的就是 AspectJ。

AspectJ 是一個基於 Java 語言的全功能的 AOP 框架,它並不是 Spring 組成部分,是一款獨立的 AOP 框架。

AspectJ 支援通過 Spring 配置 AspectJ 切面,因此它是 Spring AOP 的完美補充,通常情況下,我們都是將 AspectJ 和 Spirng 框架一起使用,簡化 AOP 操作。

本文在 “

Spring基礎知識(2)- 建立 Spring 程式” 的基礎上,匯入 AspectJ 框架。

AspectJ:http://www.eclipse.org/aspectj/

1. 匯入 AspectJ 依賴包

    訪問 http://www.mvnrepository.com/,查詢 spring-aop, spring-aspects, aspectjweaver
    
    本文選擇了 Spring 4.3.9.RELEASE、aspectjweaver 1.9.6。

    修改 pom.xml,新增依賴包:

        <project ... >
            ...

            <dependencies>
                ...

                <!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
                <dependency>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-aop</artifactId>
                    <version>4.3.9.RELEASE</version>
                </dependency>
                <!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
                <dependency>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-aspects</artifactId>
                    <version>4.3.9.RELEASE</version>
                </dependency>
                <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
                <dependency>
                    <groupId>org.aspectj</groupId>
                    <artifactId>aspectjweaver</artifactId>
                    <version>1.9.6</version>
                </dependency>


                ...
            </dependencies>

            ...
        </project>

    在IDE中專案列表 -> 點選滑鼠右鍵 -> Maven -> Reload Project

2. 基於XML配置實現 AspectJ 的 AOP 開發


    Spring 提供了基於 XML 的 AOP 支援,並提供了一個名為 “aop” 的名稱空間,該名稱空間提供了一個 <aop:config> 元素。

    標籤和含義:

        (1) <aop:config> 表示一個 aop 配置;
        (2) <aop:pointcut> 表示切入點;
        (3) <aop:advisor> 表示通知(Advice)和切入點(Pointcut) 的組合;
        (4) <aop:aspect> 表示切面,它的內部也是通知(Advice)和切入點(Pointcut) 的組合;

    <aop:advisor> 和 <aop:aspect> 區別:

        (1) 在面向切面程式設計時(比如用於日誌、快取等),我們一般會用 <aop:aspect>,<aop:aspect> 定義切面(包括通知和切入點);
        (2) 在進行事務管理時,我們一般會用<aop:advisor>,<aop:advisor> 定義通知器 (通知器跟切面一樣,也包括通知和切入點);
        (3) 如果用<aop:advisor>配置切面的話也可以配置,但切面類跟 <aop:aspect> 有所不同,需要實現介面;

    配置規則:
        (1) 在 Spring 配置中,所有的切面資訊(切面、切點、通知)都必須定義在 <aop:config> 元素中;
        (2) 在 Spring 配置中,可以使用多個 <aop:config>;
        (3) 每一個 <aop:config> 元素內可以包含 3 個子元素:<aop:pointcut>、<aop:advisor> 和 <aop:aspect> ,這些子元素必須按照這個順序進行宣告;


    1) 引入 aop 名稱空間

        需要在 Spring 配置檔案中匯入 Spring aop 名稱空間,如下所示。

 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:aop="http://www.springframework.org/schema/aop"
 5                 xmlns:context="http://www.springframework.org/schema/context"
 6                 xsi:schemaLocation="http://www.springframework.org/schema/beans
 7                                     http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
 8                                     http://www.springframework.org/schema/context
 9                                     http://www.springframework.org/schema/context/spring-context-4.0.xsd
10                                     http://www.springframework.org/schema/aop
11                                     http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
12             </beans>


    2) 定義切面 <aop:aspect>

        在 Spring 配置檔案中,使用 <aop:aspect> 元素定義切面。該元素可以將定義好的 Bean 轉換為切面 Bean,所以使用 <aop:aspect> 之前需要先定義一個普通的 Spring Bean。

        格式如下:

            <aop:config>
                <aop:aspect id="myAspect" ref="com.example.MyAspect">
                    ...
                </aop:aspect>
            </aop:config>

        其中,id 用來定義該切面的唯一標識名稱,ref 用於引用Spring Bean。


    3) 定義切入點 <aop:pointcut>

        <aop:pointcut> 用來定義一個切入點,用來表示對哪個類中的那個方法進行增強。它既可以在 <aop:config> 元素中使用,也可以在 <aop:aspect> 元素下使用。

            a) 作為 <aop:config> 元素的子元素定義時,表示該切入點是全域性切入點,它可被多個切面所共享;
            b) 作為 <aop:aspect> 元素的子元素時,表示該切入點只對當前切面有效;

        (1) 使用格式如下:

            <aop:config>
                <aop:pointcut id="beforePointCut" expression="execution(* com.example.UserDao.*.*(..))"/>
            </aop:config>

            其中,id 用於指定切入點的唯一標識名稱,execution 用於指定切入點關聯的切入點表示式。

        (2) execution 的用法

            execution 的語法格式:

                 execution([許可權修飾符] [返回值型別] [類的完全限定名] [方法名稱]([引數列表])

            語法說明:

                a) 返回值型別、方法名、引數列表是必選項,而其它引數則為可選項。
                b) 返回值型別:*表示可以為任何返回值。如果返回值為物件,則需指定全路徑的類名。
                c) 類的完全限定名:指定包名.類名。
                d) 方法名稱:*代表所有方法,set* 代表以 set 開頭的所有方法。
                e) 引數列表:(..)代表所有引數;(*)代表只有一個引數,引數型別為任意型別;(*,String) 代表有兩個引數,第一個引數可以為任何值,第二個為 String 型別的值。

            示例:

                a) 對 com.example 包下 UserDao 類中的 add() 方法進行增強,配置如下:

                    execution(* com.example.UserDao.add(..))

                b) 對 com.example 包下 UserDao 類中的所有方法進行增強,配置如下:

                    execution(* com.example.UserDao.*(..))

                c) 對 com.example 包下所有類中的所有方法進行增強,配置如下:

                    execution(* com.example.*.*(..))

    4) 定義通知

        AspectJ 支援 5 種類型的 advice (通知/增強),格式如下:

            <aop:aspect ref="myAspect">
                <!-- 前置通知 -->
                <aop:before method="before" pointcut-ref="beforePointCut"></aop:before>
                <!-- 後置通知 -->
                <aop:after method="after" pointcut-ref="afterPointCut"></aop:after>

                <!-- 後置返回通知 -->
                <aop:after-returning method="afterReturning" pointcut-ref="afterReturnPointCut"
                                    returning="returnValue"></aop:after-returning>
                <!-- 異常通知 -->
                <aop:after-throwing method="afterThrowing" pointcut-ref="throwPointCut"
                                    throwing="exception"></aop:after-throwing>
                <!-- 環繞通知 -->
                <aop:around method="around" pointcut-ref="beforePointCut"></aop:around>
            </aop:aspect>


    5) Spring 配置檔案(spring-beans.xml)

        配置格式如下:

 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:aop="http://www.springframework.org/schema/aop"
 5                 xmlns:context="http://www.springframework.org/schema/context"
 6                 xsi:schemaLocation="http://www.springframework.org/schema/beans
 7                                     http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
 8                                     http://www.springframework.org/schema/context
 9                                     http://www.springframework.org/schema/context/spring-context-4.0.xsd
10                                     http://www.springframework.org/schema/aop
11                                     http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
12 
13                 <!-- 定義 Bean -->
14                 <bean id="userDao" class="com.example.UserDaoImpl"></bean>
15                 <!-- 定義切面 -->
16                 <bean id="myAspect" class="com.example.MyAspect"></bean>
17 
18                 <aop:config>
19                     <aop:pointcut id="beforePointCut" expression="execution(* com.example.UserDao.test1(..))"/>
20                     <aop:pointcut id="afterPointCut" expression="execution(* com.example.UserDao.test2(..))"/>
21                     <aop:pointcut id="afterReturnPointCut" expression="execution(* com.example.UserDao.test3(..))"/>
22                     <aop:pointcut id="aroundPointCut" expression="execution(* com.example.UserDao.test4(..))"/>
23                     <aop:pointcut id="throwPointCut" expression="execution(* com.example.UserDao.test5(..))"/>
24                     <aop:aspect ref="myAspect">
25                         <!-- 前置增知 -->
26                         <aop:before method="before" pointcut-ref="beforePointCut"></aop:before>
27                         <!-- 後置通知 -->
28                         <aop:after method="after" pointcut-ref="afterPointCut"></aop:after>
29                         <!-- 後置返回知 -->
30                         <aop:after-returning method="afterReturning" pointcut-ref="afterReturnPointCut"
31                                             returning="returnValue"></aop:after-returning>
32                         <!-- 環繞通知 -->
33                         <aop:around method="around" pointcut-ref="aroundPointCut"></aop:around>
34                         <!-- 異常通知 -->
35                         <aop:after-throwing method="afterThrowing" pointcut-ref="throwPointCut"
36                                             throwing="exception"></aop:after-throwing>
37                     </aop:aspect>
38                 </aop:config>
39 
40             </beans>


    示例:

 1         package com.example;
 2 
 3         import org.aspectj.lang.ProceedingJoinPoint;
 4         import org.springframework.context.support.ClassPathXmlApplicationContext;
 5 
 6         public class App {
 7             public static void main( String[] args ) {
 8 
 9                 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml");
10                 UserDao userDao = context.getBean("userDao", UserDao.class);
11                 userDao.test1();
12                 userDao.test2();
13                 userDao.test3();
14                 userDao.test4();
15                 userDao.test5();
16 
17             }
18         }
19 
20         interface UserDao {
21 
22             public void test1();
23             public void test2();
24             public String test3();
25             public void test4();
26             public void test5();
27         }
28 
29         class UserDaoImpl implements UserDao {
30 
31             @Override
32             public void test1() {
33                 System.out.println("UserDaoImpl -> test1()");
34             }
35 
36             @Override
37             public void test2() {
38                 System.out.println("UserDaoImpl -> test2()");
39             }
40 
41             @Override
42             public String test3() {
43                 System.out.println("UserDaoImpl -> test3()");
44                 return "OK";
45             }
46 
47             @Override
48             public void test4() {
49                 System.out.println("UserDaoImpl -> test4()");
50             }
51 
52             @Override
53             public void test5(){
54                 System.out.println("UserDaoImpl -> test5()");
55             }
56 
57         }
58 
59         class MyAspect {
60 
61             public void before() {
62                 System.out.println("MyAspect -> before()");
63             }
64 
65             public void after() {
66                 System.out.println("MyAspect -> after()");
67             }
68 
69             public void afterReturning(Object returnValue) {
70                 System.out.println("MyAspect -> afterReturning(): returnValue = " + returnValue);
71             }
72 
73             public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
74                 System.out.println("MyAspect -> around(): before");
75                 proceedingJoinPoint.proceed();
76                 System.out.println("MyAspect -> around(): after");
77             }
78 
79             public void afterThrowing(Throwable exception) {
80                 System.out.println("MyAspect -> afterThrowing(): error = " + exception.getMessage());
81             }
82 
83         }


    輸出:

        MyAspect -> before()
        UserDaoImpl -> test1()
        UserDaoImpl -> test2()
        MyAspect -> after()
        UserDaoImpl -> test3()
        MyAspect -> afterReturning(): returnValue = OK
        MyAspect -> around(): before
        UserDaoImpl -> test4()
        MyAspect -> around(): after
        UserDaoImpl -> test5()

 

3. 基於註解方式實現 AspectJ 的 AOP 開發

    在 Spring 中,雖然我們可以使用 XML 配置檔案可以實現 AOP 開發,但如果所有的配置都集中在 XML 配置檔案中,就勢必會造成 XML 配置檔案過於臃腫,從而給維護和升級帶來一定困難。

    AspectJ 框架為 AOP 開發提供了一套 @AspectJ 註解。它允許我們直接在 Java 類中通過註解的方式對切面(Aspect)、切入點(Pointcut)和增強(Advice)進行定義,Spring 框架可以根據這些註解生成 AOP 代理。

    註解的介紹:

名稱 說明
@Aspect 用於定義一個切面。
@Pointcut 用於定義一個切入點。
@Before 用於定義前置通知,相當於 BeforeAdvice。
@After 用於定義後置通知,不管是否異常,該通知都會執行。
@AfterReturning 用於定義後置返回通知,相當於 AfterReturningAdvice。
@Around 用於定義環繞通知,相當於 MethodInterceptor。
@AfterThrowing 用於定義丟擲通知,相當於 ThrowAdvice。
@DeclareParents 用於定義引介通知,相當於 IntroductionInterceptor(不要求掌握)。


    示例:

  1         package com.example;
  2 
  3         import org.aspectj.lang.JoinPoint;
  4         import org.aspectj.lang.ProceedingJoinPoint;
  5         import org.springframework.context.annotation.AnnotationConfigApplicationContext;
  6 
  7         import org.springframework.stereotype.Component;
  8         import org.springframework.context.annotation.ComponentScan;
  9         import org.springframework.context.annotation.Configuration;
 10         import org.springframework.context.annotation.EnableAspectJAutoProxy;
 11         import org.aspectj.lang.annotation.*;
 12 
 13         public class App {
 14             public static void main( String[] args ) {
 15 
 16                 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(OrderAspectConfig.class);
 17                 OrderDao orderDao = context.getBean("orderDao", OrderDao.class);
 18                 orderDao.test1();
 19                 orderDao.test2();
 20                 orderDao.test3();
 21                 orderDao.test4();
 22                 orderDao.test5();
 23 
 24             }
 25         }
 26 
 27         interface OrderDao {
 28 
 29             public void test1();
 30             public void test2();
 31             public String test3();
 32             public void test4();
 33             public void test5();
 34 
 35         }
 36 
 37         @Component("orderDao")
 38         class OrderDaoImpl implements OrderDao {
 39 
 40             @Override
 41             public void test1() {
 42                 System.out.println("OrderDaoImpl -> test1()");
 43             }
 44 
 45             @Override
 46             public void test2() {
 47                 System.out.println("OrderDaoImpl -> test2()");
 48             }
 49 
 50             @Override
 51             public String test3() {
 52                 System.out.println("OrderDaoImpl -> test3()");
 53                 return "SUCCESS";
 54             }
 55 
 56             @Override
 57             public void test4() {
 58                 System.out.println("OrderDaoImpl -> test4()");
 59             }
 60 
 61             @Override
 62             public void test5() {
 63                 System.out.println("OrderDaoImpl -> test5()");
 64                 int i = 1 / 0;
 65             }
 66         }
 67 
 68         @Configuration
 69         @ComponentScan(basePackages = "com.example") //註解掃描
 70         @EnableAspectJAutoProxy         //開啟 AspectJ 的自動代理
 71         class OrderAspectConfig {
 72 
 73         }
 74 
 75         @Component // 定義成 Bean
 76         @Aspect    // 定義為切面
 77         class TestAspect {
 78             @Before("execution(* com.example.OrderDao.test1(..))")
 79             public void before(JoinPoint joinPoint) {
 80                 System.out.println("TestAspect -> before(): joinPoint = " + joinPoint);
 81             }
 82 
 83             @After("execution(* com.example.OrderDao.test2(..))")
 84             public void after(JoinPoint joinPoint) {
 85                 System.out.println("TestAspect -> after(): joinPoint = " + joinPoint);
 86             }
 87 
 88             @AfterReturning(value = "execution(* com.example.OrderDao.test3(..))", returning = "returnValue")
 89             public void afterReturning(Object returnValue) {
 90                 System.out.println("TestAspect -> afterReturning(): returnValue = " + returnValue);
 91             }
 92 
 93             @Pointcut(value = "execution(* com.example.OrderDao.test4(..))")
 94             public void pointCut1() {
 95                 System.out.println("TestAspect -> pointCut1()");
 96             }
 97 
 98             @Around("TestAspect.pointCut1()")
 99             public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
100                 System.out.println("TestAspect -> around(): before");
101                 proceedingJoinPoint.proceed();
102                 System.out.println("TestAspect -> around(): after");
103             }
104 
105             @Pointcut(value = "execution(* com.example.OrderDao.test5(..))")
106             public void pointCut2() {
107                 System.out.println("TestAspect -> pointCut2()");
108             }
109 
110             @AfterThrowing(pointcut = "pointCut2()", throwing = "ex")
111             public void afterThrowing(JoinPoint joinPoint, Exception ex) {
112                 String name = joinPoint.getSignature().getName();
113                 System.out.println("TestAspect -> afterThrowing(): name = " + name + ", error = " + ex.getMessage());
114             }
115         }


    輸出:

        TestAspect -> before(): joinPoint = execution(void com.example.OrderDao.test1())
        OrderDaoImpl -> test1()
        OrderDaoImpl -> test2()
        TestAspect -> after(): joinPoint = execution(void com.example.OrderDao.test2())
        OrderDaoImpl -> test3()
        TestAspect -> afterReturning(): returnValue = SUCCESS
        TestAspect -> around(): before
        OrderDaoImpl -> test4()
        TestAspect -> around(): after
        OrderDaoImpl -> test5()
        TestAspect -> afterThrowing(): name = test5, error = / by zero
        Exception in thread "main" java.lang.ArithmeticException: / by zero
            ...