使用面向切面編程時,咱們仍然在一個地方定義通用功能,可是能夠經過聲明的方式定義該功能要以何種方式在何處應用,而無需修改受影響的類。php
影響應用多處的功能(日誌、事務、安全)java
加強定義了切面要完成的功能以及何時執行這個功能。spring
Spring 切面能夠應用 5 種類型的加強:express
應用中每個有可能會被加強的點被稱爲鏈接點。編程
切點是規則匹配出來的鏈接點。安全
切面是加強和切點的結合,定義了在什麼時候和何處完成其功能。app
引入容許咱們向現有的類中添加新方法和屬性。能夠在不修改現有的類的狀況下,讓類具備新的行爲和狀態。框架
織入是把切面應用到目標對象中並建立新的代理對象的過程。在目標對象的生命週期裏有多個點能夠進行織入:dom
Spring 對 AOP 的支持在不少方面借鑑了 AspectJ 項目。目前 Spring 提供了 4 種類型的 AOP 支持:this
Spring AOP 構建在動態代理基礎之上,所以 Spring 對 AOP 的支持侷限於方法攔截。
經過在代理中包裹切面,Spring 在運行期把切面織入到 Spring 管理的 bean 中。代理類封裝了目標類,並攔截被加強方法的調用,再把調用轉發給真正的目標 bean。在代理攔截到方法調用時,在調用目標 bean 方法以前,會執行切面邏輯。
直到應用須要代理的 bean 時,Spring 才建立代理對象。若是使用 ApplicationContext
的話,在 ApplicationContext
從 BeanFactory
中加載全部 bean 的時候,Spring 纔會建立被代理的對象。
Spring 基於動態代理實現 AOP,因此 Spring 只支持方法鏈接點。其餘的 AOP 框架好比 AspectJ 與 JBoss,都提供了字段和構造器接入點,容許建立細粒度的加強。
Spring AOP 中,使用 AspectJ 的切點表達式來定義切點。Spring 只支持 AspectJ 切點指示器(pointcut designator)的一個子集。
AspectJ 指示器 | 描述 |
---|---|
arg( ) | 限制鏈接點匹配參數爲指定類型的執行方法 |
execution( ) | 用於匹配鏈接點 |
this | 指定匹配 AOP 代理的 bean 引用的類型 |
target | 指定匹配對象爲特定的類 |
within( ) | 指定鏈接點匹配的類型 |
@annotation | 匹配帶有指定註解的鏈接點 |
package concert;
public interface Performance {
public void perform();
}
複製代碼
Performance
類能夠表明任何類型的現場表演,好比電影、舞臺劇等。如今編寫一個切點表達式來限定 perform() 方法執行時觸發的加強。
execution(* concert.Performance.perform(..))
複製代碼
每一個部分的意義以下圖所示:
也能夠引入其餘註解對匹配規則作進一步限制。好比
execution(* concert.Performance.perform(..)) && within(concert.*)
複製代碼
within()
指示器限制了切點僅匹配 concert 包。
Spring 還有一個 bean() 指示器,容許咱們在切點表達式中使用 bean 的 ID 表示 bean。
execution(* concert.Performance.perform(..)) && bean('woodstock')
複製代碼
以上的切點就表示限定切點的 bean 的 ID 爲 woodstock
。
在一場演出以前,咱們須要讓觀衆將手機靜音且就座,觀衆在表演以後鼓掌,在表演失敗以後能夠退票。在觀衆類中定義這些功能。
@Aspect
public class Audience {
@Pointcut("execution(* concert.Performance.perform(..)))")
public void performance(){}
@Before("performance()")
public void silenceCellPhones() {
System.out.println("Silencing cell phones");
}
@Before("performance()")
public void takeSeats() {
System.out.println("Taking seats");
}
@AfterReturning("performance()")
public void applause() {
System.out.println("CLAP CLAP CLAP!!!");
}
@AfterThrowing("performance()")
public void demandRefund() {
System.out.println("Demanding a refund");
}
}
複製代碼
@AspectJ
註解表名了該類是一個切面。 @Pointcut
定義了一個類中可重用的切點,寫切點表達式時,若是切點相同,能夠重用該切點。 其他方法上的註解定義了加強被調用的時間,根據註解名能夠知道具體調用時間。
到目前爲止,Audience
仍然只是 Spring 容器中的一個 bean。即便使用了 AspectJ 註解,可是這些註解仍然不會解析,由於目前還缺少代理的相關配置。
若是使用 JavaConfig,在配置類的類級別上使用 @EnableAspectJAutoProxy
註解啓用自動代理功能。
@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class ConcertConfig {
@Bean
public Audience audience() {
return new Audience();
}
}
複製代碼
若是使用 xml ,那麼須要引入 <aop:aspectj-autoproxy>
元素。
<?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/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="concert"/>
<aop:aspectj-autoproxy/>
<bean class="concert.Audience"/>
</beans>
複製代碼
環繞加強就像在一個加強方法中同時編寫了前置加強和後置加強。
@Aspect
public class Audience {
@Pointcut("execution(* concert.Performance.perform(..)))")
public void performance(){}
@Around("performance()")
public void watchPerformance(ProceedingJoinPoint joinPoint) {
try {
System.out.println("Silencing cell phones");
System.out.println("Taking seats");
joinPoint.proceed();
System.out.println("CLAP CLAP CLAP!!!");
} catch (Throwable throwable) {
System.out.println("Demanding a refund");
}
}
}
複製代碼
能夠看到,這個加強達到的效果與分開寫前置加強與後置加強是同樣的,可是如今全部的功能都位於同一個方法內。 注意該方法接收 ProceedingJoinPoint
做爲參數,這個對象必需要有,由於須要經過它來調用被加強的方法。 注意,在這個方法中,咱們能夠控制不調用 proceed()
方法,從而阻塞對加強方法的訪問。一樣,咱們也能夠在加強方法失敗後,屢次調用 proceed()
進行重試。
修改 Perform#perform()
方法,添加參數
package concert;
public interface Performance {
public void perform(int audienceNumbers);
}
複製代碼
咱們能夠經過切點表達式來獲取被加強方法中的參數。
@Pointcut("execution(* concert.Performance.perform(int)) && args(audienceNumbers)))")
public void performance(int audienceNumbers){}
複製代碼
注意,此時方法接收的參數爲 int 型,args(audienceNumbers)
指定參數名爲 audienceNumbers
,與切點方法簽名中的參數匹配,該參數不必定與加強方法的參數名一致。
切面不只僅可以加強現有方法,也能爲對象新增新的方法。 咱們能夠在代理中暴露新的接口,當引入接口的方法被調用時,代理會把此調用委託給實現了新接口的某個其餘對象。實際上,就是一個 bean 的實現被拆分到多個類中了。 定義 Encoreable
接口,將其引入到 Performance
的實現類中。
public interface Encoreable {
void performEncore();
}
複製代碼
建立一個新的切面
@Aspect
public class EncoreableIntroducer {
@DeclareParents(value = "concert.Performance+",defaultImpl = DefaultEncoreable.class)
public static Encoreable encoreable;
}
複製代碼
咱們使用了 @Aspect
將 EncoreableIntroducer
標記爲一個切面,可是它沒有提供前置、後置或環繞加強。經過@DeclareParents
註解將Encoreable
接口引入到了 Performance bean
中。
@DeclareParents
註解由三部分組成:
+
號表示是 Performance
的全部子類型,而不是它自己。@DeclareParents
註解所標註的靜態屬性指明瞭要引入的接口。一樣地,咱們在 Spring 應用中將該類聲明爲一個 bean:
<bean class="concert.EncoreableIntroducer" />
複製代碼
Spring 的自動代理機制將會獲取到它的聲明,並建立相應的代理。而後將調用委託給被代理的 bean 或者被引入的實現,具體取決於調用的方法屬於被代理的 bean 仍是屬於被引入的接口。
更新一下 Audience
類,將它的 AspectJ
註解所有移除。
public class Audience {
public void silenceCellPhones() {
System.out.println("Silencing cell phones");
}
public void takeSeats() {
System.out.println("Taking seats");
}
public void applause() {
System.out.println("CLAP CLAP CLAP!!!");
}
public void demandRefund() {
System.out.println("Demanding a refund");
}
}
複製代碼
<?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" 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.xsd">
<aop:config>
<aop:aspect ref="audience">
<aop:before pointcut="execution(* concert.Performance.perform(..))" method="silenceCellPhone"/>
<aop:before pointcut="execution(* concert.Performance.perform(..))" method="takeSeats"/>
<aop:after-returning pointcut="execution(* concert.Performance.perform(..))" method="applause"/>
<aop:after-throwing pointcut="execution(* concert.Performance.perform(..))" method="demandRefund"/>
</aop:aspect>
</aop:config>
</beans>
複製代碼
如上所示,就將一個普通方法變爲了加強。 大多數的 AOP 配置元素都必須在 <aop:config>
元素的上下文內使用。元素名基本上都與註解名相對應。 這裏,咱們一樣將同一個切點表達式寫了四遍,將它提取出來。
<?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" 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.xsd">
<aop:config>
<aop:aspect ref="audience">
<aop:pointcut id="performance" expression="execution(* concert.Performance.perform(..))"/>
<aop:before pointcut-ref="performance" method="silenceCellPhone"/>
<aop:before pointcut-ref="performance" method="takeSeats"/>
<aop:after-returning pointcut-ref="performance" method="applause"/>
<aop:after-throwing pointcut-ref="performance" method="demandRefund"/>
</aop:aspect>
</aop:config>
</beans>
複製代碼
注意,此時 <aop:pointcut>
標籤位於 <aop:aspect>
下層,故只能在該切面中引用。若是想要一個切點可以被多個切面引用,能夠將 <aop:aspect>
元素放在 <aop:config>
下第一層。
定義環繞加強方法
public class Audience {
public void performance(int audienceNumbers){}
public void watchPerformance(ProceedingJoinPoint joinPoint) {
try {
System.out.println("Silencing cell phones");
System.out.println("Taking seats");
joinPoint.proceed();
System.out.println("CLAP CLAP CLAP!!!");
} catch (Throwable throwable) {
System.out.println("Demanding a refund");
}
}
}
複製代碼
在 xml 中使用 <aop:around>
指定方法名與切點便可。
<?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" 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.xsd">
<aop:config>
<aop:aspect ref="audience">
<aop:pointcut id="performance" expression="execution(* concert.Performance.perform(..))"/>
<aop:around pointcut-ref="performance" method="watchPerformance"/>
</aop:aspect>
</aop:config>
</beans>
複製代碼
獲取參數主要就在於切點表達式。
<aop:pointcut id="performance" expression="execution(* concert.Performance.perform(int)) and args(audienceNumbers)"/>
複製代碼
這樣能在 xml 中定位到一個參數類型爲 int ,參數名爲 audienceNumbers 的切點。 注意在 xml 中使用了 and
代替 &&
(在 XML 中,&
符號會被解析爲實體的開始)。
<aop:declare-parents types-matching="concert.Performance+"
implement-interface="concert.Encoreable"
default-impl="concert.DefaultEncoreable"/>
複製代碼
types-matching
指定了要匹配的類型,與註解中的 value 值功能相同。
AspectJ
切面提供了 Spring AOP
所不能支持的許多類型的切點。 切面頗有可能依賴其餘類來完成它們的工做。咱們能夠藉助 Spring 的依賴注入把 bean 裝配進 AspectJ
切面中。
建立一個新切面。
public aspect CriticAspect {
private CriticismEngine criticismEngine;
public CriticAspect() {
}
pointcut performance():execution(* perform(..));
afterReturning() : performance() {
System.out.println(criticismEngine.getCriticism());
}
public void setCriticismEngine(CriticismEngine criticismEngine) {
this.criticismEngine = criticismEngine;
}
}
複製代碼
注入的 CritismEngine
的實現類
public class CriticismEngineImple implements CriticismEngine {
public CriticismEngineImple() {
}
public String getCriticism() {
int i = (int) (Math.random() * criticismPool.length);
return criticismPool[i];
}
private String[] criticismPool;
public void setCriticismPool(String[] criticismPool) {
this.criticismPool = criticismPool;
}
}
複製代碼
CriticAspect
主要做用是在表演結束後爲表演發表評論。 實際上,CriticAspect
是調用了 CriticismEngine
的方法來發表評論。經過 setter
依賴注入爲 CriticAspect
設置 CriticismEngine
。
在配置文件中將 CriticismEngine bean
注入到 CriticAspect
中。
<bean class="om.springinaction.springidol.CriticAspect" factory-method="aspectOf">
<property name="criticismEngine" ref="criticismEngine"/>
</bean>
複製代碼
通常狀況下,Spring bean 由 Spring 容器初始化,可是 AspectJ 切面是由 AspectJ 在運行期建立的。因此在運行期間,AspectJ 建立好了 CriticAspect
實例,每一個 AspectJ 都會提供一個靜態的 aspectOf()
方法,返回切面的的單例。 使用factory-method
調用 aspectOf()
方法向 CriticAspect
中注入 CriticismEngine
。