SpringInAction--面向切片的Spring以及如何使用注解創建切面


什么叫做切片。。什么叫做AOP。。。

與大多數技術一樣,AOP已經形成了自己的術語。描述切面的常用術語有通知(advice)、切點(pointcut)和連接點(join point)。

(一大串書上的原文!!)

通知(Advice)

在AOP術語中,切面的工作被稱為通知。

當抄表員出現在我們家門口時,他們要登記用電量並回去向電力公司報告。顯然,他們必須有一份需要抄表的住戶清單,他們所匯報的信息也很重要,但記錄用電量才是抄表員的主要工作。

類似地,切面也有目標——它必須要完成的工作。在AOP術語中,切面的工作被稱為通知。

通知定義了切面是什么以及何時使用。除了描述切面要完成的工作,通知還解決了何時執行這個工作的問題。它應該應用在某個方法被調用之前?之后?之前和之后都調用?還是只在方法拋出異常時調用?

Spring切面可以應用5種類型的通知:

  • 前置通知(Before):在目標方法被調用之前調用通知功能;
  • 后置通知(After):在目標方法完成之后調用通知,此時不會關心方法的輸出是什么;
  • 返回通知(After-returning):在目標方法成功執行之后調用通知;
  • 異常通知(After-throwing):在目標方法拋出異常后調用通知;
  • 環繞通知(Around):通知包裹了被通知的方法,在被通知的方法調用之前和調用之后執行自定義的行為。

連接點(Join point)

電力公司為多個住戶提供服務,甚至可能是整個城市。每家都有一個電表,這些電表上的數字都需要讀取,因此每家都是抄表員的潛在目標。抄表員也許能夠讀取各種類型的設備,但是為了完成他的工作,他的目標應該房屋內所安裝的電表。

同樣,我們的應用可能也有數以千計的時機應用通知。這些時機被稱為連接點。連接點是在應用執行過程中能夠插入切面的一個點。這個點可以是調用方法時、拋出異常時、甚至修改一個字段時。切面代碼可以利用這些點插入到應用的正常流程之中,並添加新的行為。

切點(Poincut)
如果讓一位抄表員訪問電力公司所服務的所有住戶,那肯定是不現實的。實際上,電力公司為每一個抄表員都分別指定某一塊區域的住戶。類似地,一個切面並不需要通知應用的所有連接點。切點有助於縮小切面所通知的連接點的范圍。

如果說通知定義了切面的“什么”和“何時”的話,那么切點就定義了“何處”。切點的定義會匹配通知所要織入的一個或多個連接點。我們通常使用明確的類和方法名稱,或是利用正則表達式定義所匹配的類和方法名稱來指定這些切點。有些AOP框架允許我們創建動態的切點,可以根據運行時的決策(比如方法的參數值)來決定是否應用通知。

切面(Aspect)
當抄表員開始一天的工作時,他知道自己要做的事情(報告用電量)和從哪些房屋收集信息。因此,他知道要完成工作所需要的一切東西。

切面是通知和切點的結合。通知和切點共同定義了切面的全部內容——它是什么,在何時和何處完成其功能。

引入(Introduction)
引入允許我們向現有的類添加新方法或屬性。例如,我們可以創建一個Auditable通知類,該類記錄了對象最后一次修改時的狀態。這很簡單,只需一個方法,setLastModified(Date),和一個實例變量來保存這個狀態。然后,這個新方法和實例變量就可以被引入到現有的類中,從而可以在無需修改這些現有的類的情況下,讓它們具有新的行為和狀態。

織入(Weaving)
織入是把切面應用到目標對象並創建新的代理對象的過程。切面在指定的連接點被織入到目標對象中。在目標對象的生命周期里有多個點可以進行織入:

編譯期:切面在目標類編譯時被織入。這種方式需要特殊的編譯器。AspectJ的織入編譯器就是以這種方式織入切面的。

類加載期:切面在目標類加載到JVM時被織入。這種方式需要特殊的類加載器(ClassLoader),它可以在目標類被引入應用之前增強該目標類的字節碼。AspectJ5的加載時織入(load-time weaving,LTW)就支持以這種方式織入切面。
運行期:切面在應用運行的某個時刻被織入。一般情況下,在織入切面時,AOP容器會為目標對象動態地創建一個代理對象。Spring AOP就是以這種方式織入切面的。要掌握的新術語可真不少啊。再看一下圖4.1,現在我們已經了解了如下的知識,通知包含了需要用於多個應用對象的橫切行為;連接點是程序執行過程中能夠應用通知的所有點;切點定義了通知被應用的具體位置(在哪些連接點)。其中關鍵的概念是切點定義了哪些連接點會得到通知。

 


 

Spring借助AspectJ的切點表達式語言來定義Spring切面,對應參數如下:

  • arg(): 限制連接點匹配參數為指定類型的執行方法
  • @args(): 限制連接點匹配參數由指定注解標注的執行方法
  • execution():用於匹配是連接點的執行方法
  • this(): 限制連接點匹配AOP代理的bean引用為指定類型的類
  • target: 限制連接點匹配目標對象為指定類型的類
  • @target(): 限制連接點匹配特定的執行對象,這些對象對應的類要具有指定類型的注解
  • within(): 限制連接點匹配指定的類型
  • @within():限制連接點匹配指定注解所標注的類型(當使用Spring AOP時,方法定義在由指定的注解所標注的類里)
  • @annotation: 限定匹配帶有指定注解的連接點

 

下面就讓我們來看如何編寫切點的。

首先定一個接口,Song 表示唱的歌。。

public interface Song {
    void song();
}

 定義切片的格式如下:

 

下面就讓我們定一個切片; 觀看演唱會的切片,這邊定義的是com.aop.test1.Song中的song()方法 ,返回的類型為任意,方法參數也為任意。這里我通過@Pointcut來定一個重復的切片的,這邊一個一個定義也是可以的。

@Aspect
public class VocalConcert {

    @Pointcut("execution(* com.aop.test1.Song.song(..))")
    public void doing() {
    }

    @Before("doing()")
    public void checking() {
        System.out.println("檢票之后,找位子坐下");
    }

    @AfterReturning("doing()")
    public void beautiful() {
        System.out.println("演唱會進入精彩部分的時候,鼓掌!");
    }

    @AfterReturning("doing()")
    public void leave() {
        System.out.println("結束后,我們離開場地");
    }


/*    @Around("doing()")
    public void watchVocalConcert(ProceedingJoinPoint joinPoint) {
        try {
            System.out.println("檢票之后,找位子坐下");
            joinPoint.proceed();
            System.out.println("演唱會進入精彩部分的時候,鼓掌!");
            System.out.println("結束后,我們離開場地");
        } catch (Throwable throwable) {
            System.out.println("效果不好,退票");
        }

    }*/
}

 

pring使用AspectJ注解來聲明通知方法,注解參數如下:

  • @After: 通知方法會在目標方法返回或拋出異常后調用
  • @AfterReturning: 通知方法會在目標方法返回后調用
  • @AfterThrowing: 通知方法會在目標方法拋出異常后調用
  • @Around: 通知方法會將目標方法封裝起來
  • @Before: 通知方法會在目標方法調用之前執行

既然有歌曲的接口,就讓我們來創造個接口的實例類:

@Component
public class ManMan implements Song {

    public void song() {
        System.out.println("下面這首歌是張學友的《慢慢》");
    }
}

java配置類

@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class VocalConcertConfig {

    @Bean
    public VocalConcert vocalConcert() {
        return new VocalConcert();
    }

}

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

    <context:component-scan base-package="com.aop.test1"/>

    <bean class="com.aop.test1.VocalConcert" id="concert"/>
    
    <aop:aspectj-autoproxy/>

</beans>

然后我們測試下:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = VocalConcertConfig.class)
public class VocalConcertTest {


    @Autowired
    Song mm;

    @Test
    public void log() {
        mm.song();
    }

}

測試結果:

 

 至於環繞通知就是上面 被注釋的watchVocalConcert() 方法,這邊主要關注的是proceed()方法。

如果不調這個方法的話,那么你的通知實際上會阻塞對被通知方法的調用。有可能這就是你想要的效果,但更多的情況是你希望在某個點上執行被通知的方法。

有意思的是,你可以不調用proceed()方法,從而阻塞對被通知方法的訪問,與之類似,你也可以在通知中對它進行多次調用。要這樣做的一個場景就是實現重試邏輯,也就是在被通知方法失敗后,進行重復嘗試。


如果我們碰到所通知的方法有參數,那怎么辦呢?

 

 我們只需要根據上面的實例定義就可以了。具體的例子就略過了。。、

 

上面就是簡單總結下Spring是什么,以及如何用注解創建簡單的切面。如有錯誤,請指出,謝謝

 

 代碼:https://github.com/eoooxy/springinaction test下 的com.aop.test1 中

 


免責聲明!

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



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