Spring3系列12- Spring AOP AspectJ
本文講述使用AspectJ框架實現Spring AOP。
再重復一下Spring AOP中的三個概念,
- Advice:向程序內部注入的代碼。
- Pointcut:注入Advice的位置,切入點,一般為某方法。
- Advisor:Advice和Pointcut的結合單元,以便將Advice和Pointcut分開實現靈活配置。
AspectJ是基於注釋(Annotation)的,所以需要JDK5.0以上的支持。
AspectJ支持的注釋類型如下:
- @Before
- @After
- @AfterReturning
- @AfterThrowing
- @Around
首先定義一個簡單的bean,CustomerBo實現了接口ICustomerBo
ICustomerBo.java如下:
package com.lei.demo.aop.aspectj; public interface ICustomerBo { void addCustomer(); void deleteCustomer(); String AddCustomerReturnValue(); void addCustomerThrowException() throws Exception; void addCustomerAround(String name); }
CustomerBo.java如下:
package com.lei.demo.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結合在一起
首先沒有引入Pointcut之前,Advice和Pointcut是混在一起的
步驟,只需要兩步,如下:
- 創建一個Aspect類
- 配置Spring配置文件
第一步,創建Aspect類
LoggingAspect.java如下:
package com.lei.demo.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.lei.demo.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.lei.demo.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.lei.demo.aop.aspectj.CustomerBo.addCustomer(..))"是Aspect的切入點表達式,其中,*代表返回類型,后邊的就要定義要攔截的方法名,這里寫的的是com.lei.demo.aop.aspectj.CustomerBo.addCustomer表示攔截CustomerBo中的addCustomer方法,(..)代表參數匹配,此處表示匹配任意數量的參數,可以是0個也可以是多個,如果你確定這個方法不需要使用參數可以直接用(),還可以使用(*)來匹配一個任意類型的參數,還可以使用 (* , String),這樣代表匹配兩個參數,第二個參數必須是String 類型的參數
5. AspectJ表達式,可以對整個包定義,例如,execution(* com.lei.service..*.*(..))表示切入點是com.lei.sevice包中的任意一個類的任意方法,具體的表達式請自行百度。
第二步,配置Spring配置文件,
配置Spring-AOP-AspectJ.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-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <aop:aspectj-autoproxy/> <bean id="customerBo" class="com.lei.demo.aop.aspectj.CustomerBo"/> <bean id="logAspect" class="com.lei.demo.aop.aspectj.LoggingAspect" /> </beans>
解釋:
1. <aop:aspectj-autoproxy/>啟動AspectJ支持,這樣Spring會自動尋找用@Aspect注釋過的類,其他的配置與spring普通bean配置一樣。
測試:
執行App.java如下:
package com.lei.demo.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[] { "Spring-AOP-AspectJ.xml" }); ICustomerBo customer=(ICustomerBo)appContext.getBean("customerBo"); customer.addCustomer(); System.out.println("-------------------------------------------"); customer.deleteCustomer(); } }
結果:
logBefore() is running ...
hijacked:addCustomer
**********
addCustomer() is running ...
-------------------------------------------
deleteCustomer() is running ...
logAfter() is running ...
hijacked:deleteCustomer
**********
二、 將Advice和Pointcut分開
需要三步,
- 創建Pointcut
- 創建Advice
- 配置Spring的配置文件
第一步,PointcutsDefinition.java定義了Pointcut,如下:
package com.lei.demo.aop.aspectj; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; @Aspect public class PointcutsDefinition { @Pointcut("execution(* com.lei.demo.aop.aspectj.CustomerBo.*(..))") public void customerLog() { } }
解釋:
1. 類聲明前加入@Aspect注釋,以便被框架掃描到。
2. @Pointcut是切入點聲明,指定需要注入的代碼的位置,如上例中指定切入點為CustomerBo類中的所有方法,在實際業務中往往是指定切入點到一個邏輯層,例如 execution (* com.lei.business.service.*.*(..)),表示aop切入點為service包中所有類的所有方法,具體的表達式后邊會有介紹。
3. 方法customerLog是一個簽名,在Advice中可以用此簽名代替切入點表達式,所以不需要在方法體內編寫實際代碼,只起到助記功能,例如此處代表操作CustomerBo類時需要的切入點。
第二步,創建Advice類
LoggingAspect.java如下:
package com.lei.demo.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.lei.demo.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.lei.demo.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類LoggingAdvice,為CustomerBo中的所有方法都加入了@Before和@After兩種類型的兩種操作。
2. 對於PointcutsDefinition來說,主要職責是定義Pointcut,可以在其中第一多個切入點,並且可以用便於記憶的方法簽名進行定義。
3. 單獨定義Pointcut的好處是,一是通過使用有意義的方法名,而不是難讀的Pointcut表達式,使代碼更加直觀;二是Pointcut可以實現共享,被多個Advice直接調用。若有多個Advice調用某個Pointcut,而這個Pointcut的表達式在將來有改變時,只需修改一個地方,維護更加方便。
第三步,配置Spring配置文件,配置文件並沒有改變
配置Spring-AOP-AspectJ.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-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <aop:aspectj-autoproxy/> <bean id="customerBo" class="com.lei.demo.aop.aspectj.CustomerBo"/> <bean id="logAspect" class="com.lei.demo.aop.aspectj.LoggingAspect" /> </beans>
App.java不變,運行測試代碼App.java
輸出結果:
logBefore() is running ...
hijacked:addCustomer
**********
addCustomer() is running ...
logAfter() is running ...
hijacked:addCustomer
**********
-------------------------------------------
logBefore() is running ...
hijacked:deleteCustomer
**********
deleteCustomer() is running ...
logAfter() is running ...
hijacked:deleteCustomer
**********
三、 切入點表達式
Spring3.0.5幫助文檔中的切入點表達式如下:
Some examples of common pointcut expressions are given below.
the execution of any public method:
execution(public * *(..))
the execution of any method with a name beginning with "set":
execution(* set*(..))
the execution of any method defined by the AccountService interface:
execution(* com.xyz.service.AccountService.*(..))
the execution of any method defined in the service package:
execution(* com.xyz.service.*.*(..))
the execution of any method defined in the service package or a sub-package:
execution(* com.xyz.service..*.*(..))
any join point (method execution only in Spring AOP) within the service package:
within(com.xyz.service.*)
any join point (method execution only in Spring AOP) within the service package or a sub-package:
within(com.xyz.service..*)
any join point (method execution only in Spring AOP) where the proxy implements the AccountService interface:
this(com.xyz.service.AccountService)
'this' is more commonly used in a binding form :- see the following section on advice for how to make the proxy object available in the advice body.
any join point (method execution only in Spring AOP) where the target object implements the AccountService interface:
target(com.xyz.service.AccountService)
'target' is more commonly used in a binding form :- see the following section on advice for how to make the target object available in the advice body.
any join point (method execution only in Spring AOP) which takes a single parameter, and where the argument passed at runtime is Serializable:
args(java.io.Serializable)
'args' is more commonly used in a binding form :- see the following section on advice for how to make the method arguments available in the advice body.
Note that the pointcut given in this example is different to execution(* *(java.io.Serializable)): the args version matches if the argument passed at runtime is Serializable, the execution version matches if the method signature declares a single parameter of type Serializable.
any join point (method execution only in Spring AOP) where the target object has an @Transactional annotation:
@target(org.springframework.transaction.annotation.Transactional)
'@target' can also be used in a binding form :- see the following section on advice for how to make the annotation object available in the advice body.
any join point (method execution only in Spring AOP) where the declared type of the target object has an @Transactional annotation:
@within(org.springframework.transaction.annotation.Transactional)
'@within' can also be used in a binding form :- see the following section on advice for how to make the annotation object available in the advice body.
any join point (method execution only in Spring AOP) where the executing method has an @Transactional annotation:
@annotation(org.springframework.transaction.annotation.Transactional)
'@annotation' can also be used in a binding form :- see the following section on advice for how to make the annotation object available in the advice body.
any join point (method execution only in Spring AOP) which takes a single parameter, and where the runtime type of the argument passed has the@Classified annotation:
@args(com.xyz.security.Classified)
'@args' can also be used in a binding form :- see the following section on advice for how to make the annotation object(s) available in the advice body.
any join point (method execution only in Spring AOP) on a Spring bean named 'tradeService':
bean(tradeService)
any join point (method execution only in Spring AOP) on Spring beans having names that match the wildcard expression '*Service':
bean(*Service)