在閱讀本文之前,大家可先行參閱《簡單理解Spring之IOC和AOP及代碼示例》一文,簡單了解下ioc和aop的相關內容。下面進入正題。
本文將會一步一步創建一個最簡單的例子,來使用Spring的AOP特性,算是一個Spring AOP的入門Demo。作為一個初學者,運行出這麼簡單的一個Demo也踩了很多的坑。
OOP的問題,AOP的補充
當我們需要為分散的對象引入公共行為的時候,OOP則顯得無能為力。也就是說,OOP允許你定義從上到下的關係,但並不適合定義從左到右的關係。例如日誌功能。日誌代碼往往水平地散佈在所有對象層次中,而與它所散佈到的對象的核心功能毫無關係。對於其他類型的代碼,如安全性、異常處理和透明的持續性也是如此。這種散佈在各處的無關的代碼被稱為橫切(cross-cutting)代碼,在OOP設計中,它導致了大量代碼的重複,而不利於各個模塊的重用。
所謂“方面”,簡單地說,就是將那些與業務無關,卻為業務模塊所共同調用的邏輯或責任封裝起來,便於減少系統的重複代碼,降低模塊間的耦合度,並有利於未來的可操作性和可維護性。
Spring中對AOP的支持
Spring中AOP代理由Spring的IoC容器負責生成、管理,其依賴關係也由IoC容器負責管理。因此,AOP代理可以直接使用容器中的其他Bean實例作為目標,這種關係可由IoC容器的依賴注入提供。 Spring默認使用Java動態代理來創建AOP代理,這樣就可以為任何接口實例創建代理了。當需要代理的類不是代理接口的時候,Spring自動會切換為使用CGLIB代理,也可強制使用CGLIB。
本例子的邏輯如下:有一個Car類(業務類),在Car類中的go方法運行之前和之後,都會有相應的日誌記錄,但Car類本身並不知道日誌的任何邏輯。
創建Maven項目並添加依賴
首先,新建一個Maven項目,使用mavenarchetypequickstart模板,然後打開pom.xml文件,加入Spring AOP運行需要的依賴包
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.0.5.RELEASE</version></dependency><dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>4.0.5.RELEASE</version></dependency><dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.0.5.RELEASE</version></dependency><dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>4.0.5.RELEASE</version></dependency><dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.1</version></dependency>
編寫業務代碼
新增一個業務類Car,包含一個go()方法
package com.wowo.spring_aop_demo1;public class Car { public void go(){ System.out.println("go go go!"); }}編寫切麵類
日誌類會記錄下系統的運行情況,但日誌的邏輯不會在業務類中寫的到處都是,而是作為一個切麵類存在。
package com.wowo.spring_aop_demo1;public class CarLogger { public void beforeRun(){ System.out.println("car is going to run"); } public void afterRun(){ System.out.println("car is running"); }}該切麵類包含兩個方法,他們分別是前置通知和後置通知。
通過bean來配置關聯
新增一個配置文件,本例命名為bean.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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd" > <bean id="car"/> <bean id="logger" /> <aop:config> <aop:aspect ref="logger"> <aop:pointcut expression="execution(* com.wowo.spring_aop_demo1.Car.go(..))" id="go"/> <aop:before pointcut-ref="go" method="beforeRun" /> <aop:after pointcut-ref="go" method="afterRun" /> </aop:aspect> </aop:config></beans>
注意:這個配置文件中,aop的命名空間,以及xsi:schemaLocation中包含的幾個地址都是必須的。
execution(* com.wowo.spring_aop_demo1.Car.go(..))是一個AspectJ切點表達式,execution表示在執行時觸發,後面的*表示任意類型的返回值,com.wowo.spring_aop_demo1.Car指的是切點所在的類,go(..)是方法名,..表示任意參數。
Spring切面可以應用5種類型的通知:
・Before――在方法被調用之前調用通知
・After――在方法完成之後調用通知,無論方法是否執行成功
・After-returning――在方法成功執行之後調用通知
・After-throwing――在方法拋出異常後調用通知
・Around――通知包裹了被通知的方法,在被通知的方法調用之前和調用之後都執行自定義的行為
運行業務代碼
下面創建一個包含main()方法的類,來運行業務代碼
package com.wowo.spring_aop_demo1;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class App { public static void main( String[] args ) { ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); Car car=(Car) context.getBean("car"); car.go(); }}在上面的代碼中,由Spring創建了一個car對象。 Spring在創建該對象時,發現它的一個方法被配置成了切點(pointcut),所以,在實例化該對象時,會創建一個代理對象,當切點方法go()執行時,會被Spring創建的代理對象所攔截,運行go方法之前,會調用所對應的切麵類CarLogger的前置方法beforeRun(),然後調用Car.go()方法,再然後就調用切麵類CarLogger的後置方法afterRun()。
注意:必須使用Spring創建包含切點的對象,如果自己創建的話,Spring是監測不到的,它的運行也不會被應用任何通知。
項目輸出結果為
car is going to rungo go go!car is running
使用環繞通知
如果想使用環繞通知,我們需要修改切麵類中的通知方法以及配置文件,業務類無需做任何修改,因為他們是完全解耦的。首先修改切麵類CarLogger
import org.aspectj.lang.ProceedingJoinPoint;public class CarLogger { public void aroundRun(ProceedingJoinPoint joinpoint){ System.out.println("car is going to run"); try { //調用被代理的對象的目標方法,本例中指向Car.go()方法joinpoint.proceed(); } catch (Throwable e) { e.printStackTrace(); } System.out.println("car is running"); }}環繞通知的方法,需要接受ProceedingJoinPoint類型的參數,其proceed()方法將會調用被代理對象的目標方法,所以,正常情況下,這個方法一定要調用。我們也可以通過不調用該方法來組織被代理對象的運行。
接下來將配置文件的aop:config部分修改為如下所示
<aop:config> <aop:aspect ref="logger"> <aop:pointcut expression="execution(* com.wowo.spring_aop_demo1.Car.go(..))" id="go"/> <aop:around method="aroundRun" pointcut-ref="go"/> </aop:aspect> </aop:config>
注意:環繞通知不能和前置/後置通知同時存在。運行代碼後,輸出結果不變。
總結
以上就是本文關於Spring AOP入門Demo分享的全部內容,希望對大家有所幫助。感興趣的朋友可以繼續參閱本站其他相關專題,如有不足之處,歡迎留言指出。感謝朋友們對本站的支持!