在之前我們記錄Spring的隨筆當中,都是記錄的Spring如何對對象進行注入,如何對對象的屬性值進行注入,即我們講解的很大部分都是Spring的其中一個核心概念——依賴注入(或者說是控制翻轉,IOC)的方面,那么通過前幾天所學的《常用設計模式:代理模式》的學習之后,我們今天來學習一下與代理模式密切相關,或者說是代理模式的一個強大應用的Spring的另一個核心概念——面向切片編程,即AOP(Aspect OrientedProgramming)
首先我們都知道java是一門經典的面向對象的編程語言(OOP,Object OrientedProgramming)面向對象的編程語言有三大特性,封裝,繼承,和多態,其中的繼承允許我們定義從上到下的關系,即子類可以繼承父類的一些功能函數,也可以改寫父類的一些功能函數,但是要為分散的對象(即不是同一父類的對象,這里的父類不包括Object)引入一個公共的行為的時候,OOP就顯得很乏力,它需要在每一個對象里頭都添加這個公用的行為,這樣代碼的就顯得很重復,很冗余,最典型的就是添加日志功能,這個日志功能和對象的核心功能毫無關系,但是要為分散的對象添加日志功能,就必須向打開每一個分散的封裝好的對象,然后添加日志功能代碼,我們將這種散步在各處與對象核心功能無關的代碼,稱之為橫切代碼。為了解決這個問題,AOP就應運而生。
AOP是將多個類的公共行為封裝到一個可重用的模塊(如我們上一段所說的日志功能)並將其命名為“Aspect”,即是說將那些和類的核心業務無關的,卻為業務模塊所公共調用的邏輯處理封裝起來,便可以減少系統的重復代碼,降低模塊間的耦合度,並有利於未來的可操作性和可維護性。AOP彌補了OOP在橫向關系上操作的不足。而實現AOP的技術的主要兩種方式,其中一種就是我們《常用設計模式:代理模式》所提到的動態代理技術;還有一種方式就是采用靜態織入的方式,引入特定的語法創建“方面”,從而使得編譯器可以在編譯期間織入有關“方面”的代碼。
而Spring當中的AOP的實現原理就是我們之前所提到的動態代理技術(如有對原理不是很理解的,可以翻看《常用設計模式:代理模式》)。那么接下來我們要看看如何去使用Spring當中的面向切片編程去實現一些功能,首先我們先來看看在Spring的面向切片編程當中的一些常涉及的概念:
1.連接點(Joinpoint): 程序執行過程中明確的點,如方法的調用或特定的異常被拋出。
2.切入點(Pointcut): 指定一個通知將被引發的一系列連接點的集合。AOP框架必須允許開發者指定切入點:例如,使用正則表達式。 Spring定義了Pointcut接口,用來組合MethodMatcher和ClassFilter,可以通過名字很清楚的理解, MethodMatcher是用來檢查目標類的方法是否可以被應用此通知,而ClassFilter是用來檢查Pointcut是否應該應用到目標類上
3.目標對象(Target Object): 包含連接點的對象。也被稱作被通知或被代理對象。POJO
4.AOP代理(AOP Proxy): AOP框架創建的對象,包含通知。 在Spring中,AOP代理可以是JDK動態代理或者CGLIB代理。
5.切面(Advisor):包含切入點和增強類的對象,為攔截器提供切入點和對應的增強類。
接下來我們將分幾種在Spring當中使用AOP的不同形式來講解如何使用AOP。
1.基於代理的AOP
還是以我們動態代理的買鞋子和買手機的例子為例,我們要做一個業務接口是買鞋子還有一個業務接口是買手機,而需要編寫相應的類去實現買鞋子和買手機的業務接口,並且通過Spring當中的配置文件去做配置。首先先上我們的業務接口和業務接口實現類的代碼:
package wellhold.bjtu.AOP; public interface BuyPhoneITF { public void BuyPhone(); }
package wellhold.bjtu.AOP; public interface BuyShoesITF { public void BuyShoes(); }
package wellhold.bjtu.AOP; public class PhoneSeller implements BuyPhoneITF { @Override public void BuyPhone() { System.out.println("這里是手機店,您要的手機有貨"); } }
package wellhold.bjtu.AOP; public class ShoesSeller implements BuyShoesITF { @Override public void BuyShoes() { System.out.println("您要的鞋子已售出"); } }
之后我們在寫一個統一的售前處理的方法和售后的處理方法的代碼模塊:
package wellhold.bjtu.AOP; import java.lang.reflect.Method; import org.springframework.aop.AfterReturningAdvice; import org.springframework.aop.MethodBeforeAdvice; public class BuyHelper implements AfterReturningAdvice,MethodBeforeAdvice{ @Override public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable { System.out.println("需要提前詢問是否有貨"); } @Override public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable { System.out.println("買到之后需要需要開發票"); } }
這個BuyHelper類實現了兩個Spring框架當中AOP部分的兩個接口,一個AfterReturningAdvice,一個MethodBeforeAdvice,這兩個接口需要重寫兩個方法,這兩個方法分別對應的是統一的售前處理方法和售后處理方法,可以把這個BuyHelper當做是所有與Buy有關的類的方法的增強類,可以增強Buy有關的方法,提供統一的售前處理和售后處理。之后我們就需要配置我們的Spring配置文件了,配置文件如下:
<?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: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-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor" /> <context:annotation-config /> <!--對應bean注冊入Spring --> <bean id="buyHelper" class="wellhold.bjtu.AOP.BuyHelper"/> <bean id="shoesSeller" class="wellhold.bjtu.AOP.ShoesSeller"/> <bean id="phoneSeller" class="wellhold.bjtu.AOP.PhoneSeller"/> <!-- 定義切入點,匹配所有的Buyshoes方法 --> <bean id ="buyShoesPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut"> <property name="pattern" value=".*BuyShoes"></property> </bean> <!-- 定義切入點,匹配所有的Buyshoes方法 --> <bean id ="buyPhonePointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut"> <property name="pattern" value=".*BuyPhone"></property> </bean> <!-- 定義一個基於Advisor的buyPhone切面 增強類+切點結合 --> <bean id="buyPhoneHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"> <property name="advice" ref="buyHelper"/> <property name="pointcut" ref="buyPhonePointcut"/> </bean> <!-- 定義一個基於Advisor的buyPhone切面 增強類+切點結合 --> <bean id="buyShoesHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"> <property name="advice" ref="buyHelper"/> <property name="pointcut" ref="buyShoesPointcut"/> </bean> <!-- 定義代理對象 --> <bean id="buyPhoneProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target" ref="phoneSeller"/> <property name="interceptorNames" value="buyPhoneHelperAdvisor"/> </bean> <!-- 定義代理對象 --> <bean id="buyShoesProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target" ref="shoesSeller"/> <property name="interceptorNames" value="buyShoesHelperAdvisor"/> </bean> </beans>
這里主要解釋一下什么是切入點,所謂的切入點,是為了給Spring提供何時切入信息的,比如說,我們的代碼在運行到.BuyPhone方法的時候(這里使用正則表達式去統一匹配),如果匹配上是一個定義了的切入點,那么Spring會去尋找他的對應的增強類,說白了就去找對應這個切入點的預前處理方法和事后處理方法。一個切入點與他對應的增強類可以被看做是一個切面,這里是基於Advisor的切面,定義了兩個切面,之后定義了兩個代理對象去為這兩個業務接口進行代理。之后我們運行以下我們的測試代碼:
package wellhold.bjtu.AOP; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class maintest { public static void main(String[] args) { ApplicationContext cfg=new ClassPathXmlApplicationContext("beans.xml"); BuyPhoneITF ps =cfg.getBean("buyPhoneProxy",BuyPhoneITF.class); ps.BuyPhone(); BuyShoesITF ss =cfg.getBean("buyShoesProxy",BuyShoesITF.class); ss.BuyShoes(); } }
簡單的總結一下這種方式實現的AOP,這種方式有點類似於靜態代理模式,每一個目標類需要對應一個代理,但是與靜態代理模式不同,它抽象出了一個統一的預前處理和事后處理模塊,不需要在代理類當中重寫方法的時候重復寫預前處理和事后處理,不過這種方式在配置文件上需要配置的東西有點繁多,需要將目標類和增強類都注冊到容器中,還需要定義相應的切入點,再結合切入點和增強類定義切面,最后還需要定義代理,過程很繁瑣,現在已經不常用。
2.POJO類的切面
從第一種方式可以看到,應對每一個不同的目標類還需要定義不同的代理去進行方法增強,那么在Spring中,我們也可以讓它去自動幫我們配置代理,而不需要我們再手動定義,主要不同在於配置文件,之前的業務接口和接口實現類,增強類的代碼這里就不重復了,直接上配置文件:
<?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: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-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor" /> <context:annotation-config /> <!--對應bean注冊入Spring --> <bean id="buyHelper" class="wellhold.bjtu.AOP.BuyHelper"/> <bean id="shoesSeller" class="wellhold.bjtu.AOP.ShoesSeller"/> <bean id="phoneSeller" class="wellhold.bjtu.AOP.PhoneSeller"/> <!-- 定義切入點,匹配所有的Buyshoes方法 --> <bean id ="buyShoesPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut"> <property name="pattern" value=".*BuyShoes"></property> </bean> <!-- 定義切入點,匹配所有的Buyshoes方法 --> <bean id ="buyPhonePointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut"> <property name="pattern" value=".*BuyPhone"></property> </bean> <!-- 定義一個基於Advisor的buyPhone切面 增強類+切點結合 --> <bean id="buyPhoneHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"> <property name="advice" ref="buyHelper"/> <property name="pointcut" ref="buyPhonePointcut"/> </bean> <!-- 定義一個基於Advisor的buyPhone切面 增強類+切點結合 --> <bean id="buyShoesHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"> <property name="advice" ref="buyHelper"/> <property name="pointcut" ref="buyShoesPointcut"/> </bean> <!-- 自動掃描配置文件中的Advisor,並且去匹配其對應的實現切入點攔截方法接口的目標類 --> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> </beans>
與1章節當中配置文件不同的地方就是之前的手動配置代理變為了讓Spring自動去幫我們配置代理,利用DefaultAdvisorAutoProxyCreator去掃描Spring配置文件當中的Advisor,並且為每一個Advisor匹配對應實現了切點攔截方法的業務接口的實現類,舉個例子,buyShoesHelperAdvisor的切點的攔截方法是BuyPhone(),那么Spring會自動匹配究竟有那個類去實現了聲明了BuyPhone()方法的接口,就找到了phoneSeller(如果還有其他的類實現了這個方法的業務接口,也會一起被代理),並且自動為其配置代理。
接下來我們來看下測試代碼和效果:
package wellhold.bjtu.AOP; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class maintest { public static void main(String[] args) { ApplicationContext cfg=new ClassPathXmlApplicationContext("beans.xml"); BuyPhoneITF ps =cfg.getBean("phoneSeller",BuyPhoneITF.class); ps.BuyPhone(); BuyShoesITF ss =cfg.getBean("shoesSeller",BuyShoesITF.class); ss.BuyShoes(); } }
這里你一定會有一個和我當時剛看的時候的疑問,為什么是從ioc容器中獲取的是目標類的實例呢?而且最關鍵是為什么還能實現在切入點去調用增強類的方法呢?為此我也進行了一番調研,發現原來在使用DefaultAdvisorAutoProxyCreator的時候,這時候我們去調用getBean(“phoneSeller”),它其實給我們返回的是自動生成的AOP代理,而不是真實的phoneSeller這個目標類的實例,具體Spring怎么實現的,我就沒有在深究,有知道的朋友可以和我相互交流一下。
用這種方式去進行AOP的實現,可以說比第一種方式來的好方便很多,但是也有可能造成一定的不便。如我們需要在代碼中調用到原始的目標類,那這時候我們就沒辦法再從Spring容器當中去獲取了,應為通過getBean方法去獲取的話,返回的是AOP代理。至於如何返回原始目標類,目前自己也沒有想到什么方法。
3.基於注解的AOP實現
看到這里,也許你還是會覺得這個配置文件還是需要寫好長一串,還需要定義什么切入點,還有什么切面什么的,還是相對比較麻煩的,那么不要着急,接下來我們要介紹的方法,可以讓你擺脫這種困擾,那就是基於注解的方式去實現AOP。
基於注解的形式,我們的業務接口和實現對象是不需要變化的,但是我們的增強類的編寫就大為不同了,代碼如下:
package wellhold.bjtu.AOP; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Aspect @Component("buyHelperByAnnotation") public class BuyHelperByAnnotation { // @Pointcut("execution(* *.BuyShoes(..))") @Pointcut("execution(* wellhold.bjtu.AOP.*.*(..))") public void BuyPhonepoint(){} @Before("BuyPhonepoint()") public void before() { System.out.println("需要提前詢問是否有貨"); } @AfterReturning("BuyPhonepoint()") public void after() { System.out.println("買到之后需要需要開發票"); } }
在這個增強類當中,我們通過:
1. @Aspect注解告訴了Spring這個類是用來提供切面的預前處理方法、事后處理方法、以及切點的。
2. 在@Pointcut()注解修飾的方法,則是聲明了一個切點,用正則表達式來表示,在代碼中注釋的那一行的含義是切點是所有名為.BuyShoes()方法,而沒有被注釋的那一行則是將在包wellhold.bjtu.AOP包下的所有類的所有方法都作為切點,即都攔截下來添加預前處理和時候處理。我們就以攔截所有類的所有方法這個注釋為例。
3. 而@Before()注釋則是用來修飾預前處理方法,且要為注釋提供切點方法,即之前用@Pointcut()注解修飾的方法名。
4. @AfterReturnning的作用是用於修飾事后處理方法,且要為注釋提供切點方法,即之前用@Pointcut()注解修飾的方法名。
了解了基於注解的增強類的編寫之后,我們可以看看我們的配置文件,這時候的配置文件就顯得十分輕量級了。
<?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: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-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor" /> <context:annotation-config /> <!--掃描包 --> <context:component-scan base-package="wellhold.bjtu.AOP" annotation-config="true"/> <!-- ASPECTJ注解 --> <aop:aspectj-autoproxy proxy-target-class="true" /> <!-- 目標類 --> <bean id="phoneSeller" class="wellhold.bjtu.AOP.PhoneSeller"/> <bean id="shoesSeller" class="wellhold.bjtu.AOP.ShoesSeller"/> </beans>
只需要打開掃描注解的功能,以及提供目標類即可,其實熟悉Spring的朋友,都可以為PhoneSeller和ShoesSeller兩個目標類使用@Component注解的分支去為其注入Spring容器,所以配置文件就顯得十分輕量級,所以說基於注解的使用方式是很優雅的一種方式。當然初學者為了更好的理解其中的一些概念,還是需要像筆者這樣一步一步學習來的印象較為深刻。
接下來我們看一下我們的測試代碼和運行效果:
package wellhold.bjtu.AOP; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class maintest { public static void main(String[] args) { ApplicationContext cfg=new ClassPathXmlApplicationContext("beans2.xml"); BuyPhoneITF ps =cfg.getBean("phoneSeller",BuyPhoneITF.class); ps.BuyPhone(); System.out.println(); System.out.println(); BuyShoesITF ss =cfg.getBean("shoesSeller",BuyShoesITF.class); ss.BuyShoes(); } }
可以看到和之前一樣,我們通過獲取PhoneSeller和ShoesSeller的實例,Spring會給我們自動返回他們的代理對象,然后通過他們的代理對象去調用相應方法的接口,實現目標類的方法的增強,效果與其他方式一致。
4. 半注解的形式
我們從3章節里頭可以看出,完全基於注解的形式可以很優雅的完成切片編程,但是有的時候,如果我們全是用注解的形式去處理切邊編程,那么有的時候需要修改切邊對應的增強類或者增強方法的時候,就只能重新打開源碼進行修改,這樣有時候會很不方便,所以我們有時候會選擇半注解的形式去做切片編程,即到時候需要修改的時候,我們可以通過修改配置文件的方式,然后再重啟代碼就可以實現預前處理方法和事后處理方法的修改。
在這里目標類和業務方法接口的代碼就不重復上了,直接上我們的增強類方法:
package wellhold.bjtu.AOP; import org.springframework.stereotype.Service; @Service("buyHelper02") public class BuyHelper02 { public void before() { System.out.println("需要提前詢問是否有貨--------------------------"); } public void after() { System.out.println("買到之后詢問是否需要發票--------------------"); } }
可以看到,在這個部分的增強類方法當中的事情處理和事后處理方法都沒有任何注解去修飾,唯一的注解是將這個增強類注入到Spring的Ioc容器當中。
接下來我們可以看下我們的配置文件:
<?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: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-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor" /> <context:annotation-config /> <!--掃描包 --> <context:component-scan base-package="wellhold.bjtu.AOP" annotation-config="true"/> <!-- ASPECTJ注解 --> <!-- <aop:aspectj-autoproxy proxy-target-class="true" /> --> <!-- 目標類 --> <bean id="phoneSeller" class="wellhold.bjtu.AOP.PhoneSeller"/> <bean id="shoesSeller" class="wellhold.bjtu.AOP.ShoesSeller"/> <aop:config> <aop:aspect ref="buyHelper02"> <aop:before method="before" pointcut="execution(* wellhold.bjtu.AOP.*.*(..))"/> <aop:after method="after" pointcut="execution(* wellhold.bjtu.AOP.*.*(..))"/> </aop:aspect> </aop:config> </beans>
在配置文件當中,我們用標簽<aop:comfig>去對其進行配置,配置方法見配置文件即可,一目了然。通過配置文件的形式,我們可以在配置文件中直接修改他的切入點,以及對應的方法名,還有引用的增強類,而不需要打開源碼進行修改,在某些應用場景當中還是很方便的。
到此,四種實現SpringAOP的方式就講解完畢了,其中第一種和第二種方法,幾乎是沒有再使用了的,但是為了更好的理解AOP的一些概念,比如切點,切面,建議,攔截器,目標類,增強類,我們還是手動實現了一遍方式一和方式二。就本人來說,還是比較喜歡使用方式三的去進行編寫,不過再某些WEB開發當中,因為要把代碼放到服務器當中打包成war包,所以要修改代碼會比較困難,這時候方式四的優勢就體現出來了,可以通過修改配置文件的形式去完成方法的重指向,切點的重指向,還是比較便利的。
5. 補充內容
我們之前的內容,都是以預前處理方法和事后處理方法進行舉例子的,但在AOP當中,除了這兩種方法之外,還有其他的方法,接下來我們來一一介紹,首先我們先來補充一個概念,那就是連接點的概念,連接點(JointPoint)是可以為通知方法被提供調用的業務接口方法的簽名和傳入的參數的,簡單的說,在運行過程中,Spring會自動向JoinPoint對象自動注入被調用的業務接口方法的方法名和傳入的參數,常常與通知方法聯用,接下來我們一一介紹這些通知方法:
1. 前置通知:其對應的注解為@Before ,也就是我們前邊經常提到的通俗說法的預前處理方法,執行目標方法前攔截到的方法,常常可以與一個JointPoint聯用,用來獲取被調用的業務方法名稱和參數,可以在這里做一個分支處理,比如調用方法一的時候,需要什么預前處理,調用方法二的時候需要什么預前處理。
2. 后置通知:其對應的注解為@After,無論方法是否拋出異常,都必須運行這個注解修飾的方法,常常可以與一個JointPoint聯用,用來獲取被調用的業務方法名稱和參數,可以在這里做一個分支處理,比如調用方法一的時候,需要什么處理,調用方法二的時候需要什么處理。
3. 返回通知:其對應的注解為@AfterReturning,該通知是在目標方法執行成功后執行的通知,不僅僅使用JointPoint獲取連接點信息,還可以在注解里通過result關鍵詞獲得目標方法執行的結果。
4. 異常通知:其對應的注解為@AfterThrowing,該通知是在執行目標方法的過程中,出現異常,就會執行,其和返回通知不可能同時執行,使用方法和返回通知相似,不過不同的是通過throwing關鍵詞獲取跑出的異常。
5.環繞通知:其對應的注解為@Around,環繞通知執行在前置通知之前,環繞通知需要攜帶ProceedingJoinPoint 這個類型的參數,環繞通知類似於動態代理的全過程ProceedingJoinPoint類型的參數可以決定是否執行目標函數環繞通知必須有返回值。其實就是包含了所有通知的全過程。
接下來我們上一個代碼,大家就可以理解我們的各個通知的作用和所處的位置了
package wellhold.bjtu.AOP; import org.springframework.stereotype.Component; @Component("adviceType") public class adviceType implements AdviceTypeITF{ @Override public String Method1(String name) { System.out.println("這是方法1"); return "方法1執行成功"; } @Override public void Method2(String name, String id) { System.out.println("這是方法2"); } }
package wellhold.bjtu.AOP; public interface AdviceTypeITF { public String Method1(String name); public void Method2(String name,String id); }
package wellhold.bjtu.AOP; import org.aopalliance.intercept.Joinpoint; 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; @Aspect @Component("buyHelperByAnnotation") public class BuyHelperByAnnotation { // @Pointcut("execution(* *.BuyShoes(..))") @Pointcut("execution(* wellhold.bjtu.AOP.*.*(..))") public void BuyPhonepoint(){} @Before("BuyPhonepoint()") public void before(JoinPoint joinpoint) { System.out.print("這是前置通知 : "); for (int i = 0; i < joinpoint.getArgs().length; i++) { System.out.print(joinpoint.getArgs()[i]+" "); } System.out.println(joinpoint.getSignature().getName()); } @After("BuyPhonepoint()") public void after(JoinPoint joinpoint) { System.out.print("這是最終通知 : "); for (int i = 0; i < joinpoint.getArgs().length; i++) { System.out.print(joinpoint.getArgs()[i]+" "); } System.out.println(joinpoint.getSignature().getName()); } @AfterReturning(pointcut="BuyPhonepoint()",returning="result") public void afterReturning(JoinPoint jointpoint ,String result) { System.out.print("這是后置通知 : "+"result= "+result+" "); for (int i = 0; i < jointpoint.getArgs().length; i++) { System.out.print(jointpoint.getArgs()[i]+" "); } System.out.println(jointpoint.getSignature().getName()); } @AfterThrowing(pointcut="BuyPhonepoint()",throwing="e") public void exception(Exception e) { System.out.println("這是后置異常通知 : "+e); } //環繞通知:需要攜帶ProceedingJoinPoint類型的參數 //環繞通知類似於動態代理的全過程:ProceedingJoinPoint類型的參數可以決定是否執行目標方法 //且環繞通知必須有返回值,返回值即目標方法的返回值。 @Around("BuyPhonepoint()") public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { //可以在環繞通知之下進行權限判斷 System.out.print("這是環繞通知 : "); for (int i = 0; i < pjp.getArgs().length; i++) { System.out.print(pjp.getArgs()[i]+" "); } System.out.println(pjp.getSignature().getName()); Object result=pjp.proceed(); return result; } }
相信通過之前的講解,這部分的代碼就不需要在贅述了,之后我們來看看輸出結果:
至此,AOP部分的內容就講解完畢了,水平有限,如有問題,可以聯系或者站內留言交流。