AOP簡(jiǎn)介
AOP的全稱是 Aspect-Oriented Programming,即面向切面編程(也稱面向方面編程)。它是面向?qū)ο缶幊蹋∣OP)的一種補(bǔ)充,目前已成為一種比較成熟的編程方式。
AOP作用
在軟件開發(fā)中,散布于應(yīng)用中多處的功能被稱為橫切關(guān)注點(diǎn),通常來(lái)講,這些橫切關(guān)注點(diǎn)從概念上是與應(yīng)用的業(yè)務(wù)邏輯相分離的(但是往往會(huì)直接嵌入到應(yīng)用的業(yè)務(wù)邏輯之中),比如通常會(huì)進(jìn)行事務(wù)處理、日志記錄等操作。在OOP設(shè)計(jì)中,它們導(dǎo)致了大量代碼的重復(fù),而不利于各個(gè)模塊的重用;但在面向切面編程中,我們?nèi)匀辉谝粋€(gè)地方定義這些功能,但是可以通過(guò)聲明的方式定義這些功能要以何種方式在何處使用,而無(wú)需修改受影響的類,這樣使得服務(wù)模塊更加簡(jiǎn)潔,因?yàn)檫@些次要關(guān)注點(diǎn)的代碼已經(jīng)被轉(zhuǎn)移到切面中了。
AOP相關(guān)術(shù)語(yǔ)
1.Aspect(切面):是通知和切點(diǎn)的結(jié)合,共同定義了切面的全部?jī)?nèi)容——它是什么,在何時(shí)和何處完成其功能。
2.Joinpoint(連接點(diǎn)):是應(yīng)用執(zhí)行過(guò)程中能夠插入切面的一個(gè)點(diǎn),切面代碼可以利用這些點(diǎn)插入到應(yīng)用的正常流程之中來(lái)添加新的行為,也就是指方法的調(diào)用。
3.Pointcut(切入點(diǎn)):定義了切面中何處完成其功能,即在哪些類以及哪些方法上切入。
4.Advice(通知/增強(qiáng)處理):定義了切面中何時(shí)做什么功能。比如應(yīng)該在目標(biāo)方法被調(diào)用之前、之后還是拋出異常后調(diào)用通知?
5.Introduction(引入):在不修改代碼的前提下,允許我們向現(xiàn)有的類添加新方法或?qū)傩浴?br>
6.Weaving(織入):是把切面應(yīng)用到目標(biāo)對(duì)象并創(chuàng)建新的代理對(duì)象的過(guò)程。
相關(guān)通知類型
1.@After:通知方法會(huì)在目標(biāo)方法返回或拋出異常后調(diào)用。
2.@AfterReturning:通知方法會(huì)在目標(biāo)方法返回后調(diào)用。
3.@AfterThrowing:通知方法會(huì)在目標(biāo)方法拋出異常后調(diào)用。
4.@Around:通知方法會(huì)將目標(biāo)方法封裝起來(lái)。
5.@Before:通知方法會(huì)在目標(biāo)方法調(diào)用之前執(zhí)行。
使用注解創(chuàng)建切面
現(xiàn)在模擬一個(gè)場(chǎng)景,在某個(gè)演出中我們需要引入觀眾的一些反應(yīng),這里演出是核心功能,而觀眾的反應(yīng)是次要功能,因此要將觀眾定義為一個(gè)切面并應(yīng)用到表演家演出上。
1.AOP相關(guān)的pom依賴
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
2.創(chuàng)建表演類
@Component("performance") //聲明為一個(gè)bean
public class Performance {
public void perform(){
System.out.println("表演家正在表演中!");
}
}
3.創(chuàng)建觀眾類
@Component("audience") //聲明為一個(gè)bean
@Aspect //聲明此類是個(gè)切面
public class Audience {
/**
* 表演家演出前需要的觀眾反應(yīng)
*/
@Before("execution(* com.mybean.concert.Performance.perform())")
public void beforePerform(){
System.out.println("觀眾入席!");
System.out.println("觀眾手機(jī)調(diào)至靜音!");
}
/**
* 表演家演出成功后需要的觀眾反應(yīng)
*/
@AfterReturning("execution(* com.mybean.concert.Performance.perform())")
public void afterReturningPerform(){
System.out.println("觀眾鼓掌!");
}
/**
* 表演家演出出狀況后需要的觀眾反應(yīng)
*/
@AfterThrowing("execution(* com.mybean.concert.Performance.perform())")
public void afterThrowingPerform(){
System.out.println("觀眾要求退款!");
}
}
可以看出我們?cè)陬l繁地使用切點(diǎn)表達(dá)式,可以用@Pointcut使代碼變得簡(jiǎn)介:
@Component("audience")
@Aspect
public class Audience {
@Pointcut("execution(* com.mybean.concert.Performance.perform())")
public void performance(){
}
@Before("performance()")
public void beforePerform(){
System.out.println("觀眾入席!");
System.out.println("觀眾手機(jī)調(diào)至靜音!");
}
@AfterReturning("performance()")
public void afterReturningPerform(){
System.out.println("觀眾鼓掌!");
}
@AfterThrowing("performance()")
public void afterThrowingPerform(){
System.out.println("觀眾要求退款!");
}
}
通過(guò)兩個(gè)注解可以看出,雖然此類被聲明為一個(gè)切面,但是它仍然能夠像普通類一樣被其他java類調(diào)用,它的方法也能夠進(jìn)行獨(dú)立的單元測(cè)試,只不過(guò)@Aspect表明它會(huì)被作為切面使用而已,這里還將它聲明為一個(gè)bean。
4.在配置類上啟動(dòng)注解的自動(dòng)代理
@Configuration //說(shuō)明此類是個(gè)配置類
@ComponentScan //啟動(dòng)組件掃描
@EnableAspectJAutoProxy //啟用Aspect自動(dòng)代理
public class BeanConfig {
}
5.測(cè)試
public class App {
public static void main( String[] args ) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
Performance performance = (Performance) applicationContext.getBean("performance");
performance.perform();
}
}
控制臺(tái)打印結(jié)果如下:

