AOP 中必須明白的幾個概念
1仰禀、切面(Aspect)
官方的抽象定義為“ 一個關注點的模塊化,這個關注點可能會橫切多個對象” 蚕愤〈鸲瘢“ 切面”在ApplicationContext 中<aop:aspect>來配置饺蚊。
連接點(Joinpoint) :程序執(zhí)行過程中的某一行為,例如悬嗓,MemberService .get 的調(diào)用或者MemberService .delete 拋出異常等行為污呼。
2聘裁、通知(Advice)
“切面”對于某個“連接點”所產(chǎn)生的動作诬辈。其中艺普,一個“切面”可以包含多個“Advice”哼审。
3岂嗓、切入點(Pointcut)
匹配連接點的斷言抚笔,在AOP 中通知和一個切入點表達式關聯(lián)崭参。切面中的所有通知所關注的連接點磷籍,都由切入點表達式來決定堰氓。
4挤渐、目標對象(Target Object)
被一個或者多個切面所通知的對象。例如双絮,AServcieImpl 和BServiceImpl浴麻,當然在實際運行時,SpringAOP 采用代理實現(xiàn)囤攀,實際AOP 操作的是TargetObject 的代理對象软免。
5、AOP 代理(AOP Proxy)
在Spring AOP 中有兩種代理方式焚挠,JDK 動態(tài)代理和CGLib 代理膏萧。默認情況下,TargetObject 實現(xiàn)了接口時蝌衔,則采用JDK 動態(tài)代理榛泛,例如,AServiceImpl噩斟;反之曹锨,采用CGLib 代理,例如剃允,BServiceImpl沛简。強制使用CGLib 代理需要將<aop:config>的proxy-target-class 屬性設為true。
通知(Advice)類型:
6斥废、前置通知(Before Advice)
在某連接點(JoinPoint)之前執(zhí)行的通知椒楣,但這個通知不能阻止連接點前的執(zhí)行。ApplicationContext中在<aop:aspect>里面使用<aop:before>元素進行聲明牡肉。例如捧灰,TestAspect 中的doBefore 方法。
7统锤、后置通知(After Advice)
當某連接點退出的時候執(zhí)行的通知(不論是正常返回還是異常退出)凤壁。ApplicationContext 中在<aop:aspect>里面使用<aop:after>元素進行聲明吩屹。例如,ServiceAspect 中的returnAfter 方法拧抖,所以Teser 中調(diào)用UserService.delete 拋出異常時煤搜,returnAfter 方法仍然執(zhí)行。
8唧席、返回后通知(After Return Advice)
在某連接點正常完成后執(zhí)行的通知擦盾,不包括拋出異常的情況。ApplicationContext 中在<aop:aspect>里面使用<after-returning>元素進行聲明淌哟。
9迹卢、環(huán)繞通知(Around Advice)
包圍一個連接點的通知,類似Web 中Servlet 規(guī)范中的Filter 的doFilter 方法徒仓「睿可以在方法的調(diào)用前后完成自定義的行為, 也可以選擇不執(zhí)行掉弛。ApplicationContext 中在<aop:aspect> 里面使用<aop:around>元素進行聲明症见。例如,ServiceAspect 中的around 方法殃饿。
10谋作、異常通知(After Throwing Advice)
在方法拋出異常退出時執(zhí)行的通知。ApplicationContext 中在<aop:aspect> 里面使用<aop:after-throwing>元素進行聲明乎芳。例如遵蚜,ServiceAspect 中的returnThrow 方法。
注:可以將多個通知應用到一個目標對象上奈惑,即可以將多個切面織入到同一目標對象吭净。
AOP的使用
使用Spring AOP 可以基于兩種方式,一種是比較方便和強大的注解方式肴甸,另一種則是中規(guī)中矩的xml配置方式寂殉。
先說注解,使用注解配置Spring AOP 總體分為兩步雷滋,第一步是在xml 文件中聲明激活自動掃描組件功能,同時激活自動代理功能(來測試AOP 的注解功能):
為Aspect 切面類添加注解
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
//聲明這是一個組件
@Component
//聲明這是一個切面Bean
@Aspect
@Slf4j
public class AnnotaionAspect {
//配置切入點,該方法無方法體,主要為方便同類中其他方法使用此處配置的切入點
@Pointcut("execution(* com.gupaoedu.vip.pattern.spring.aop.service..*(..))")
public void aspect() {
}
/*
* 配置前置通知,使用在方法aspect()上注冊的切入點
* 同時接受JoinPoint 切入點對象,可以沒有該參數(shù)
*/
@Before("aspect()")
public void before(JoinPoint joinPoint) {
log.info("before 通知" + joinPoint);
}
//配置后置通知,使用在方法aspect()上注冊的切入點
@After("aspect()")
public void after(JoinPoint joinPoint) {
log.info("after 通知" + joinPoint);
}
//配置環(huán)繞通知,使用在方法aspect()上注冊的切入點
@Around("aspect()")
public void around(JoinPoint joinPoint) {
long start = System.currentTimeMillis();
try {
((ProceedingJoinPoint) joinPoint).proceed();
long end = System.currentTimeMillis();
log.info("around 通知" + joinPoint + "\tUse time : " + (end - start) + " ms!");
} catch (Throwable e) {
long end = System.currentTimeMillis();
log.info("around 通知" + joinPoint + "\tUse time : " + (end - start) + " ms with exception :
" + e.getMessage());
}
}
//配置后置返回通知,使用在方法aspect()上注冊的切入點
@AfterReturning("aspect()")
public void afterReturn(JoinPoint joinPoint) {
log.info("afterReturn 通知" + joinPoint);
}
//配置拋出異常后通知,使用在方法aspect()上注冊的切入點
@AfterThrowing(pointcut = "aspect()", throwing = "ex")
public void afterThrow(JoinPoint joinPoint, Exception ex) {
log.info("afterThrow 通知" + joinPoint + "\t" + ex.getMessage());
}
}
測試代碼
@ContextConfiguration(locations = {"classpath*:application-context.xml"})
@RunWith(SpringJUnit4ClassRunner.class)
public class AnnotationTester {
@Autowired
MemberService annotationService;
@Autowired
ApplicationContext app;
@Test
// @Ignore
public void test() {
System.out.println("=====這是一條華麗的分割線======");
AnnotaionAspect aspect = app.getBean(AnnotaionAspect.class);
System.out.println(aspect);
annotationService.save(new Member());
System.out.println("=====這是一條華麗的分割線======");
try {
annotationService.delete(1L);
} catch (Exception e) {
//e.printStackTrace();
}
}
}
簡單說一下xml 配置方式
<bean id="xmlAspect" class="com.gupaoedu.vip.pattern.spring.aop.aspect.XmlAspect"></bean>
<!-- AOP 配置-->
<aop:config>
<!-- 聲明一個切面,并注入切面Bean,相當于@Aspect -->
<aop:aspect ref="xmlAspect">
<!-- 配置一個切入點,相當于@Pointcut -->
<aop:pointcut expression="execution(* com.gupaoedu.vip.pattern.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>
簡單地介紹一下切入點表達式的配置規(guī)則
通常情況下澳泵,表達式中使用”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:參數(shù)名
throws-pattern:異常