我們現在做的一些非業務,如:日誌、事務、安全等都會寫在業務代碼中(也即是說,這些非業務類橫切於業務類),但這些代碼往往是重覆,複製——粘貼式的代碼會給程式的維護帶來不便,AOP就實現了把這些業務需求與系統需求分開來做。這種解決的方式也稱代理機制。 先來瞭解一下AOP的相關概念,《Spring參考手冊
AOP是OOP的延續,是Aspect Oriented Programming的縮寫,意思是面向切麵編程。可以通過預編譯方式和運行期動態代理實現在不修改源代碼的情況下給程式動態統一添加功能的一種技術。 AOP實際是GoF設計模式的延續,設計模式孜孜不倦追求的是調用者和被調用者之間的解耦,AOP可以說也是這種目標的一種實現。
我們現在做的一些非業務,如:日誌、事務、安全等都會寫在業務代碼中(也即是說,這些非業務類橫切於業務類),但這些代碼往往是重覆,複製——粘貼式的代碼會給程式的維護帶來不便,AOP就實現了把這些業務需求與系統需求分開來做。這種解決的方式也稱代理機制。
先來瞭解一下AOP的相關概念,《Spring參考手冊》中定義了以下幾個AOP的重要概念,結合以上代碼分析如下:
- 切麵(Aspect):官 方的抽象定義為“一個關註點的模塊化,這個關註點可能會橫切多個對象”,在本例中,“切麵”就是類TestAspect所關註的具體行為,例 如,AServiceImpl.barA()的調用就是切麵TestAspect所關註的行為之一。“切麵”在ApplicationContext 中<aop:aspect>來配置。
- 連接點(Joinpoint) :程式執行過程中的某一行為,例如,UserService.get的調用或者UserService.delete拋出異常等行為。
- 通知(Advice) :“切麵”對於某個“連接點”所產生的動作,例如,TestAspect中對com.spring.service包下所有類的方法進行日誌記錄的動作就是一個Advice。其中,一個“切麵”可以包含多個“Advice”,例如ServiceAspect。
- 切入點(Pointcut) :匹配連接點的斷言,在AOP中通知和一個切入點表達式關聯。例如,TestAspect中的所有通知所關註的連接點,都由切入點表達式execution(* com.spring.service.*.*(..))來決定。
- 目標對象(Target Object) :被一個或者多個切麵所通知的對象。例如,AServcieImpl和BServiceImpl,當然在實際運行時,Spring AOP採用代理實現,實際AOP操作的是TargetObject的代理對象。
- AOP代理(AOP Proxy) : 在Spring AOP中有兩種代理方式,JDK動態代理和CGLIB代理。預設情況下,TargetObject實現了介面時,則採用JDK動態代理,例 如,AServiceImpl;反之,採用CGLIB代理,例如,BServiceImpl。強制使用CGLIB代理需要將 <aop:config>的 proxy-target-class屬性設為true。
通知(Advice)類型:
- 前置通知(Before advice):在 某連接點(JoinPoint)之前執行的通知,但這個通知不能阻止連接點前的執行。ApplicationContext中 在<aop:aspect>裡面使用<aop:before>元素進行聲明。例如,TestAspect中的doBefore方 法。
- 後置通知(After advice):當 某連接點退出的時候執行的通知(不論是正常返回還是異常退出)。ApplicationContext中在<aop:aspect>裡面使 用<aop:after>元素進行聲明。例如,ServiceAspect中的returnAfter方法,所以Teser中調用 UserService.delete拋出異常時,returnAfter方法仍然執行。
- 返回後通知(After return advice):在某連接點正常完成後執行的通知,不包括拋出異常的情況。ApplicationContext中在<aop:aspect>裡面使用<after-returning>元素進行聲明。
- 環繞通知(Around advice):包 圍一個連接點的通知,類似Web中Servlet規範中的Filter的doFilter方法。可以在方法的調用前後完成自定義的行為,也可以選擇不執 行。ApplicationContext中在<aop:aspect>裡面使用<aop:around>元素進行聲明。例 如,ServiceAspect中的around方法。
- 拋出異常後通知(After throwing advice):在方法拋出異常退出時執行的通知。ApplicationContext中在<aop:aspect>裡面使用<aop:after-throwing>元素進行聲明。例如,ServiceAspect中的returnThrow方法。
註:可以將多個通知應用到一個目標對象上,即可以將多個切麵織入到同一目標對象。
使用Spring AOP可以基於兩種方式,一種是比較方便和強大的註解方式,另一種則是中規中矩的xml配置方式。
先說註解,使用註解配置Spring AOP總體分為兩步,
第一步是在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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <!-- 激活組件掃描功能,在包cn.ysh.studio.spring.aop及其子包下麵自動掃描通過註解配置的組件 --> <context:component-scan base-package="cn.ysh.studio.spring.aop"/> <!-- 激活自動代理功能 --> <aop:aspectj-autoproxy proxy-target-class="true"/> <!-- 用戶服務對象 --> <bean id="userService" class="cn.ysh.studio.spring.aop.service.UserService" /> </beans>
第二步是為Aspect切麵類添加註解:
/** * 系統服務組件Aspect切麵Bean * @author Shenghany * @date 2013-5-28 */ //聲明這是一個組件 @Component //聲明這是一個切麵Bean @Aspect public class ServiceAspect { private final static Log log = LogFactory.getLog(ServiceAspect.class); //配置切入點,該方法無方法體,主要為方便同類中其他方法使用此處配置的切入點 @Pointcut("execution(* cn.ysh.studio.spring.aop.service..*(..))") public void aspect(){ } /* * 配置前置通知,使用在方法aspect()上註冊的切入點 * 同時接受JoinPoint切入點對象,可以沒有該參數 */ @Before("aspect()") public void before(JoinPoint joinPoint){ if(log.isInfoEnabled()){ log.info("before " + joinPoint); } } //配置後置通知,使用在方法aspect()上註冊的切入點 @After("aspect()") public void after(JoinPoint joinPoint){ if(log.isInfoEnabled()){ log.info("after " + joinPoint); } } //配置環繞通知,使用在方法aspect()上註冊的切入點 @Around("aspect()") public void around(JoinPoint joinPoint){ long start = System.currentTimeMillis(); try { ((ProceedingJoinPoint) joinPoint).proceed(); long end = System.currentTimeMillis(); if(log.isInfoEnabled()){ log.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms!"); } } catch (Throwable e) { long end = System.currentTimeMillis(); if(log.isInfoEnabled()){ log.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms with exception : " + e.getMessage()); } } } //配置後置返回通知,使用在方法aspect()上註冊的切入點 @AfterReturning("aspect()") public void afterReturn(JoinPoint joinPoint){ if(log.isInfoEnabled()){ log.info("afterReturn " + joinPoint); } } //配置拋出異常後通知,使用在方法aspect()上註冊的切入點 @AfterThrowing(pointcut="aspect()", throwing="ex") public void afterThrow(JoinPoint joinPoint, Exception ex){ if(log.isInfoEnabled()){ log.info("afterThrow " + joinPoint + "\t" + ex.getMessage()); } } }
測試代碼:
/** * Spring AOP測試 * @author Shenghany * @date 2013-5-28 */ public class Tester { private final static Log log = LogFactory.getLog(Tester.class); public static void main(String[] args) { //啟動Spring容器 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); //獲取service組件 UserService service = (UserService) context.getBean("userService"); //以普通的方式調用UserService對象的三個方法 User user = service.get(1L); service.save(user); try { service.delete(1L); } catch (Exception e) { if(log.isWarnEnabled()){ log.warn("Delete user : " + e.getMessage()); } } } }
控制台輸出如下:
INFO [spring.aop.aspect.ServiceAspect:40] before execution(User cn.ysh.studio.spring.aop.service.UserService.get(long)) INFO [spring.aop.service.UserService:19] getUser method . . . INFO [spring.aop.aspect.ServiceAspect:60] around execution(User cn.ysh.studio.spring.aop.service.UserService.get(long)) Use time : 42 ms! INFO [spring.aop.aspect.ServiceAspect:48] after execution(User cn.ysh.studio.spring.aop.service.UserService.get(long)) INFO [spring.aop.aspect.ServiceAspect:74] afterReturn execution(User cn.ysh.studio.spring.aop.service.UserService.get(long)) INFO [spring.aop.aspect.ServiceAspect:40] before execution(void cn.ysh.studio.spring.aop.service.UserService.save(User)) INFO [spring.aop.service.UserService:26] saveUser method . . . INFO [spring.aop.aspect.ServiceAspect:60] around execution(void cn.ysh.studio.spring.aop.service.UserService.save(User)) Use time : 2 ms! INFO [spring.aop.aspect.ServiceAspect:48] after execution(void cn.ysh.studio.spring.aop.service.UserService.save(User)) INFO [spring.aop.aspect.ServiceAspect:74] afterReturn execution(void cn.ysh.studio.spring.aop.service.UserService.save(User)) INFO [spring.aop.aspect.ServiceAspect:40] before execution(boolean cn.ysh.studio.spring.aop.service.UserService.delete(long)) INFO [spring.aop.service.UserService:32] delete method . . . INFO [spring.aop.aspect.ServiceAspect:65] around execution(boolean cn.ysh.studio.spring.aop.service.UserService.delete(long)) Use time : 5 ms with exception : spring aop ThrowAdvice演示 INFO [spring.aop.aspect.ServiceAspect:48] after execution(boolean cn.ysh.studio.spring.aop.service.UserService.delete(long)) INFO [spring.aop.aspect.ServiceAspect:74] afterReturn execution(boolean cn.ysh.studio.spring.aop.service.UserService.delete(long)) WARN [studio.spring.aop.Tester:32] Delete user : Null return value from advice does not match primitive return type for: public boolean cn.ysh.studio.spring.aop.service.UserService.delete(long) throws java.lang.Exception
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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <!-- 系統服務組件的切麵Bean --> <bean id="serviceAspect" class="cn.ysh.studio.spring.aop.aspect.ServiceAspect"/> <!-- AOP配置 --> <aop:config> <!-- 聲明一個切麵,並註入切麵Bean,相當於@Aspect --> <aop:aspect id="simpleAspect" ref="serviceAspect"> <!-- 配置一個切入點,相當於@Pointcut --> <aop:pointcut expression="execution(* cn.ysh.studio.spring.aop.service..*(..))" id="simplePointcut"/> <!-- 配置通知,相當於@Before、@After、@AfterReturn、@Around、@AfterThrowing --> <aop:before pointcut-ref="simplePointcut" method="before"/> <aop:after pointcut-ref="simplePointcut" method="after"/> <aop:after-returning pointcut-ref="simplePointcut" method="afterReturn"/> <aop:after-throwing pointcut-ref="simplePointcut" method="afterThrow" throwing="ex"/> </aop:aspect> </aop:config> </beans>
通常情況下,表達式中使用”execution“就可以滿足大部分的要求。表達式格式如下:
1)execution(* *(..))
表示匹配所有方法
2)execution(public * com. savage.service.UserService.*(..))表示匹配com.savage.server.UserService中所有的公有方法
3)execution(* com.savage.server..*.*(..))
表示匹配com.savage.server包及其子包下的所有方法
除了execution表示式外,還有within、this、target、args等Pointcut表示式。一個Pointcut定義由Pointcut表示式和Pointcut簽名組成
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
- modifiers-pattern:方法的操作許可權
- :返回值
- :方法所在的包
- :方法名
- :參數名
- :異常
其中,除ret-type-pattern和name-pattern之外,其他都是可選的。上例中,execution(* com.spring.service.*.*(..))表示com.spring.service包下,返回值為任意類型;方法名任意;參數不作限制的所有方法。
Pointcut定義時,還可以使用&&、||、! 運算,如
- @Pointcut("execution(* com.savage.aop.MessageSender.*(..))")
- private void logSender(){}
- @Pointcut("execution(* com.savage.aop.MessageReceiver.*(..))")
- private void logReceiver(){}
- @Pointcut("logSender() || logReceiver()")
- private void logMessage(){}
來源:http://ntzrj513.blog.163.com/blog/static/27945612201362232315/
來源:http://www.eclipse.org/aspectj/doc/released/runtime-api/org/aspectj/lang/Signature.html