Spring:AOP面向切面編程


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 這里面定義了切面。這里需要先引入aspectjrtaspectjweaver的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>
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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM