一.Spring--面向切面
在軟件開發中,散佈於應用中多處的功能被稱爲橫切關注點(cross- cutting concern)。一般來說,這些橫切關注點從概念上是與應用的業 務邏輯相分離的(可是每每會直接嵌入到應用的業務邏輯之中)。把 這些橫切關注點與業務邏輯相分離正是面向切面編程(AOP)所要解決的問題。
--什麼是面向切面編程
切面能幫助咱們模塊化橫切關注點。簡而言之,橫切關注 點能夠被描述爲影響應用多處的功能。例如,安全就是一個橫切關注 點,應用中的許多方法都會涉及到安全規則:java
--如上圖展示的模塊劃分的典型應用.每一個模塊的核心功能都是爲特定業務領域提供服務,可是這些模塊都須要相似的輔助功能,例如:安全及事務.若是要重用通用功能的話,那麼最多見的面嚮對象的技術就是繼承和委託,可是若是在整個應用中都是用相同的基類,繼承每每會致使一個脆弱的對象體系;而使用委託則須要對委託對象進行復雜的調用.而使用切面編程的技術,則能夠經過聲明的方式定義這個功能要以何種方式在何處應用,而無需修改受影響的類.橫切關注點就能夠被模塊化爲特殊的類,這些類被稱爲切面(sapect).這樣作有兩個好處:首先,如今每一個 關注點都集中於一個地方,而不是分散到多處代碼中;其次,服務模 塊更簡潔,由於它們只包含主要關注點(或核心功能)的代碼,而次 要關注點的代碼被轉移到切面中了。
--定義AOP術語
與大多數技術同樣,AOP已經造成了本身的術語。描述切面的經常使用術 語有通知(advice)、切點(pointcut)和鏈接點(join point):spring
--通知(Advice):正如以前所提到的Knight的示例,騎士在接收了任務以後,不須要去強迫一個吟遊詩人來完成對其功績的歌頌,他只須要最好本身份內的事(拯救公主或者殺死惡龍),無需去關心吟遊詩人是在何時.怎麼樣稱讚他的.所以切面也有目標——它必需要完成的工做。在AOP術語中,切 面的工做被稱爲通知。通知定義了切面是什麼以及什麼時候使用。除了描述切面要完成的工做, 通知還解決了什麼時候執行這個工做的問題。它應該應用在某個方法被調 用以前?以後?以前和以後都調用?仍是隻在方法拋出異常時調用?在Spring之中,提供了5種通知的類型:
1.前置通知(Before):在目標方法被調用以前調用通知功能;
2.後置通知(After):在目標方法完成以後調用通知,此時不會關 心方法的輸出是什麼;
3.返回通知(After-returning):在目標方法成功執行以後調用通 知;
4.異常通知(After-throwing):在目標方法拋出異常後調用通知;
5.環繞通知(Around):通知包裹了被通知的方法,在被通知的方 法調用以前和調用以後執行自定義的行爲。
--鏈接點(Join Point):對於騎士來講,完成了他的任務以後,必然會在懸賞公告牌中告知你們這個騎士完成了怎樣的壯舉,而對於吟遊詩人來講這個發佈騎士完成任務的點就是他所須要進行讚頌準備的點.對於切面來講,這就是拯救點.事實上,在切面編程之中,這個鏈接點能夠是調用方法時(騎士正在執行任務),拋出異常時(騎士執行任務遇到困難時),程序修改一個字段時(騎士在中途進行修整時),切面代碼 能夠利用這些點插入到應用的正常流程之中,並添加新的行爲。
--切點(Pointcut):對於吟遊詩人來講,他不須要去關心這個騎士中途多有詳細任務報告的公佈(各個鏈接點),對於他來講,他十分清楚本身的職責(專門負責歌頌騎士的凱旋歸來又或者是專門歌頌騎士在中途遇到困難時的堅韌不拔),而這就是所謂的切點.相似地,一個切面並不須要通知應用的全部鏈接點。切點有助於 縮小切面所通知的鏈接點的範圍。若是說通知定義了切面的「什麼」和「什麼時候」的話,那麼切點就定義 了「何處」。切點的定義會匹配通知所要織入的一個或多個鏈接點。
--切面(Aspect):當一個吟遊詩人準備歌頌一個騎士的時候,他須要明確的整理本身收集到的這個騎士他所須要稱讚的點(執行過程仍是凱旋歸來的結局)的全部細節.切面就是通知和切點的結合通知和切點共同定義了切面的所有內容 ——它是什麼,在什麼時候和何處完成其功能。
--引入(Introduction):引入容許咱們向現有的類添加新方法或屬性。例如,咱們能夠建立一 個Auditable通知類,該類記錄了對象最後一次修改時的狀態。這 很簡單,只需一個方法,setLastModified(Date),和一個實例 變量來保存這個狀態。而後,這個新方法和實例變量就能夠被引入到 現有的類中,從而能夠在無需修改這些現有的類的狀況下,讓它們具 有新的行爲和狀態。
--織入(Weaving):織入是把切面應用到目標對象並建立新的代理對象的過程。切面在指 定的鏈接點被織入到目標對象中。在目標對象的生命週期裏有多個點 能夠進行織入:
1.編譯期:切面在目標類編譯時被織入。這種方式須要特殊的編譯 器。AspectJ的織入編譯器就是以這種方式織入切面的。
2.類加載期:切面在目標類加載到JVM時被織入。這種方式須要特 殊的類加載器(ClassLoader),它能夠在目標類被引入應用 以前加強該目標類的字節碼。AspectJ 5的加載時織入(load-time weaving,LTW)就支持以這種方式織入切面。
3.運行期:切面在應用運行的某個時刻被織入。通常狀況下,在織 入切面時,AOP容器會爲目標對象動態地建立一個代理對象。 Spring AOP就是以這種方式織入切面的。
--總結:通知包含了須要用於多個應用對象的橫切行爲;鏈接點是程序執行過程當中可以應用通知的全部點;切點定義了通知被應用的 具體位置(在哪些鏈接點)。其中關鍵的概念是切點定義了哪些鏈接 點會獲得通知。express
二.Spring對AOP的支持
Spring提供了四種類型的AOP支持:
1.基於代理的經典Spring AOP;
2.純POJO切面;
3.@AspectJ註解驅動的切面;
4.注入式AspectJ切面(適用於Spring各版本)。
--前三種都是Spring AOP實現的變體,Spring AOP構建在動態代理基礎 之上,所以,Spring對AOP的支持侷限於方法攔截。藉助Spring的aop命名空間,咱們能夠將純POJO轉換爲切面。實際 上,這些POJO只是提供了知足切點條件時所要調用的方法。這種技術須要XML配置.在深刻探討Spring的AOP技術以前,咱們須要對Spring AOP框架的一些關鍵性技術進行了解:
1.Spring通知是Java編寫的:Spring所建立的通知都是用標準的Java類編寫的。這樣的話,咱們就 可使用與普通Java開發同樣的集成開發環境(IDE)來開發切面。 並且,定義通知所應用的切點一般會使用註解或在Spring配置文件裏 採用XML來編寫,這兩種語法對於Java開發者來講都是至關熟悉的。
2.Spring在運行時通知對象:經過在代理類中包裹切面,Spring在運行期把切面織入到Spring管理 的bean中,代理類封裝了目標類,並攔截被通知方法的 調用,再把調用轉發給真正的目標bean。當代理攔截到方法調用時, 在調用目標bean方法以前,會執行切面邏輯:編程
直到應用須要被代理的bean時,Spring才建立代理對象。若是使用的 是ApplicationContext的話,在ApplicationContext從 BeanFactory中加載全部bean的時候,Spring纔會建立被代理的對 象。由於Spring運行時才建立代理對象,因此咱們不須要特殊的編譯 器來織入Spring AOP的切面。
3.Spring只支持方法級別的鏈接點:由於Spring基於動態代理,因此Spring只支持方法鏈接點。Spring缺乏對字段鏈接點的 支持,沒法讓咱們建立細粒度的通知,例如攔截對象字段的修改。而 且它不支持構造器鏈接點,咱們就沒法在bean建立時應用通知。安全
三,經過切點來選擇鏈接點
在Spring AOP中,使用AspectJ的切點表達式語言來定義切點;Spring是基於代理的,而某些切點表達式是於基於代理的AOP無關的,給出SpringAOP支持的AspectJ切點指示器:app
--當咱們查看如上所展現的這些Spring支持的指示器時,注意只 有execution指示器是實際執行匹配的,而其餘的指示器都是用來 限制匹配的。這說明execution指示器是咱們在編寫切點定義時最 主要使用的指示器。在此基礎上,咱們使用其餘指示器來限制所匹配的切點。
--編寫切點:定義Performance接口做爲咱們的自定義切面的切點:框架
1 package 面向切面編程; 2
3 /**
4 * @author : S K Y 5 * @version :0.0.1 6 */
7 public interface Performance { 8 /**
9 * 觸發方法 10 */
11 void perform(); 12 }
--觀察切點表達式的構成:編程語言
--使用execution()指示器選擇Performance的perform()方 法。方法表達式以「*」號開始,代表了咱們不關心方法返回值的類 型。而後,咱們指定了全限定類名和方法名。對於方法參數列表,我 們使用兩個點號(..)代表切點要選擇任意的perform()方法,無 論該方法的入參是什麼,假設如今所須要的切點僅匹配concert包:ide
--使用"&&"操做符講execution()和within()指示器鏈接在一塊兒(切點必須匹配全部的指示器),類 似地,咱們可使用「||」操做符來標識或(or)關係,而使用「!」操 做符來標識非(not)操做(由於「&」在XML中有特殊含義,因此在Spring的XML配置裏面描述切 點時,咱們可使用and來代替「&&」。一樣,or和not能夠分別用來 代替「||」和「!」).
--在切點中選擇bean,Spring還引入了一個新的bean指示,他容許咱們在切點表達式中使用bean的ID做爲標識來限定指示器:execution(* concert.Performance.perform()) and bean('student');這個表達式表名了咱們但願執行performance的perform()方法時應用通知,可是限定了bean的ID爲student,固然也能夠限定在bean的ID不是'student'的狀況下應用通知:execution(* concert.Performance.perform()) and !bean('student');
--使用註解建立切面模塊化
1 package 面向切面編程.concert; 2
3 import org.aspectj.lang.annotation.AfterReturning; 4 import org.aspectj.lang.annotation.AfterThrowing; 5 import org.aspectj.lang.annotation.Aspect; 6 import org.aspectj.lang.annotation.Before; 7
8 /**
9 * @author : S K Y 10 * @version :0.0.1 11 */
12 @Aspect //使用@Aspect註解將該類設置爲特殊的切面類
13 public class Audience { 14 private String name; 15
16 @Before("execution(* *.concert.Performance.perform(..))") 17 public void silenceCellPhone() { //在表演開始以前,觀衆須要將他的手機靜音
18 System.out.println("表演即將開始,觀衆" + name + "將他的手機設置爲了靜音狀態;"); 19 } 20
21 @Before("execution(* *.concert.Performance.perform(..))") 22 public void takeSeats() { 23 System.out.println("表演還有5分鐘就要開始了,觀衆" + name + "入座等待;"); 24 } 25
26 @AfterReturning("execution(* *.concert.Performance.perform(..))") 27 public void applause() { 28 System.out.println("表演結束,觀衆" + name + "爲演員們熱烈的鼓掌;"); 29 } 30
31 @AfterThrowing("execution(* *.concert.Performance.perform(..))") 32 public void demandRefund() { 33 System.out.println("因爲線程設備故障,觀衆" + name + "要求退款...."); 34 } 35 }
--在AspectJ中提供了五個註解來定義通知:
--在配置文件中,如我咱們想要開啓切面支持,那麼須要讓Spring配置文件知道咱們切面類的存在,在JavaConfig中咱們可使用@EnableAspectJAutoProxy註解來開啓AspectJ的自動代理,同時還須要開啓@ComponentScan註解來實現咱們的包掃描,從而Spring才能識別咱們@AspectJ註解聲明的類:
1 package 面向切面編程.concert; 2
3 /**
4 * @author : S K Y 5 * @version :0.0.1 6 */
7 public class DancingPerformance implements Performance { 8 @Override 9 public void perform() { 10 System.out.println("演員們表演精彩的舞蹈...."); 11 } 12 }
1 package 面向切面編程.concert; 2
3 import org.springframework.context.annotation.Bean; 4 import org.springframework.context.annotation.ComponentScan; 5 import org.springframework.context.annotation.Configuration; 6 import org.springframework.context.annotation.EnableAspectJAutoProxy; 7
8 /**
9 * @author : S K Y 10 * @version :0.0.1 11 */
12 @Configuration 13 @EnableAspectJAutoProxy //啓用AspectJ的自動代理
14 @ComponentScan(basePackages = {"面向切面編程.concert"}) 15 public class AudienceConfig { 16 @Bean 17 public Audience audienceA() { 18 return new Audience("觀衆A"); 19 } 20
21 @Bean 22 public Performance performance() { 23 return new DancingPerformance(); 24 } 25 }
1 package 面向切面編程.concert; 2
3 import org.junit.Test; 4 import org.junit.runner.RunWith; 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.test.context.ContextConfiguration; 7 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 8
9 /**
10 * @author : S K Y 11 * @version :0.0.1 12 */
13 @RunWith(SpringJUnit4ClassRunner.class) 14 @ContextConfiguration(classes = AudienceConfig.class) 15 public class AudienceTest { 16
17 @Autowired 18 private Performance performance; 19
20 @Test 21 public void testAudienceA() { 22 performance.perform(); 23 } 24 }
--運行結果
表演即將開始,觀衆觀衆A將他的手機設置爲了靜音狀態; 表演還有5分鐘就要開始了,觀衆觀衆A入座等待; 演員們表演精彩的舞蹈.... 表演結束,觀衆觀衆A爲演員們熱烈的鼓掌; Process finished with exit code 0
--能夠看到使用註解輕鬆的實現了咱們的AOP編程,可是,此時仍然存在問題,在Audience中咱們屢次書寫了相同的切面表達式,事實上,咱們可使用@Pointcut註解來聲明咱們的切點表達式
1 package 面向切面編程.concert; 2
3 import org.aspectj.lang.annotation.*; 4
5 /**
6 * @author : S K Y 7 * @version :0.0.1 8 */
9 @Aspect //使用@Aspect註解將該類設置爲特殊的切面類
10 public class Audience { 11 private String name; 12
13 public Audience(String name) { 14 this.name = name; 15 } 16
17 @Pointcut("execution(* *.concert.Performance.perform(..))") 18 public void performance() { 19 } 20
21 @Before("performance()") 22 public void silenceCellPhone() { //在表演開始以前,觀衆須要將他的手機靜音
23 System.out.println("表演即將開始,觀衆" + name + "將他的手機設置爲了靜音狀態;"); 24 } 25
26 @Before("performance()") 27 public void takeSeats() { 28 System.out.println("表演還有5分鐘就要開始了,觀衆" + name + "入座等待;"); 29 } 30
31 @AfterReturning("performance()") 32 public void applause() { 33 System.out.println("表演結束,觀衆" + name + "爲演員們熱烈的鼓掌;"); 34 } 35
36 @AfterThrowing("performance()") 37 public void demandRefund() { 38 System.out.println("因爲線程設備故障,觀衆" + name + "要求退款...."); 39 } 40
41 }
--在Audience中,performance()方法使用了@Pointcut註解。 爲@Pointcut註解設置的值是一個切點表達式,就像以前在通知注 解上所設置的那樣。經過在performance()方法上添 加@Pointcut註解,咱們實際上擴展了切點表達式語言,這樣就可 以在任何的切點表達式中使用performance()了,若是不這樣作的 話,你須要在這些地方使用那個更長的切點表達式。performance()方法的實際內容並不重要,在這裏它實際上應該是 空的。其實該方法自己只是一個標識,供@Pointcut註解依附。
--使用XML完成配置:若是想要在Spring的XML配置文件中來裝配這個@AspectJ聲明的bean的話,那麼須要使用Spring aop命名空間中的<aop:aspectj-autoproxy/>來完成:
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
4 xmlns:c="http://www.springframework.org/schema/c" xmlns:context="http://www.springframework.org/schema/context"
5 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
6 <!--開啓AspectJ動態代理-->
7 <aop:aspectj-autoproxy/>
8 <bean class="面向切面編程.concert.Audience" c:name="小明"/>
9 <bean class="面向切面編程.concert.DancingPerformance" id="performance"/>
10 </beans>
1 package 面向切面編程.concert; 2
3 import org.junit.Test; 4 import org.junit.runner.RunWith; 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.test.context.ContextConfiguration; 7 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 8
9 /**
10 * @author : S K Y 11 * @version :0.0.1 12 */
13 @RunWith(SpringJUnit4ClassRunner.class) 14 @ContextConfiguration(locations = {"classpath:面向切面編程/concert/audienct-config.xml"}) 15 public class XMLConfigTest { 16 @Autowired 17 private Performance performance; 18
19 @Test 20 public void testAudience() { 21 performance.perform(); 22 } 23 }
--建立環繞通知
環繞通知是最爲強大的通知類型,能夠在一個通知方法中同時完成前置通知,後置通知以及異常通知,重寫咱們的Audience類:
1 package 面向切面編程.concert; 2
3 import org.aspectj.lang.ProceedingJoinPoint; 4 import org.aspectj.lang.annotation.*; 5
6 /**
7 * @author : S K Y 8 * @version :0.0.1 9 */
10 @Aspect //使用@Aspect註解將該類設置爲特殊的切面類
11 public class Audience { 12 private String name; 13
14 public Audience(String name) { 15 this.name = name; 16 } 17
18 @Pointcut("execution(* *.concert.Performance.perform(..))") 19 public void performance() { 20 } 21
22 @Around("performance()") 23 public void watchPerform(ProceedingJoinPoint point) { //觀衆觀看演出
24 try { 25 System.out.println("表演即將開始,觀衆" + name + "將他的手機設置爲了靜音狀態;"); 26 System.out.println("表演還有5分鐘就要開始了,觀衆" + name + "入座等待;"); 27 point.proceed(); //執行方法
28 System.out.println("表演結束,觀衆" + name + "爲演員們熱烈的鼓掌;"); 29 } catch (Throwable throwable) { 30 System.out.println("因爲線程設備故障,觀衆" + name + "要求退款...."); 31 } 32 } 33 }
--在這個新的通知方法中,接受一個ProceedingJoinPoint做爲參數,而且這個對象是必需要有的,由於咱們須要在通知中經過他來調用被通知的方法,通知方法中能夠作任何的事情,當要將控制權交給被通知的方法時,那麼會調用ProceedingJoinPoint的proceed()方法.若是咱們不去調用這個方法,咱們的通知會阻塞咱們被通知方法的調用.有意思的是,你能夠不調用proceed()方法,從而阻塞對被通知方 法的訪問,與之相似,你也能夠在通知中對它進行屢次調用。要這樣 作的一個場景就是實現重試邏輯,也就是在被通知方法失敗後,進行 重複嘗試。
--處理通知中的參數
到目前爲止,咱們的切面都很簡單,沒有任何參數。惟一的例外是我 們爲環繞通知所編寫的watchPerformance()示例方法中使用了 ProceedingJoinPoint做爲參數。除了環繞通知,咱們編寫的其 他通知不須要關注傳遞給被通知方法的任意參數。這很正常,由於我 們所通知的perform()方法自己沒有任何參數。可是若是咱們進行切面通知的方法確實有參數存在,切面如何訪問和使用傳遞給被通知的方法的參數呢?例如:對於一個晚會表演來講,主持人也是必不可少的,對於Performance來講,可能存在多個須要表演的節目,可是對於當前的表演來講,須要通知觀衆進行表演的不該該是演員和表演自己(即不該該由Performance類來完成對於晚會的播報環節):
1 package 面向切面編程.concert; 2
3 /**
4 * @author : S K Y 5 * @version :0.0.1 6 */
7 public interface Performance { 8 /**
9 * 進行表演 10 */
11 void perform(); 12
13 /**
14 * 在擁有多個表演的節目的時候,表演當前指定的節目 15 * 16 * @param index 當前指定的節目的索引 17 * @param name 當前表演的節目的名稱 18 */
19 void perform(int index, String name); 20 }
1 package 面向切面編程.concert; 2
3 import org.aspectj.lang.annotation.Aspect; 4 import org.aspectj.lang.annotation.Before; 5 import org.aspectj.lang.annotation.Pointcut; 6
7 import java.util.List; 8
9 /**
10 * @author : S K Y 11 * @version :0.0.1 12 */
13 @Aspect 14 public class Host { 15 private List<String> dancingList; 16
17 @Pointcut("execution(* *.concert.Performance.perform(.. )) && args(index,name)") 18 private void performance(int index, String name) { 19 } 20
21 @Before("performance(index,name)") 22 public void host(int index, String name) { 23 System.out.println("[主持人]:當前爲你們帶來的是第" + (index + 1) + "個表演" + name +
24 ".請你們欣賞;"); 25 } 26
27 public List<String> getDancingList() { 28 return dancingList; 29 } 30
31 public void setDancingList(List<String> dancingList) { 32 this.dancingList = dancingList; 33 } 34 }
--只有主持人知道當前所須要進行的表演的序號和名稱,而全部表演的演員只須要按照主持人所給出的清單進行表演:
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
4 xmlns:c="http://www.springframework.org/schema/c" xmlns:context="http://www.springframework.org/schema/context"
5 xmlns:util="http://www.springframework.org/schema/util"
6 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
7 <!--開啓AspectJ動態代理-->
8 <aop:aspectj-autoproxy/>
9 <bean class="面向切面編程.concert.Audience" c:name="小明"/>
10 <util:list id="dancingList">
11 <value>芭蕾舞表演</value>
12 <value>孔雀舞表演</value>
13 <value>名族舞表演</value>
14 <value>花式敲代碼表演</value>
15 </util:list>
16 <bean class="面向切面編程.concert.DancingPerformance" id="performance"/>
17 <bean class="面向切面編程.concert.Host">
18 <property name="dancingList" ref="dancingList"/>
19 </bean>
20
21 </beans>
1 package 面向切面編程.concert; 2
3 import org.junit.Test; 4 import org.junit.runner.RunWith; 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.test.context.ContextConfiguration; 7 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 8
9 import java.util.List; 10
11 /**
12 * @author : S K Y 13 * @version :0.0.1 14 */
15 @RunWith(SpringJUnit4ClassRunner.class) 16 @ContextConfiguration(locations = {"classpath:面向切面編程/concert/audienct-config.xml"}) 17 public class XMLConfigTest { 18 @Autowired 19 private Performance performance; 20 @Autowired 21 private Host host; 22
23 @Test 24 public void testAudienceWithInt() { 25 List<String> dancingList = host.getDancingList(); 26 for (int i = 0; i < dancingList.size(); i++) { 27 performance.perform(i, dancingList.get(i)); 28 } 29 } 30 }
--運行結果
表演即將開始,觀衆小明將他的手機設置爲了靜音狀態; 表演還有5分鐘就要開始了,觀衆小明入座等待; [主持人]:當前爲你們帶來的是第1個表演芭蕾舞表演.請你們欣賞; 演員們表演精彩的芭蕾舞表演... 表演結束,觀衆小明爲演員們熱烈的鼓掌; 表演即將開始,觀衆小明將他的手機設置爲了靜音狀態; 表演還有5分鐘就要開始了,觀衆小明入座等待; [主持人]:當前爲你們帶來的是第2個表演孔雀舞表演.請你們欣賞; 演員們表演精彩的孔雀舞表演... 表演結束,觀衆小明爲演員們熱烈的鼓掌; 表演即將開始,觀衆小明將他的手機設置爲了靜音狀態; 表演還有5分鐘就要開始了,觀衆小明入座等待; [主持人]:當前爲你們帶來的是第3個表演名族舞表演.請你們欣賞; 演員們表演精彩的名族舞表演... 表演結束,觀衆小明爲演員們熱烈的鼓掌; 表演即將開始,觀衆小明將他的手機設置爲了靜音狀態; 表演還有5分鐘就要開始了,觀衆小明入座等待; [主持人]:當前爲你們帶來的是第4個表演花式敲代碼表演.請你們欣賞; 演員們表演精彩的花式敲代碼表演... 表演結束,觀衆小明爲演員們熱烈的鼓掌; 九月 08, 2019 6:24:05 下午 org.springframework.context.support.GenericApplicationContext doClose 信息: Closing org.springframework.context.support.GenericApplicationContext@67b6d4ae: startup date [Sun Sep 08 18:24:05 CST 2019]; root of context hierarchy Process finished with exit code 0
--經過註解引入新功能
一些編程語言,例如Ruby和Groovy,有開放類的理念。它們能夠不用 直接修改對象或類的定義就可以爲對象或類增長新的方法。不過, Java並非動態語言。一旦類編譯完成了,咱們就很難再爲該類添加 新的功能了。可是事實上,咱們一直在使用AOP功能爲已經編寫完的方法來增長額外的功能,此外,其實咱們也能夠爲一個對象新增方法,利用被稱爲引入的AOP概念,切面能夠爲Spring bean添加新方法.回顧一下,在Spring中,切面只是實現了它們所包裝bean相同接口的 代理。若是除了實現這些接口,代理也能暴露新接口的話,會怎麼樣 呢?那樣的話,切面所通知的bean看起來像是實現了新的接口,即使 底層實現類並無實現這些接口也無所謂。
--爲Performance實現引入MoreForPerformance接口
1 package 面向切面編程.concert; 2
3 /**
4 * @author : S K Y 5 * @version :0.0.1 6 */
7 public interface MoreForPerformance { 8 /**
9 * 在表演之中作更多的事 10 */
11 void moreForPerformance(); 12 }
--咱們須要一種方式將這個接口應用到Performance實現中.若是當前Performance的實現數量並很少,找到全部的實現,爲他們都實現MoreForPerformance接口是沒有什麼問題的,可是若是當前具備上百個Performance的實現,這樣作就顯得特別的麻煩和不可取了,並且咱們須要知道的是並非全部的Performance的實現都須要MoreForPerformance支持的.同時若是有部分實現是由第三方jar來完成的,那麼咱們也沒法爲全部的實現都添加MoreForPerformance接口.所以咱們能夠藉助AOP的引入功能,咱們能夠沒必要在設計上妥協或者侵入性的改變現有的實現.爲此,咱們建立一個新的切面:
1 package 面向切面編程.concert; 2
3 /**
4 * @author : S K Y 5 * @version :0.0.1 6 */
7 public class MoreForPerformanceImpl implements MoreForPerformance { 8 @Override 9 public void moreForPerformance() { 10 System.out.println("[附加]舞蹈表演特別的棒,每個舞者臉上都流露着燦爛的笑容..."); 11 } 12 }
1 package 面向切面編程.concert; 2
3 import org.aspectj.lang.annotation.Aspect; 4 import org.aspectj.lang.annotation.DeclareParents; 5
6 /**
7 * @author : S K Y 8 * @version :0.0.1 9 */
10 @Aspect 11 public class PointcutForMore { 12 @DeclareParents(value = "面向切面編程.concert.Performance+", defaultImpl = MoreForPerformanceImpl.class) 13 public static MoreForPerformance more; 14 }
--PointcutForMore是一個切面,可是其實現與其餘所建立的切面都不一樣,他沒有提供前置,後置,環繞等通知,而是經過@DeclareParents註解,將MoreForPerformance接口引入到了Performance bean中.
--@DeclareParents註解由三部分組成:
1.value屬性指定了哪一種類型的bean要引入該接口。在本例中,也 就是全部實現Performance的類型。(標記符後面的加號表示 是Performance的全部子類型,而不是Performance本 身。)
2.defaultImpl屬性指定了爲引入功能提供實現的類。在這裏, 咱們指定的是MoreForPerformanceImpl提供實現。
3.@DeclareParents註解所標註的靜態屬性指明瞭要引入了接 口。在這裏,咱們所引入的是MoreForPerformance 接口。
--在Spring中,註解和自動代理提供了一種很便利的方式來建立切面。 它很是簡單,而且只涉及到最少的Spring配置。可是,面向註解的切 面聲明有一個明顯的劣勢:你必須可以爲通知類添加註解。爲了作到 這一點,必需要有源碼。 若是你沒有源碼的話,或者不想將AspectJ註解放到你的代碼之中, Spring爲切面提供了另一種可選方案:在Spring XML配置文件中聲明切面.
--在XML中聲明切面
在Spring的aop命名空間之中,提供了多個元素用來在XML中聲明切面:
1 package 面向切面編程.concert; 2
3 import org.aspectj.lang.ProceedingJoinPoint; 4 import org.aspectj.lang.annotation.*; 5
6 /**
7 * @author : S K Y 8 * @version :0.0.1 9 */
10 @Aspect //使用@Aspect註解將該類設置爲特殊的切面類
11 public class Audience { 12 private String name; 13
14 public Audience(String name) { 15 this.name = name; 16 } 17
18 @Pointcut("execution(* *.concert.Performance.perform(..))") 19 public void performance() { 20 } 21
22 public void silenceCellPhone() { //在表演開始以前,觀衆須要將他的手機靜音
23 System.out.println("表演即將開始,觀衆" + name + "將他的手機設置爲了靜音狀態;"); 24 } 25
26 public void takeSeats() { 27 System.out.println("表演還有5分鐘就要開始了,觀衆" + name + "入座等待;"); 28 } 29
30 public void applause() { 31 System.out.println("表演結束,觀衆" + name + "爲演員們熱烈的鼓掌;"); 32 } 33
34 public void demandRefund() { 35 System.out.println("因爲線程設備故障,觀衆" + name + "要求退款...."); 36 } 37
38 @Around("performance()") 39 public void watchPerform(ProceedingJoinPoint point) { //觀衆觀看演出
40 try { 41 System.out.println("表演即將開始,觀衆" + name + "將他的手機設置爲了靜音狀態;"); 42 System.out.println("表演還有5分鐘就要開始了,觀衆" + name + "入座等待;"); 43 point.proceed(); 44 System.out.println("表演結束,觀衆" + name + "爲演員們熱烈的鼓掌;"); 45 } catch (Throwable throwable) { 46 System.out.println("因爲線程設備故障,觀衆" + name + "要求退款...."); 47 } 48 } 49 }
--如今在Audience中,除了watchPerform爲環繞通知外,其餘的方法都不是通知,爲了不測試結果混亂,咱們能夠先將@Around註解註釋掉,定義咱們的XML文件:
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
4 xmlns:c="http://www.springframework.org/schema/c" xmlns:context="http://www.springframework.org/schema/context"
5 xmlns:util="http://www.springframework.org/schema/util"
6 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
7 <!--開啓AspectJ動態代理-->
8 <aop:aspectj-autoproxy/>
9 <bean class="面向切面編程.concert.Audience" c:name="小明" id="audience"/>
10 <util:list id="dancingList">
11 <value>芭蕾舞表演</value>
12 <value>孔雀舞表演</value>
13 <value>名族舞表演</value>
14 <value>花式敲代碼表演</value>
15 </util:list>
16 <bean class="面向切面編程.concert.DancingPerformance" id="performance"/>
17 <bean class="面向切面編程.concert.Host">
18 <property name="dancingList" ref="dancingList"/>
19 </bean>
20 <bean class="面向切面編程.concert.PointcutForMore"/>
21
22 <aop:config>
23 <aop:aspect ref="audience">
24 <aop:pointcut id="pointcut" expression="execution(* *.concert.Performance.perform(..))"/>
25 <aop:before method="silenceCellPhone" pointcut-ref="pointcut"/>
26 <aop:before method="takeSeats" pointcut-ref="pointcut"/>
27 <aop:after-returning method="applause" pointcut-ref="pointcut"/>
28 <aop:after-throwing method="demandRefund" pointcut-ref="pointcut"/>
29 </aop:aspect>
30 </aop:config>
31
32 </beans>
--在<aop:config>元素內,咱們能夠聲明一個或多個通知器、切面 或者切點,給出AOP的總體業務邏輯:
--可是目前在前置通知和後置通知中有一些限制.若是不使用成員變量存儲信息的話,在前置通知和後置通知之間共享信息特別的麻煩.假設除了進場關閉手機和表演結束後鼓掌,咱們還但願觀衆確 保一直關注演出,並報告每一個參賽者表演了多長時間。使用前置通知 和後置通知實現該功能的惟一方式是在前置通知中記錄開始時間並在 某個後置通知中報告表演耗費的時間。但這樣的話咱們必須在一個成 員變量中保存開始時間。由於Audience是單例的,若是像這樣保存 狀態的話,將會存在線程安全問題。相對於前置通知和後置通知,環繞通知在這點上有明顯的優點。使用 環繞通知,咱們能夠完成前置通知和後置通知所實現的相同功能,而 且只須要在一個方法中 實現。由於整個通知邏輯是在一個方法內實 現的,因此不須要使用成員變量保存.
--在XML中實現環繞通知的方式和其餘通知是同樣的.一樣,對於具備參數傳遞的通知,咱們也可使用XML配置文件的形式來實現:
1 <aop:config>
2 <aop:pointcut id="pointcut" expression="execution(* *.concert.Performance.perform(..))"/>
3 <aop:pointcut id="pointcutWithParam"
4 expression="execution(* *.concret.Performance.perform(..)) and args(index,name)"/>
5 <aop:aspect ref="audience">
6 <!-- <aop:before method="silenceCellPhone" pointcut-ref="pointcut"/> 7 <aop:before method="takeSeats" pointcut-ref="pointcut"/> 8 <aop:after-returning method="applause" pointcut-ref="pointcut"/> 9 <aop:after-throwing method="demandRefund" pointcut-ref="pointcut"/>-->
10 <aop:around method="watchPerform" pointcut-ref="pointcut"/>
11 </aop:aspect>
12 <aop:aspect ref="host">
13 <!--注意在XML中不該該使用&& 而應該使用關鍵字and-->
14 <aop:before method="host" pointcut-ref="pointcutWithParam" arg-names="index,name"/>
15
16 </aop:aspect>
17 </aop:config>
--一樣咱們也能夠在XML中使用Spring AOP命名空間中的<aop:declare-parents>標籤完成引入功能:
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
4 xmlns:c="http://www.springframework.org/schema/c" xmlns:context="http://www.springframework.org/schema/context"
5 xmlns:util="http://www.springframework.org/schema/util"
6 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
7 <!--開啓AspectJ動態代理-->
8 <aop:aspectj-autoproxy/>
9 <bean class="面向切面編程.concert.Audience" c:name="小明" id="audience"/>
10 <util:list id="dancingList">
11 <value>芭蕾舞表演</value>
12 <value>孔雀舞表演</value>
13 <value>名族舞表演</value>
14 <value>花式敲代碼表演</value>
15 </util:list>
16 <bean class="面向切面編程.concert.DancingPerformance" id="performance"/>
17 <bean class="面向切面編程.concert.Host" id="host">
18 <property name="dancingList" ref="dancingList"/>
19 </bean>
20 <bean class="面向切面編程.concert.PointcutForMore" id="forMore"/>
21
22 <aop:config>
23 <aop:pointcut id="pointcut" expression="execution(* *.concert.Performance.perform(..))"/>
24 <aop:pointcut id="pointcutWithParam"
25 expression="execution(* *.concret.Performance.perform(..)) and args(index,name)"/>
26 <aop:aspect ref="audience">
27 <!-- <aop:before method="silenceCellPhone" pointcut-ref="pointcut"/> 28 <aop:before method="takeSeats" pointcut-ref="pointcut"/> 29 <aop:after-returning method="applause" pointcut-ref="pointcut"/> 30 <aop:after-throwing method="demandRefund" pointcut-ref="pointcut"/>-->
31 <aop:around method="watchPerform" pointcut-ref="pointcut"/>
32 </aop:aspect>
33 <aop:aspect ref="host">
34 <!--注意在XML中不該該使用&& 而應該使用關鍵字and-->
35 <aop:before method="host" pointcut-ref="pointcutWithParam" arg-names="index,name"/>
36
37 </aop:aspect>
38 <aop:aspect ref="forMore">
39 <aop:declare-parents types-matching="面向切面編程.concert.Performance+"
40 implement-interface="面向切面編程.concert.MoreForPerformance"
41 default-impl="面向切面編程.concert.MoreForPerformanceImpl"/>
42 </aop:aspect>
43 </aop:config>
44
45 </beans>
--顧名思義,<aop:declare-parents>聲明瞭此切面所通知的bean 要在它的對象層次結構中擁有新的父類型。具體到本例中,類型匹 配Performance接口(由types-matching屬性指定)的那些bean 在父類結構中會增長MoreForPerformance接口(由implement- interface屬性指定)。最後要解決的問題是MoreForPerformance接口中 的方法實現要來自於何處。
--咱們能夠進行測試,查看代理是否成功
1 package 面向切面編程.concert; 2
3 import org.junit.Test; 4 import org.junit.runner.RunWith; 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.context.ApplicationContext; 7 import org.springframework.test.context.ContextConfiguration; 8 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 9
10 import java.util.List; 11
12 /**
13 * @author : S K Y 14 * @version :0.0.1 15 */
16 @RunWith(SpringJUnit4ClassRunner.class) 17 @ContextConfiguration(locations = {"classpath:面向切面編程/concert/audienct-config.xml"}) 18 public class XMLConfigTest { 19 @Autowired 20 private Performance performance; 21 @Autowired 22 private Host host; 23 @Autowired 24 private PointcutForMore more; 25
26 @Autowired 27 private ApplicationContext context; 28 @Autowired 29 private MoreForPerformance moreForPerformance; 30
31 @Test 32 public void testMoreForPerformance() { 33 MoreForPerformance performanceMore = context.getBean("performance", MoreForPerformance.class); 34 performanceMore.moreForPerformance(); 35 Performance performance = context.getBean("performance", Performance.class); 36 performance.perform(); 37 } 38
39 }
--運行結果
[附加]舞蹈表演特別的棒,每個舞者臉上都流露着燦爛的笑容... 表演即將開始,觀衆小明將他的手機設置爲了靜音狀態; 表演還有5分鐘就要開始了,觀衆小明入座等待; 演員們表演精彩的舞蹈... 表演結束,觀衆小明爲演員們熱烈的鼓掌; Process finished with exit code 0
--咱們能夠發現,成功的爲performance bean添加了新的方法完成了咱們的實現
--雖然Spring AOP可以知足許多應用的切面需求,可是與AspectJ相比, Spring AOP 是一個功能比較弱的AOP解決方案。AspectJ提供了Spring AOP所不能支持的許多類型的切點。當咱們須要在建立對象時應用通知,構造器切點就很是方便。 不像某些其餘面嚮對象語言中的構造器,Java構造器不一樣於其餘的正 常方法。這使得Spring基於代理的AOP沒法把通知應用於對象的建立 過程。
附:AspectJ語法:
execution()表達內部寫方法名,方法名加全類名,內部參數也須要表名全類名(兩個方法用execution() or execution()鏈接):
public boolean addStudent(com.sky.model.Student):全部返回類型爲Boolean而且返回參數爲此的方法
public boolean com.sky.model.StudentService.addStudent(com.sky.model.Student): 只有該類下的addStudent方法
public * addStudent(com.sky.model.Student):返回值任意
public void * (com.sky.model.Student):方法名任意
public void addStudent(..) :任意參數列表
*com.sky.service.*.*(..):com.sky.service.StudentServic中的全部方法,不包含子包中的
*com.sky.service..*.*(..):com.sky.service.StudentServic中的全部方法,包含子包中的