我們現在做的一些非業務,如:日志、事務、安全等都會寫在業務代碼中(也即是說,這些非業務類橫切於業務類),但這些代碼往往是重復,復制——粘貼式的代碼會給程序的維護帶來不便,AOP就實現了把這些業務需求與系統需求分開來做。這種解決的方式也稱代理機制。
先來了解一下AOP的相關概念,《Spring參考手冊》中定義了以下幾個AOP的重要概念,結合以上代碼分析如下:
通知(Advice)類型:
注:可以將多個通知應用到一個目標對象上,即可以將多個切面織入到同一目標對象。
使用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.*(..))
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
其中,除ret-type-pattern和name-pattern之外,其他都是可選的。上例中,execution(* com.spring.service.*.*(..))表示com.spring.service包下,返回值為任意類型;方法名任意;參數不作限制的所有方法。
Pointcut定義時,還可以使用&&、||、! 運算,如
來源:http://ntzrj513.blog.163.com/blog/static/27945612201362232315/
來源:http://www.eclipse.org/aspectj/doc/released/runtime-api/org/aspectj/lang/Signature.html