Spring詳解(五)------AOP
這章我們接著講 Spring 的核心概念---AOP,這也是 Spring 框架中最為核心的一個概念。
PS:本篇博客源碼下載鏈接:http://pan.baidu.com/s/1skZjg7r 密碼:dn42
1、AOP 什麽?
AOP(Aspect Oriented Programming),通常稱為面向切面編程。它利用一種稱為"橫切"的技術,剖解開封裝的對象內部,並將那些影響了多個類的公共行為封裝到一個可重用模塊,並將其命名為"Aspect",即切面。所謂"切面",簡單說就是那些與業務無關,卻為業務模塊所共同調用的邏輯或責任封裝起來,便於減少系統的重復代碼,降低模塊之間的耦合度,並有利於未來的可操作性和可維護性。
什麽是切面,什麽是公共模塊,那麽我們概念少說,直接通過一個實例來看看 AOP 到底是什麽。
2、需求
現在有一張表 User,然後我們要在程序中實現對 User 表的增加和刪除操作。
要求:增加和刪除操作都必須要開啟事務,操作完成之後要提交事務。
User.java
package com.ys.aop.one; public class User { private int uid; private String uname; public int getUid() { return uid; } public void setUid(int uid) { this.uid = uid; } public String getUname() { return uname; } public void setUname(String uname) { this.uname = uname; } }
3、解決辦法1:使用靜態代理
第一步:創建 UserService 接口
package com.ys.aop.one; public interface UserService { //添加 user public void addUser(User user); //刪除 user public void deleteUser(int uid); }
第二步:創建 UserService的實現類
package com.ys.aop.one; public class UserServiceImpl implements UserService{ @Override public void addUser(User user) { System.out.println("增加 User"); } @Override public void deleteUser(int uid) { System.out.println("刪除 User"); } }
第三步:創建事務類 MyTransaction
package com.ys.aop.one; public class MyTransaction { //開啟事務 public void before(){ System.out.println("開啟事務"); } //提交事務 public void after(){ System.out.println("提交事務"); } }
第四步:創建代理類 ProxyUser.java
package com.ys.aop.one; public class ProxyUser implements UserService{ //真實類 private UserService userService; //事務類 private MyTransaction transaction; //使用構造函數實例化 public ProxyUser(UserService userService,MyTransaction transaction){ this.userService = userService; this.transaction = transaction; } @Override public void addUser(User user) { transaction.before(); userService.addUser(user); transaction.after(); } @Override public void deleteUser(int uid) { transaction.before(); userService.deleteUser(uid); transaction.after(); } }
測試:
@Test public void testOne(){ MyTransaction transaction = new MyTransaction(); UserService userService = new UserServiceImpl(); //產生靜態代理對象 ProxyUser proxy = new ProxyUser(userService, transaction); proxy.addUser(null); proxy.deleteUser(0); }
結果:
這是一個很基礎的靜態代理,業務類UserServiceImpl 只需要關註業務邏輯本身,保證了業務的重用性,這也是代理類的優點,沒什麽好說的。我們主要說說這樣寫的缺點:
①、代理對象的一個接口只服務於一種類型的對象,如果要代理的方法很多,勢必要為每一種方法都進行代理,靜態代理在程序規模稍大時就無法勝任了。
②、如果接口增加一個方法,比如 UserService 增加修改 updateUser()方法,則除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法。增加了代碼維護的復雜度。
4、解決辦法2:使用JDK動態代理
動態代理就不要自己手動生成代理類了,我們去掉 ProxyUser.java 類,增加一個 ObjectInterceptor.java 類
package com.ys.aop.two; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import com.ys.aop.one.MyTransaction; public class ObjectInterceptor implements InvocationHandler{ //目標類 private Object target; //切面類(這裏指事務類) private MyTransaction transaction; //通過構造器賦值 public ObjectInterceptor(Object target,MyTransaction transaction){ this.target = target; this.transaction = transaction; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //開啟事務 this.transaction.before(); //調用目標類方法 method.invoke(this.target, args); //提交事務 this.transaction.after(); return null; } }
測試:
@Test public void testOne(){ //目標類 Object target = new UserServiceImpl(); //事務類 MyTransaction transaction = new MyTransaction(); ObjectInterceptor proxyObject = new ObjectInterceptor(target, transaction); /** * 三個參數的含義: * 1、目標類的類加載器 * 2、目標類所有實現的接口 * 3、攔截器 */ UserService userService = (UserService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), proxyObject); userService.addUser(null); }
結果:
那麽使用動態代理來完成這個需求就很好了,後期在 UserService 中增加業務方法,都不用更改代碼就能自動給我們生成代理對象。而且將 UserService 換成別的類也是可以的。
也就是做到了代理對象能夠代理多個目標類,多個目標方法。
註意:我們這裏使用的是 JDK 動態代理,要求是必須要實現接口。與之對應的另外一種動態代理實現模式 Cglib,則不需要,我們這裏就不講解 cglib 的實現方式了。
不管是哪種方式實現動態代理。本章的主角:AOP 實現原理也是動態代理
5、AOP 關鍵術語
1.target:目標類,需要被代理的類。例如:UserService
2.Joinpoint(連接點):所謂連接點是指那些可能被攔截到的方法。例如:所有的方法
3.PointCut 切入點:已經被增強的連接點。例如:addUser()
4.advice 通知/增強,增強代碼。例如:after、before
5. Weaving(織入):是指把增強advice應用到目標對象target來創建新的代理對象proxy的過程.
6.proxy 代理類:通知+切入點
7. Aspect(切面): 是切入點pointcut和通知advice的結合
具體可以根據下面這張圖來理解:
6、AOP 的通知類型
Spring按照通知Advice在目標類方法的連接點位置,可以分為5類
- 前置通知 org.springframework.aop.MethodBeforeAdvice
- 在目標方法執行前實施增強,比如上面例子的 before()方法
- 後置通知 org.springframework.aop.AfterReturningAdvice
- 在目標方法執行後實施增強,比如上面例子的 after()方法
- 環繞通知 org.aopalliance.intercept.MethodInterceptor
- 在目標方法執行前後實施增強
- 異常拋出通知 org.springframework.aop.ThrowsAdvice
- 在方法拋出異常後實施增強
- 引介通知 org.springframework.aop.IntroductionInterceptor
在目標類中添加一些新的方法和屬性
7、使用 Spring AOP 解決上面的需求
我們只需要在Spring 的配置文件 applicationContext.xml 進行如下配置:
<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" xsi:schemaLocation="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"> <!--1、 創建目標類 --> <bean id="userService" class="com.ys.aop.UserServiceImpl"></bean> <!--2、創建切面類(通知) --> <bean id="transaction" class="com.ys.aop.one.MyTransaction"></bean> <!--3、aop編程 3.1 導入命名空間 3.2 使用 <aop:config>進行配置 proxy-target-class="true" 聲明時使用cglib代理 如果不聲明,Spring 會自動選擇cglib代理還是JDK動態代理 <aop:pointcut> 切入點 ,從目標對象獲得具體方法 <aop:advisor> 特殊的切面,只有一個通知 和 一個切入點 advice-ref 通知引用 pointcut-ref 切入點引用 3.3 切入點表達式 execution(* com.ys.aop.*.*(..)) 選擇方法 返回值任意 包 類名任意 方法名任意 參數任意 --> <aop:config> <!-- 切入點表達式 --> <aop:pointcut expression="execution(* com.ys.aop.*.*(..))" id="myPointCut"/> <aop:aspect ref="transaction"> <!-- 配置前置通知,註意 method 的值要和 對應切面的類方法名稱相同 --> <aop:before method="before" pointcut-ref="myPointCut"></aop:before> <aop:after-returning method="after" pointcut-ref="myPointCut"/> </aop:aspect> </aop:config> </beans>
測試:
@Test public void testAop(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService useService = (UserService) context.getBean("userService"); useService.addUser(null); }
結果:
上面的配置我們在註釋中寫的很清楚了。這裏我們重點講解一下:
①、 切入點表達式,一個完整的方法表示如下:
execution(modifiers-pattern? ref-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) 類修飾符 返回值 方法所在的包 方法名 方法拋出的異常
那麽根據上面的對比,我們就很好理解:
execution(* com.ys.aop.*.*(..)) 選擇方法 返回值任意 包 類名任意 方法名任意 參數任意
②、springAOP 的具體加載步驟:
1、當 spring 容器啟動的時候,加載了 spring 的配置文件
2、為配置文件中的所有 bean 創建對象
3、spring 容器會解析 aop:config 的配置
1、解析切入點表達式,用切入點表達式和納入 spring 容器中的 bean 做匹配
如果匹配成功,則會為該 bean 創建代理對象,代理對象的方法=目標方法+通知
如果匹配不成功,不會創建代理對象
4、在客戶端利用 context.getBean() 獲取對象時,如果該對象有代理對象,則返回代理對象;如果沒有,則返回目標對象
說明:如果目標類沒有實現接口,則 spring 容器會采用 cglib 的方式產生代理對象,如果實現了接口,則會采用 jdk 的方式
Spring詳解(五)------AOP