spring AOP 動態代理原理 虛擬碼
一.AOP的概念
在軟體業,AOP為Aspect Oriented Programming的縮寫,意為:面向切面程式設計,通過預編譯方式和執行期動態代理實現程式功能的統一維護的一種技術。AOP是OOP的延續,是軟體開發中的一個熱點,也是Spring框架中的一個重要內容,是函數語言程式設計的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。
二.主要用途
將日誌記錄,效能統計,安全控制,事務處理,
三.代理
AOP的實現關鍵在於AOP框架自動建立的AOP代理。AOP代理主要分為兩大類:
1.靜態代理:使用AOP框架提供的命令進行編譯,從而在編譯階段就可以生成AOP代理類,因此也稱為編譯時增強;靜態代理一Aspectj為代表。
2.動態代理:在執行時藉助於JDK動態代理,CGLIB等在記憶體中臨時生成AOP動態代理類,因此也被稱為執行時增強,Spring AOP用的就是動態代理。
那麼其實在Spring aop框架中用到就是JDK動態代理或者是CGLIB代理。
四.JDK動態代理
JDK動態代理用到了一個類Proxy,下面舉個例子來說明一下,Proxy這個類是如何實現JDK的動態代理的。
有一個需求就是,當用戶名為空的時候,不能呼叫業務方法,當用戶名不為空是可以呼叫業務方法。
1.新建一個業務介面,並書寫業務方法。
package cn.service; public interface DoService { //新增 public void add(); //查詢 public void selectInfo(); //更新 public void update(); }
2.新建一個業務介面實現類
package cn.service.impl; import cn.service.DoService; public class DoServiceImpl implements DoService{ //使用者名稱 private String userName; @Override public void add() { System.out.println("新增的方法"); } @Override public void selectInfo() { System.out.println("查詢的方法"); } @Override public void update() { System.out.println("更新的方法"); } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } }
一般如果我們需要進行這樣一個業務需求我們會相到 一個辦法就是在所有的方法中進行一個判斷就是判讀使用者名稱是否為空,顯然這樣很麻煩,所有我們用到了JDK動態代理,主函式 -->代理-->目標物件的方法。這樣,以後不管是修改判斷條件,還是查詢等,可以直接在代理類中進行處理。
3.建立代理物件工廠類
package cn.aop; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import cn.service.impl.DoServiceImpl; /** * 代理工廠類 * @author hyj * */ public class JDKProxyFactory implements InvocationHandler{ //要返回的代理物件 private Object obj; /** * 通過代理工廠的方式來建立代理類 * @param obj 要代理的類的物件 * @return */ public Object createProxyIntance(Object obj){ this.obj=obj; //第一個引數是目標物件的類載入器 //第二個引數是目標物件實現的介面 //第三個引數傳入一個InvocationHandler例項,該引數和回撥有關係。 return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object proxyObject=null; DoServiceImpl ds=(DoServiceImpl)obj; if(ds.getUserName()!=null){ proxyObject= method.invoke(ds, args); }else{ System.out.println("使用者名稱為空,已攔截"); } return proxyObject; } public Object getObj() { return obj; } public void setObj(Object obj) { this.obj = obj; } }
4.測試類
package cn.test; import cn.aop.JDKProxyFactory; import cn.service.DoService; import cn.service.impl.DoServiceImpl; public class TestHappy { public static void main(String[] args) { JDKProxyFactory jpf=new JDKProxyFactory(); DoService ds=(DoService)jpf.createProxyIntance(new DoServiceImpl("fsf")); ds.selectInfo(); } }
步驟:
- 首先呼叫代理工廠的createProxyIntance(Object obj)建立DoServiceImpl類的代理類.
- 在該方法內,呼叫Proxy.newProxyInstance()方法建立代理物件。第一個引數是目標物件的類載入器,第二個引數是目標物件實現的介面,第三個引數傳入一個InvocationHandler例項,該引數和回撥有關係。
- 每當呼叫目標物件的方法的時候,就會回撥該InvocationHandler例項的方法,也就是public Object invoke()方法,我們就可以把限制的條件放在這裡,條件符合的時候,就可以呼叫method.invoke()方法真正的呼叫目標物件的方法,否則,則可以在這裡過濾掉不符合條件的呼叫。
五.CGLIB代理
在這一步中,我們使用一個Enhancer類來建立代理物件,不再使用Proxy。使用Enhancer類,需要為其例項指定一個父類,也就是我們 的目標物件。這樣,我們新創建出來的物件就是目標物件的子類,有目標物件的一樣。除此之外,還要指定一個回撥函式,這個函式就和Proxy的 invoke()類似。
總體來說,使用CGlib的方法和使用Proxy的方法差不多,只是Proxy創建出來的代理物件和目標物件都實現了同一個介面。而CGlib的方法則是直接繼承了目標物件。
1.引入jar包
2.建立CGLIB實現代理的類
package cn.aop; import java.lang.reflect.Method; import cn.service.impl.DoServiceImpl; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; /** * CGLIB實現代理 * @author hyj * */ public class CGLIBProxyFactory implements MethodInterceptor { //要放回的代理物件 private Object obj; public Object createProxy(Object obj){ //把傳進來的代理物件賦值給obj this.obj=obj; Enhancer enhancer = new Enhancer(); //需要為其例項指定一個父類,也就是我們 的目標物件,那麼我們新創建出來的物件就是目標物件的子類,有目標物件的一樣 enhancer.setSuperclass(this.obj.getClass()); //除此之外,還要指定一個回撥函式,這個函式就和Proxy的 invoke()類似 enhancer.setCallback(this); return enhancer.create(); } @Override public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { Object proxyObject=null; DoServiceImpl ds=(DoServiceImpl)obj; if(ds.getUserName()!=null){ proxyObject= methodProxy.invoke(ds, args); }else{ System.out.println("使用者名稱為空,已攔截"); } return proxyObject; } }
測試類
@Test public void CGLIBProxyTest(){ CGLIBProxyFactory gb=new CGLIBProxyFactory(); DoServiceImpl ds= (DoServiceImpl)gb.createProxy(new DoServiceImpl("sf")); ds.add(); }