在軟件開發中,散佈於應用中多處的功能被稱爲橫切關注點。這些橫切關注點從概念上是與應用的業務邏輯相分離的。把這些橫切關注點與業務邏輯相分離正是面向切面編程(AOP)所要解決的問題。AspectJ---另外一種流行的AOP實現。 若是是要重用通用功能的話,最多見的面向對象技術是繼承或委託。可是,若是在整個應用中都使用相同的基類,繼承每每會致使一個脆弱的對象體系;而使用委託可能須要對委託對象進行復雜的調用。正則表達式
通知(Advice):定義了切面是什麼以及什麼時候使用。除了描述切面要完成的工做,通知還解決了什麼時候執行這個工做的問題。spring
Spring切面能夠應用五種類型的通知:express
鏈接點(Join Point):在用於執行過程當中可以插入切面的一個點。切面代碼能夠利用這些點插入到應用的正常流程中,並添加新的行爲。編程
切點(PointCut):切點的定義會匹配通知全部織入的一個或多個鏈接點。一般使用明確的類和方法名稱,或是利用正則表達式定義所匹配的類和方法名稱來指定這些切點。maven
切面(Aspect):切面是通知和切點的結合,通知和切點共同定義了切面的所有內容--它是什麼,在什麼時候和何處完成其功能。ide
引入(Introduction):引入容許咱們向現有的類添加新方法或屬性。post
織入(Weaving):織入是把切面應用到目標對象並建立新的代理對象的過程。切面在指定的鏈接點被織入到目標對象中。在目標對象的生命週期裏有多個點能夠進行織入:ui
2.類加載期:切面在目標類加載到JVM時被織入。這種方式須要特殊的類加載器(ClssLoader),他能夠在目標類被引入應用以前加強該目標類的字節碼。this
3.運行期:切面在應用運行的某個時刻被織入。通常狀況下,在織入切面時,AOP容器會爲目標對象動態地建立一個代理對象。spa
Spring提供了4中類型的AOP支持。 1.基於代理的經典SpringAOP。
2.純POJO切面。
3.@AspectJ註解驅動的切面。
4.注入式AspectJ切面
首先:定義一個HelloWorld接口:
public interface HelloWorld { void printHelloWorld(); void doPrint(); }
切點的配置
execution(* com.xrq.aop.HelloWorld.(..)) 切點表達式。execution 在方法執行時觸發, com.xrq.aop.HelloWorld.(..) 篩選制定的方法。 com.xrq.aop.HelloWorld.(..)) 是鏈接點。??
<aop:config> <aop:aspect id="time" ref="timeHandler"> <aop:pointcut id="addAllMethod" expression="execution(* com.xrq.aop.HelloWorld.*(..))" /> <aop:before method="printTime" pointcut-ref="addAllMethod" /> <aop:after method="printTime" pointcut-ref="addAllMethod" /> </aop:aspect> </aop:config>
使用@AspectJ註解進行了標註,該註解代表 Advices 不只是一個POJO類,也是一個切面。 使用@PoinCut定義命名的切點,能夠簡化重複的代碼。
package com.zhangguo.Spring052.aop02; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; /** * 通知類,橫切邏輯 * */ @Component @Aspect public class Advices { @Before("execution(* com.zhangguo.Spring052.aop02.Math.*(..))") public void before(JoinPoint jp){ System.out.println("----------前置通知----------"); System.out.println(jp.getSignature().getName()); } @After("execution(* com.zhangguo.Spring052.aop02.Math.*(..))") public void after(JoinPoint jp){ System.out.println("----------最終通知----------"); } }
2.JavaConfig啓用切面的代理 在配置類的級別上經過使用@EnableAspectJAutoProxy 註解啓動代理功能。
package com.zhangguo.Spring052.aop05; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; @Configuration //用於表示當前類爲容器的配置類,相似<beans/> @ComponentScan(basePackages="com.zhangguo.Spring052.aop05") //掃描的範圍,至關於xml配置的結點<context:component-scan/> @EnableAspectJAutoProxy(proxyTargetClass=true) //自動代理,至關於<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy> public class ApplicationCfg { //在配置中聲明一個bean,至關於<bean id=getUser class="com.zhangguo.Spring052.aop05.User"/> @Bean public User getUser(){ return new User(); } }
3.xml裝配Bean,啓動註解: aspectj-autoproxy 元素聲明aop的啓動。
<?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:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" 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-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> <context:component-scan base-package="com.zhangguo.Spring052.aop02"> </context:component-scan> <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy> </beans>
環繞通知接受 ProceedingJoinPoint做爲參數。這個對象是必需要有的,由於你要在通知中經過它來調用被通知的方法。通知方法中能夠作任何的事情,當要將控制權交給被通知的方法時,它須要調用ProceedingJoinPoint的Proceed()方法。
若是忘記調用Proceed()方法,通知實際上會阻塞對被通知方法的調用。也能夠在通知中對它進行多長調用,實現充實邏輯。
package com.zhangguo.Spring052.aop04; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; /** * 通知類,橫切邏輯 */ @Component @Aspect public class Advices { //切點 @Pointcut("execution(* com.zhangguo.Spring052.aop04.Math.a*(..))") public void pointcut(){ } //前置通知 @Before("pointcut()") public void before(JoinPoint jp){ System.out.println(jp.getSignature().getName()); System.out.println("----------前置通知----------"); } //最終通知 @After("pointcut()") public void after(JoinPoint jp){ System.out.println("----------最終通知----------"); } //環繞通知 @Around("execution(* com.zhangguo.Spring052.aop04.Math.s*(..))") public Object around(ProceedingJoinPoint pjp) throws Throwable{ System.out.println(pjp.getSignature().getName()); System.out.println("----------環繞前置----------"); Object result=pjp.proceed(); System.out.println("----------環繞後置----------"); return result; } //返回結果通知 @AfterReturning(pointcut="execution(* com.zhangguo.Spring052.aop04.Math.m*(..))",returning="result") public void afterReturning(JoinPoint jp,Object result){ System.out.println(jp.getSignature().getName()); System.out.println("結果是:"+result); System.out.println("----------返回結果----------"); } //異常後通知 @AfterThrowing(pointcut="execution(* com.zhangguo.Spring052.aop04.Math.d*(..))",throwing="exp") public void afterThrowing(JoinPoint jp,Exception exp){ System.out.println(jp.getSignature().getName()); System.out.println("異常消息:"+exp.getMessage()); System.out.println("----------異常通知----------"); } }
@DeclareParents註解:
面向註解的切面聲明有一個明顯的劣勢:你必須可以爲通知類添加註解。若是你沒有源碼的話,或者不想將AspectJ註解放入到代碼中,SpringXml能夠實現。
優先的原則:基於註解的配置要優於基於Java的配置,基於Java的配置要優於基於XMl的配置。 基於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:p="http://www.springframework.org/schema/p" 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/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> <!-- 被代理對象 --> <bean id="math" class="com.zhangguo.Spring052.aop01.Math"></bean> <!-- 通知 --> <bean id="advices" class="com.zhangguo.Spring052.aop01.Advices"></bean> <!-- aop配置 --> <aop:config proxy-target-class="true"> <!--切面 --> <aop:aspect ref="advices"> <!-- 切點 --> <aop:pointcut expression="execution(* com.zhangguo.Spring052.aop01.Math.*(..))" id="pointcut1"/> <!--鏈接通知方法與切點 --> <aop:before method="before" pointcut-ref="pointcut1"/> <aop:after method="after" pointcut-ref="pointcut1"/> </aop:aspect> </aop:config> </beans>
當咱們須要建立更細粒度的通知或想監測bean的建立時,Spring所支持的AOP就比較弱了,這時,能夠選擇使用AspectJ提供的構造器切點,而且能夠藉助Spring的依賴注入把bean裝配進AspectJ切面中。下面就來舉個栗子:
//定義表演接口 package concert; public interface Performance { void perform(); void finishPerform(String performer, String title); }
//定義鋼琴表演 package concert; public class PianoPerform implements Performance { public PianoPerform(){ System.out.println("有請鋼琴表演"); } @Override public void perform() { System.out.println("鋼琴表演開始"); } @Override public void finishPerform(String performer, String title) { System.out.println(performer + "演奏鋼琴曲:" + title); } }
//定義小提琴表演 package concert; public class ViolinPerform implements Performance { public ViolinPerform(){ System.out.println("有請小提琴表演"); } @Override public void perform() { System.out.println("小提琴表演開始"); } @Override public void finishPerform(String performer, String title){ System.out.println(performer + "演奏了小提琴曲:" + title); } }
//定義工做人員,將做爲切面的協做bean package concert; public class Worker { public void take(){ System.out.println("觀衆已所有交出手機"); } public void sendMsg(String name){ System.out.println(name + "表演即將開始,請各位觀衆交出手機"); } public void broadcast(String performer, String title){ System.out.println(performer + "演奏完畢,剛纔演奏的曲子叫:" + title); } }
//定義切面 package concert; public aspect Audience { private Worker worker; public Audience(){} //經過setter方法注入 public void setWorker(Worker worker){ this.worker = worker; System.out.println("工做人員已入場"); } //定義piano構造器切點和後置通知 pointcut piano():execution(concert.PianoPerform.new()); after():piano(){ worker.sendMsg("鋼琴"); } //定義violin構造器切點和後置通知 pointcut violin():execution(concert.ViolinPerform.new()); after():violin(){ worker.sendMsg("小提琴"); } //定義不帶參數方法切點和前置通知 pointcut perform():execution(* concert.Performance.perform()); before():perform(){ worker.take(); } //定義帶兩個參數的切點和後置通知 pointcut finishPerform(String performer, String title):execution(* concert.Performance.finishPerform(String, String)) && args(performer, title); after(String performer, String title):finishPerform(performer, title){ worker.broadcast(performer, title); } }
XML配置文件:spring.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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--<bean id="piano" class="concert.PianoPerform" lazy-init="true"/>--> <bean id="worker" class="concert.Worker"/> <!--Spring須要經過靜態方法aspectOf得到audience實例,Audience切面編譯後的class文件附在文末--> <bean class="concert.Audience" factory-method="aspectOf"> <property name="worker" ref="worker" /><!--經過Spring把協做的bean注入到切面中--> </bean> <!--這裏注意一下bean的順序,由於在構造器切點後置通知時調用了worker的sendMsg(String)方法,因此避免出現空指針異常,我們先把worker聲明在前--> <!--若是要將piano或者violin聲明在前,能夠設置lazy-init="true"--> <!--因此spring是從上到下解析並實例化bean?仍是解析完整個文件再實例化呢?歡迎評論區留言交流--> <bean id="piano" class="concert.PianoPerform"/> <bean id="violin" class="concert.ViolinPerform"/> </beans>
//主程序 package concert; import org.springframework.context.support.ClassPathXmlApplicationContext; public class MainClass { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring.xml"); Performance piano = context.getBean("piano", Performance.class); piano.perform(); piano.finishPerform("亞莎·海菲茲", "致愛麗斯"); Performance violin = context.getBean("violin", Performance.class); violin.perform(); violin.finishPerform("霍洛維茨", "愛之喜悅"); } }
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.8.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.3.8.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>4.3.8.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>4.3.8.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.7</version> </dependency> </dependencies> <build> <plugins> <!--編譯aspect的插件,必需要將Audience.aj編譯爲class文件, 否則spring建立audience bean的時候找不到類--> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>aspectj-maven-plugin</artifactId> <version>1.8</version> <executions> <execution> <goals> <goal>compile</goal> </goals> </execution> </executions> <configuration> <complianceLevel>1.8</complianceLevel> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build>
Audience.class文件
package concert; import concert.Worker; import org.aspectj.lang.NoAspectBoundException; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class Audience { private Worker worker; static { try { ajc$postClinit(); } catch (Throwable var1) { ajc$initFailureCause = var1; } } public void setWorker(Worker worker) { this.worker = worker; System.out.println("工做人員已入場"); } public Audience() { } @After( value = "piano()", argNames = "" ) public void ajc$after$concert_Audience$1$dd71540a() { this.worker.sendMsg("鋼琴"); } @After( value = "violin()", argNames = "" ) public void ajc$after$concert_Audience$2$57c630b6() { this.worker.sendMsg("小提琴"); } @Before( value = "perform()", argNames = "" ) public void ajc$before$concert_Audience$3$1cad9822() { this.worker.take(); } @After( value = "finishPerform(performer, title)", argNames = "performer,title" ) public void ajc$after$concert_Audience$4$1840cdb9(String performer, String title) { this.worker.broadcast(performer, title); } //Aspect提供的靜態方法,返回Audience切面的一個實例,Spring便可經過factory-method屬性得到該實例 //<bean class="concert.Audience" factory-method="aspectOf"> public static Audience aspectOf() { if(ajc$perSingletonInstance == null) { throw new NoAspectBoundException("concert_Audience", ajc$initFailureCause); } else { return ajc$perSingletonInstance; } } public static boolean hasAspect() { return ajc$perSingletonInstance != null; } }
寫在最後: Spring所支持的AOP已經能夠知足不少需求,若是要求更高,可使用AspectJ提供的更豐富的切點類型,固然須要熟悉AspectJ語法。 本文只是簡單的舉出了部分切點類型和通知類型,更多的類型讀者能夠自行嘗試。歡迎留言指正,感謝您的閱讀!