AOP 中必須明白的幾個(gè)概念
1、切面(Aspect)
官方的抽象定義為“ 一個(gè)關(guān)注點(diǎn)的模塊化,這個(gè)關(guān)注點(diǎn)可能會(huì)橫切多個(gè)對(duì)象” ?!?切面”在ApplicationContext 中<aop:aspect>來配置。
連接點(diǎn)(Joinpoint) :程序執(zhí)行過程中的某一行為,例如,MemberService .get 的調(diào)用或者M(jìn)emberService .delete 拋出異常等行為。
2、通知(Advice)
“切面”對(duì)于某個(gè)“連接點(diǎn)”所產(chǎn)生的動(dòng)作。其中,一個(gè)“切面”可以包含多個(gè)“Advice”。
3、切入點(diǎn)(Pointcut)
匹配連接點(diǎn)的斷言,在AOP 中通知和一個(gè)切入點(diǎn)表達(dá)式關(guān)聯(lián)。切面中的所有通知所關(guān)注的連接點(diǎn),都由切入點(diǎn)表達(dá)式來決定。
4、目標(biāo)對(duì)象(Target Object)
被一個(gè)或者多個(gè)切面所通知的對(duì)象。例如,AServcieImpl 和BServiceImpl,當(dāng)然在實(shí)際運(yùn)行時(shí),SpringAOP 采用代理實(shí)現(xiàn),實(shí)際AOP 操作的是TargetObject 的代理對(duì)象。
5、AOP 代理(AOP Proxy)
在Spring AOP 中有兩種代理方式,JDK 動(dòng)態(tài)代理和CGLib 代理。默認(rèn)情況下,TargetObject 實(shí)現(xiàn)了接口時(shí),則采用JDK 動(dòng)態(tài)代理,例如,AServiceImpl;反之,采用CGLib 代理,例如,BServiceImpl。強(qiáng)制使用CGLib 代理需要將<aop:config>的proxy-target-class 屬性設(shè)為true。
通知(Advice)類型:
6、前置通知(Before Advice)
在某連接點(diǎn)(JoinPoint)之前執(zhí)行的通知,但這個(gè)通知不能阻止連接點(diǎn)前的執(zhí)行。ApplicationContext中在<aop:aspect>里面使用<aop:before>元素進(jìn)行聲明。例如,TestAspect 中的doBefore 方法。
7、后置通知(After Advice)
當(dāng)某連接點(diǎn)退出的時(shí)候執(zhí)行的通知(不論是正常返回還是異常退出)。ApplicationContext 中在<aop:aspect>里面使用<aop:after>元素進(jìn)行聲明。例如,ServiceAspect 中的returnAfter 方法,所以Teser 中調(diào)用UserService.delete 拋出異常時(shí),returnAfter 方法仍然執(zhí)行。
8、返回后通知(After Return Advice)
在某連接點(diǎn)正常完成后執(zhí)行的通知,不包括拋出異常的情況。ApplicationContext 中在<aop:aspect>里面使用<after-returning>元素進(jìn)行聲明。
9、環(huán)繞通知(Around Advice)
包圍一個(gè)連接點(diǎn)的通知,類似Web 中Servlet 規(guī)范中的Filter 的doFilter 方法??梢栽诜椒ǖ恼{(diào)用前后完成自定義的行為, 也可以選擇不執(zhí)行。ApplicationContext 中在<aop:aspect> 里面使用<aop:around>元素進(jìn)行聲明。例如,ServiceAspect 中的around 方法。
10、異常通知(After Throwing Advice)
在方法拋出異常退出時(shí)執(zhí)行的通知。ApplicationContext 中在<aop:aspect> 里面使用<aop:after-throwing>元素進(jìn)行聲明。例如,ServiceAspect 中的returnThrow 方法。
注:可以將多個(gè)通知應(yīng)用到一個(gè)目標(biāo)對(duì)象上,即可以將多個(gè)切面織入到同一目標(biāo)對(duì)象。
AOP的使用
使用Spring AOP 可以基于兩種方式,一種是比較方便和強(qiáng)大的注解方式,另一種則是中規(guī)中矩的xml配置方式。
先說注解,使用注解配置Spring AOP 總體分為兩步,第一步是在xml 文件中聲明激活自動(dòng)掃描組件功能,同時(shí)激活自動(dòng)代理功能(來測(cè)試AOP 的注解功能):
為Aspect 切面類添加注解
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
//聲明這是一個(gè)組件
@Component
//聲明這是一個(gè)切面Bean
@Aspect
@Slf4j
public class AnnotaionAspect {
//配置切入點(diǎn),該方法無方法體,主要為方便同類中其他方法使用此處配置的切入點(diǎn)
@Pointcut("execution(* com.gupaoedu.vip.pattern.spring.aop.service..*(..))")
public void aspect() {
}
/*
* 配置前置通知,使用在方法aspect()上注冊(cè)的切入點(diǎn)
* 同時(shí)接受JoinPoint 切入點(diǎn)對(duì)象,可以沒有該參數(shù)
*/
@Before("aspect()")
public void before(JoinPoint joinPoint) {
log.info("before 通知" + joinPoint);
}
//配置后置通知,使用在方法aspect()上注冊(cè)的切入點(diǎn)
@After("aspect()")
public void after(JoinPoint joinPoint) {
log.info("after 通知" + joinPoint);
}
//配置環(huán)繞通知,使用在方法aspect()上注冊(cè)的切入點(diǎn)
@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()上注冊(cè)的切入點(diǎn)
@AfterReturning("aspect()")
public void afterReturn(JoinPoint joinPoint) {
log.info("afterReturn 通知" + joinPoint);
}
//配置拋出異常后通知,使用在方法aspect()上注冊(cè)的切入點(diǎn)
@AfterThrowing(pointcut = "aspect()", throwing = "ex")
public void afterThrow(JoinPoint joinPoint, Exception ex) {
log.info("afterThrow 通知" + joinPoint + "\t" + ex.getMessage());
}
}
測(cè)試代碼
@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();
}
}
}
簡(jiǎn)單說一下xml 配置方式
<bean id="xmlAspect" class="com.gupaoedu.vip.pattern.spring.aop.aspect.XmlAspect"></bean>
<!-- AOP 配置-->
<aop:config>
<!-- 聲明一個(gè)切面,并注入切面Bean,相當(dāng)于@Aspect -->
<aop:aspect ref="xmlAspect">
<!-- 配置一個(gè)切入點(diǎn),相當(dāng)于@Pointcut -->
<aop:pointcut expression="execution(* com.gupaoedu.vip.pattern.spring.aop.service..*(..))"
id="simplePointcut"/>
<!-- 配置通知,相當(dāng)于@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>
簡(jiǎn)單地介紹一下切入點(diǎn)表達(dá)式的配置規(guī)則
通常情況下,表達(dá)式中使用”execution“就可以滿足大部分的要求。表達(dá)式格式如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?
modifiers-pattern:方法的操作權(quán)限
ret-type-pattern:返回值 (必須)
declaring-type-pattern:方法所在的包
name-pattern:方法名 (必須)
parm-pattern:參數(shù)名
throws-pattern:異常