1. 程式人生 > 實用技巧 >使用 AspectJ 框架實現 Spring AOP

使用 AspectJ 框架實現 Spring AOP

AspectJ 是基於註釋(Annotation)的,所以需要 JDK5.0 以上的支援。

AspectJ 支援的註解型別如下:

  • @Before
  • @After
  • @AfterReturning
  • @AfterThrowing
  • @Around

準備工作

首先定義一個簡單的 bean,CustomerBo 實現了介面 ICustomerBo。ICustomerBo.java 如下:

package com.shiyanlou.spring.aop.aspectj;

public interface ICustomerBo {
    void addCustomer();
    void deleteCustomer();
    String AddCustomerReturnValue();
    void addCustomerThrowException() throws Exception;
    void addCustomerAround(String name);
}

CustomerBo.java 如下:

package com.shiyanlou.spring.aop.aspectj;

public class CustomerBo implements ICustomerBo {

    public void addCustomer() {
        System.out.println("addCustomer() is running ...");
    }

    public void deleteCustomer() {
        System.out.println("deleteCustomer() is running ...");
    }

    public String AddCustomerReturnValue() {
        System.out.println("AddCustomerReturnValue() is running ...");
        return "abc";
    }

    public void addCustomerThrowException() throws Exception {
        System.out.println("addCustomerThrowException() is running ...");
        throw new Exception("Generic Error");
    }

    public void addCustomerAround(String name) {
        System.out.println("addCustomerAround() is running ,args:"+name);

    }

}

簡單的 AspectJ,Advice 和 Pointcut 結合在一起

首先沒有引入 Aspect 之前,Advice 和 Pointcut 是混在一起的,步驟如下:

  • 建立一個 Aspect 類
  • 配置 Spring 配置檔案

由於接下來要使用 aspectj 的 jar 包,首先新增 maven 依賴。需要在 pom.xml 新增:

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.2</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjtools</artifactId>
            <version>1.9.2</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.2</version>
        </dependency>

注:我們在新建專案時就已經添加了這些依賴,這裡寫出來只是讓同學們知道這些包的作用

建立 AspectJ 類,LoggingAspect.java 如下:

package com.shiyanlou.spring.aop.aspectj;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class LoggingAspect {

    @Before("execution(public * com.shiyanlou.spring.aop.aspectj.CustomerBo.addCustomer(..))")
    public void logBefore(JoinPoint joinPoint){
        System.out.println("logBefore() is running ...");
        System.out.println("hijacked:"+joinPoint.getSignature().getName());
        System.out.println("**********");
    }

    @After("execution(public * com.shiyanlou.spring.aop.aspectj.CustomerBo.deleteCustomer(..))")
    public void logAfter(JoinPoint joinPoint){
        System.out.println("logAfter() is running ...");
        System.out.println("hijacked:"+joinPoint.getSignature().getName());
        System.out.println("**********");
    }
}

解釋:

1、必須使用 @Aspect 在 LoggingAspect 宣告之前註釋,以便被框架掃描到。
2、此例 Advice 和 Pointcut 結合在一起,類中的具體方法 logBefore 和 logAfter 即為 Advice,是要注入的程式碼,Advice 方法上的表示式為 Pointcut 表示式,即定義了切入點,上例中 @Before 註釋的表示式代表執行 CustomerBo.addCustomer 方法時注入 logBefore 程式碼。
3、在 LoggingAspect 方法上加入 @Before 或者 @After 等註釋。
4、execution(public * com.shiyanlou.spring.aop.aspectj.CustomerBo.addCustomer(..)) 是 Aspect 的切入點表示式,其中,* 代表返回型別,後邊的就要定義要攔截的方法名。這裡寫的的是 com.shiyanlou.spring.aop.aspectj.CustomerBo.addCustomer 表示攔截 CustomerBo 中的 addCustomer 方法,(..) 代表引數匹配,此處表示匹配任意數量的引數,可以是 0 個也可以是多個,如果你確定這個方法不需要使用引數可以直接用 (),還可以使用 () 來匹配一個任意型別的引數,還可以使用 ( , String ),這樣代表匹配兩個引數,第二個引數必須是 String 型別的引數。
5、AspectJ 表示式,可以對整個包定義,例如 execution ( * com.shiyanlou.spring.aop.aspectj..(..)) 表示切入點是 com.shiyanlou.spring.aop.aspectj 包中的任意一個類的任意方法,具體的表示式請自行搜尋。

配置 SpringAopAspectj.xml 檔案,如下:

<?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"
    xsi:schemaLocation = "http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd">

    <aop:aspectj-autoproxy/>

    <bean id = "customerBo" class = "com.shiyanlou.spring.aop.aspectj.CustomerBo"/>

    <bean id = "logAspect" class = "com.shiyanlou.spring.aop.aspectj.LoggingAspect" />

</beans>

<aop:aspectj-autoproxy/> 啟動 AspectJ 支援,這樣 Spring 會自動尋找用 @Aspect 註釋過的類,其他的配置與 Spring 普通 bean 配置一樣。

執行 App.java 如下:

package com.shiyanlou.spring.aop.aspectj;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class App {
    public static void main(String[] args) {

        ApplicationContext appContext = new ClassPathXmlApplicationContext(new String[] { "SpringAOPAspectj.xml" });
        ICustomerBo customer = (ICustomerBo)appContext.getBean("customerBo");

        customer.addCustomer();

        System.out.println("-------------------------------------------");

        customer.deleteCustomer();

    }
}

控制檯輸入命令:

mvn clean compile
mvn exec:java -Dexec.mainClass="com.shiyanlou.spring.aop.aspectj.App"

實驗結果如下:

將 Advice 和 Pointcut 分開

需要三步:

1、建立 Pointcut
2、建立 Advice
3、配置 Spring 的配置檔案

定義 Pointcut,建立 PointcutsDefinition.java,如下:

package com.shiyanlou.spring.aop.aspectj;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class PointcutsDefinition {

    @Pointcut("execution(* com.shiyanlou.spring.aop.aspectj.CustomerBo.*(..))")
    public void customerLog() {
    }
}

解釋:

1、類宣告前加入 @Aspect 註釋,以便被框架掃描到。
2、@Pointcut 是切入點宣告,指定需要注入的程式碼的位置,如上例中指定切入點為 CustomerBo 類中的所有方法,在實際業務中往往是指定切入點到一個邏輯層,例如 execution (* com.shiyanlou.spring.aop.aspectj..(..)),表示 aop 切入點為 aspectj 包中所有類的所有方法,具體的表示式後邊會有介紹。
3、方法 customerLog 是一個簽名,在 Advice 中可以用此簽名代替切入點表示式,所以不需要在方法體內編寫實際程式碼,只起到助記功能,例如此處代表操作 CustomerBo 類時需要的切入點。
建立 LoggingAspect.java:

package com.shiyanlou.spring.aop.aspectj;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class LoggingAspect {

    @Before("com.shiyanlou.spring.aop.aspectj.PointcutsDefinition.customerLog()")
    public void logBefore(JoinPoint joinPoint){
        System.out.println("logBefore() is running ...");
        System.out.println("hijacked:"+joinPoint.getSignature().getName());
        System.out.println("**********");
    }

    @After("com.shiyanlou.spring.aop.aspectj.PointcutsDefinition.customerLog()")
    public void logAfter(JoinPoint joinPoint){
        System.out.println("logAfter() is running ...");
        System.out.println("hijacked:"+joinPoint.getSignature().getName());
        System.out.println("**********");
    }
}

解釋:

1、@Before 和 @After 使用 PointcutsDefinition 中的方法簽名代替 Pointcut 表示式找到相應的切入點,即通過簽名找到 PointcutsDefinition 中 customerLog 簽名上的 Pointcut 表示式,表示式指定切入點為 CustomerBo 類中的所有方法。所以此例中 Advice 類 LoggingAspect,為 CustomerBo 中的所有方法都加入了 @Before 和 @After 兩種型別的兩種操作。
2、對於 PointcutsDefinition 來說,主要職責是定義 Pointcut,可以在其中定義多個切入點,並且可以用便於記憶的方法簽名進行定義。
3、單獨定義 Pointcut 的好處是,一是通過使用有意義的方法名,而不是難讀的 Pointcut 表示式,使程式碼更加直觀;二是 Pointcut 可以實現共享,被多個 Advice 直接呼叫。若有多個 Advice 呼叫某個 Pointcut,而這個 Pointcut 的表示式在將來有改變時,只需修改一個地方,維護更加方便。

配置 Spring 配置檔案。

配置 SpringAOPAspectJ.xml 檔案,如下:

<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"
    xsi:schemaLocation = "http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd">

    <aop:aspectj-autoproxy/>

    <bean id = "customerBo" class = "com.shiyanlou.spring.aop.aspectj.CustomerBo"/>

    <bean id = "logAspect" class = "com.shiyanlou.spring.aop.aspectj.LoggingAspect" />

</beans>

App.java 不變,執行測試程式碼 App.java:

mvn clean compile
mvn exec:java -Dexec.mainClass="com.shiyanlou.spring.aop.aspectj.App"

結果如下: