Spring AOP 筆記三(引入Introductions)

此文是Sping in Action 第4版 英文原版切面部分的讀書筆記,僅限交流使用,有不足之處,一定聽取修改。

系列目錄:
Spring AOP 筆記一(基礎概念,一個簡單切面)
Spring AOP 筆記二(環繞通知,切面中獲取參數)
Spring AOP 筆記三(切面注解引入新的方法)

1. 注解引入(Annotating introductions)

在一些高級語言中,比如Rudy與Groovy,都有開放類的概念,能夠在不改變對象和類的代碼的前提下,為對象和類添加新的方法。然而對于Java,很不幸,他不是動態的,類一旦編譯完成,就很難再為他添加新的功能。
但是你可以稍微想一想我們再次之前寫的切面。雖然沒有為向對象中添加新的方法,但是已經向其中添加了新的功能。既然我們能夠向現有的方法添加新的功能,為什么不能為一個對象添加新的方法那?實際上,使用名為 引入(introduction) 的AOP概念,就能夠實現。

之前好像沒有提到切面實現的原理。切面是用到了叫做 代理 的設計模式。這里簡單比喻一下,代理就好像是藝人(被切入的bean的方法)盡心盡責的經紀人[笑臉],在日常工作中,外部的通告或者節目邀請都會先送到經紀人的手里,經紀人會自己幫助藝人處理完一些必須事務(@before),,不需要藝人關系或者插手,然后經紀人處理完后,再告知(調用)藝人,去演戲或者參加節目,例如下圖:

基本的代理

感興趣的同學可以深入學習一下。

于是乎當我們想在對象中加入新的方法時,比如當前的藝人擅長表演,而有些節目想讓他獻聲,此時經紀人就可以找個會唱歌的藝人(一個新的接口,里面有我們想要新增得唱歌方法),當欄目索要(調用)歌曲時,此時經紀人就可以直接讓唱歌的藝人唱一首歌送個欄目,在欄目(調用者)那里,并不會知道是誰完成可唱歌這個任務,如下圖:

添加新的方法

這樣說應該差不多能理解。

然后看一個小栗子:

我們先定義一個Singer接口,代表會唱歌的藝人。
/* 
* 一個歌者 
* Created by Henvealf on 2016/9/3. 
*/
public interface Singer { 
        void sing();
}
然后新定義一個切面,類名為SingerIntroducer,歌手引入者,也就是經紀人。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
/** 
* 歌手引入者 
* Created by Henvealf on 2016/9/3. 
*/
@Aspectpublic 
class SingerIntroducer {           
     @DeclareParents(value="com.mengxiang.concert.Performance+", 
                defaultImpl = BackSinger.class)
     public static Singer singer;
}

可以發現他沒有前置before,后置after或環繞round通知,只有一個 @DeclareParents ,通過她,就能將 Singer 接口的實現引入到 Performance 的實現類中,即相當于Performance 得到了新的方法。

詳細解釋一下:

  • value 指定了哪個類的bean將會被引入@DeclareParents 注解的接口。在上面的例子中,Performance 后面的加號表示的是所有 Performance的子類型,而不是 Performance 本身。

  • defaultImpl 屬性就指定了一個明確的 Singer 接口的實現類,只有接口是沒用的,所以就需要用他來提供引入一個接口的具體實現。

  • 那最后 @DeclareParents 注解的 Singer 就是要被引入的接口(Singer)了。注意這里是靜態的。

所以很明顯,我們需要寫一個名為 BackSingerSinger的實現類,幕后歌者:
/** 
* 幕后歌者 
* Created by Henvealf on 2016/9/3. 
*/
public class BackSinger implements Singer{ 
      public void sing() { 
              System.out.println("嘿嘿嘿~~~哈哈哈~~~你們去哪里啊?。。?); 
      }
}
最后將 Performance 的實現dancer 與切面 SingerIntroducer 注入到容器中。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import javax.sound.midi.Track;
/** 
* 
* Created by Henvealf on 2016/8/26. 
*/
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.henvealf.learn.spring.concert")
public class ConcertConfig { 
        @Bean(name = "dancer") 
        public Performance dancer(){ 
                return new Dancer(); 
        } 
        @Bean 
        SingerIntroducer singerIntroducer(){ 
                return new SingerIntroducer(); 
        }
}

關于 dance 可以查看 Spring AOP 筆記一(基礎概念,一個簡單切面)

最后就是運行看看效果了。

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/** 
* 
* 
Created by Henvealf on 2016/8/27.
 */
public class Main { 
    public static void main(String[] args) {
        ApplicationContext context =
                 new AnnotationConfigApplicationContext("com.mengxiang.concert"); 
        Performance per = (Performance) context.getBean("dancer"); 
        Singer singer = (Singer)per; singer.sing(); 
        //或者這樣: 
        //((Singer)per).sing(); 
    }
}

執行結果:

嘿嘿嘿~~~哈哈哈~~~你們去哪里啊?。。?/p>

我們可以發現,為了執行在Performance中引入的sing()方法,需要先將其bean轉型為Singer類型才能執行sing()方法,偽裝一下,不然被人發現可就鬧大發了,編譯器自己就不愿意。

本篇就結束嘍! 謝謝大家的支持!或許還有下一篇 !

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容