Spring:AOP面向切面編程

AOP主要實現的目的是針對業務處理過程當中的切面進行提取,它所面對的是處理過程當中的某個步驟或階段,以得到邏輯過程當中各部分之間低耦合性的隔離效果。

AOP是軟件開發思想階段性的產物,咱們比較熟悉面向過程OPP和麪向對象OOP,AOP是OOP的延續,但不是OOP的替代,而是做爲OOP的有益補充。html

參考《Spring 實戰 (第4版)》和《精通Spring4.x 企業應用開發實戰》兩本書的AOP章節和其餘資料將其知識點整理起來。java

部分代碼實例摘自《精通Spring4.x 企業應用開發實戰》,文末我會給出兩本書的PDF下載地址!git

AOP概述

AOP爲Aspect Oriented Programming的縮寫,意爲:面向切面編程。那麼什麼又是面向切面呢?github

我知道面向對象的特性:封裝、繼承、多態。經過抽象出代碼中共有特性來優化編程,可是這種方式又每每不能徹底適用任何場景,沒法避免的形成代碼之間的耦合。web

以下面的代碼,運用的OOP思想將共有操做抽象了出來,抽象出了兩個處理類:性能監視 PerformanceMonitor 和事務管理 TransactionManager spring

 1 package com.smart.concept;
 2 
 3 public class ForumService {
 4     private TransactionManager transManager;
 5     private PerformanceMonitor pmonitor;
 6     private TopicDao topicDao;
 7     private ForumDao forumDao;
 8 
 9     public void removeTopic(int topicId) {
10         pmonitor.start();
11         transManager.beginTransaction();
12         topicDao.removeTopic(topicId); //
13         transManager.commit();
14         pmonitor.end();
15     }
16 
17     public void createForum(Forum forum) {
18         pmonitor.start();
19         transManager.beginTransaction();
20         forumDao.create(forum); //
21         transManager.commit();
22         pmonitor.end();
23     }
24 }

①、②處是兩個方法 removeTopic 和 createForum 獨有的業務邏輯,但它們淹沒在了重複化非業務性代碼之中。這種抽象爲縱向抽取。數據庫

將removeTopic和createForum進行橫切從新審視:express

咱們沒法經過縱向抽取的方式來消除代碼的重複性。然而切面能幫助咱們模塊化橫切關注點,橫切關注點能夠被描述爲影響應用多處的功能。例如,性能監視和事務管理分別就是一個橫切關注點。apache

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

1 package com.spring05;
2 
3 interface ForumService {
4     public void removeTopic(int topicId);
5     public void removeForum(int forumId);
6 }

ForumServiceImpl.java

 1 package com.spring05;
 2 
 3 public class ForumServiceImpl implements ForumService{
 4     @Override
 5     public void removeTopic(int topicId) {
 6 
 7         // 開始對該方法進行性能監視
 8         PerformanceMonitor.begin("com.spring05.ForumServiceImpl.removeTopic");
 9         System.out.println("模擬刪除Topic記錄:"+topicId);
10         try {
11             Thread.currentThread().sleep(20);
12         } catch (InterruptedException e) {
13             e.printStackTrace();
14         }
15         // 結束對該方法的性能監視
16         PerformanceMonitor.end();
17     }
18 
19     @Override
20     public void removeForum(int forumId) {
21 
22         // 開始對該方法進行性能監視
23         PerformanceMonitor.begin("com.spring05.ForumServiceImpl.removeForum");
24         System.out.println("模擬刪除Forum記錄:"+forumId);
25         try {
26             Thread.currentThread().sleep(40);
27         } catch (InterruptedException e) {
28             e.printStackTrace();
29         }
30         // 結束對該方法的性能監視
31         PerformanceMonitor.end();
32     }
33 }

MethodPerformance.java

 1 package com.spring05;
 2 
 3 public class MethodPerformance {
 4     private long begin;
 5     private long end;
 6     private String serviceMethod;
 7 
 8     public MethodPerformance(String serviceMethod) {
 9         this.serviceMethod = serviceMethod;
10         this.begin = System.currentTimeMillis(); // 記錄目標類方法開始執行點的系統時間
11     }
12 
13     public void printPerformance() {
14         this.end = System.currentTimeMillis(); // 獲取目標類方式執行完成後的系統時間,進而計算出目標類方法的執行時間
15         long elapse = end - begin;
16         System.out.println(serviceMethod + "花費" + elapse + "毫秒");
17     }
18 }

PerformanceMonitor.java

 1 package com.spring05;
 2 
 3 public class PerformanceMonitor {
 4     // 經過一個ThreadLocal保存與調用線程相關的性能監視信息
 5     private static ThreadLocal<MethodPerformance> performanceRecord = new ThreadLocal<MethodPerformance>();
 6 
 7     // 啓動對某一目標方法的性能監視
 8     public static void begin(String method) {
 9         System.out.println("begin monitor...");
10         MethodPerformance mp = new MethodPerformance(method);
11         performanceRecord.set(mp);
12     }
13 
14     public static void end() {
15         System.out.println("end monitor...");
16         MethodPerformance mp = performanceRecord.get();
17 
18         // 打印出方法性能監視的結果信息
19         mp.printPerformance();
20     }
21 }

TestForumService.java

1 package com.spring05;
2 
3 public class TestForumService {
4     public static void main(String[] args) {
5         ForumService forumService = new ForumServiceImpl();
6         forumService.removeForum(10);
7         forumService.removeTopic(1012);
8     }
9 }

執行 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

 1 package com.spring06;
 2 
 3 import java.lang.reflect.InvocationHandler;
 4 import java.lang.reflect.Method;
 5 
 6 /**
 7  * 橫切代碼處理類,實現InvocationHandler接口
 8  */
 9 public class PerformanceHandler implements InvocationHandler {
10     private Object target;
11 
12     public PerformanceHandler(Object target) {
13         // 設置目標業務類
14         this.target = target;
15     }
16 
17     @Override
18     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
19         // 開始性能監視
20         PerformanceMonitor.begin(target.getClass().getName() + "." + method.getName());
21         // 經過反射機制調用目標對象方法
22         Object obj = method.invoke(target, args);
23         // 結束性能監視
24         PerformanceMonitor.end();
25         return obj;
26     }
27 }

修改 TestForumService 調用流程:

 1 package com.spring06;
 2 
 3 import java.lang.reflect.Proxy;
 4 
 5 public class TestForumService {
 6     public static void main(String[] args) {
 7         
 8         // 但願被代理的目標業務類
 9         ForumService target = new ForumServiceImpl();
10 
11         // 將目標業務類和橫切代碼編織到一塊兒
12         PerformanceHandler handler = new PerformanceHandler(target);
13 
14         // 根據編織了目標業務邏輯和性能監視橫切邏輯的InvocationHandler實例建立代理實例
15         ForumService proxy = (ForumService) Proxy.newProxyInstance(
16                 target.getClass().getClassLoader(),
17                 target.getClass().getInterfaces(),
18                 handler);
19         
20         // 調用代理實例
21         proxy.removeForum(10);
22         proxy.removeTopic(1012);
23     }
24 }

運行 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

 1 package com.spring07;
 2 
 3 import net.sf.cglib.proxy.Enhancer;
 4 import net.sf.cglib.proxy.MethodInterceptor;
 5 import net.sf.cglib.proxy.MethodProxy;
 6 
 7 import java.lang.reflect.Method;
 8 
 9 public class CglibProxy implements MethodInterceptor {
10     private Enhancer enhancer = new Enhancer();
11 
12     public Object getProxy(Class clazz) {
13 
14         // 設置須要建立子類的類
15         enhancer.setSuperclass(clazz);
16         enhancer.setCallback(this);
17         // 經過字節碼技術動態建立子類實例
18         return enhancer.create();
19     }
20 
21     @Override
22     public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
23         // 攔截父類全部的方法
24         // 開始性能監視
25         PerformanceMonitor.begin(o.getClass().getName() + "." + method.getName());
26         // 經過代理類調用父類中的方法
27         Object result = methodProxy.invokeSuper(o, objects);
28         // 結束性能監視
29         PerformanceMonitor.end();
30         return result;
31     }
32 }

修改 TestForumService 調用流程:

 1 package com.spring07;
 2 
 3 public class TestForumService {
 4     public static void main(String[] args) {
 5         CglibProxy proxy = new CglibProxy();
 6         // 經過動態生成子類的方式建立代理類
 7         ForumServiceImpl forumService = (ForumServiceImpl) proxy.getProxy(ForumServiceImpl.class);
 8         forumService.removeForum(10);
 9         forumService.removeTopic(1023);
10     }
11 }

運行 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 

 1 package com.spring09;
 2 
 3 public interface Phone {
 4 
 5     // 打電話
 6     public void call(String str);
 7 
 8     // 發短信
 9     public void sendMsg(String str);
10 }

手機接口實現類: BndPhone 

 1 package com.spring09;
 2 
 3 public class BndPhone implements Phone {
 4     @Override
 5     public void call(String str) {
 6         System.out.println("打電話 - " + str);
 7     }
 8 
 9     @Override
10     public void sendMsg(String str) {
11         System.out.println("發短信 - " + str);
12     }
13 }

手機擴展功能接口: PhoneExtend 

 1 package com.spring09;
 2 
 3 public interface PhoneExtend {
 4 
 5     // 聽音樂
 6     public void listenMusic(String str);
 7 
 8     // 看視頻
 9     public void watchVideo(String str);
10 }

手機擴展功能接口實現類: BndPhoneExtend 

 1 package com.spring09;
 2 
 3 public class BndPhoneExtend implements PhoneExtend {
 4     @Override
 5     public void listenMusic(String str) {
 6         System.out.println("聽音樂 - " + str);
 7     }
 8 
 9     @Override
10     public void watchVideo(String str) {
11         System.out.println("看視頻 - " + str);
12     }
13 }

註解類: EnablePhoneExtendAspect 這裏面定義了切面。這裏須要先引入aspectjrtaspectjweaver的jar包,Maven的配置代碼會在後面貼出。

 1 package com.spring09;
 2 
 3 import org.aspectj.lang.annotation.Aspect;
 4 import org.aspectj.lang.annotation.DeclareParents;
 5 import org.springframework.stereotype.Component;
 6 
 7 @Component
 8 @Aspect
 9 public class EnablePhoneExtendAspect {
10     @DeclareParents(value = "com.spring09.BndPhone",  // 指定手機具體的實現
11             defaultImpl = BndPhoneExtend.class) // 手機擴展具體的實現
12     public PhoneExtend phoneExtend; // 要實現的目標接口
13 }

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 

 1 package com.spring09;
 2 
 3 import org.springframework.context.ApplicationContext;
 4 import org.springframework.context.support.ClassPathXmlApplicationContext;
 5 
 6 public class Test {
 7     public static void main(String[] args) {
 8         ApplicationContext ctx = new ClassPathXmlApplicationContext("META-INF/applicationContext.xml");
 9 
10         Phone phone = (Phone) ctx.getBean("phone");
11 
12         // 調用手機原有的方法
13         phone.call("BNDong");
14         phone.sendMsg("BNDong");
15 
16         // 經過引介/引入切面已經將phone實現了PhoneExtend接口,因此能夠強制轉換
17         PhoneExtend phoneExtend = (PhoneExtend) phone;
18         phoneExtend.listenMusic("BNDong");
19         phoneExtend.watchVideo("BNDong");
20     }
21 }

執行 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>
View Code
當引入接口方法被調用時,代理對象會把此調用委託給實現了新接口的某個其餘對象。實際上,一個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

相關文章
相關標籤/搜索