使用 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"
結果如下: