(三)Spring的AOP思想2018-06-29

weixin_33782386發表於2018-06-29

AOP —> 面向切面程式設計

想要學習AOP思想,我們必須要理解幾個名詞:

橫切關注點: 分佈於應用中的眾多功能被稱為橫切關注點。將橫切關注點與業務邏輯相分離正是面向切面程式設計(AOP),橫切關注點可以被模組化為特殊的類,這些類被稱為切面

通知(Advice): 定義了切面是什麼以及何時使用切面。

連線點(Joinpoint): 定義了應用被通知的時機。

切點(Poincut): 定義了切面在何處使用。

切面(Aspect): 通知和切點共同定義了切面的全部內容(知道完成工作的一切事宜)。

引入(Introduction): 以上整個過程我們可以稱其為引入;是實現將現有類中新增新方法和屬性。

織入(Wearving): 織入是當切面應用到目標物件後建立新的代理物件的過程。

那麼我們用一張圖來概括上面的內容吧:

12613204-68d178e792e2f258.png
15-1.png

解釋幾個點:

  1. Spring切面可以應用五種型別的通知:
    • Before —>在方法被呼叫之前呼叫通知
    • After —>在方法完成之後呼叫通知,無論方法執行是否成功。
    • After-returning —>在方法成功執行之後呼叫通知。
    • After-throwing —>在方法是丟擲異常後呼叫通知。
    • Around —>通知包裹了唄通知的方法,在唄通知的方法呼叫之前後呼叫之後執行自定義的行為。
  2. 切面在指定的連線點被織入到目標物件中。在目標物件的宣告週期裡有多個點可以進行織入:
    • 編譯期 —>切面在目標類編譯時被織入。這種方式需要特殊的編譯器。AspectJ的織入編譯器就是以這種方式織入切面的。
    • 類載入期 —>切面在目標類載入到JVM時被織入。這種方式需要特殊的類載入器(ClassLoader),它可以在目標類被引入應用之前增強該目標類的位元組碼。AspectJ5就支援以這種方式的織入切面。
    • 執行期 —>切面在應用執行的某個時刻被織入。一般情況下,在織入切面時AOP容器會為目標物件動態的建立一個代理物件。Spring AOP就是以這種方式織入切面的。

Spring對AOP的支援

AOP領域主要以下列三種框架為主:

  • AspectJ
  • JBoss AOP
  • Spring AOP

不是所有的AOP框架都是一樣的,它們在連線點的模型上可能有強弱之分,它們織入切面的方式和時機也會有所不同。但是無論哪種,建立切點來定義切面織入的連線點是AOP框架的基本功能。

Spring提供了4種各具特色的AOP支援:

基於代理的經典AOP

@AspectJ註解驅動的切面

純POJO切面

注入式AspectJ切面(適合Spring各版本)

注意: 前三種都是Spring基於代理的AOP的變體,Spring對AOP的支援侷限於方法攔截

關於Spring AOP框架的一些關鍵點:

  • Spring通知是Java編寫的(AspectJ則是用以Java語言擴充套件的方式實現的)
  • Spring在執行期通知物件
  • Spring只支援方法連線點

解釋:

  1. 我們必須瞭解一下Spring是如何實現在執行期通知物件的:


    12613204-14a3a281feb7e80d.png
    15-2.png
  1. 因為Spring基於動態代理,所以Spring只支援方法連線點。其他框架如AspectJ和Jboss,除了方法切點,它們還提供了欄位和構造器接入點。若需要方法攔截之外的連線點可利用AspectJ來協助。

使用切點選擇連線點

在Spring AOP中,需要使用AspectJ的切點表示式語言來定義切點。但是:Spring僅支援AspectJ切點指示器的一個子集(Spring是基於代理的,而某些切點表示式是與基於代理的AOP無關的)。

Spring AOP所支援的切點表示式:

arg() —>限制連線點匹配引數為指定型別的執行方法

@args() —>限制連線點匹配引數由指定註解標註的執行方法

execution() —>用於匹配是連線點的執行方法

this() —>限制連線點匹配AOP代理的Bean引用為指定型別的類

target() —>限制連線點匹配目標物件為指定型別的類

@target() —>限制連線點匹配特定的執行物件,這些物件對應的類要具備指定型別的註解

within() —>限制連線點匹配指定的型別

@within() —>限制連線點匹配指定註解所標註的型別(使用SpringAOP,方法定義在由指定的註解所標註的類裡)

@annotation —>限制匹配帶有指定註解連線點

編寫切點

以上可以看到只有execution()是唯一的執行匹配,其他都是限制匹配的,那麼我們以一張圖來分析一下如何編寫切點。

12613204-208c381dfa7a7188.png
15-3.png

如上圖,整個execution方法表示式以*開頭,標識我們不關係方法的返回值的型別。然後我們指定了全限定類名和方法名。對於引數列表,使用(..)表示可以匹配任意的play()方法。另外:

  • 使用within()指示器限制匹配,比如within(* cn.tycoding.demo1.*)
  • 可以使用一些表示式來連線多個限定。比如&& ||
  • 使用Spring的bean()指示器,允許在切點表示式中使用Bean的ID表示Bean。bean()使用BeanID或Bean名稱作為引數來限制切點只匹配特定的Bean。例如:execution(* xxx.price()) and bean(beanID)

在XML中宣告切面

<aop:advisor> 定義AOP通知器

<aop:after> 定義AOP後置通知(不管被通知的方法是否執行成功)

<aop:after-returning> 定義AOP after-retturning通知

<aop:after-throwing> 定義after-throwing通知

<aop:around> 定義AOP環繞通知

<aop:aspect> 定義切面

<aop:aspectj-autoproxy> 啟用@AspectJ註解驅動的切面

<aop:before> 定義AOP的前置通知

<aop:config> 頂層的AOP配置元素。大多數的<aop:*>元素必須包含在<aop:config>元素內

<aop:declare-parents> 為被通知的物件引入額外的介面,並透明的實現

<aop:pointcut> 定義切點

綜上我們已經解釋了Spring AOP的一下概念性問題,那麼下面我們寫一個小案例來體會一下Spring AOP

在寫案例之前必須要提示一下,之前我們已經給出了Spring的全部jar包,其中也包含針對AOP功能的jar:

12613204-dc8269c13996e18a.png
15-5.png

要注意這裡需要Aspectjweaver的jar檔案

案例

  1. 定義一個Advice.java通知類
package demo3;
public class Advice {
    public void changeBefore(){
        System.out.println("price change before...");
    }
    public void changeAfter(){
        System.out.println("price change after...");
    }
    public void errorAfter(){
        System.out.println("price change error...");
    }
}
  1. 定義一個Fruits.java介面
package demo3;
public interface Fruits {
    void price();
}
  1. 定義一個Fruits的實現類Apple.java
package demo3;
public class Apple implements Fruits {
    public void price(){
        System.out.println("apple wang change price...");
    }
}
  1. 編寫配置檔案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: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-3.0.xsd
          http://www.springframework.org/schema/aop
          http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    <bean id="apple" class="demo3.Apple"/>
    <bean id="advice" class="demo3.Advice"/>
    <aop:config>
        <aop:aspect ref="advice">
            <aop:before pointcut="execution(* demo3.Apple.price(..))" method="changeBefore"/>
            <aop:after pointcut="execution(* demo3.Apple.price(..))" method="changeAfter"/>
            <aop:after-throwing pointcut="execution(* demo3.Apple.price(..))" method="errorAfter"/>
        </aop:aspect>
    </aop:config>
</beans>

解釋:

  1. 大多數的AOP配置元素必須在<aop:config>元素的上下文內使用。
  2. <aop:config>元素內,可以宣告多個通知器、切面、或者切點。
  3. 使用<aop:aspect>元素宣告一個簡單的切面。
  1. Test測試類
@Test
public void run(){
    ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
    //Apple apple = (Apple) ac.getBean("apple");
    //apple.price();
    Fruits fruits = (Fruits) ac.getBean("apple");
    fruits.price();
}

列印結果:

12613204-5abe7e05f1d95fac.png
15-4.png

擴充:

我們使用Test測試類中被註釋的部分去獲取Bean會怎樣呢?

@Test
public void run(){
    ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
    Apple apple = (Apple) ac.getBean("apple");
    apple.price();
    //Fruits fruits = (Fruits) ac.getBean("apple");
    //fruits.price();
}
12613204-bc3540d2d0afddd6.png
15-6.png

就會出現如上報錯,為什麼呢?

原因

對於Spring AOP採用兩種代理方法,一種是JDK的動態代理,一種是CGLIB。當代理物件實現了至少一個介面時,預設使用JDK動態建立代理物件。當代理物件沒有實現任何介面時就是使用CGLIB

綜上: JDK的動態代理是基於介面的; CJLIB是基於類的

