把橫切關注點和業務邏輯相分離是面向切面編程所要解決的問題。若是要重用通用功能的話,最多見的面向對象技術是繼承(inheritance)或 組成(delegation)。可是,若是在整個應用中都使用相同的基類,繼承每每會致使一個脆弱的對象體系;而使用組成可能須要對委託對象進行復雜的調用。切面提供了取代繼承和委託的另外一種可選方案,並且在不少場景下更清晰簡潔。Spring AOP 基於動態代理,因此Spring只支持方法鏈接點,這與一些其餘的AOP框架是不一樣的,例如AspectJ和JBoss,除了方法切點,它們還提供了字段和構造器接入點。
java
橫切關注點(cross-cutuing concern):散佈在應用中多處的功能。web
切面(aspect) : 橫切關注點模塊化爲特殊的類。切面是通知和切點的結合。spring
通知(advice):定義了切面是什麼以及什麼時候使用。express
Spring切面能夠應用5種類型的通知:
編程
前置通知(Before):在目標方法被調用以前調用通知功能;
後置通知(After):在目標方法完成以後調用通知,此時不會關心方法的輸出是什麼;
返回通知(After-returning):在目標方法成功執行以後調用通知;
異常通知(After-throwing):在目標方法拋出異常後調用通知;
環繞通知(Around):通知包裹了被通知的方法,在被通知的方法調用以前和調用以後執行自定義的行爲。app
切點(pointcut):定義了切面在何處調用,會匹配通知所要織入的一個或多個鏈接點。
鏈接點(join point):在應用執行過程當中可以插入切面的一個點。這個點能夠是調用方法時、拋出異常時、甚至修改一個字段時。框架
織入(Weaving):織入是把切面應用到目標對象並建立新的代理對象的過程。ide
織入有三種方式能夠實現,Spring採用的是第三種,在運行期織入的:模塊化
編譯期:切面在目標類編譯時被織入。這種方式須要特殊的編譯器。AspectJ的織入編譯器就是以這種方式織入切面的。
類加載期:切面在目標類加載到JVM時被織入。這種方式須要特殊的類加載器(ClassLoader),它能夠在目標類被引入應用以前加強該目標類的字節碼。AspectJ 5的加載時織入(load-time weaving,LTW)就支持以這種方式織入切面。
運行期:切面在應用運行的某個時刻被織入。通常狀況下,在織入切面時,AOP容器會爲目標對象動態地建立一個代理對象。代理類封裝了目標類,並攔截被通知方法的調用,再把調用轉發給真正的目標bean。SpringAOP就是以這種方式織入切面的。測試
注意:只有execution指示器是實際執行匹配的,而其餘的指示器都是用來限制匹配的。這說明execution指示器是咱們在編寫切點定義時最主要使用的指示器 。同時須要注意的是, 表達式之間容許用 &&(and)、||(or)、!(not) 來匹配複雜的被通知類。除了上面羅列的表達式外,Spring 還提供了一個Bean 表達式來匹配 Bean 的id,例如 execution(* com.service.Performance.perform(..)) && bean(performance)
@args的正確用法:自定義一個ElementType.TYPE的註解,這個註解用來修飾自定義類型(好比本身寫的一個類),一個方法以這個自定義的類的實例爲參數且只能有這惟一一參數,那這個方法在調用時會被匹配@args(自定義註解)的切面攔截。
@annotation的正確用法:在切面類上用@annotation加自定義註解就能夠攔截使用這個註解的方法。好比匹配 @RequestMapping 註解的類 @annotation(org.springframework.web.bind.annotation.RequestMapping)
@target (cn.javass.spring.chapter6.Secure) 任何目標對象持有Secure註解的類方法;必須是在目標對象上聲明這個註解,在接口上聲明的對它不起做用。
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.6.11</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.6.11</version> </dependency>
@Aspect //表示這是一個切面類 public class Audience { //使用簡明的PointCut @Pointcut("execution(* com.service.Performance.perform(..))") public void performance(){} //前置通知 即 @Before("execution(* com.service.Performance.perform(..))") @Before("performance()") public void silenceCellPhones(){ System.out.println("Silencing cell phones"); } //前置通知 即 @Before("execution(* com.service.Performance.perform(..))") @Before("performance()") public void takeSeats(){ System.out.println("Taking seats"); } //方法調用結束通知(並非指返回值通知,即便是void的返回值,仍然會觸發通知) 即 @AfterReturning("execution(* com.service.Performance.perform(..))") @AfterReturning("performance()") public void applause(){ System.out.println("CLAP CLAP CLAP!!!"); } //有異常拋出的時候通知,即 @AfterThrowing("execution(* com.service.Performance.perform(..))") @AfterThrowing("performance()") public void demandRefund(){ System.out.println("Demanding a refund"); } }
有兩種方式能夠啓用AspectJ 註解的自動代理:
(1)在 Java 配置文件中顯示配置
@Configuration @EnableAspectJAutoProxy //啓用Aop自動代理 public class JavaConfig { @Bean public Audience getAudience(){ return new Audience(); } }
(2)在XML文件中配置
<!--啓用AspectJ自動代理--> <aop:aspectj-autoproxy/> <bean id="audience" class="com.aspect.Audience"/>
無論你是使用JavaConfig仍是XML,AspectJ自動代理都會爲使用@Aspect註解的bean建立一個代理,這個代理會圍繞着全部該切面的切點所匹配的bean。當程序執行到鏈接點的時候,就會由代理轉到切面觸發相應的通知。
@Aspect public class Audience3 { @Pointcut("execution(* com.service.Performance.perform(..))") public void performance(){} @Around("performance()") public void watchPerformance(ProceedingJoinPoint joinPoint) { System.out.println("Silencing cell phones"); System.out.println("Taking seats"); try { joinPoint.proceed(); System.out.println("CLAP CLAP CLAP!!!"); } catch (Throwable throwable) { System.out.println("Demanding a refund"); throwable.printStackTrace(); } } }
注意 ProceedingJoinPoint 做爲參數。這個對象是必需要有的,由於你要在通知中經過它來調用被通知的方法。當要將控制權交給被通知的方法時,它須要調用ProceedingJoinPoint的proceed()方法。
@Aspect public class TrackCounter { private Map<Integer,Integer> trackCounts=new HashMap<Integer, Integer>(); //@Pointcut("execution(* com.service.CompactDisc.playTrack(int)) && args(trackNumber)") //帶入輸入參數 //@Pointcut("target(com.service.CompactDisc) && args(trackNumber)") // target 匹配目標對象(非AOP對象)爲指定類型 //@Pointcut("within(com.service..*) && args(trackNumber)") //com.service 包以及子包下的全部方法都執行 //@Pointcut("within(com.service..CompactDisc+) && args(trackNumber)") //com.service 包的CompactDisc類型以及子類型 @Pointcut("this(com.service.CompactDisc) && args(trackNumber)") //匹配當前AOP代理對象類型,必須是類型全稱,不支持通配符 public void trackPlayed(int trackNumber){} @Before("trackPlayed(trackNumber)") public void countTrack(int trackNumber){ int playCount = getPlayCount(trackNumber); trackCounts.put(trackNumber,playCount+1); System.out.println(trackCounts.toString()); } public int getPlayCount(int trackNumber){ return trackCounts.containsKey(trackNumber) ? trackCounts.get(trackNumber) : 0; } }
Java並非動態語言。一旦類編譯完成了,咱們就很難爲該類添加新的功能了。可是,咱們的切面編程卻能夠作到動態的添加方法...話雖如此,其實也不過是障眼法罷了。實際上,面向切面編程,不過是把方法添加到切面代理中,當要對添加的方法調用的時候,能夠把被通知的 Bean 轉換成相應的接口。也就是代理會把此調用委託給實現了新接口的某個其餘對象。實際上,一個bean的實現被拆分到了多個類中。(說實話,想了半天,實在想不到這個功能有什麼做用......)
(1) 從新定義一個接口和實現類
public interface Encoreable { void performEncode(); }
public class DefaultEncoreable implements Encoreable { public void performEncode() { System.out.println("this is DefaultEncoreable"); } }
(2) 把接口實現類嵌入到目標類代理中
@Aspect public class EncoreableIntroducer { @DeclareParents(value = "com.service.CompactDisc+", defaultImpl = DefaultEncoreable.class) //value 表示要嵌入哪些目標類的代理 。 defaultImpl:表示要嵌入的接口的默認實現方法 public static Encoreable encoreable; }
(3) JUnit 測試
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:applicationContext.xml") public class Test02 { @Autowired private CompactDisc compactDisc; @Test public void test02(){ compactDisc.playTrack(123); Encoreable compactDisc = (Encoreable) this.compactDisc; //當要調用添加的新功能的時候,這個用法至關於由代理轉換到對應類實現,不會報類型轉換錯誤 compactDisc.performEncode(); } }
public class AudienceXML { public void silenceCellPhones(){ System.out.println("Silencing cell phones"); } public void takeSeats(){ System.out.println("Taking seats"); } public void applause(){ System.out.println("CLAP CLAP CLAP!!!"); } public void demandRefund(){ System.out.println("Demanding a refund"); } }
<aop:config> <aop:aspect ref="audienceXML"> <aop:pointcut id="performance" expression="execution(* com.service.Performance.perform(..))"/> <aop:before method="silenceCellPhones" pointcut-ref="performance"/> <aop:before method="takeSeats" pointcut-ref="performance"/> <aop:after-returning method="applause" pointcut-ref="performance"/> <aop:after-throwing method="demandRefund" pointcut-ref="performance"/> </aop:aspect> </aop:config>
public class Audience3XML { public void watchPerformance(ProceedingJoinPoint joinPoint) { System.out.println("Silencing cell phones"); System.out.println("Taking seats"); try { joinPoint.proceed(); System.out.println("CLAP CLAP CLAP!!!"); } catch (Throwable throwable) { System.out.println("Demanding a refund"); throwable.printStackTrace(); } } }
<aop:config> <aop:aspect ref="audience3XML"> <aop:pointcut id="performance3" expression="execution(* com.service.Performance.perform(..))"/> <aop:around method="watchPerformance" pointcut-ref="performance3"/> </aop:aspect> </aop:config>
<aop:config> <aop:aspect ref="trackCounter"> <aop:pointcut id="trackPlayed" expression="execution(* com.service.CompactDisc.playTrack(int)) and args(trackNumber)"/> <aop:before method="countTrack" pointcut-ref="trackPlayed"/> </aop:aspect> </aop:config>
<aop:config> <aop:aspect> <aop:declare-parents types-matching="com.service.CompactDisc+" implement-interface="com.service.Encoreable" default-impl="com.service.impl.DefaultEncoreable" delegate-ref="encoreableDelegate"/> </aop:aspect> </aop:config>