AOP主要實現的目的是針對業務處理過程中的切面進行提取,它所面對的是處理過程中的某個步驟或階段,以獲得邏輯過程中各部分之間低耦合性的隔離效果。
AOP是軟件開發思想階段性的產物,我們比較熟悉面向過程OPP和面向對象OOP,AOP是OOP的延續,但不是OOP的替代,而是作為OOP的有益補充。
參考《Spring 實戰 (第4版)》和《精通Spring4.x 企業應用開發實戰》兩本書的AOP章節和其他資料將其知識點整理起來。
部分代碼實例摘自《精通Spring4.x 企業應用開發實戰》,文末我會給出兩本書的PDF下載地址!
AOP概述
AOP為Aspect Oriented Programming的縮寫,意為:面向切面編程。那么什么又是面向切面呢?
我知道面向對象的特性:封裝、繼承、多態。通過抽象出代碼中共有特性來優化編程,但是這種方式又往往不能完全適用任何場景,無法避免的造成代碼之間的耦合。
如下面的代碼,運用的OOP思想將共有操作抽象了出來,抽象出了兩個處理類:性能監視 PerformanceMonitor 和事務管理 TransactionManager
package com.smart.concept; public class ForumService { private TransactionManager transManager; private PerformanceMonitor pmonitor; private TopicDao topicDao; private ForumDao forumDao; public void removeTopic(int topicId) { pmonitor.start(); transManager.beginTransaction(); topicDao.removeTopic(topicId); // ① transManager.commit(); pmonitor.end(); } public void createForum(Forum forum) { pmonitor.start(); transManager.beginTransaction(); forumDao.create(forum); // ② transManager.commit(); pmonitor.end(); } }
①、②處是兩個方法 removeTopic 和 createForum 獨有的業務邏輯,但它們淹沒在了重復化非業務性代碼之中。這種抽象為縱向抽取。
將removeTopic和createForum進行橫切重新審視:
我們無法通過縱向抽取的方式來消除代碼的重復性。然而切面能幫助我們模塊化橫切關注點,橫切關注點可以被描述為影響應用多處的功能。例如,性能監視和事務管理分別就是一個橫切關注點。
AOP提供了取代繼承和委托的另一種可選方案,那就是橫向抽取,可以在很多場景下更清晰簡潔。在使用面向切面編程時,我們仍然在一個地方定義通用功能,但是可以通過聲明的方式定義這個功能要以何種方式在何處應用,而無需修改受影響的類。橫切關注點可以被模塊化為特殊的類,這些類被稱為切面(aspect)。這樣做有兩個好處:首先,現在每個關注點都集中於一個地方,而不是分散到多處代碼中;其次,服務模塊更簡潔,因為它們只包含主要關注點(或核心功能)的代碼,而次要關注點的代碼被轉移到切面中了。
AOP術語
AOP不容易理解的一方面原因就是概念太多,並且由英語直譯過來的名稱也不統一,這里我選擇使用較多的譯名並給出英文供大家參考。
連接點(Joinpoint)
程序執行的某個時間點,如類初始化前/后,某個方法執行前/后,拋出異常前/后。
Spring僅支持方法的連接點,即僅能在方法調用前、方法調用后、方法拋出異常時這些程序執行點織入增強。
切點(Poincut)
一個程序類可以有多個連接點,但是如果某一部分連接點需要用什么來定位呢?那就是切點,這么說可能有點抽象。借助數據庫查詢的概念來理解切點和連接點的關系再適合不過了:連接點相當於數據庫中的記錄,而切點相當於查詢條件。切點和連接點不是一對一的關系,一個切點可以匹配多個連接點。
在 Spring 中, 所有的方法都可以認為是 joinpoint, 但是我們並不希望在所有的方法上都添加 Advice, 而 Pointcut 的作用就是提供一組規則(使用 AspectJ pointcut expression language 來描述) 來匹配joinpoint, 給滿足規則的 joinpoint 添加 Advice.
增強/通知(Advice)
將一段執行邏輯添加到切點,並結合切點信息定位到具體的連接點,通過攔截來執行邏輯。
Spring切面可以應用5種類型的通知:
- 前置通知(Before):在目標方法被調用之前調用通知功能;
- 后置通知(After):在目標方法完成之后調用通知,此時不會關心方法的輸出是什么;
- 返回通知(After-returning):在目標方法成功執行之后調用通知;
- 異常通知(After-throwing):在目標方法拋出異常后調用通知;
- 環繞通知(Around):通知包裹了被通知的方法,在被通知的方法調用之前和調用之后執行自定義的行為。
目標對象(Target)
增強邏輯的織入目標對象。目標對象也被稱為 advised object
因為 Spring AOP 使用運行時代理的方式來實現 aspect, 因此 adviced object 總是一個代理對象(proxied object)
注意, adviced object 指的不是原來的類, 而是織入 advice 后所產生的代理類.
織入(Weaving)
將增強添加到目標類的具體連接點上的過程。在目標對象的生命周期里有多個點可以進行織入:
- 編譯期:切面在目標類編譯時被織入。這種方式需要特殊的編譯器。
- 類加載期:切面在目標類加載到JVM時被織入。這種方式需要特殊的類加載器(ClassLoader),它可以在目標類被引入應用之前增強該目標類的字節碼。
- 運行期:切面在應用運行的某個時刻被織入。一般情況下,在織入切面時,AOP容器會為目標對象動態地創建一個代理對象。Spring AOP就是以這種方式織入切面的。
引介/引入(Introduction)
向現有的類添加新的方法或屬性。
Spring AOP 允許我們為 目標對象 引入新的接口(和對應的實現). 例如我們可以使用 introduction 來為一個 bean 實現 IsModified 接口, 並以此來簡化 caching 的實現。
代理(Proxy)
一個類被AOP織入增強后,就產生了一個結果類,它是融合了原類和增強邏輯的代理類。根據不同的代理方式,代理類既可能是和原類具有相同接口的類,也可能就是原類的子類,所以可以采用與調用原類相同的方式調用代理類。
在 Spring AOP 中, 一個 AOP 代理是一個 JDK 動態代理對象或 CGLIB 代理對象.
切面(Aspect)
切面由切點和增強/通知組成,它既包括了橫切邏輯的定義、也包括了連接點的定義。
Spring AOP就是負責實施切面的框架,它將切面所定義的橫切邏輯織入切面所指定的連接點中。
Spring對AOP的支持
Spring提供了4種類型的AOP支持:
- 基於代理的經典Spring AOP;
- 純POJO切面;
- @AspectJ注解驅動的切面;
- 注入式AspectJ切面(適用於Spring各版本)。
Spring AOP原理
Spring AOP的底層原理就是動態代理。
Java實現動態代理的方式有兩種:
- 基於JDK的動態代理。
- 基於CGLib的動態代理。
JDK動態代理是需要實現某個接口了,而我們類未必全部會有接口,於是CGLib代理就有了。
- CGLib代理其生成的動態代理對象是目標類的子類。
- Spring AOP默認是使用JDK動態代理,如果代理的類沒有接口則會使用CGLib代理。
那么JDK代理和CGLib代理我們該用哪個呢??在《精通Spring4.x 企業應用開發實戰》給出了建議:
- 如果是單例的我們最好使用CGLib代理,如果是多例的我們最好使用JDK代理
原因:
- JDK在創建代理對象時的性能要高於CGLib代理,而生成代理對象的運行性能卻比CGLib的低。
- 如果是單例的代理,推薦使用CGLib
看到這里我們就應該知道什么是Spring AOP(面向切面編程)了:將相同邏輯的重復代碼橫向抽取出來,使用動態代理技術將這些重復代碼織入到目標對象方法中,實現和原來一樣的功能。
- 這樣一來,我們就在寫業務時只關心業務代碼,而不用關心與業務無關的代碼
代碼實例
帶有橫切邏輯的實例
ForumService.java
package com.spring05; interface ForumService { public void removeTopic(int topicId); public void removeForum(int forumId); }
ForumServiceImpl.java
package com.spring05; public class ForumServiceImpl implements ForumService{ @Override public void removeTopic(int topicId) { // 開始對該方法進行性能監視 PerformanceMonitor.begin("com.spring05.ForumServiceImpl.removeTopic"); System.out.println("模擬刪除Topic記錄:"+topicId); try { Thread.currentThread().sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } // 結束對該方法的性能監視 PerformanceMonitor.end(); } @Override public void removeForum(int forumId) { // 開始對該方法進行性能監視 PerformanceMonitor.begin("com.spring05.ForumServiceImpl.removeForum"); System.out.println("模擬刪除Forum記錄:"+forumId); try { Thread.currentThread().sleep(40); } catch (InterruptedException e) { e.printStackTrace(); } // 結束對該方法的性能監視 PerformanceMonitor.end(); } }
MethodPerformance.java
package com.spring05; public class MethodPerformance { private long begin; private long end; private String serviceMethod; public MethodPerformance(String serviceMethod) { this.serviceMethod = serviceMethod; this.begin = System.currentTimeMillis(); // 記錄目標類方法開始執行點的系統時間 } public void printPerformance() { this.end = System.currentTimeMillis(); // 獲取目標類方式執行完成后的系統時間,進而計算出目標類方法的執行時間 long elapse = end - begin; System.out.println(serviceMethod + "花費" + elapse + "毫秒"); } }
PerformanceMonitor.java
package com.spring05; public class PerformanceMonitor { // 通過一個ThreadLocal保存與調用線程相關的性能監視信息 private static ThreadLocal<MethodPerformance> performanceRecord = new ThreadLocal<MethodPerformance>(); // 啟動對某一目標方法的性能監視 public static void begin(String method) { System.out.println("begin monitor..."); MethodPerformance mp = new MethodPerformance(method); performanceRecord.set(mp); } public static void end() { System.out.println("end monitor..."); MethodPerformance mp = performanceRecord.get(); // 打印出方法性能監視的結果信息 mp.printPerformance(); } }
TestForumService.java
package com.spring05; public class TestForumService { public static void main(String[] args) { ForumService forumService = new ForumServiceImpl(); forumService.removeForum(10); forumService.removeTopic(1012); } }
執行 TestForumService.main 輸出結果:
begin monitor...
模擬刪除Forum記錄:10
end monitor...
com.spring05.ForumServiceImpl.removeForum花費42毫秒
begin monitor...
模擬刪除Topic記錄:1012
end monitor...
com.spring05.ForumServiceImpl.removeTopic花費28毫秒
在 ForumServiceImpl 中的方法中仍然有非業務邏輯的性能監視代碼(每個業務方法都有性能監視的開啟和關閉代碼),這破壞了方法的純粹性。下面通過JDK動態代理和CGLib動態代理使非業務邏輯的性能監視代碼動態的織入目標方法,以優化代碼結構。
PS:上面代碼可以拷貝出一份,下面舉例大多都是以上面代碼為基礎改進的。
JDK動態代理
先注釋掉 ForumServiceImpl 中非業務邏輯的代碼:
package com.spring06; public class ForumServiceImpl implements ForumService{ @Override public void removeTopic(int topicId) { // 開始對該方法進行性能監視 // PerformanceMonitor.begin("com.smart.proxy.ForumServiceImpl.removeTopic"); System.out.println("模擬刪除Topic記錄:"+topicId); try { Thread.currentThread().sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } // 結束對該方法的性能監視 // PerformanceMonitor.end(); } @Override public void removeForum(int forumId) { // 開始對該方法進行性能監視 // PerformanceMonitor.begin("com.smart.proxy.ForumServiceImpl.removeForum"); System.out.println("模擬刪除Forum記錄:"+forumId); try { Thread.currentThread().sleep(40); } catch (InterruptedException e) { e.printStackTrace(); } // 結束對該方法的性能監視 // PerformanceMonitor.end(); } }
創建橫切代碼處理類:
PerformanceHandler.java
package com.spring06; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * 橫切代碼處理類,實現InvocationHandler接口 */ public class PerformanceHandler implements InvocationHandler { private Object target; public PerformanceHandler(Object target) { // 設置目標業務類 this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 開始性能監視 PerformanceMonitor.begin(target.getClass().getName() + "." + method.getName()); // 通過反射機制調用目標對象方法 Object obj = method.invoke(target, args); // 結束性能監視 PerformanceMonitor.end(); return obj; } }
修改 TestForumService 調用流程:
package com.spring06; import java.lang.reflect.Proxy; public class TestForumService { public static void main(String[] args) { // 希望被代理的目標業務類 ForumService target = new ForumServiceImpl(); // 將目標業務類和橫切代碼編織到一起 PerformanceHandler handler = new PerformanceHandler(target); // 根據編織了目標業務邏輯和性能監視橫切邏輯的InvocationHandler實例創建代理實例 ForumService proxy = (ForumService) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler); // 調用代理實例 proxy.removeForum(10); proxy.removeTopic(1012); } }
運行 TestForumService.main() 輸出結果:
begin monitor...
模擬刪除Forum記錄:10
end monitor...
com.spring06.ForumServiceImpl.removeForum花費41毫秒
begin monitor...
模擬刪除Topic記錄:1012
end monitor...
com.spring06.ForumServiceImpl.removeTopic花費21毫秒
運行效果是一致的,橫切邏輯代碼抽取到了 PerformanceHandler 中。當其他業務類的業務方法需要性能監視時候,只需要為他們創建代理類就行了。
CGLib動態代理
使用JDK創建代理有一個限制,即它只能為接口創建代理實例,CGLib作為一個替代者,填補了這項空缺。
使用CGLib之前,需要先導入CGLib的jar包:
GitHub:https://github.com/cglib/cglib
Maven:
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.8</version> </dependency>
CglibProxy.java
package com.spring07; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class CglibProxy implements MethodInterceptor { private Enhancer enhancer = new Enhancer(); public Object getProxy(Class clazz) { // 設置需要創建子類的類 enhancer.setSuperclass(clazz); enhancer.setCallback(this); // 通過字節碼技術動態創建子類實例 return enhancer.create(); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { // 攔截父類所有的方法 // 開始性能監視 PerformanceMonitor.begin(o.getClass().getName() + "." + method.getName()); // 通過代理類調用父類中的方法 Object result = methodProxy.invokeSuper(o, objects); // 結束性能監視 PerformanceMonitor.end(); return result; } }
修改 TestForumService 調用流程:
package com.spring07; public class TestForumService { public static void main(String[] args) { CglibProxy proxy = new CglibProxy(); // 通過動態生成子類的方式創建代理類 ForumServiceImpl forumService = (ForumServiceImpl) proxy.getProxy(ForumServiceImpl.class); forumService.removeForum(10); forumService.removeTopic(1023); } }
運行 TestForumService.main() 輸出結果:
begin monitor...
模擬刪除Forum記錄:10
end monitor...
com.spring07.ForumServiceImpl$$EnhancerByCGLIB$$3123332f.removeForum花費60毫秒
begin monitor...
模擬刪除Topic記錄:1023
end monitor...
com.spring07.ForumServiceImpl$$EnhancerByCGLIB$$3123332f.removeTopic花費21毫秒
這里可以看到類名變成了com.spring07.ForumServiceImpl$$EnhancerByCGLIB$$3123332f.removeTopic,這個就是CGLib動態創建的子類。
由於CGLib采用動態創建子類的方式生成代理對象,所以不能對目標類中的 final 或 private 方法進行代理。
基於注解和命名空的AOP編程
Spring在新版本中對AOP功能進行了增強,體現在這么幾個方面:
- 在XML配置文件中為AOP提供了aop命名空間
- 增加了AspectJ切點表達式語言的支持
- 可以無縫地集成AspectJ
Spring借鑒了AspectJ的切面,以提供注解驅動的AOP。這種AOP風格的好處在於能夠不使用XML來完成功能。
使用引介/引入功能實現為Bean引入新方法
為了更好的理解,這里我新舉個例子:
手機接口: Phone
package com.spring09; public interface Phone { // 打電話 public void call(String str); // 發短信 public void sendMsg(String str); }
手機接口實現類: BndPhone
package com.spring09; public class BndPhone implements Phone { @Override public void call(String str) { System.out.println("打電話 - " + str); } @Override public void sendMsg(String str) { System.out.println("發短信 - " + str); } }
手機擴展功能接口: PhoneExtend
package com.spring09; public interface PhoneExtend { // 聽音樂 public void listenMusic(String str); // 看視頻 public void watchVideo(String str); }
手機擴展功能接口實現類: BndPhoneExtend
package com.spring09; public class BndPhoneExtend implements PhoneExtend { @Override public void listenMusic(String str) { System.out.println("聽音樂 - " + str); } @Override public void watchVideo(String str) { System.out.println("看視頻 - " + str); } }
注解類: EnablePhoneExtendAspect 這里面定義了切面。這里需要先引入aspectjrt和aspectjweaver的jar包,Maven的配置代碼會在后面貼出。
package com.spring09; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.DeclareParents; import org.springframework.stereotype.Component; @Component @Aspect public class EnablePhoneExtendAspect { @DeclareParents(value = "com.spring09.BndPhone", // 指定手機具體的實現 defaultImpl = BndPhoneExtend.class) // 手機擴展具體的實現 public PhoneExtend phoneExtend; // 要實現的目標接口 }
Spring 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:p="http://www.springframework.org/schema/p" 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="com.spring09"/> <!-- 開啟aop注解方式,默認為false --> <aop:aspectj-autoproxy/> <bean id="phone" class="com.spring09.BndPhone"/> <bean id="phoneExtend" class="com.spring09.BndPhoneExtend"/> <bean class="com.spring09.EnablePhoneExtendAspect"/> </beans>
測試類: Test
package com.spring09; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("META-INF/applicationContext.xml"); Phone phone = (Phone) ctx.getBean("phone"); // 調用手機原有的方法 phone.call("BNDong"); phone.sendMsg("BNDong"); // 通過引介/引入切面已經將phone實現了PhoneExtend接口,所以可以強制轉換 PhoneExtend phoneExtend = (PhoneExtend) phone; phoneExtend.listenMusic("BNDong"); phoneExtend.watchVideo("BNDong"); } }
執行 Test,main() 輸出結果:
打電話 - BNDong 發短信 - BNDong 聽音樂 - BNDong 看視頻 - BNDong
可以看到 BndPhone 並沒有實現 PhoneExtend 接口,但是通過引介/引入切面 BndPhone 擁有了 PhoneExtend 的實現。
我是通過Maven構建的Spring,這里我附上我Maven的pom:

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.spring09</groupId> <artifactId>spring09-demo</artifactId> <version>1.0-SNAPSHOT</version> <name>spring09-demo</name> <!-- FIXME change it to the project's website --> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> <org.springframework.version>4.3.7.RELEASE</org.springframework.version> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <!-- spring start --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-instrument</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-instrument-tomcat</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jms</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-messaging</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-oxm</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc-portlet</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>${org.springframework.version}</version> </dependency> <!-- spring end --> <dependency> <groupId>aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.5.4</version> </dependency> <dependency> <groupId>aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.5.4</version> </dependency> </dependencies> <build> <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --> <plugins> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.0.0</version> </plugin> <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging --> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>3.0.2</version> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.7.0</version> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.20.1</version> </plugin> <plugin> <artifactId>maven-jar-plugin</artifactId> <version>3.0.2</version> </plugin> <plugin> <artifactId>maven-install-plugin</artifactId> <version>2.5.2</version> </plugin> <plugin> <artifactId>maven-deploy-plugin</artifactId> <version>2.8.2</version> </plugin> </plugins> </pluginManagement> </build> </project>
當引入接口方法被調用時,代理對象會把此調用委托給實現了新接口的某個其他對象。實際上,一個Bean的實現被拆分到多個類中。
在XML中聲明切面
基於注解的配置要優於基於Java的配置,基於Java的配置要優於基於XML的配置。
Spring的AOP配置元素能夠以非侵入性的方式聲明切面。
同樣使用上面手機的實例代碼,我們去掉注解類通過XML配置的方式實現切面。
首先刪除注解類 EnablePhoneExtendAspect ,這時再運行 Test.main() 就會拋出異常:
打電話 - BNDong
發短信 - BNDong
Exception in thread "main" java.lang.ClassCastException: com.spring10.BndPhone cannot be cast to com.spring10.PhoneExtend
at com.spring10.Test.main(Test.java:17)
修改Spring 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:p="http://www.springframework.org/schema/p" 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="com.spring10"/> <!-- 開啟aop注解方式,默認為false --> <aop:aspectj-autoproxy/> <aop:config> <!-- 頂層aop配置元素 --> <aop:aspect> <!-- 定義一個切面 --> <aop:declare-parents types-matching="com.spring10.BndPhone" implement-interface="com.spring10.PhoneExtend" delegate-ref="phoneExtend"/> </aop:aspect> </aop:config> <bean id="phone" class="com.spring10.BndPhone"/> <bean id="phoneExtend" class="com.spring10.BndPhoneExtend"/> </beans>
- <aop:config> :頂層aop配置元素。
- <aop:aspect> :定義一個切面。
- <aop:declare-parents> :聲明切面所通知的bean要在它的對象層次結構中擁有新的父類型。
- types-matching :類型匹配的接口實現。
- implement-interface :要實現的接口。
- delegate-ref :引用一個Spring bean作為引入的委托。
- default-impl :用全限定類名來顯示指定實現。
這時運行 Test.main() 發現切面配置成功:
打電話 - BNDong
發短信 - BNDong
聽音樂 - BNDong
看視頻 - BNDong
切面類型總結圖:
總結
- Spring采用JDK動態代理和CGLib動態代理技術在運行期織入增強。
- JDK動態代理需要目標類實現接口,而CGLib不對目標類作任何限制。
- JDK在創建代理對象時的性能高於CGLib,而生成的代理對象的運行性能卻比CGLib的低。
- Spring只能在方法級別上織入增強。
- Spring的AOP配置元素能夠以非侵入性的方式聲明。
- 當Spring AOP不能滿足需求時,我們必須轉向更為強大的AspectJ。
參考資料
《Spring實戰(第4版)》Craig Walls 著 / 張衛濱 譯 下載(密碼:8ts2)
《精通Spring 4.x 企業應用開發實戰》陳雄華 林開雄 文建國 編著 下載(密碼:my25)
https://baike.baidu.com/item/AOP/1332219?fr=aladdin
https://juejin.im/post/5b06bf2df265da0de2574ee1
https://segmentfault.com/a/1190000007469968