一、基本概念
DI能夠讓互相協作的軟體元件保持松散耦合;而面向切面程式設計(aspect-oriented programming,AOP)允許你把遍布應用各處的功能分離出來形成可重用的元件。把這些橫切關注點與業務邏輯相分離正是面向切面程式設計(AOP)所要解決的問題
常見場景:日志、安全、事物、緩存

項目中每個子產品的核心功能都是為特定業務領域提供服務,但是這些子產品都需要類似的輔助功能,例如安全和事務管理,這時候需要引入AOP的概念。
通知定義了切面是什麼以及何時使用, Spring切面可以應用5種類型的通知:
前置通知(Before):在目标方法被調用之前調用通知功能;
後置通知(After):在目标方法完成之後調用通知,此時不會關心方法的輸出是什麼;
傳回通知(After-returning):在目标方法成功執行之後調用通知;
異常通知(After-throwing):在目标方法抛出異常後調用通知;
環繞通知(Around):通知包裹了被通知的方法,在被通知的方法調用之前和調用之後執行自定義的行為。
連接配接點(join potint)是在應用執行過程中能夠插入切面的一個點。這個點可以是調用方法時、抛出異常時、甚至修改一個字段時。切面代碼可以利用這些點插入到應用的正常流程之中,并添加新的行為
切點(poincut)的定義會比對通知所要織入的一個或多個連接配接點。我們通常使用明确的類和方法名稱,或是利用正規表達式定義所比對的類和方法名稱來指定這些切點
<code>public</code> <code>class</code> <code>CategoryService1 {</code>
<code> </code><code>public</code> <code>void</code> <code>add(</code><code>int</code> <code>id) {</code>
<code> </code><code>System.out.println(</code><code>"CategoryService1.add()"</code><code>);</code>
<code> </code><code>}</code>
<code>}</code>
<code>public</code> <code>class</code> <code>CategoryService2{</code>
<code> </code><code>System.out.println(</code><code>"CategoryService2.add()"</code><code>);</code>
<code><?xml version=</code><code>"1.0"</code> <code>encoding=</code><code>"UTF-8"</code><code>?></code>
<code><beans xmlns=</code><code>"http://www.springframework.org/schema/beans"</code>
<code>xmlns:xsi=</code><code>"http://www.w3.org/2001/XMLSchema-instance"</code>
<code>xmlns:aop=</code><code>"http://www.springframework.org/schema/aop"</code>
<code>xmlns:tx=</code><code>"http://www.springframework.org/schema/tx"</code>
<code>xsi:schemaLocation="http:</code><code>//www.springframework.org/schema/beans</code>
<code> </code><code>http:</code><code>//www.springframework.org/schema/beans/spring-beans-4.2.xsd</code>
<code> </code><code>http:</code><code>//www.springframework.org/schema/aop</code>
<code> </code><code>http:</code><code>//www.springframework.org/schema/aop/spring-aop-4.2.xsd"></code>
<code><bean id=</code><code>"categoryServiceImpl"</code> <code>class</code><code>=</code><code>"service.CategoryService1"</code><code>></bean></code>
<code><bean id=</code><code>"CategoryServiceImpl2"</code> <code>class</code><code>=</code><code>"service.CategoryService2"</code><code>></bean></code>
<code></beans></code>
<code>@Test</code>
<code>public</code> <code>void</code> <code>test(){</code>
<code> </code><code>ApplicationContext context=</code><code>new</code> <code>ClassPathXmlApplicationContext(</code><code>"aop.xml"</code><code>);</code>
<code> </code><code>CategoryService1 service1=context.getBean(CategoryService1.</code><code>class</code><code>);</code>
<code> </code><code>service1.add(</code><code>1</code><code>);</code>
<code> </code><code>CategoryService2 service2=context.getBean(CategoryService2.</code><code>class</code><code>);</code>
<code> </code><code>service2.add(</code><code>2</code><code>);</code>
運作結果:
CategoryService1.add() CategoryService2.add()
Spring所建立的通知都是用标準的Java類編寫的, 定義通知所應用的切點通常會使用注解或在Spring配置檔案裡采用XML來編寫,這兩種文法對于Java開發者來說都是相當熟悉的。
注意Spring隻支援方法級别的連接配接點。
切入點表達式
execution訓示器是我們在編寫切點定義時最主要使用的訓示器
Demo
我們要實作的一個簡單示例是:在service方法調用前和調用後列印日志“write log”。
<code>public</code> <code>class</code> <code>LogHandler {</code>
<code> </code><code>public</code> <code>void</code> <code>log(){</code>
<code> </code><code>System.out.println(</code><code>"write log."</code><code>);</code>
aop.xml添加配置:
<code><bean id=</code><code>"logHandler"</code> <code>class</code><code>=</code><code>"pointcut.LogHandler"</code><code>></bean></code>
<code> </code><code><aop:config></code>
<code> </code><code><aop:aspect id=</code><code>"log"</code> <code>ref=</code><code>"logHandler"</code><code>></code>
<code> </code><code><aop:pointcut id=</code><code>"addLog"</code> <code>expression=</code><code>"execution(* service.*.*(..))"</code><code>></aop:pointcut></code>
<code> </code><code><aop:before method=</code><code>"log"</code> <code>pointcut-ref=</code><code>"addLog"</code><code>></aop:before></code>
<code> </code><code><aop:after method=</code><code>"log"</code> <code>pointcut-ref=</code><code>"addLog"</code><code>></aop:after></code>
<code> </code><code></aop:aspect></code>
<code> </code><code></aop:config> </code>
單元測試:
<code>public</code> <code>class</code> <code>AopTests {</code>
<code> </code><code>@Test</code>
<code> </code><code>public</code> <code>void</code> <code>test() {</code>
<code> </code><code>ApplicationContext context = </code><code>new</code> <code>ClassPathXmlApplicationContext(</code><code>"aop.xml"</code><code>);</code>
<code> </code><code>CategoryService1 service1 = context.getBean(CategoryService1.</code><code>class</code><code>);</code>
<code> </code><code>service1.add(</code><code>1</code><code>);</code>
<code> </code><code>CategoryService2 service2 = context.getBean(CategoryService2.</code><code>class</code><code>);</code>
<code> </code><code>service2.add(</code><code>2</code><code>);</code>
運作報錯:
org.aspectj.weaver.reflect.ReflectionWorld$ReflectionWorldException
原來是忘了pom依賴:
<code><dependency></code>
<code> </code><code><groupId>org.springframework</groupId></code>
<code> </code><code><artifactId>spring-aspects</artifactId></code>
<code> </code><code><version>${spring.version}</version></code>
<code></dependency> </code>
write log.
完整的pom.xml
通過使用環繞通知,可以實作前置通知和後置通知所實作的功能,而且隻需要在一個方法中實作。
<code>public</code> <code>class</code> <code>LogTimeHandler {</code>
<code> </code><code>public</code> <code>void</code> <code>log(ProceedingJoinPoint jp) </code><code>throws</code> <code>Throwable {</code>
<code> </code><code>try</code> <code>{</code>
<code> </code><code>System.out.println(</code><code>"1.before log "</code><code>+</code><code>new</code> <code>Date().getTime());</code><code>//記錄開始時間</code>
<code> </code><code>jp.proceed();</code>
<code> </code><code>System.out.println(</code><code>"2.after log "</code><code>+</code><code>new</code> <code>Date().getTime());</code><code>//記錄結束時間</code>
<code> </code><code>}</code><code>catch</code> <code>(Exception e){</code>
<code> </code><code>System.out.println(</code><code>"log fail "</code><code>);</code>
<code> </code><code>}</code>
在aop1.xml中配置aop:round通知
<code>xmlns:xsi=</code><code>"http://www.w3.org/2001/XMLSchema-instance"</code> <code>xmlns:aop=</code><code>"http://www.springframework.org/schema/aop"</code>
<code>xsi:schemaLocation=</code><code>"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"</code><code>></code>
<code> </code><code><bean id=</code><code>"categoryService"</code> <code>class</code><code>=</code><code>"service.CategoryService1"</code><code>></bean></code>
<code> </code><code><bean id=</code><code>"logHanlder"</code> <code>class</code><code>=</code><code>"pointcut.LogTimeHandler"</code><code>></bean></code>
<code> </code><code><aop:aspect id=</code><code>"log"</code> <code>ref=</code><code>"logHanlder"</code><code>></code>
<code> </code><code><aop:pointcut id=</code><code>"addlog"</code> <code>expression=</code><code>"execution(* service.*.*(..))"</code><code>></aop:pointcut></code>
<code> </code><code><aop:around method=</code><code>"log"</code> <code>pointcut-ref=</code><code>"addlog"</code><code>></aop:around></code>
<code> </code><code></aop:config></code>
<code>public</code> <code>class</code> <code>AopTest1 {</code>
<code> </code><code>public</code> <code>void</code> <code>test(){</code>
<code> </code><code>ApplicationContext context=</code><code>new</code> <code>ClassPathXmlApplicationContext(</code><code>"aop1.xml"</code><code>);</code>
<code> </code><code>CategoryService1 service1=context.getBean(CategoryService1.</code><code>class</code><code>);</code>
<code>1</code><code>.before log </code><code>1489990832246</code>
<code>CategoryService1.add()</code>
<code>2</code><code>.after log </code><code>1489990832263</code>
定義切面需要給類添加@Aspect注解。然後需要給方法添加注解來聲明通知方法,各通知類型對應的注解:
@After 通知方法會在目标方法傳回或抛出異常後
@AfterReturning 通知方法會在目标方法傳回後調用
@AfterThrowing 通知方法會在目标方法抛出異常後調用
@Around 通知方法會将目标方法封裝起來
@Before 通知方法會在目标方法調用之前執行
<code>@Component</code>
<code>@Aspect</code>
<code>public</code> <code>class</code> <code>LogHelper3 {</code>
<code> </code><code>@Before</code><code>(</code><code>"execution(* service.*.*(..))"</code><code>)</code>
<code> </code><code>public</code> <code>void</code> <code>logStart(){</code>
<code> </code><code>System.out.println(</code><code>"log start "</code><code>+</code><code>new</code> <code>Date().getTime());</code>
然後定義JavaConfig類,注意需要給類添加@EnableAspectJAutoProxy注解啟用自動代理功能。
<code>@Configuration</code>
<code>@EnableAspectJAutoProxy</code>
<code>@ComponentScan</code><code>(basePackageClasses = {service.CategoryService3.</code><code>class</code><code>,pointcut.LogHelper3.</code><code>class</code><code>})</code>
<code>public</code> <code>class</code> <code>BeanConfig {</code>
單元測試:
<code>@RunWith</code><code>(SpringJUnit4ClassRunner.</code><code>class</code><code>)</code>
<code>@ContextConfiguration</code><code>(classes = BeanConfig.</code><code>class</code><code>)</code>
<code>public</code> <code>class</code> <code>AopTest3 {</code>
<code> </code><code>@Autowired</code>
<code> </code><code>CategoryService3 service;</code>
<code> </code><code>public</code> <code>void</code> <code>testConfigAop(){</code>
<code> </code><code>service.add(</code><code>100</code><code>);</code>
<code>log start </code><code>1489990977264</code>
<code>add category id=</code><code>100</code>
結尾:
參考:《spring實戰》
源碼下載下傳:https://github.com/cathychen00/learnjava/tree/master/DemoAOP
本文轉自 陳敬(Cathy) 部落格園部落格,原文連結:http://www.cnblogs.com/janes/p/6873732.html,如需轉載請自行聯系原作者