spring AOP 代理(靜態與動態+使用cglib實現)
阿新 • • 發佈:2018-12-17
一、沒有代理模式
缺點:
1、工作量特別大,如果專案中有多個類,多個方法,則要修改多次。
2、違背了設計原則:開閉原則(OCP),對擴充套件開放,對修改關閉,而為了增加功能把每個方法都修改了,也不便於維護。
3、違背了設計原則:單一職責(SRP),每個方法除了要完成自己本身的功能,還要計算耗時、延時;每一個方法引起它變化的原因就有多種。
4、違背了設計原則:依賴倒轉(DIP),抽象不應該依賴細節,兩者都應該依賴抽象。而在Test類中,Test與Math都是細節。
假設需實現一個計算的類Math、完成加、減、乘、除功能,如下所示:
Math類:
publicView Codeclass Math { //加 public int add(int n1,int n2){ int result=n1+n2; System.out.println(n1+"+"+n2+"="+result); return result; } //減 public int sub(int n1,int n2){ int result=n1-n2; System.out.println(n1+"-"+n2+"="+result); returnresult; } //乘 public int mut(int n1,int n2){ int result=n1*n2; System.out.println(n1+"X"+n2+"="+result); return result; } //除 public int div(int n1,int n2){ int result=n1/n2; System.out.println(n1+"/"+n2+"="+result); returnresult; } }
現在需求發生了變化,要求專案中所有的類在執行方法時輸出執行耗時。最直接的辦法是修改原始碼,如下所示:
public class Math { //加 public int add(int n1,int n2){ //開始時間 long start = getTime(); delay(); int result=n1+n2; System.out.println(n1+"+"+n2+"="+result); System.out.println("共用時:" + (getTime() - start)); return result; } //減 public int sub(int n1,int n2){ //開始時間 long start = getTime(); delay(); int result=n1*n2; System.out.println(n1+"-"+n2+"="+result); System.out.println("共用時:" + (getTime() - start)); return result; } //乘 public int mut(int n1,int n2){ //開始時間 long start = getTime(); delay(); int result=n1*n2; System.out.println(n1+"X"+n2+"="+result); System.out.println("共用時:" + (getTime() - start)); return result; } //除 public int div(int n1,int n2){ //開始時間 long start = getTime(); delay(); int result=n1/n2; System.out.println(n1+"/"+n2+"="+result); System.out.println("共用時:" + (getTime() - start)); return result; } public static long getTime() { return System.currentTimeMillis(); } public static void delay(){ try { int n = (int) new Random().nextInt(500); Thread.sleep(n); } catch (InterruptedException e) { e.printStackTrace(); } } }View Code
測試執行:
Math math = new Math(); int n=100; int m=5; math.add(n,m); math.sub(n,m); math.mut(n,m); math.div(n,m);
二、靜態代理
1、定義抽象主題介面。
IMath:
public interface IMath { int add(int n1,int n2); int sub(int n1,int n2); int mut(int n1,int n2); int div(int n1,int n2); }
2、實現介面:
MathImpl
public class MathImpl implements IMath{ //加 public int add(int n1,int n2){ int result=n1+n2; System.out.println(n1+"+"+n2+"="+result); return result; } //減 public int sub(int n1,int n2){ int result=n1-n2; System.out.println(n1+"-"+n2+"="+result); return result; } //乘 public int mut(int n1,int n2){ int result=n1*n2; System.out.println(n1+"X"+n2+"="+result); return result; } //除 public int div(int n1,int n2){ int result=n1/n2; System.out.println(n1+"/"+n2+"="+result); return result; } }View Code
3、代理類
MathProxy
public class MathProxy implements IMath { IMath math = new MathImpl(); @Override public int add(int n1, int n2) { //開始時間 long start = getTime(); delay(); int result = math.add(n1, n2); System.out.println("共用時:" + (getTime() - start)); return result; } @Override public int sub(int n1, int n2) { //開始時間 long start = getTime(); delay(); int result = math.sub(n1, n2); System.out.println("共用時:" + (getTime() - start)); return result; } @Override public int mut(int n1, int n2) { //開始時間 long start = getTime(); delay(); int result = math.mut(n1, n2); System.out.println("共用時:" + (getTime() - start)); return result; } @Override public int div(int n1, int n2) { //開始時間 long start = getTime(); delay(); int result = math.div(n1, n2); System.out.println("共用時:" + (getTime() - start)); return result; } public static long getTime() { return System.currentTimeMillis(); } public static void delay(){ try { int n = (int) new Random().nextInt(500); Thread.sleep(n); } catch (InterruptedException e) { e.printStackTrace(); } } }View Code
測試執行:
IMath math = new MathProxy(); int n1=100,n2=5; math.add(n1, n2); math.sub(n1, n2); math.mut(n1, n2); math.div(n1, n2);View Code
通過靜態代理,是否完全解決了上述的4個問題:
已解決:
1、解決了“開閉原則(OCP)”的問題,因為並沒有修改Math類,而擴展出了MathProxy類。
2、解決了“依賴倒轉(DIP)”的問題,通過引入介面。
3、解決了“單一職責(SRP)”的問題,Math類不再需要去計算耗時與延時操作,但從某些方面講MathProxy還是存在該問題。
未解決:
4、如果專案中有多個類,則需要編寫多個代理類,工作量大,不好修改,不好維護,不能應對變化。
如果要解決上面的問題,可以使用動態代理。
三、動態代理
1、介面不變:
2、實現類介面不變
3、修改代理類,實現介面InvocationHandler
DynamicProxy
package com.wbg.springAOP.springdynamic; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Random; /** * 動態代理類 */ public class DynamicProxy implements InvocationHandler { IMath math = new MathImpl(); //目標代理物件 Object targetObject; public DynamicProxy(Object target){ this.targetObject=target; } public DynamicProxy(){ } /** * 獲得被代理後的物件 * @param object 被代理的物件 * @return 代理後的物件 */ public Object getProxyObject(Object object){ this.targetObject=object; return Proxy.newProxyInstance( targetObject.getClass().getClassLoader(), //類載入器 targetObject.getClass().getInterfaces(), //獲得被代理物件的所有介面 this); //InvocationHandler物件 //loader:一個ClassLoader物件,定義了由哪個ClassLoader物件來生成代理物件進行載入 //interfaces:一個Interface物件的陣列,表示的是我將要給我需要代理的物件提供一組什麼介面,如果我提供了一組介面給它,那麼這個代理物件就宣稱實現了該介面(多型),這樣我就能呼叫這組介面中的方法了 //h:一個InvocationHandler物件,表示的是當我這個動態代理物件在呼叫方法的時候,會關聯到哪一個InvocationHandler物件上,間接通過invoke來執行 } /** * 獲取時間 * @return */ public static long getTime() { return System.currentTimeMillis(); } /** * 延遲 */ public static void delay(){ try { int n = (int) new Random().nextInt(500); Thread.sleep(n); } catch (InterruptedException e) { e.printStackTrace(); } } /** * 當用戶呼叫物件中的每個方法時都通過下面的方法執行,方法必須在介面 * proxy 被代理後的物件 * method 將要被執行的方法資訊(反射) * args 執行方法時需要的引數 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long start=getTime(); delay(); Object result=method.invoke(targetObject,args); System.out.println("共用時:" + (getTime() - start)); return result; } }
4、測試:
方式一:
DynamicProxy dynamicProx = new DynamicProxy(new MathImpl()); IMath math = (IMath) Proxy.newProxyInstance(Test.class.getClassLoader(),new Class[]{IMath.class},dynamicProx); int n1=100,n2=5; math.add(n1, n2); math.sub(n1, n2); math.mut(n1, n2); math.div(n1, n2);
方式二:
//例項化一個MathProxy代理物件 //通過getProxyObject方法獲得被代理後的物件 IMath math=(IMath)new DynamicProxy().getProxyObject(new MathImpl()); int n1=100,n2=5; math.add(n1, n2); math.sub(n1, n2); math.mut(n1, n2); math.div(n1, n2);
執行結果
JDK內建的Proxy動態代理可以在執行時動態生成位元組碼,而沒必要針對每個類編寫代理類。中間主要使用到了一個介面InvocationHandler與Proxy.newProxyInstance靜態方法,引數說明如下:
使用內建的Proxy實現動態代理有一個問題:被代理的類必須實現介面,未實現介面則沒辦法完成動態代理。
如果專案中有些類沒有實現介面,則不應該為了實現動態代理而刻意去抽出一些沒有例項意義的介面,通過cglib可以解決該問題。
四、動態代理,使用cglib實現
CGLIB(Code Generation Library)是一個開源專案,是一個強大的,高效能,高質量的Code生成類庫,它可以在執行期擴充套件Java類與實現Java介面,通俗說cglib可以在執行時動態生成位元組碼。
1、引用cglib,通過maven
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.9</version> </dependency>
2、實現DynamicProxyCgilb類 介面MethodInterceptor
package com.wbg.springAOP.springdynamic; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; import java.util.Random; /* * 動態代理類 * 實現了一個方法攔截器介面 */ public class DynamicProxyCgilb implements MethodInterceptor { //被代理物件 Object targetObject; //動態生成一個新的類,使用父類的無參構造方法建立一個指定了特定回撥的代理例項 public Object getProxyObject(Object object){ this.targetObject = object; //增強器,動態程式碼生成器 Enhancer enhancer=new Enhancer(); //回撥方法 enhancer.setCallback(this); //設定生成類的父類型別 enhancer.setSuperclass(targetObject.getClass()); //動態生成位元組碼並返回代理物件 return enhancer.create(); } // 攔截方法 @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { long start=getTime(); delay(); Object result=methodProxy.invoke(targetObject,objects); System.out.println("共用時:" + (getTime() - start)); return result; } /** * 獲取時間 * @return */ public static long getTime() { return System.currentTimeMillis(); } /** * 延遲 */ public static void delay(){ try { int n = (int) new Random().nextInt(500); Thread.sleep(n); } catch (InterruptedException e) { e.printStackTrace(); } } }View Code
3、測試
MathImpl math = (MathImpl) new DynamicProxyCgilb().getProxyObject(new MathImpl()); int n1=100,n2=5; math.add(n1, n2); math.sub(n1, n2); math.mut(n1, n2); math.div(n1, n2);View Code
使用cglib可以實現動態代理,即使被代理的類沒有實現介面,但被代理的類必須不是final類。