前言:
前面已經有兩篇文章講了Spring IOC/DI 以及 使用xml和注解兩種方法開發的案例, 下面就來梳理一下Spring的另一核心AOP.
一, 什么是AOP
在軟件業,AOP為Aspect Oriented Programming的縮寫,意為:面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。AOP是OOP的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生范型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。
AOP采取橫向抽取機制,取代了傳統縱向繼承體系重復性代碼.
Spring AOP使用純Java實現,不需要專門的編譯過程和類加載器,在運行期通過代理方式向目標類織入增強代碼.
二,AOP開發中的專業術語
- Joinpoint(連接點):所謂連接點是指那些被攔截到的點。在spring中,這些點指的是方法,因為spring只支持方法類型的連接點.
- Pointcut(切入點):所謂切入點是指我們要對哪些Joinpoint進行攔截的定義.
- Advice(通知/增強):所謂通知是指攔截到Joinpoint之后所要做的事情就是通知.通知分為前置通知,后置通知,異常通知,最終通知,環繞通知(切面要完成的功能)
- Introduction(引介):引介是一種特殊的通知在不修改類代碼的前提下, Introduction可以在運行期為類動態地添加一些方法或Field.
- Target(目標對象):代理的目標對象
- Weaving(織入):是指把增強應用到目標對象來創建新的代理對象的過程.
spring采用動態代理織入,而AspectJ采用編譯期織入和類裝在期織入. - Proxy(代理):一個類被AOP織入增強后,就產生一個結果代理類Aspect(切面): 是切入點和通知(引介)的結合
三, 動態代理
spring AOP核心技術就是使用了Java 的動態代理技術, 這里簡單的總結下JDK和CGLIB兩種動態代理機制.
概念:
當一個對象(客戶端)不能或者不想直接引用另一個對象(目標對象),這時可以應用代理模式在這兩者之間構建一個橋梁--代理對象。按照代理對象的創建時期不同,可以分為兩種:
靜態代理:程序員事先寫好代理對象類,在程序發布前就已經存在了;
動態代理:應用程序發布后,通過動態創建代理對象。
其中動態代理又可分為:JDK/Cglib 動態代理.
3.1 JDK動態代理
此時代理對象和目標對象實現了相同的接口,目標對象作為代理對象的一個屬性,具體接口實現中,可以在調用目標對象相應方法前后加上其他業務處理邏輯。
代理模式在實際使用時需要指定具體的目標對象,如果為每個類都添加一個代理類的話,會導致類很多,同時如果不知道具體類的話,怎樣實現代理模式呢?這就引出動態代理。
JDK動態代理只能針對實現了接口的類生成代理。
代碼實例:
UserService.java:
public interface UserService { public void save(); public void update(); public void delete(); public void find(); }
UserServiceImpl.java:
1 public class UserServiceImpl implements UserService { 2 3 @Override 4 public void save() { 5 System.out.println("保存用戶..."); 6 } 7 8 @Override 9 public void update() { 10 System.out.println("修改用戶..."); 11 } 12 13 @Override 14 public void delete() { 15 System.out.println("刪除用戶..."); 16 } 17 18 @Override 19 public void find() { 20 System.out.println("查詢用戶..."); 21 } 22 23 }
MyJdbProxy.java:
1 /** 2 * 使用JDK的動態代理實現代理機制 3 * 4 */ 5 public class MyJdbProxy implements InvocationHandler{ 6 7 private UserService userService; 8 9 public MyJdbProxy(UserService userService){ 10 this.userService = userService; 11 } 12 13 public UserService createProxy(){ 14 // 生成UserSErvice的代理: 15 UserService userServiceProxy = (UserService) Proxy.newProxyInstance( 16 userService.getClass().getClassLoader(), userService.getClass() 17 .getInterfaces(), this); 18 return userServiceProxy; 19 } 20 21 @Override 22 public Object invoke(Object proxy, Method method, Object[] args) 23 throws Throwable { 24 // 判斷是否是save方法: 25 if("save".equals(method.getName())){ 26 // 增強: 27 System.out.println("權限校驗==========="); 28 return method.invoke(userService, args); 29 } 30 return method.invoke(userService, args); 31 } 32 33 }
SpringDemo.java 測試類:
1 public class SpringDemo1 { 2 3 @Test 4 // 沒有代理的時候的調用方式 5 public void demo1() { 6 // 創建目標對象 7 UserService userService = new UserServiceImpl(); 8 9 userService.save(); 10 userService.update(); 11 userService.delete(); 12 userService.find(); 13 } 14 15 @Test 16 // 使用代理 17 public void demo2() { 18 // 創建目標對象 19 UserService userService = new UserServiceImpl(); 20 UserService proxy = new MyJdbProxy(userService).createProxy(); 21 22 proxy.save(); 23 proxy.update(); 24 proxy.delete(); 25 proxy.find(); 26 } 27 }
3.2 CGLIB 動態代理
CGLIB(CODE GENERLIZE LIBRARY)代理是針對類實現代理,主要是對指定的類生成一個子類,覆蓋其中的所有方法,所以該類或方法不能聲明稱final的。
如果目標對象沒有實現接口,則默認會采用CGLIB代理;
如果目標對象實現了接口,可以強制使用CGLIB實現代理(添加CGLIB庫,並在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>
代碼實例:
CustomerService.java:
1 public class CustomerService { 2 public void save(){ 3 System.out.println("保存客戶..."); 4 } 5 public void update(){ 6 System.out.println("修改客戶..."); 7 } 8 public void delete(){ 9 System.out.println("刪除客戶..."); 10 } 11 public void find(){ 12 System.out.println("查詢客戶..."); 13 } 14 }
MyCglibProxy.java:
1 /** 2 * 使用Cglib產生代理 3 * 4 */ 5 public class MyCglibProxy implements MethodInterceptor{ 6 7 private CustomerService customerService; 8 9 public MyCglibProxy(CustomerService customerService){ 10 this.customerService = customerService; 11 } 12 13 public CustomerService createProxy(){ 14 // 創建核心類: 15 Enhancer enhancer = new Enhancer(); 16 // 設置父類: 17 enhancer.setSuperclass(customerService.getClass()); 18 // 設置回調: 19 enhancer.setCallback(this); 20 // 創建代理: 21 CustomerService customerServiceProxy = (CustomerService) enhancer.create(); 22 return customerServiceProxy; 23 } 24 25 @Override 26 public Object intercept(Object proxy, Method method, Object[] arg, 27 MethodProxy methodProxy) throws Throwable { 28 if("delete".equals(method.getName())){ 29 Object obj = methodProxy.invokeSuper(proxy, arg); 30 System.out.println("日志記錄=========="); 31 return obj; 32 } 33 return methodProxy.invokeSuper(proxy, arg); 34 } 35 }
SpringDemo.java 測試類:
1 public class SpringDemo2 { 2 3 @Test 4 public void demo1(){ 5 CustomerService customerService = new CustomerService(); 6 customerService.save(); 7 customerService.update(); 8 customerService.delete(); 9 customerService.find(); 10 } 11 12 @Test 13 public void demo2(){ 14 CustomerService customerService = new CustomerService(); 15 // 產生代理: 16 CustomerService proxy = new MyCglibProxy(customerService).createProxy(); 17 proxy.save(); 18 proxy.update(); 19 proxy.delete(); 20 proxy.find(); 21 } 22 }
AOP包括切面(aspect)、通知(advice)、連接點(joinpoint),實現方式就是通過對目標對象的代理在連接點前后加入通知,完成統一的切面操作。
四, Spring的傳統的AOP:基於ProxyFactoryBean的方式的代理
4.1 Spring的通知類型
前置通知 org.springframework.aop.MethodBeforeAdvice
在目標方法執行前實施增強
后置通知 org.springframework.aop.AfterReturningAdvice
在目標方法執行后實施增強
環繞通知 org.aopalliance.intercept.MethodInterceptor
在目標方法執行前后實施增強
異常拋出通知 org.springframework.aop.ThrowsAdvice
在方法拋出異常后實施增強
4.2 Spring的切面類型
Advisor : 代表一般切面,Advice本身就是一個切面,對目標類所有方法進行攔截.(不帶有切入點的切面,默認增強類中所有方法)
PointcutAdvisor : 代表具有切點的切面,可以指定攔截目標類哪些方法.(帶有切入點的切面)
4.3 Spring傳統AOP的快速入門
4.3.1 不帶有切入點的切面開發
AOP開發需要引入的相關jar包:
ProductService.java:

1 /** 2 * 商品業務層接口 3 * 4 * 5 */ 6 public interface ProductService { 7 public void save(); 8 9 public void update(); 10 11 public void delete(); 12 13 public void find(); 14 }
ProductServiceImpl.java:

1 /** 2 * 商品業務層實現類 3 * 4 */ 5 public class ProductServiceImpl implements ProductService { 6 7 @Override 8 public void save() { 9 System.out.println("保存商品..."); 10 } 11 12 @Override 13 public void update() { 14 System.out.println("修改商品..."); 15 } 16 17 @Override 18 public void delete() { 19 System.out.println("刪除商品..."); 20 } 21 22 @Override 23 public void find() { 24 System.out.println("查詢商品..."); 25 } 26 27 }
MyBeforeAdvice.java:

1 /** 2 * 自定義的前置增強: 3 * 4 */ 5 public class MyBeforeAdvice implements MethodBeforeAdvice { 6 7 @Override 8 public void before(Method method, Object[] args, Object target) 9 throws Throwable { 10 System.out.println("前置增強============="); 11 } 12 13 }
SpringDemo.java 測試類:

1 @RunWith(SpringJUnit4ClassRunner.class) 2 @ContextConfiguration("classpath:applicationContext.xml") 3 public class SpringDemo3 { 4 @Resource(name="productServiceProxy") 5 private ProductService productService; 6 7 @Test 8 /** 9 * 傳統方式: 10 */ 11 public void demo1(){ 12 productService.save(); 13 productService.update(); 14 productService.delete(); 15 productService.find(); 16 } 17 }
4.3.1 帶有切入點的切面開發
OrderService.java:

1 public class OrderService { 2 public void save(){ 3 System.out.println("添加訂單..."); 4 } 5 public void update(){ 6 System.out.println("修改訂單..."); 7 } 8 public void delete(){ 9 System.out.println("刪除訂單..."); 10 } 11 public void find(){ 12 System.out.println("查詢訂單..."); 13 } 14 }
MyAroundAdvice.java:

1 /** 2 * 定義的環繞增強: 3 * 4 */ 5 public class MyAroundAdvice implements MethodInterceptor{ 6 7 @Override 8 public Object invoke(MethodInvocation methodInvocation) throws Throwable { 9 System.out.println("環繞前增強==============="); 10 // 執行目標方法: 11 Object obj = methodInvocation.proceed(); 12 System.out.println("環繞后增強==============="); 13 return obj; 14 } 15 16 }
SpringDemo.java 測試類:

1 @RunWith(SpringJUnit4ClassRunner.class) 2 @ContextConfiguration("classpath:applicationContext.xml") 3 public class SpringDemo4 { 4 5 @Resource(name="orderServiceProxy") 6 private OrderService orderService; 7 8 @Test 9 public void demo1(){ 10 orderService.save(); 11 orderService.update(); 12 orderService.delete(); 13 orderService.find(); 14 } 15 }
applicationContext.xml 配置文件:

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" 4 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> 5 6 7 <!-- 配置目標對象 --> 8 <bean id="productService" class="cn.augmentum.aop.demo3.ProductServiceImpl"></bean> 9 10 <!-- 配置前置增強 --> 11 <bean id="myBeforeAdvice" class="cn.augmentum.aop.demo3.MyBeforeAdvice"/> 12 13 <!-- 配置生成代理 --> 14 <bean id="productServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> 15 <!-- ProxyFactoryBean常用可配置屬性 16 target : 代理的目標對象 17 proxyInterfaces : 代理要實現的接口 18 如果多個接口可以使用以下格式賦值 19 <list> 20 <value></value> 21 .... 22 </list> 23 proxyTargetClass : 是否對類代理而不是接口,設置為true時,使用CGLib代理 24 interceptorNames : 需要織入目標的Advice 25 singleton : 返回代理是否為單實例,默認為單例 26 optimize : 當設置為true時,強制使用CGLib 27 --> 28 <property name="target" ref="productService"/> 29 <property name="proxyInterfaces" value="cn.augmentum.aop.demo3.ProductService"/> 30 <property name="interceptorNames" value="myBeforeAdvice"/> 31 32 </bean> 33 34 35 <bean id="orderService" class="cn.augmentum.aop.demo4.OrderService"/> 36 37 <bean id="myAroundAdvice" class="cn.augmentum.aop.demo4.MyAroundAdvice"/> 38 39 <bean id="advisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> 40 <!-- 正則表達式: . :代表任意字符 *:任意次數 --> 41 <!-- <property name="pattern" value=".*"/> --> 42 <property name="pattern" value="cn\.augmentum\.aop\.demo4\.OrderService\.save"/> 43 <property name="advice" ref="myAroundAdvice"/> 44 </bean> 45 46 <bean id="orderServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> 47 <property name="target" ref="orderService"/> 48 <property name="proxyTargetClass" value="true"/> 49 <property name="interceptorNames" value="advisor"/> 50 </bean> 51 52 </beans>
基於Spring FactoryBean代理的總結:
Spring會根據類是否實現接口采用不同的代理方式:
* 實現接口:JDK動態代理.
* 沒有實現接口:CGLIB生成代理.
基於ProxyFactoryBean的方式生成代理的過程中不是特別理想:
* 配置繁瑣,不利為維護.
* 需要為每個需要增強的類都配置一個ProxyFactoryBean.
五, Spring的傳統的AOP:基於自動代理
5.1 自動代理和基於ProxyFactoryBean代理方式比較:
自動代理基於BeanPostProcessor(前一篇文章講過的工廠鈎子)完成的代理.
* 在類的生成過程中就已經是代理對象.
基於ProxyFactoryBean方式代理:
* 先有目標對象,根據目標對象產生代理.
5.2自動代理的方式:
BeanNameAutoProxyCreator:基於Bean名稱的自動代理
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" 4 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> 5 6 <!-- 配置目標對象 --> 7 <bean id="productService" class="cn.augmentum.aop.demo3.ProductServiceImpl"></bean> 8 <bean id="orderService" class="cn.augmentum.aop.demo4.OrderService"/> 9 10 <!-- 配置前置增強 --> 11 <bean id="myBeforeAdvice" class="cn.augmentum.aop.demo3.MyBeforeAdvice"/> 12 <bean id="myAroundAdvice" class="cn.augmentum.aop.demo4.MyAroundAdvice"/> 13 14 <!-- 基於Bean名稱的自動代理 --> 15 <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> 16 <property name="beanNames" value="*Service"/> 17 <property name="interceptorNames" value="myBeforeAdvice"/> 18 </bean> 19 </beans>
DefaultAdvisorAutoProxyCreator:基於切面信息的自動代理
1 <beans xmlns="http://www.springframework.org/schema/beans" 2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> 4 5 <!-- 配置目標對象 --> 6 <bean id="productService" class="cn.augmentum.aop.demo3.ProductServiceImpl"></bean> 7 <bean id="orderService" class="cn.augmentum.aop.demo4.OrderService"/> 8 9 <!-- 配置前置增強 --> 10 <bean id="myBeforeAdvice" class="cn.augmentum.aop.demo3.MyBeforeAdvice"/> 11 <bean id="myAroundAdvice" class="cn.augmentum.aop.demo4.MyAroundAdvice"/> 12 13 <!-- 配置切面信息 --> 14 <bean id="advisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> 15 <property name="advice" ref="myAroundAdvice"/> 16 <!-- <property name="pattern" value="cn\.augmentum\.aop\.demo4\.OrderService\.save"/> --> 17 <property name="patterns" value="cn\.itcast\.aop\.demo4\.OrderService\.save,cn\.augmentum\.aop\.demo3\.ProductService\.update"/> 18 </bean> 19 20 <!-- 配置基於切面信息自動代理 --> 21 <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> 22 </beans>
Spring AOP最基礎的知識大概就是這些了, 由於篇幅的限制及閱讀的觀感, 所以打算再寫個Spring AOP基礎入門二 來總結下 Spring基於AspectJ的AOP的開發, 這個地方才是重點, 當然這一篇博文的知識也是有利於大家對Spring AOP有了一個整體的認識了. 文章內容皆是自己學習工作整理所得, 如若有問題 歡迎指正.