創(chuàng)建環(huán)繞通知
環(huán)繞通知是最為強(qiáng)大的通知類型,它能夠讓你編寫的邏輯將被通知的目標(biāo)方法完全包裝起來(lái)。實(shí)際上就像在一個(gè)通知方法中同時(shí)編寫前置通知和后置通知。
例如上方的觀眾類可寫為:
@Component("audience")
@Aspect
public class Audience {
@Pointcut("execution(* com.mybean.concert.Performance.perform())")
public void performance(){
}
@Around("performance()")
public void aroundPerformance(ProceedingJoinPoint joinPoint) {
try {
System.out.println("觀眾入席!");
System.out.println("觀眾手機(jī)調(diào)至靜音!");
joinPoint.proceed();
System.out.println("觀眾鼓掌!");
} catch (Throwable e) {
System.out.println("觀眾要求退款!");
}
}
}
其它代碼不變,運(yùn)行后控制臺(tái)結(jié)果和上方一樣。
其中通過(guò)ProceedingJoinPoint來(lái)調(diào)用被通知的方法,當(dāng)要將控制權(quán)交給被通知的方法時(shí)(這里指表演類的表演方法),需要調(diào)用ProceedingJoinPoint的proceed()方法。
處理通知中的參數(shù)
如果被通知的方法中有參數(shù),且切面需要使用到這些參數(shù)呢?接著上面的情景,如果表演家表演的內(nèi)容需要被指定,通過(guò)以下例子切面能夠獲得被通知方法中的參數(shù):
@Component("performance")
public class Performance {
public void perform(String performanceName){
System.out.println("表演家正在演奏" + performanceName);
}
}
@Component("performanceNumber")
@Aspect
public class PerformanceNumber {
@Pointcut("execution(* com.mybean.concert.Performance.perform(String))" + "&& args(performanceName)")
public void performance(String performanceName){
}
@Before("performance(performanceName)")
public void beforePerform(String performanceName){
System.out.println("觀眾入席!");
System.out.println("觀眾手機(jī)調(diào)至靜音!");
}
@AfterReturning("performance(performanceName)")
public void afterReturningPerform(String performanceName){
System.out.println("觀眾為表演家演奏的" + performanceName + "節(jié)目鼓掌!");
}
@AfterThrowing("performance(performanceName)")
public void afterThrowingPerform(String performanceName){
System.out.println("觀眾要求退款!");
}
}
args(performanceName)表明傳遞給perform()方法的string類型的參數(shù)也會(huì)傳遞到通知去,參數(shù)的名稱performanceName也與切點(diǎn)方法簽名中的參數(shù)相匹配。
如果是環(huán)繞通知,則:
@Component("performanceNumber")
@Aspect
public class Audience {
@Pointcut("execution(* com.mybean.concert.Performance.perform(String))")
public void performance(){
}
@Around("performance()")
public void aroundPerformance(ProceedingJoinPoint joinPoint) {
try {
System.out.println("觀眾入席!");
System.out.println("觀眾手機(jī)調(diào)至靜音!");
joinPoint.proceed();
Object[] args = joinPoint.getArgs();
System.out.println("觀眾為表演家演奏的" + args[0] + "節(jié)目鼓掌!");
} catch (Throwable e) {
System.out.println("觀眾要求退款!");
}
}
}
部分ProceedingJoinPoint對(duì)象的方法信息如下:

測(cè)試:
public class App {
public static void main( String[] args ) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
Performance performance = (Performance) applicationContext.getBean("performance");
performance.perform("鋼琴");
}
}
運(yùn)行結(jié)果如下:

通過(guò)注解為被通知方法引入新功能
切面可以為Spring bean添加新方法。 在Spring中,切面只是實(shí)現(xiàn)了它們所包裝bean相同接口的代理。實(shí)際上,除了實(shí)現(xiàn)這些接口,代理也能暴露新接口,那樣的話,切面所通知的bean看起來(lái)像是實(shí)現(xiàn)了新的接口,即便底層實(shí)現(xiàn)類并沒有實(shí)現(xiàn)這些接口也無(wú)所謂。
先引入新接口和對(duì)應(yīng)的實(shí)現(xiàn):
public interface Encoreable {
void performEncore();
}
@Component("encoreableImpl")
public class EncoreableImpl implements Encoreable{
@Override
public void performEncore() {
System.out.println("添加的新方法");
}
}
創(chuàng)建一個(gè)用于注入新方法的切面EncoreableIntroducer:
@Component("encoreableIntroducer")
@Aspect
public class EncoreableIntroducer {
@DeclareParents(value="com.mybean.concert.Performance+",defaultImpl= EncoreableImpl.class)
public static Encoreable encoreable;
}
可以看到,EncoreableIntroducer是一個(gè)切面,但是它與我們之前所創(chuàng)建的切面不同,它并沒有提供前置、后置或環(huán)繞通知,而是通過(guò)@DeclareParents注解,將Encoreable接口引入 到Performance bean中。@DeclareParents注解由三部分組成:
1.value屬性:指定了哪種類型的bean要引入該接口。
2.defaultImpl:指定了為引入功能提供實(shí)現(xiàn)的類。
3.@DeclareParents:所標(biāo)注的靜態(tài)屬性指明了要引入的接口。
測(cè)試:
public class App {
public static void main( String[] args ) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
Performance performance = (Performance) applicationContext.getBean("performance");
((Encoreable) performance).performEncore();
}
}
控制臺(tái)打印如下,說(shuō)明Encoreable的方法已經(jīng)添加到Performance中,注意要進(jìn)行強(qiáng)制轉(zhuǎn)換:

在xml中聲明切面
如果需要聲明切面,但是又不能為通知類添加注解的時(shí)候,就必須轉(zhuǎn)向xml配置了。
去掉上面類的@Aspect和@Component注解后,把聲明bean和切面全在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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<bean id="audience" class="com.mybean.aspect.Audience">
</bean>
<bean id="performance" class="com.mybean.concert.Performance">
</bean>
<aop:config>
<aop:aspect ref="audience">
<aop:before pointcut="execution(* com.mybean.concert.Performance.perform())" method="beforePerform"/>
<aop:after-returning pointcut="execution(* com.mybean.concert.Performance.perform())" method="afterReturningPerform"/>
<aop:after-throwing pointcut="execution(* com.mybean.concert.Performance.perform())" method="afterThrowingPerform"/>
</aop:aspect>
</aop:config>
</beans>
如果用pointcut,則:
<aop:config>
<aop:aspect ref="audience">
<aop:pointcut expression="execution(* com.mybean.concert.Performance.perform())" id="performance"/>
<aop:before pointcut-ref="performance" method="beforePerform"/>
<aop:after-returning pointcut-ref="performance" method="afterReturningPerform"/>
<aop:after-throwing pointcut-ref="performance" method="afterThrowingPerform"/>
</aop:aspect>
</aop:config>
如果是聲明環(huán)繞通知,則:
<aop:config>
<aop:aspect ref="audience">
<aop:pointcut expression="execution(* com.mybean.concert.Performance.perform())" id="performance"/>
<aop:around method="aroundPerformance" pointcut-ref="performance"></aop:around>
</aop:aspect>
</aop:config>
如果是為通知傳遞參數(shù),則:
<aop:config>
<aop:aspect ref="audience">
<aop:pointcut expression="execution(* com.mybean.concert.Performance.perform(String)) and args(performanceName)" id="performance"/>
<aop:before pointcut-ref="performance" method="beforePerform"/>
<aop:after-returning pointcut-ref="performance" method="afterReturningPerform"/>
<aop:after-throwing pointcut-ref="performance" method="afterThrowingPerform"/>
</aop:aspect>
</aop:config>
因?yàn)椤?amp;”在xml中有特殊含義,是轉(zhuǎn)義字符的前綴,所以用and來(lái)代替“&&”。
如果要通過(guò)切面引入新功能,則:
<aop:config>
<aop:aspect ref="audience">
<aop:declare-parents types-matching="com.mybean.concert.Performance+" implement-interface="com.mybean.concert.Encoreable" default-impl="com.mybean.concert.EncoreableImpl"/>
</aop:aspect>
</aop:config>