首先我們要明白Spring AOP是通過什麼方式建立代理物件的(可以檢視這篇博文:代理物件生成)。上面的案例中,Apple實現了介面Fruits,這一點就決定了Spring會採用JDK的動態代理方式建立代理物件。而我們使用getBean即獲取到一個返回的代理物件,這個代理物件其實是和Apple一樣實現了Fruits介面。那麼getBean()返回的代理物件讓Apple這個實現類的引用來用,只能由他們的共同介面來引用。

改進

觀察spring.xml我們發現,下面的通知元素中pointcut屬性值都是一樣的,這是因為所有的通知都是應用到同一個切點上,那麼我們就可以通過<aop:pointcut>改進上述程式碼:

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: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-3.0.xsd
          http://www.springframework.org/schema/aop
          http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    <bean id="apple" class="demo3.Apple"/>
    <bean id="advice" class="demo3.Advice"/>
    <aop:config>
        <aop:aspect ref="advice">
            <aop:pointcut id="price" expression="execution(* demo3.Apple.price(..))"/>
            <aop:before pointcut-ref="price" method="changeBefore"/>
            <aop:after pointcut-ref="price" method="changeAfter"/>
            <aop:after-throwing pointcut-ref="price" method="errorAfter"/>
        </aop:aspect>
    </aop:config>
</beans>

宣告環繞通知

使用環繞通知可以完成之前前置通知和後置通知所實現相同的功能。首先環繞通知需要使用ProceedingJoinPoint作為方法的入參。這個物件可以讓我們在通知裡呼叫被通知方法proceed()

  1. 建立環繞通知類:AroundAdvice.java
package demo3;
import org.aspectj.lang.ProceedingJoinPoint;
public class AroundAdvice {
    public void watchPrice(ProceedingJoinPoint joinPoint) {
        try{
            //前置通知
            System.out.println("around: price change before...");
            //執行被通知的方法
            joinPoint.proceed();
            //後置通知
            System.out.println("around: price change after...");
        } catch(Throwable t){
            System.out.println("around: change error...");
        }
    }
}
  1. spring.xml中需要使用<aop:pointcut>即可
<?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-3.0.xsd
          http://www.springframework.org/schema/aop
          http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    <bean id="apple" class="demo3.Apple"/>
    <bean id="aroundAdvice" class="demo3.AroundAdvice"/>
    <aop:config>
        <aop:aspect ref="aroundAdvice">
            <aop:pointcut id="price" expression="execution(* demo3.Apple.price(..))"/>
            <!-- 宣告環繞通知 -->
            <aop:around method="watchPrice" pointcut-ref="price"/>
        </aop:aspect>
    </aop:config>
</beans>
12613204-c94db5566a9f5187.png
15-7.png

通知方法可以完成任何他需要做的事情,如果我們想講控制權轉給被通知的方法時,就可以呼叫proceed()實現。

為通知傳遞引數

  1. Fruits.java
package demo3;
public interface Fruits {
    void changePrice(int price);
    int getPrice();
}

在介面定義宣告一個方法changePrice(),並在其引數列表中寫入一個引數。為了方便觀察通知前後引數的變化,在這裡又宣告一個方法getPrice()

  1. Apple.java
package demo3;
public class Apple implements Fruits {
    private int price;
    {
        System.out.println("at first,apple price is: " + price);
    }
    public void changePrice(int price) {
        this.price = price;
    }
    public int getPrice() {
        return price;
    }
}

為了觀察引數price的初始值,我們寫了一個程式碼塊列印price的初始值。因為Spring AOP是採用動態代理方式建立代理物件的,所以都要定義介面和實現類,這裡Apple實現了Fruits.

  1. 建立實現傳遞引數功能的通知類:ParamAdvice.java
package demo3;
public class ParamAdvice  {
    private int price;
    public void changePrice(int price) {
        System.out.println("boss decision change price...");
        this.price = price;
    }
}

這是一個可以傳遞引數的通知類,可以看到,這與普通的類並沒有什麼區別,只是多了一個引數而已。

  1. 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: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-3.0.xsd
          http://www.springframework.org/schema/aop
          http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
    
    <!-- 通知類 -->
    <bean id="paramAdvice" class="demo3.ParamAdvice"/>
    <!-- 目標類 -->
    <bean id="apple" class="demo3.Apple"/>
    <aop:config>
        <aop:aspect ref="paramAdvice">
            <aop:pointcut id="fruits" expression="execution(* demo3.Fruits.changePrice(int)) and args(price)"/>
            <aop:after method="changePrice" pointcut-ref="fruits" arg-names="price"/>
        </aop:aspect>
    </aop:config>
</beans>

織入一個可以實現傳遞引數的通知,我們首先要在<aop:config>下的<aop:aspect>中引入通知類的id;然後可以配置一個通用的切入點<aop:pointcut>,在使用execution()編寫切點有所不同,首先在切入的方法changePrice()中指明切入的引數資料型別,再通過and args()來指明切入點的引數名稱。

  1. Test測試類
@Test
public void run() {
    ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
    Fruits fruits = (Fruits) ac.getBean("apple");
    fruits.changePrice(12);
    System.out.println("now,this apple price is :" + fruits.getPrice());
}

這裡還是要注意:因為Spring AOP採用JDK的動態代理和CGLIB的方式建立代理物件,所以對於這裡有介面的實現類就必須用實現類和代理物件夠共同具有的介面來接收getBean()獲取的代理物件,然後我們就可以通過Fruits介面中的changePrice()設定引數的值。

12613204-8262a126982c0367.png
15-8.png

通過切面引入新功能

之前我們一直探討的是Spring AOP如何編寫一個簡單的通知類,即使上面我們實現了為通知傳遞引數,也僅僅是在目標類原有方法上進行的操作。其實Spring AOP可以藉助<aop:declare-parent>元素來為目標物件新增資訊的功能。我們用一張圖來看一下實現流程:

12613204-a34620fc551f008e.png
15-10.png

實現程式碼

  1. 建立一個新的介面Orange.java
package demo3;
public interface Orange {
    void wantChangePrice();
}
  1. 建立Orange介面的實現類
package demo3;
public class OrangeAdvice implements Orange {
    public void wantChangePrice() {
        System.out.println("It's new method.Orange want  change price...");
    }
}
  1. 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: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-3.0.xsd
          http://www.springframework.org/schema/aop
          http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    <bean id="orangeAdvice" class="demo3.OrangeAdvice"/>
    <aop:config>
        <aop:aspect>
            <aop:declare-parents types-matching="demo3.Fruits+" implement-interface="demo3.Orange" delegate-ref="orangeAdvice"/>
        </aop:aspect>
    </aop:config>
</beans>

<aop:declare-parents>宣告瞭此切面所通知的Bean在它的物件層次結構中擁有新的父型別;types-matching指明建立的新型別匹配此介面;delegate-refdefault-impl都是指明匹配介面的實現類,區別是前者指明一個引用Bean,後置是直接寫Bean的全限定名。

  1. Test測試類
@Test
public void run() {
    ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
    Orange orange = (Orange) ac.getBean("orangeAdvice");
    orange.wantChangePrice();
}
12613204-3020557febbebd2c.png
15-9.png

註解切面

Spring AOP提供了一個@AspectJ註解,實現了使其不需要額外的類或Bean宣告就能將它轉換成一個切面。不同的通知型別對應不同的註解,如:@Before() @After() @AfterReturning() @AfterThrowing()。下面我們通過一個案例來觀察:

  1. 首先新建立一個Melon
package demo3;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Melon {
    //使用註解宣告Bean物件
    @Bean
    public Melon melon(){
        return new Melon();
    }
    public void changePrice(){
        System.out.println("melon also need change price...");
    }
}

如果使用註解注入一個Bean物件,要使用@Configuration標記Java類,並通過@Bean標記一個return 物件的方法,告訴Spring這個方法將返回一個物件,且該物件應該被註冊為Spring上下文中的一個Bean。

  1. 建立一個基於註解的通知類AnnoAdvice
package demo3;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Aspect
@Configuration
public class AnnoAdvice {
    @Pointcut("execution(* demo3.Melon.changePrice(..))")
    public void price(){
    }
    //後置通知
    @After("price()")
    public void changeAfter(){
        System.out.println("after melon price change...");
    }
    //使用註解注入Bean物件
    @Bean
    public AnnoAdvice annoAdvice (){
        return new AnnoAdvice();
    }
}

使用@Aspect標記的類會被Spring轉換為一個切面,那麼我們可以用@Pointcut定義一個切點,但是,注意被@Pointcut標記的方法內容並無意義,但是此方法名稱則是切點名稱,該方法文字只是一個標識,供@pointcut註解依附。

  1. 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: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-3.0.xsd
          http://www.springframework.org/schema/aop
          http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
    <!-- 開啟註解掃描 -->
    <context:component-scan base-package="demo3"/>
    <!-- 宣告自動代理的Bean,該Bean知道如何把@AspectJ註解所標註的Bean轉變為代理通知 -->
    <aop:aspectj-autoproxy/>
</beans>

通過<context:component-scan>可以實現註解掃描,這樣我們在類中寫的註解就會被掃描到。<aop:aspectj-autoproxy>是在Spring上下文中宣告一個自動代理的Bean,這樣Spring就會將被@Aspect註解標記的Bean定義為一個切面,而Spring自動代理的Bean就會與用@Pointcut註解定義的切點相匹配。

  1. Test測試類
@Test
public void run2(){
    ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
    Melon melon = (Melon) ac.getBean("melon");
    melon.changePrice();
}
12613204-ea04ce672c675890.png
15-11.png

注意

  1. <aop:aspectj-autoproxy>僅僅使用@AspectJ註解作為指引來建立基於代理的切面,但本質上它仍然是一個Spring風格的切面。這就意味著我們仍然限於代理方法的呼叫,想使用AspectJ的全部功能就必須在執行時使用AspectJ並不依賴Spring來建立基於代理的切面。
    1. <aop:aspect>元素和@Aspect註解都是把一個POJO轉變成一個切面的有效方式。但是<aop:aspect>相比@Aspect優勢就是不需要實現切面的程式碼,而@Aspect必須標註類和方法,需要由原始碼。

註解環繞通知

使用Spring的@Around註解就可以實現環繞通知,Demo如下:

我們改進上面的AnnoAdvice.java

package demo3;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.Configuration;
@Aspect
@Configuration
public class AnnoAdvice {
    @Pointcut("execution(* demo3.Melon.changePrice(..))")
    public void price(){
    }
    //建立環繞通知
    @Around("price()")
    public void aroundAdvice(ProceedingJoinPoint joinPoint){
        try {
            System.out.println("before, it's around advice...");
            joinPoint.proceed();
            System.out.println("after, it's around advice...");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }
}

同樣,@Around標註的方法也需要一個ProceedingJoinPoint物件作為入參,以便可以通過proceed()呼叫被代理的方法。

12613204-cd14a81bcb19e15f.png
15-12.png

傳遞引數給所標註的通知

通過註解傳遞引數與XML傳遞引數相似,只需要改變切點的表示式就能實現。

@Pointcut("execution(* demo3.Xxx(int)) && args(xxx)")

@Before("Xxx(xx)")

標註引入

前面我們使用了XML實現在原有類中引入新的介面,其實使用註解也可以實現。XML的<aop:declare-parents> 對應 @AspectJ的@DeclareParents。觀察一下程式碼:

Melon“瓜”類

package demo3;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Melon implements WaterMelon{
    //使用註解宣告Bean物件
    @Bean
    public Melon melon(){
        return new Melon();
    }
    public void changePrice(){
        System.out.println("melon also need change price...");
    }
}

建立WaterMelon“西瓜”介面

package demo3;
public interface WaterMelon {
    void changePrice();
}

通知類AnnoAdvice

package demo3;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Aspect
@Configuration
public class AnnoAdvice {
    //使用註解注入Bean物件
    @Bean
    public AnnoAdvice annoAdvice (){
        return new AnnoAdvice();
    }
    @DeclareParents(value = "demo3.Fruits", defaultImpl = Melon.class)
    private static WaterMelon waterMelon;
}

如上所示,AnnoAdvice其實就是一個切面,通過@DeclareParents註解,為Fruits引入了一個新的介面WaterMelon,該介面的實現類是Melon,
對於@DeclareParents註解由3個部分組成:

  • value屬性 等同於<aop:declare-parents>types-matching屬性。它表示應該被引入指定介面的Bean型別。(目標物件)
  • defaultImpl屬性 等同於<aop:declare-parents>default-impl屬性。它標識該類提供了所引入介面的實現。(引入的新介面的實現)
  • @DeclareParents 註解所標註的static指定了被引入的介面。(引入的新的介面)


交流

如果大家有興趣,歡迎大家加入我的Java交流群:671017003 ,一起交流學習Java技術。博主目前一直在自學JAVA中,技術有限,如果可以,會盡力給大家提供一些幫助,或是一些學習方法,當然群裡的大佬都會積極給新手答疑的。所以,別猶豫,快來加入我們吧!


聯絡

If you have some questions after you see this article, you can contact me or you can find some info by clicking these links.

相關文章