AOP是OOP的延續,是Aspect Oriented Programming的縮寫,意思是面向切面編程。能夠經過預編譯方式和運行期動態代理實如今不修改源代碼的狀況下給程序動態統一添加功能的一種技術。AOP實際是GoF設計模式的延續,設計模式孜孜不倦追求的是調用者和被調用者之間的解耦,AOP能夠說也是這種目標的一種實現。java
咱們如今作的一些非業務,如:日誌、事務、安全等都會寫在業務代碼中(也便是說,這些非業務類橫切於業務類),但這些代碼每每是重複,複製——粘貼式的代碼會給程序的維護帶來不便,AOP就實現了把這些業務需求與系統需求分開來作。這種解決的方式也稱代理機制。spring
先來了解一下AOP的相關概念,《Spring參考手冊》中定義瞭如下幾個AOP的重要概念,結合以上代碼分析以下:express
切面(Aspect):官方的抽象定義爲「一個關注點的模塊化,這個關注點可能會橫切多個對象」,在本例中,「切面」就是類TestAspect所關注的具體行爲,例如,AServiceImpl.barA()的調用就是切面TestAspect所關注的行爲之一。「切面」在ApplicationContext中<aop:aspect>來配置。apache
鏈接點(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的代理對象。mvc
AOP代理(AOP Proxy) :在Spring AOP中有兩種代理方式,JDK動態代理和CGLIB代理。默認狀況下,TargetObject實現了接口時,則採用JDK動態代理,例如,AServiceImpl;反之,採用CGLIB代理,例如,BServiceImpl。強制使用CGLIB代理須要將 <aop:config>的 proxy-target-class屬性設爲true。app
通知(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中添加一個UserService的普通服務層組件,來測試AOP的註解功能):
<?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切面類添加註解:
package cn.ysh.studio.spring.aop.aspect;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.After;import org.aspectj.lang.annotation.AfterReturning;import org.aspectj.lang.annotation.AfterThrowing;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;import org.springframework.stereotype.Component;/** * 系統服務組件Aspect切面Bean * @author Shenghany * @date 2013-5-28 *///聲明這是一個組件@Component//聲明這是一個切面Bean@Aspectpublic 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()); } } }
測試代碼:
package cn.ysh.studio.spring.aop;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import cn.ysh.studio.spring.aop.service.UserService;import cn.ysh.studio.spring.mvc.bean.User;/** * 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
能夠看到,正如咱們預期的那樣,雖然咱們並無對UserSerivce類包括其調用方式作任何改變,可是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"> <!-- 系統服務組件的切面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>
我的以爲不如註解靈活和強大,你能夠不一樣意這個觀點,可是不知道以下的代碼會不會讓你的想法有所改善:
//配置前置通知,攔截返回值爲cn.ysh.studio.spring.mvc.bean.User的方法@Before("execution(cn.ysh.studio.spring.mvc.bean.User cn.ysh.studio.spring.aop.service..*(..))")public void beforeReturnUser(JoinPoint joinPoint){ if(log.isInfoEnabled()){ log.info("beforeReturnUser " + joinPoint); }}//配置前置通知,攔截參數爲cn.ysh.studio.spring.mvc.bean.User的方法@Before("execution(* cn.ysh.studio.spring.aop.service..*(cn.ysh.studio.spring.mvc.bean.User))")public void beforeArgUser(JoinPoint joinPoint){ if(log.isInfoEnabled()){ log.info("beforeArgUser " + joinPoint); }}//配置前置通知,攔截含有long類型參數的方法,並將參數值注入到當前方法的形參id中@Before("aspect()&&args(id)")public void beforeArgId(JoinPoint joinPoint, long id){ if(log.isInfoEnabled()){ log.info("beforeArgId " + joinPoint + "\tID:" + id); }}
附上UserService的代碼(其實很簡單):
package cn.ysh.studio.spring.aop.service;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import cn.ysh.studio.spring.mvc.bean.User;/** * 用戶服務模型 * @author Shenghany * @date 2013-5-28 */public class UserService { private final static Log log = LogFactory.getLog(UserService.class); public User get(long id){ if(log.isInfoEnabled()){ log.info("getUser method . . ."); } return new User(); } public void save(User user){ if(log.isInfoEnabled()){ log.info("saveUser method . . ."); } } public boolean delete(long id) throws Exception{ if(log.isInfoEnabled()){ log.info("delete method . . ."); throw new Exception("spring aop ThrowAdvice演示"); } return false; } }
應該說學習Spring AOP有兩個難點,第一點在於理解AOP的理念和相關概念,第二點在於靈活掌握和使用切入點表達式。概念的理解一般不在一朝一夕,慢慢浸泡的時間長了,天然就明白了,下面咱們簡單地介紹一下切入點表達式的配置規則吧。
一般狀況下,表達式中使用」execution「就能夠知足大部分的要求。表達式格式以下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
modifiers-pattern:方法的操做權限
ret-type-pattern:返回值
declaring-type-pattern:方法所在的包
name-pattern:方法名
parm-pattern:參數名
throws-pattern:異常
其中,除ret-type-pattern和name-pattern以外,其餘都是可選的。上例中,execution(* com.spring.service.*.*(..))表示com.spring.service包下,返回值爲任意類型;方法名任意;參數不做限制的全部方法。
最後說一下通知參數
能夠經過args來綁定參數,這樣就能夠在通知(Advice)中訪問具體參數了。例如,<aop:aspect>配置以下:
<aop:config> <aop:aspect id="TestAspect" ref="aspectBean"> <aop:pointcut id="businessService" expression="execution(* com.spring.service.*.*(String,..)) and args(msg,..)" /> <aop:after pointcut-ref="businessService" method="doAfter"/> </aop:aspect></aop:config>
上面的代碼args(msg,..)是指將切入點方法上的第一個String類型參數添加到參數名爲msg的通知的入參上,這樣就能夠直接使用該參數啦。
訪問當前的鏈接點
在上面的Aspect切面Bean中已經看到了,每一個通知方法第一個參數都是JoinPoint。其實,在Spring中,任何通知(Advice)方法均可以將第一個參數定義爲 org.aspectj.lang.JoinPoint類型用以接受當前鏈接點對象。JoinPoint接口提供了一系列有用的方法, 好比 getArgs() (返回方法參數)、getThis() (返回代理對象)、getTarget() (返回目標)、getSignature() (返回正在被通知的方法相關信息)和 toString() (打印出正在被通知的方法的有用信息)。