1. 程式人生 > >AOP實現方式

AOP實現方式

一、前言

AOP的一些理解

二、AOP相關概念

(1)AOP是什麼?AOP與攔截器的區別?

太抽象的不說,如果你知道Struts2的攔截器,攔截器就是應用的AOP的思想,它用於攔截Action以進行一些預處理或結果處理。而Spring的AOP是一種更通用的模式,可以攔截Spring管理的Bean,功能更強大,適用範圍也更廣,它是通過動態代理與反射機制實現的。(更詳細的解釋可參看部落格 http://blog.csdn.net/zhangliangzi/article/details/51648032 )

(2)使用AOP需要的一些概念。

1.通知(Advice)

通知定義了在切入點程式碼執行時間點附近需要做的工作。

Spring支援五種型別的通知:

Before(前)  org.apringframework.aop.MethodBeforeAdvice

After-returning(返回後) org.springframework.aop.AfterReturningAdvice

After-throwing(丟擲後) org.springframework.aop.ThrowsAdvice

Arround(周圍) org.aopaliance.intercept.MethodInterceptor

Introduction(引入) org.springframework.aop.IntroductionInterceptor

2.連線點(Joinpoint)

程式能夠應用通知的一個“時機”,這些“時機”就是連線點,例如方法呼叫時、異常丟擲時、方法返回後等等。

3.切入點(Pointcut)

通知定義了切面要發生的“故事”,連線點定義了“故事”發生的時機,那麼切入點就定義了“故事”發生的地點,例如某個類或方法的名稱,Spring中允許我們方便的用正則表示式來指定。

4.切面(Aspect)

通知、連線點、切入點共同組成了切面:時間、地點和要發生的“故事”。

5.引入(Introduction)

引入允許我們向現有的類新增新的方法和屬性(Spring提供了一個方法注入的功能)。

6.目標(Target)

即被通知的物件,如果沒有AOP,那麼通知的邏輯就要寫在目標物件中,有了AOP之後它可以只關注自己要做的事,解耦合!

7.代理(proxy)

應用通知的物件,詳細內容參見設計模式裡面的動態代理模式。

8.織入(Weaving)

把切面應用到目標物件來建立新的代理物件的過程,織入一般發生在如下幾個時機:

(1)編譯時:當一個類檔案被編譯時進行織入,這需要特殊的編譯器才可以做的到,例如AspectJ的織入編譯器;

(2)類載入時:使用特殊的ClassLoader在目標類被載入到程式之前增強類的位元組程式碼;

(3)執行時:切面在執行的某個時刻被織入,SpringAOP就是以這種方式織入切面的,原理應該是使用了JDK的動態代理技術。

 

三、使用AOP的幾種方式

1.經典的基於代理的AOP

[email protected]註解驅動的切面

3.純POJO切面

4.注入式AspectJ切面

 

四、Demo詳解

在講Demo之前,先把專案結構貼一下,我用的的一般的Java Project+Maven進行測試,用Web Project的小區別一會會說到。有一點很重要,jar依賴必須匯入正確,我在測試過程中,很多bug都是因為依賴問題引起的,這裡也貼一下。

包結構:

 

 

pom.xml:

  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  3. <modelVersion>4.0.0</modelVersion>
  4.  
  5. <groupId>com.springAOP</groupId>
  6. <artifactId>springAOP</artifactId>
  7. <version>0.0.1-SNAPSHOT</version>
  8. <packaging>jar</packaging>
  9.  
  10. <name>springAOP</name>
  11. <url>http://maven.apache.org</url>
  12.  
  13. <properties>
  14. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  15. <org.springframework.version>3.0.5.RELEASE</org.springframework.version>
  16. </properties>
  17.  
  18.  
  19. <dependencies>
  20.  
  21. <dependency>
  22. <groupId>junit</groupId>
  23. <artifactId>junit</artifactId>
  24. <version>3.8.1</version>
  25. <scope>test</scope>
  26. </dependency>
  27.  
  28. <!-- Spring -->
  29. <dependency>
  30. <groupId>org.springframework</groupId>
  31. <artifactId>spring-context</artifactId>
  32. <version>${org.springframework.version}</version>
  33. </dependency>
  34.  
  35. <!-- Spring AOP + AspectJ -->
  36. <dependency>
  37. <groupId>org.springframework</groupId>
  38. <artifactId>spring-aop</artifactId>
  39. <version>${org.springframework.version}</version>
  40. </dependency>
  41.  
  42. <dependency>
  43. <groupId>org.aspectj</groupId>
  44. <artifactId>aspectjrt</artifactId>
  45. <version>1.8.9</version>
  46. </dependency>
  47.  
  48. <dependency>
  49. <groupId>org.aspectj</groupId>
  50. <artifactId>aspectjweaver</artifactId>
  51. <version>1.8.9</version>
  52. </dependency>
  53.  
  54. </dependencies>
  55. </project>

 

下面開始正式的講解:

1、經典的基於代理的AOP實現,以一個睡覺的例子實現。

(1)可睡覺的介面,任何可以睡覺的人或機器都可以實現它。

  1. public interface Sleepable {
  2. public void sleep();
  3. }

(2)介面實現類,“Me”可以睡覺,“Me”就實現可以睡覺的介面。

  1. public class Me implements Sleepable{
  2. public void sleep() {
  3. System.out.println("\n睡覺!不休息哪裡有力氣學習!\n");
  4. }
  5. }

(3)Me關注於睡覺的邏輯,但是睡覺需要其他功能輔助,比如睡前脫衣服,起床脫衣服,這裡開始就需要AOP替“Me”完成!解耦!首先需要一個SleepHelper類。因為一個是切入點前執行、一個是切入點之後執行,所以實現對應介面。

  1. public class SleepHelper implements MethodBeforeAdvice, AfterReturningAdvice {
  2.  
  3. public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
  4. System.out.println("睡覺前要脫衣服!");
  5. }
  6.  
  7. public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
  8. System.out.println("起床後要穿衣服!");
  9. }
  10.  
  11. }

