我們現在做的一些非業務,如:日志、事務、安全等都會寫在業務代碼中(也即是說,這些非業務類橫切於業務類),但這些代碼往往是重復,復制——粘貼式的代碼會給程序的維護帶來不便,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