第4章 面向切面編程的Spring


一、在軟件系統中,有不少功能是被動調用的(這些功能不是主要關注的),在不少地方都須要明確的調用。這種被分散到多處的功能稱爲「橫切關注點」。將橫切關注點和業務邏輯分析是AOP要解決的問題。AOP將橫切關注點模塊化爲了一些特殊的類,這些類稱爲切面。(以聲明的方法定義橫切關注點要以什麼時候何地調用)
二、AOP術語:
1:通知(advice)
切面必需要完成的工做稱之爲通知。
通知除了描述要完成的工做以外,還描述了什麼時候執行這個工做,它應該應用在什麼方法以前,什麼方法以後(方法鏈接點)。
5種類型的通知:
1:前置通知:在目標方法調用以前調用通知功能
2:後置通知:在目標方法完成以後調用通知,此時不會關心方法的輸出是什麼
3:返回通知:在目錄方法成功執行以後調用的通知
4:異常通知:在目標方法拋出異常時調用的通知
5:環繞通知:包含了被通知的方法,在被通知方法執行以前和執行以後執行的自定義行爲。
2:鏈接點(Join point)
應用執行過程當中可以插入切面的一個點,能夠是一個方法調用時、拋出異常時、修改字段時(其實就是一個觸發的時機,查電錶的電錶)。
3:切點(pointcut)
切點匹配一部分鏈接點。
4:切面(Aspect)
切面是切點和通知的結合(切面知道本身要對誰作什麼事情),通知和切點共同定義了切面的所有內容(是什麼,在什麼時候何處完成其功能)
5:引入(Introduction):
引入容許咱們向現有的類添加新方法和新屬性。
6:織入(Weaving):
把切面應用到目標對象,並建立新的代理對象的過程。切面在指定的鏈接點被織入到目標對象中。在目標對象的生命週期中的編譯器、類加載期、運行期被織入:
三、Spring AOP 使用代理類將目標對象包裹。當調用者調用目標對象時,會被代理類攔截。
因爲Spring AOP 是基於代理的,因此只支持方法鏈接點(不支持構造方法鏈接點)。
四、Spring AOP僅支持AspectJ切點指示器的一個子集。
arg() 限制鏈接點匹配參數爲指定類型的執行方法
@args() 限制鏈接點匹配參數由指定註解標註的執行方法
execution() 用於匹配是鏈接點的執行方法
this() 限制鏈接點匹配 AOP 代理的 bean 引用爲指定類型的類
target 限制鏈接點匹配目標對象爲指定類型的類
@target() 限制鏈接點匹配特定的執行對象,這些對象對應的類要具備指定類型的註解
within() 限制鏈接點匹配指定的類型
@within() 限制鏈接點匹配指定註解所標註的類型(當使用 Spring AOP 時,方法定義在由指定的註解所標註的類裏)
@annotation 限定匹配帶有指定註解的鏈接點
五、編寫切點
例如:
//定義一個表示表演的接口
package concert;
public interface performance{
public void perform();
}
//咱們要編寫一個perform()觸發的通知。下面是一個切點表達式,這個切點表達式設置了perform調用觸發通知的調用。
      execution(* concert.Performance.perform(..))
            說明:
 execution:在方法執行時觸發
*:不關心返回值
concert.Performance.perform:方法全路徑
(..)任意參數
如今咱們配置的切點只匹配concert包。可使用within()指示器來限制匹配。
execution(* concert.Performance.perform(..)) && within(concert.*)
說明:
&& : 且(還有|| or !)
within(concert.*) : concert包下的任意方法被調用。
六、在切點中選擇bean
bean() 是Spring新引入的指示器,能夠在切點表達式中使用bean的ID來標識bean。
使用beanID或者名稱做爲參數來限制切點只匹配到特定的bean。
例如:
      execution(* concert.Performance.perform()) and bean('BeanID')
 執行concert.Performance.perform()時觸發,可是bean的ID是'BeanID'
相似的:
execution(* concert.Performance.perform()) and !bean('BeanID')
七、使用註解建立切面:
AspectJ提供了5個註解來代表應該何時調用:
@After 通知方法會在目標方法返回或拋出異常後調用
@AfterReturning 通知方法會在目標方法返回後調用
@AfterThrowing 通知方法會在目標方法拋出異常後調用
@Around 通知方法會將目標方法封裝起來
@Before 通知方法會在目標方法調用以前執行
下面使用@Aspect註解定義了一個「觀衆」切面(咱們認爲:觀衆是表演的橫向關注點)
         package concert;
         @Aspect    //定義了一個切面
         public class Audience{
            @Before("execution("** concert.Performance.perform(..)")")      //這個通知方法會在目標方法調用以前調用
            public void 手機靜音(){//演出以前鼓掌}
            @AfterReturning("execution("** concert.Performance.perform(..)")")  //這個通知方法會在目標方法調用以後調用
            public void 鼓掌(){//表演以後鼓掌}
            @AfterThrowing("execution("** concert.Performance.perform(..)")")   //這個通知方法會在目標方法拋出異常時調用
            public void 退款(){//表演失敗時要求退款}
         }//上面這段代碼重複的傳入了3次切點表達式。
 可使用@Pointcut註解定義重用的節點。
         package concert;
         @Aspect    //定義了一個切面
         public class Audience{
            @Pointcut("execution("** concert.Performance.perform(..)")")    //定義命名的切點
            public void performance(){}     //這個方法的方法體不重要,這個方法只是@Pointcut的載體
            @Before("performance()")
            public void 手機靜音(){}
            @AfterReturning("performance()")
            public void 鼓掌(){}
            @AfterThrowing("performance()")
            public void 退款(){}
         }
八、啓動AspectJ註解的自動代理
(7中配置了切面,只有啓用了註解的自動代理才能夠用)
1:在JavaConfig中配置:
        @Configuration
        @EnableAspectJAutoproxy     //啓動AspectJ自動代理
        @ComponentScan
        public class Config{
            @Bean
            public Audience audience(){return new Audience();}      //聲明Audience的Bean
        }
 2:使用xml配置:
       <beans xmlns:xxx/yyy/aop>    //引入Spring AOP命名空間
            <context:component-scan base-package = "concert">
            <aop:aspectJ-autoproxy />           //啓用AspectJ自動代理
            <bean class = "concert.Audience">
       </beans>
 注意:不管使用哪一種方法,@Aspect註解的bean都會被建立一個代理,包裹切點匹配到的全部的bean周圍。(造成了代理包裹目標bean的模型)
九、建立環繞通知
在一個通知方法中同時編寫前置和後置通知。
例如:
        @Aspect
        public class Audience{
            @Pointcut("execution(** concert.Performance.perform(..))")  //定義命名的切點
            public void performance(){}

            @Around("performance()")        //環繞通知方法(表示watchPerformance方法將做爲performance切點的環繞通知)
            public void watchPerformance(ProceedingJoinPoint jp){
                try{
                    //調用前置通知
                    jp.proceed();       //掉用ProceedingJoinPoint的proceed()將控制權交給目標方法。(若是不寫,將致使目標方法的調用阻塞。屢次調用能夠實現重試)
                    //調用後置通知
                }catch(Throwable e){
                    //調用異常通知
                }
            }
        }
十、處理通知中的參數:
以前說過光盤中的磁道。假設:playTrack()方法用於播放光盤的某一個磁道,若是想要記錄磁道的播放次數。最簡單的辦法是在playTrack()中添加一個計數的標記。可是計數這個事情怎麼看都應該又切面來完成。
使用切面記錄磁道播放次數的例子:
        @Aspect
        public class TrackCount{
            private Map<Integer , Integer>  trackCounts = new HashMap<Integer , Integer>();
             //playTrack(int):接收int類型的參數  args(trackNumber):指定參數。這個參數最終會被傳遞到通知中去。
            @Pointcut("execution(* xxx.yyy.playTrack(int)) && args(trackNumber)")
            public void trackPlayed(int trackNumber){}
            @Before("trackPlayed(trackNumber)")
            public void countTrack(int trackNumber){
                int currentCount = trackCounts.containsKey(trackNumber) ? trackCount.get(trackNumber) : 0;
                trackCount.put(trackNumber , currentCount + 1);
            }
        }
 //接下來將光盤類和TrackCount類定義爲Bean,並啓用AspectJ自動代理:
        @Configuration
        @EnableAspectJAutoproxy             //啓動AspectJ自動代理
        public class TrackCounterConfig{
            @Bean
            public 光盤類 方法名(){
                光盤 盤 = new 光盤();
                。。。
                return 盤;
            }
            @Bean
            public TrackCounter trackCounter(){
                return new TrackCounter();
            }
        }
十一、經過註解引入新功能
?????????????????????????????
十二、在xml中聲明切面
Spring的aop命名空間中,提供了多個元素在xml中聲明切面:
<aop:advisor> 定義 AOP 通知器
<aop:after> 定義 AOP 後置通知(無論被通知的方法是否執行成功)
<aop:after-returning> 定義 AOP 返回通知
<aop:after-throwing> 定義 AOP 異常通知
<aop:around> 定義 AOP 環繞通知
<aop:aspect> 定義一個切面
<aop:aspectj-autoproxy> 啓用@AspectJ註解驅動的切面
<aop:before> 定義一個 AOP 前置通知
<aop:config> 頂層的 AOP 配置元素。大多數的<aop:*>元素必須包含在<aop:config>元素內
<aop:declare-parents> 以透明的方式爲被通知的對象引入額外的接口
<aop:pointcut> 定義一個切點
例如:
        <aop:config>
            <aop:aspect ref = "pojoBean">     //這個POJOBean中定義了切面的功能(就是前置通知方法,後置通知方法等)
                <aop:before pointcut = "execution(** xxx.yyy.perform(..))" method = "method1" />        //method1.2.3都定義在了上面引入的pojoBean
                <aop:after-returning  "pointcut = "execution(** xxx.yyy.perform(..))" method = "method2" />
                <aop:after-throwing   "pointcut = "execution(** xxx.yyy.perform(..))" method = "method3" />
            </aop:aspect>
        </aop:config>
 //上面的切點重複編寫了。
抽取切點聲明:
       <aop:config>
           <aop:aspect ref = "pojoBean">     //這個POJOBean中定義了切面的功能(就是前置通知方法,後置通知方法等)
               <aop:pointcut id = "performance" expression = "execution(** xxx.yyy.perform(..))" />
               <aop:before pointcut-ref = "performance" method = "method1" />        //method1.2.3都定義在了上面引入的pojoBean
               <aop:after-returning  pointcut-ref = "performance" method = "method2" />
               <aop:after-throwing   pointcut-ref = "performance" method = "method3" />
           </aop:aspect>
       </aop:config>
1三、使用xml定義環繞通知
去掉註解的AOP環繞通知方法
        public class Audience{
            public void watchPerformance(ProceedingJoinPoint jp){
                try{
                    前置通知();
                    jp.proceed();
                    後置通知();
                }catch(Throwable e){
                    異常通知();
                }
            }
        }
 在xml中使用<aop:around>元素聲明環繞通知
        <aop:config>
            <aop:aspect ref = "audience">   //連接環繞方法所在的Bean
            <aop:pointcut id = "performance" expression = "execution(** xxx.yyy.perform(..))">  //定義切點
            <aop:around pointcut-ref = "performance" method = "watchPerformance">   //給切點關聯環繞通知方法。
        </aop:config>
1四、使用xml爲通知傳遞參數:
上面的筆記中有提到。記錄光盤的磁道賭取次數的案例,下面是使用xml配置的相同案例:
將切面註解去掉後的代碼:
            public class TrackCount{
                private Map<Integer , Integer>  trackCounts = new HashMap<Integer , Integer>();
                public void countTrack(int trackNumber){
                    int currentCount = trackCounts.containsKey(trackNumber) ? trackCount.get(trackNumber) : 0;
                    trackCount.put(trackNumber , currentCount + 1);
                }
            }
 xml配置文件;
        <beans>
            <bean id = "trackCount" class = "xxx.yyy.TrackCount">
            <bean id = "cd" class = "xxx.yyy.光盤">
                <property name = "" value = "">
                <property name = "磁道">
                    <list>
                        <value>"..."</value>
                    </list>
                </property>
            </bean>
            <aop:config>
                <aop:aspect ref = "trackCount">     //將TrackCount聲明爲切面
                    <aop:pointcut id = "trackPlayed" expression = "execution(* xxx.yyy.playTrack(int)) and args(trackNumber)" />
                    <aop:before pointcut-ref = "trackPlayed" method = "countTrack" />
                </aop:aspect>
            </aop:config>
        </beans>
1五、經過xml引入新的功能
????????????
1六、注入AspectJ切面
Spring的AOP解決方法相對於AspectJ很是粗糙的。可使用Spring的依賴注入將AspectJ切面注入到Spring中使用。
???????????
相關文章
相關標籤/搜索