(4)最關鍵的來了,Spring核心配置檔案application.xml配置AOP。

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. <span style="white-space:pre"> </span>xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. <span style="white-space:pre"> </span>xmlns:aop="http://www.springframework.org/schema/aop"
  5. <span style="white-space:pre"> </span>xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. <span style="white-space:pre"> </span>http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  7. <span style="white-space:pre"> </span>http://www.springframework.org/schema/aop
  8. <span style="white-space:pre"> </span>http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
  9.  
  10. <!-- 定義被代理者 -->
  11. <bean id="me" class="com.springAOP.bean.Me"></bean>
  12.  
  13. <!-- 定義通知內容,也就是切入點執行前後需要做的事情 -->
  14. <bean id="sleepHelper" class="com.springAOP.bean.SleepHelper"></bean>
  15.  
  16. <!-- 定義切入點位置 -->
  17. <bean id="sleepPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
  18. <property name="pattern" value=".*sleep"></property>
  19. </bean>
  20.  
  21. <!-- 使切入點與通知相關聯,完成切面配置 -->
  22. <bean id="sleepHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
  23. <property name="advice" ref="sleepHelper"></property>
  24. <property name="pointcut" ref="sleepPointcut"></property>
  25. </bean>
  26.  
  27. <!-- 設定代理 -->
  28. <bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
  29. <!-- 代理的物件,有睡覺能力 -->
  30. <property name="target" ref="me"></property>
  31. <!-- 使用切面 -->
  32. <property name="interceptorNames" value="sleepHelperAdvisor"></property>
  33. <!-- 代理介面,睡覺介面 -->
  34. <property name="proxyInterfaces" value="com.springAOP.bean.Sleepable"></property>
  35. </bean>
  36.  
  37. </beans>

其中:

<beans>是Spring的配置標籤,beans裡面幾個重要的屬性:

xmlns:

是預設的xml文件解析格式,即spring的beans。地址是http://www.springframework.org/schema/beans;通過設定這個屬性,所有在beans裡面宣告的屬性,可以直接通過<>來使用,比如<bean>等等。一個XML檔案,只能宣告一個預設的語義解析的規範。例如上面的xml中就只有beans一個是預設的,其他的都需要通過特定的標籤來使用,比如aop,它自己有很多的屬性,如果要使用,前面就必須加上aop:xxx才可以。類似的,如果預設的xmlns配置的是aop相關的語義解析規範,那麼在xml中就可以直接寫config這種標籤了。

xmlns:xsi:

是xml需要遵守的規範,通過URL可以看到,是w3的統一規範,後面通過xsi:schemaLocation來定位所有的解析檔案。

xmlns:aop:

這個是重點,是我們這裡需要使用到的一些語義規範,與面向切面AOP相關。

xmlns:tx:

Spring中與事務相關的配置內容。

