前面兩篇文章記錄了 Spring IOC 的相關知識,本文記錄 Spring 中的另一特性 AOP 相關知識。
部分參考資料:
《Spring實戰(第4版)》
《輕量級 JavaEE 企業應用實戰(第四版)》
Spring 官方文件
W3CSchool Spring教程
易百教程 Spring教程
一、AOP——另一種程式設計思想
1.1 什麼是 AOP
AOP (Aspect Orient Programming),直譯過來就是 面向切面程式設計。AOP 是一種程式設計思想,是物件導向程式設計(OOP)的一種補充。物件導向程式設計將程式抽象成各個層次的物件,而面向切面程式設計是將程式抽象成各個切面。
從《Spring實戰(第4版)》圖書中扒了一張圖:
從該圖可以很形象地看出,所謂切面,相當於應用物件間的橫切點,我們可以將其單獨抽象為單獨的模組。
1.2 為什麼需要 AOP
想象下面的場景,開發中在多個模組間有某段重複的程式碼,我們通常是怎麼處理的?顯然,沒有人會靠“複製貼上”吧。在傳統的程式導向程式設計中,我們也會將這段程式碼,抽象成一個方法,然後在需要的地方分別呼叫這個方法,這樣當這段程式碼需要修改時,我們只需要改變這個方法就可以了。然而需求總是變化的,有一天,新增了一個需求,需要再多出做修改,我們需要再抽象出一個方法,然後再在需要的地方分別呼叫這個方法,又或者我們不需要這個方法了,我們還是得刪除掉每一處呼叫該方法的地方。實際上涉及到多個地方具有相同的修改的問題我們都可以通過 AOP 來解決。
1.3 AOP 實現分類
AOP 要達到的效果是,保證開發者不修改原始碼的前提下,去為系統中的業務元件新增某種通用功能。AOP 的本質是由 AOP 框架修改業務元件的多個方法的原始碼,看到這其實應該明白了,AOP 其實就是前面一篇文章講的代理模式的典型應用。
按照 AOP 框架修改原始碼的時機,可以將其分為兩類:
- 靜態 AOP 實現, AOP 框架在編譯階段對程式原始碼進行修改,生成了靜態的 AOP 代理類(生成的 *.class 檔案已經被改掉了,需要使用特定的編譯器),比如 AspectJ。
- 動態 AOP 實現, AOP 框架在執行階段對動態生成代理物件(在記憶體中以 JDK 動態代理,或 CGlib 動態地生成 AOP 代理類),如 SpringAOP。
下面給出常用 AOP 實現比較
如不清楚動態代理的,可參考我前面的一篇文章,有講解靜態代理、JDK動態代理和 CGlib 動態代理。
靜態代理和動態代理 https://www.cnblogs.com/joy99/p/10865391.html
二、AOP 術語
AOP 領域中的特性術語:
- 通知(Advice): AOP 框架中的增強處理。通知描述了切面何時執行以及如何執行增強處理。
- 連線點(join point): 連線點表示應用執行過程中能夠插入切面的一個點,這個點可以是方法的呼叫、異常的丟擲。在 Spring AOP 中,連線點總是方法的呼叫。
- 切點(PointCut): 可以插入增強處理的連線點。
- 切面(Aspect): 切面是通知和切點的結合。
- 引入(Introduction):引入允許我們向現有的類新增新的方法或者屬性。
- 織入(Weaving): 將增強處理新增到目標物件中,並建立一個被增強的物件,這個過程就是織入。
概念看起來總是有點懵,並且上述術語,不同的參考書籍上翻譯還不一樣,所以需要慢慢在應用中理解。
三、初步認識 Spring AOP
3.1 Spring AOP 的特點
AOP 框架有很多種,1.3節中介紹了 AOP 框架的實現方式有可能不同, Spring 中的 AOP 是通過動態代理實現的。不同的 AOP 框架支援的連線點也有所區別,例如,AspectJ 和 JBoss,除了支援方法切點,它們還支援欄位和構造器的連線點。而 Spring AOP 不能攔截對物件欄位的修改,也不支援構造器連線點,我們無法在 Bean 建立時應用通知。
3.2 Spring AOP 的簡單例子
下面先上程式碼,對著程式碼說比較好說,看下面這個例子:
這個例子是基於gradle建立的,首先 build.gradle 檔案新增依賴:
dependencies {
compile 'org.springframework:spring-context:5.0.6.RELEASE'
}
首先建立一個介面 IBuy.java
package com.sharpcj.aopdemo.test1;
public interface IBuy {
String buy();
}
Boy 和 Gril 兩個類分別實現了這個介面:
Boy.java
package com.sharpcj.aopdemo.test1;
import org.springframework.stereotype.Component;
@Component
public class Boy implements IBuy {
@Override
public String buy() {
System.out.println("男孩買了一個遊戲機");
return "遊戲機";
}
}
Girl.java
package com.sharpcj.aopdemo.test1;
import org.springframework.stereotype.Component;
@Component
public class Girl implements IBuy {
@Override
public String buy() {
System.out.println("女孩買了一件漂亮的衣服");
return "衣服";
}
}
配置檔案, AppConfig.java
package com.sharpcj.aopdemo;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackageClasses = {com.sharpcj.aopdemo.test1.IBuy.class})
public class AppConfig {
}
測試類, AppTest.java
package com.sharpcj.aopdemo;
import com.sharpcj.aopdemo.test1.Boy;
import com.sharpcj.aopdemo.test1.Girl;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AppTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Boy boy = context.getBean("boy",Boy.class);
Girl girl = (Girl) context.getBean("girl");
boy.buy();
girl.buy();
}
}
執行結果:
這裡運用SpringIOC裡的自動部署。現在需求改變了,我們需要在男孩和女孩的 buy 方法之前,需要列印出“男孩女孩都買了自己喜歡的東西”。用 Spring AOP 來實現這個需求只需下面幾個步驟:
1、 既然用到 Spring AOP, 首先在 build.gralde
檔案中引入相關依賴:
dependencies {
compile 'org.springframework:spring-context:5.0.6.RELEASE'
compile 'org.springframework:spring-aspects:5.0.6.RELEASE'
}
2、 定義一個切面類,BuyAspectJ.java
package com.sharpcj.aopdemo.test1;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class BuyAspectJ {
@Before("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
public void haha(){
System.out.println("男孩女孩都買自己喜歡的東西");
}
}
這個類,我們使用了註解 @Component
表明它將作為一個Spring Bean 被裝配,使用註解 @Aspect
表示它是一個切面。
類中只有一個方法 haha
我們使用 @Before
這個註解,表示他將在方法執行之前執行。關於這個註解後文再作解釋。
引數("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
宣告瞭切點,表明在該切面的切點是com.sharpcj.aopdemo.test1.Ibuy
這個介面中的buy
方法。至於為什麼這麼寫,下文再解釋。
3、 在配置檔案中啟用AOP切面功能
package com.sharpcj.aopdemo;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan(basePackageClasses = {com.sharpcj.aopdemo.test1.IBuy.class})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
}
我們在配置檔案類增加了@EnableAspectJAutoProxy
註解,啟用了 AOP 功能,引數proxyTargetClass
的值設為了 true 。預設值是 false,兩者的區別下文再解釋。
OK,下面只需測試程式碼,執行結果如下:
我們看到,結果與我們需求一致,我們並沒有修改 Boy 和 Girl 類的 Buy 方法,也沒有修改測試類的程式碼,幾乎是完全無侵入式地實現了需求。這就是 AOP 的“神奇”之處。
四、通過註解配置 Spring AOP
4.1 通過註解宣告切點指示器
Spring AOP 所支援的 AspectJ 切點指示器
在spring中嘗試使用AspectJ其他指示器時,將會丟擲IllegalArgumentException異常。
當我們檢視上面展示的這些spring支援的指示器時,注意只有execution指示器是唯一的執行匹配,而其他的指示器都是用於限制匹配的。這說明execution指示器是我們在編寫切點定義時最主要使用的指示器,在此基礎上,我們使用其他指示器來限制所匹配的切點。
下圖的切點表示式表示當Instrument的play方法執行時會觸發通知。
我們使用execution指示器選擇Instrument的play方法,方法表示式以 *
號開始,標識我們不關心方法的返回值型別。然後我們指定了全限定類名和方法名。對於方法引數列表,我們使用 ..
標識切點選擇任意的play方法,無論該方法的入參是什麼。
多個匹配之間我們可以使用連結符 &&
、||
、!
來表示 “且”、“或”、“非”的關係。但是在使用 XML 檔案配置時,這些符號有特殊的含義,所以我們使用 “and”、“or”、“not”來表示。
舉例:
限定該切點僅匹配的包是 com.sharpcj.aopdemo.test1
,可以使用
execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..)) && within(com.sharpcj.aopdemo.test1.*)
在切點中選擇 bean,可以使用
execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..)) && bean(girl)
修改 BuyAspectJ.java
package com.sharpcj.aopdemo.test1;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class BuyAspectJ {
@Before("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..)) && within(com.sharpcj.aopdemo.test1.*) && bean(girl)")
public void hehe(){
System.out.println("男孩女孩都買自己喜歡的東西");
}
}
此時,切面只會對 Girl.java
這個類生效,執行結果:
細心的你,可能發現了,切面中的方法名,已經被我悄悄地從haha
改成了hehe
,絲毫沒有影響結果,說明方法名沒有影響。和 Spring IOC 中用 java 配置檔案裝配 Bean 時,用@Bean
註解修飾的方法名一樣,沒有影響。
4.2 通過註解宣告 5 種通知型別
Spring AOP 中有 5 中通知型別,分別如下:
下面修改切面類:
package com.sharpcj.aopdemo.test1;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class BuyAspectJ {
@Before("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
public void hehe() {
System.out.println("before ...");
}
@After("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
public void haha() {
System.out.println("After ...");
}
@AfterReturning("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
public void xixi() {
System.out.println("AfterReturning ...");
}
@Around("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
public void xxx(ProceedingJoinPoint pj) {
try {
System.out.println("Around aaa ...");
pj.proceed();
System.out.println("Around bbb ...");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
為了方便看效果,我們測試類中,只要 Boy 類:
package com.sharpcj.aopdemo;
import com.sharpcj.aopdemo.test1.Boy;
import com.sharpcj.aopdemo.test1.Girl;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AppTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Boy boy = context.getBean("boy",Boy.class);
Girl girl = (Girl) context.getBean("girl");
boy.buy();
// girl.buy();
}
}
執行結果如下:
結果顯而易見。指的注意的是 @Around
修飾的環繞通知型別,是將整個目標方法封裝起來了,在使用時,我們傳入了 ProceedingJoinPoint
型別的引數,這個物件是必須要有的,並且需要呼叫 ProceedingJoinPoint
的 proceed()
方法。 如果沒有呼叫 該方法,執行結果為 :
Around aaa ...
Around bbb ...
After ...
AfterReturning ...
可見,如果不呼叫該物件的 proceed() 方法,表示原目標方法被阻塞呼叫,當然也有可能你的實際需求就是這樣。
4.3 通過註解宣告切點表示式
如你看到的,上面我們寫的多個通知使用了相同的切點表示式,對於像這樣頻繁出現的相同的表示式,我們可以使用 @Pointcut
註解宣告切點表示式,然後使用表示式,修改程式碼如下:
BuyAspectJ.java
package com.sharpcj.aopdemo.test1;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class BuyAspectJ {
@Pointcut("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
public void point(){}
@Before("point()")
public void hehe() {
System.out.println("before ...");
}
@After("point()")
public void haha() {
System.out.println("After ...");
}
@AfterReturning("point()")
public void xixi() {
System.out.println("AfterReturning ...");
}
@Around("point()")
public void xxx(ProceedingJoinPoint pj) {
try {
System.out.println("Around aaa ...");
pj.proceed();
System.out.println("Around bbb ...");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
程式執行結果沒有變化。
這裡,我們使用
@Pointcut("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
public void point(){}
宣告瞭一個切點表示式,該方法 point 的內容並不重要,方法名也不重要,實際上它只是作為一個標識,供通知使用。
4.4 通過註解處理通知中的引數
上面的例子,我們要進行增強處理的目標方法沒有引數,下面我們來說說有引數的情況,並且在增強處理中使用該引數。
下面我們給介面增加一個引數,表示購買所花的金錢。通過AOP 增強處理,如果女孩買衣服超過了 68 元,就可以贈送一雙襪子。
更改程式碼如下:
IBuy.java
package com.sharpcj.aopdemo.test1;
public interface IBuy {
String buy(double price);
}
Girl.java
package com.sharpcj.aopdemo.test1;
import org.springframework.stereotype.Component;
@Component
public class Girl implements IBuy {
@Override
public String buy(double price) {
System.out.println(String.format("女孩花了%s元買了一件漂亮的衣服", price));
return "衣服";
}
}
Boy.java
package com.sharpcj.aopdemo.test1;
import org.springframework.stereotype.Component;
@Component
public class Boy implements IBuy {
@Override
public String buy(double price) {
System.out.println(String.format("男孩花了%s元買了一個遊戲機", price));
return "遊戲機";
}
}
再看 BuyAspectJ 類,我們將之前的通知都註釋掉。用一個環繞通知來實現這個功能:
package com.sharpcj.aopdemo.test1;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class BuyAspectJ {
/*
@Pointcut("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
public void point(){}
@Before("point()")
public void hehe() {
System.out.println("before ...");
}
@After("point()")
public void haha() {
System.out.println("After ...");
}
@AfterReturning("point()")
public void xixi() {
System.out.println("AfterReturning ...");
}
@Around("point()")
public void xxx(ProceedingJoinPoint pj) {
try {
System.out.println("Around aaa ...");
pj.proceed();
System.out.println("Around bbb ...");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
*/
@Pointcut("execution(String com.sharpcj.aopdemo.test1.IBuy.buy(double)) && args(price) && bean(girl)")
public void gif(double price) {
}
@Around("gif(price)")
public String hehe(ProceedingJoinPoint pj, double price){
try {
pj.proceed();
if (price > 68) {
System.out.println("女孩買衣服超過了68元,贈送一雙襪子");
return "衣服和襪子";
}
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return "衣服";
}
}
前文提到,當不關心方法返回值的時候,我們在編寫切點指示器的時候使用了 *
, 當不關心方法引數的時候,我們使用了 ..
。現在如果我們需要傳入引數,並且有返回值的時候,則需要使用對應的型別。在編寫通知的時候,我們也需要宣告對應的返回值型別和引數型別。
測試類:AppTest.java
package com.sharpcj.aopdemo;
import com.sharpcj.aopdemo.test1.Boy;
import com.sharpcj.aopdemo.test1.Girl;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AppTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Boy boy = context.getBean("boy",Boy.class);
Girl girl = (Girl) context.getBean("girl");
String boyBought = boy.buy(35);
String girlBought = girl.buy(99.8);
System.out.println("男孩買到了:" + boyBought);
System.out.println("女孩買到了:" + girlBought);
}
}
測試結果:
可以看到,我們成功通過 AOP 實現了需求,並將結果列印了出來。
4.5 通過註解配置織入的方式
前面還有一個遺留問題,在配置檔案中,我們用註解 @EnableAspectJAutoProxy()
啟用Spring AOP 的時候,我們給引數 proxyTargetClass
賦值為 true
,如果我們不寫引數,預設為 false。這個時候執行程式,程式丟擲異常
這是一個強制型別轉換異常。為什麼會丟擲這個異常呢?或許已經能夠想到,這跟Spring AOP 動態代理的機制有關,這個 proxyTargetClass
引數決定了代理的機制。當這個引數為 false 時,
通過jdk的基於介面的方式進行織入,這時候代理生成的是一個介面物件,將這個介面物件強制轉換為實現該介面的一個類,自然就丟擲了上述型別轉換異常。
反之,proxyTargetClass
為 true
,則會使用 cglib 的動態代理方式。這種方式的缺點是擴充類的方法被final
修飾時,無法進行織入。
測試一下,我們將 proxyTargetClass
引數設為 true
,同時將 Girl.java 的 Buy 方法用 final
修飾:
AppConfig.java
package com.sharpcj.aopdemo;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan(basePackageClasses = {com.sharpcj.aopdemo.test1.IBuy.class})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
}
Girl.java
package com.sharpcj.aopdemo.test1;
import org.springframework.stereotype.Component;
@Component
public class Girl implements IBuy {
@Override
public final String buy(double price) {
System.out.println(String.format("女孩花了%s元買了一件漂亮的衣服", price));
return "衣服";
}
}
此時執行結果:
可以看到,我們的切面並沒有織入生效。
五、通過 XML 配置檔案宣告切面
前面的示例中,我們已經展示瞭如何通過註解配置去宣告切面,下面我們看看如何在 XML 檔案中宣告切面。下面先列出 XML 中宣告 AOP 的常用元素:
我們依然可以使用 <aop:aspectj-autoproxy>
元素,他能夠自動代理AspectJ註解的通知類。
5.1 XML 配置檔案中切點指示器
在XML配置檔案中,切點指示器表示式與通過註解配置的寫法基本一致,區別前面有提到,即XML檔案中需要使用 “and”、“or”、“not”來表示 “且”、“或”、“非”的關係。
5.2 XML 檔案配置 AOP 例項
下面我們不使用任何註解改造上面的例子:
BuyAspectJ.java
package com.sharpcj.aopdemo.test2;
import org.aspectj.lang.ProceedingJoinPoint;
public class BuyAspectJ {
public void hehe() {
System.out.println("before ...");
}
public void haha() {
System.out.println("After ...");
}
public void xixi() {
System.out.println("AfterReturning ...");
}
public void xxx(ProceedingJoinPoint pj) {
try {
System.out.println("Around aaa ...");
pj.proceed();
System.out.println("Around bbb ...");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
在 Resource 目錄下新建一個配置檔案 aopdemo.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"
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">
<bean id="boy" class="com.sharpcj.aopdemo.test2.Boy"></bean>
<bean id="girl" class="com.sharpcj.aopdemo.test2.Girl"></bean>
<bean id="buyAspectJ" class="com.sharpcj.aopdemo.test2.BuyAspectJ"></bean>
<aop:config proxy-target-class="true">
<aop:aspect id="qiemian" ref="buyAspectJ">
<aop:before pointcut="execution(* com.sharpcj.aopdemo.test2.IBuy.buy(..))" method="hehe"/>
<aop:after pointcut="execution(* com.sharpcj.aopdemo.test2.IBuy.buy(..))" method="haha"/>
<aop:after-returning pointcut="execution(* com.sharpcj.aopdemo.test2.IBuy.buy(..))" method="xixi"/>
<aop:around pointcut="execution(* com.sharpcj.aopdemo.test2.IBuy.buy(..))" method="xxx"/>
</aop:aspect>
</aop:config>
</beans>
這裡分別定義了一個切面,裡面包含四種型別的通知。
測試檔案中,使用
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("aopdemo.xml");
來獲取 ApplicationContext,其它程式碼不變。
5.3 XML 檔案配置宣告切點
對於頻繁重複使用的切點表示式,我們也可以宣告成切點。
配置檔案如下:aopdemo.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"
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">
<bean id="boy" class="com.sharpcj.aopdemo.test2.Boy"></bean>
<bean id="girl" class="com.sharpcj.aopdemo.test2.Girl"></bean>
<bean id="buyAspectJ" class="com.sharpcj.aopdemo.test2.BuyAspectJ"></bean>
<aop:config proxy-target-class="true">
<aop:pointcut id="apoint" expression="execution(* com.sharpcj.aopdemo.test2.IBuy.buy(..))"/>
<aop:aspect id="qiemian" ref="buyAspectJ">
<aop:before pointcut-ref="apoint" method="hehe"/>
<aop:after pointcut-ref="apoint" method="haha"/>
<aop:after-returning pointcut-ref="apoint" method="xixi"/>
<aop:around pointcut-ref="apoint" method="xxx"/>
</aop:aspect>
</aop:config>
</beans>
5.4 XML檔案配置為通知傳遞引數
BuyAspectJ.java
package com.sharpcj.aopdemo.test2;
import org.aspectj.lang.ProceedingJoinPoint;
public class BuyAspectJ {
public String hehe(ProceedingJoinPoint pj, double price){
try {
pj.proceed();
if (price > 68) {
System.out.println("女孩買衣服超過了68元,贈送一雙襪子");
return "衣服和襪子";
}
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return "衣服";
}
}
aopdemo.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"
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">
<bean id="boy" class="com.sharpcj.aopdemo.test2.Boy"></bean>
<bean id="girl" class="com.sharpcj.aopdemo.test2.Girl"></bean>
<bean id="buyAspectJ" class="com.sharpcj.aopdemo.test2.BuyAspectJ"></bean>
<aop:config proxy-target-class="true">
<aop:pointcut id="apoint" expression="execution(String com.sharpcj.aopdemo.test2.IBuy.buy(double)) and args(price) and bean(girl)"/>
<aop:aspect id="qiemian" ref="buyAspectJ">
<aop:around pointcut-ref="apoint" method="hehe"/>
</aop:aspect>
</aop:config>
</beans>
5.5 Xml 檔案配置織入的方式
同註解配置類似,
CGlib 代理方式:
<aop:config proxy-target-class="true"> </aop:config>
JDK 代理方式:
<aop:config proxy-target-class="false"> </aop:config>
六、總結
本文簡單記錄了 AOP 的程式設計思想,然後介紹了 Spring 中 AOP 的相關概念,以及通過註解方式和XML配置檔案兩種方式使用 Spring AOP進行程式設計。 相比於 AspectJ 的面向切面程式設計,Spring AOP 也有一些侷限性,但是已經可以解決開發中的絕大多數問題了,如果確實遇到了 Spring AOP 解決不了的場景,我們依然可以在 Spring 中使用 AspectJ 來解決。