(5)測試類,Test,其中,通過AOP代理的方式執行Me的sleep()方法,會把執行前、執行後的操作執行,實現了AOP的效果!

  1. public class Test {
  2. public static void main(String[] args){
  3. @SuppressWarnings("resource")
  4. //如果是web專案,則使用註釋的程式碼載入配置檔案,這裡是一般的Java專案,所以使用下面的方式
  5. //ApplicationContext appCtx = new ClassPathXmlApplicationContext("application.xml");
  6. ApplicationContext appCtx = new FileSystemXmlApplicationContext("application.xml");
  7. Sleepable me = (Sleepable)appCtx.getBean("proxy");
  8. me.sleep();
  9. }
  10. }

執行結果:

 

 

org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator簡化配置。

將配置檔案中設定代理的程式碼去掉,加上:

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

然後,在Test中,直接獲取me物件,執行sleep方法,就可以實現同樣的功能!

通過自動匹配,切面會自動匹配符合切入點的bean,會被自動代理,實現功能!

 

2、更簡單的方式,通過AspectJ提供的註解實現AOP。

(1)同樣的例子,修改後的SleepHelper:

  1. @Aspect
  2. public class SleepHelper{
  3.  
  4. public SleepHelper(){
  5.  
  6. }
  7.  
  8. @Pointcut("execution(* *.sleep())")
  9. public void sleeppoint(){}
  10.  
  11. @Before("sleeppoint()")
  12. public void beforeSleep(){
  13. System.out.println("睡覺前要脫衣服!");
  14. }
  15.  
  16. @AfterReturning("sleeppoint()")
  17. public void afterSleep(){
  18. System.out.println("睡醒了要穿衣服!");
  19. }
  20.  
  21. }

(2)在方法中,可以加上JoinPoint引數以進行相關操作,如:

  1. //當丟擲異常時被呼叫
  2. public void doThrowing(JoinPoint point, Throwable ex)
  3. {
  4. System.out.println("doThrowing::method "
  5. + point.getTarget().getClass().getName() + "."
  6. + point.getSignature().getName() + " throw exception");
  7. System.out.println(ex.getMessage());
  8. }

(3)然後修改配置為:

  1. <aop:aspectj-autoproxy />
  2. <!-- 定義通知內容,也就是切入點執行前後需要做的事情 -->
  3. <bean id="sleepHelper" class="com.springAOP.bean.SleepHelper"></bean>
  4. <!-- 定義被代理者 -->
  5. <bean id="me" class="com.springAOP.bean.Me"></bean>

 

(4)最後測試,一樣的結果!

  1. public class Test {
  2. public static void main(String[] args){
  3. @SuppressWarnings("resource")
  4. //如果是web專案,則使用註釋的程式碼載入配置檔案,這裡是一般的Java專案,所以使用下面的方式
  5. //ApplicationContext appCtx = new ClassPathXmlApplicationContext("application.xml");
  6. ApplicationContext appCtx = new FileSystemXmlApplicationContext("application.xml");
  7. Sleepable me = (Sleepable)appCtx.getBean("me");
  8. me.sleep();
  9. }
  10. }

 

3、使用Spring來定義純粹的POJO切面(名字很繞口,其實就是純粹通過<aop:fonfig>標籤配置,也是一種比較簡單的方式)。

(1)修改後的SleepHelper類,很正常的類,所以這種方式的優點就是在程式碼中不體現任何AOP相關配置,純粹使用xml配置。

  1. public class SleepHelper{
  2.  
  3. public void beforeSleep(){
  4. System.out.println("睡覺前要脫衣服!");
  5. }
  6.  
  7. public void afterSleep(){
  8. System.out.println("睡醒了要穿衣服!");
  9. }
  10.  
  11. }

(2)配置檔案:

  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. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  6. http://www.springframework.org/schema/aop
  7. http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
  8.  
  9. <!-- 定義通知內容,也就是切入點執行前後需要做的事情 -->
  10. <bean id="sleepHelper" class="com.springAOP.bean.SleepHelper"></bean>
  11. <!-- 定義被代理者 -->
  12. <bean id="me" class="com.springAOP.bean.Me"></bean>
  13.  
  14. <aop:config>
  15. <aop:aspect ref="sleepHelper">
  16. <aop:before method="beforeSleep" pointcut="execution(* *.sleep(..))" />
  17. <aop:after method="afterSleep" pointcut="execution(* *.sleep(..))" />
  18. </aop:aspect>
  19. </aop:config>
  20.  
  21. </beans>

(3)配置的另一種寫法

  1. <aop:config>
  2. <aop:aspect ref="sleepHelper">
  3. <aop:pointcut id="sleepHelpers" expression="execution(* *.sleep(..))" />
  4. <aop:before pointcut-ref="sleepHelpers" method="beforeSleep" />
  5. <aop:after pointcut-ref="sleepHelpers" method="afterSleep" />
  6. </aop:aspect>
  7. </aop:config>