什么是AOP?
AOP(Aspect Oriented Programming) 面向切面編程。
指在程序運行期間,將某段代碼動態切入到指定位置進行運行的這種編程方式。
什么是動態代理?
有一個這樣的場景 在執行業務邏輯代碼的時候加上日志輸出 我們可以嘗試用動態代理的方法實現
首先定義一個業務邏輯的接口(很重要!下面解釋), 里面有業務邏輯的一些方法
public interface MyTask{ void method1(Object arg1,Object arg2); void method2(Object arg1); }
真實的業務實現了這個接口
public class Task1 implements MyTask{ public void method1(Object arg1, Object arg2) { System.out.println("執行業務1..."); } public void method2(Object arg1) { System.out.println("執行業務2..."); } }
現在我們可以添加日志功能,如果不使用動態代理 我們可以在每個業務方法內部加入日志輸入 如下
public class Task1 implements MyTask{ public void method1(Object arg1, Object arg2) { System.out.println("日志:method1方法前……"); System.out.println("執行業務1..."); System.out.println("日志:method1方法后……"); } public void method2(Object arg1) { System.out.println("日志:method2方法前……"); System.out.println("執行業務2..."); System.out.println("日志:method2方法后……"); } }
但是這樣做有明顯的缺點:
1)如果有大量的方法 日志寫起來非常的麻煩
2)日志是輔助功能,業務是核心功能 這樣輔助和核心就寫到了一起 存在耦合問題!
我們可以用動態代理模式解決上述問題
首先我們的Task2 中的業務邏輯 不加任何的日志輸出
public class Task2 implements MyTask{ public void method1(Object arg1, Object arg2) { System.out.println("執行業務1..."); } public void method2(Object arg1) { System.out.println("執行業務2..."); } }
我們需要創建一個代理類ProxyHandler 實現 InvocationHandler 方法如下
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class ProxyHandler implements InvocationHandler { /** * 傳入真實角色 */ private Object target; /** * * @param target 注入真實角色 */ public void setTarget(Object target) { this.target = target; } /** * @param proxy 代理對象;給jdk使用,任何時候都不要動這個對象 * @param method 當前將要執行目標對象的方法 * @param args 這個方法調用時候 外界傳入的值 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("日志:執行" + method.getName() + "前"); method.invoke(target,args); System.out.println("日志:執行" + method.getName() + "后"); return null; } }
最后可以測試執行
import java.lang.reflect.Proxy; public class Client { public static void main(String[] args) { Task2 task2 = new Task2(); ProxyHandler handler = new ProxyHandler(); handler.setTarget(task2); // 設置要代理類的對象 /* 通過Proxy類的newProxyInstance方法創建代理對象,我們來看下方法中的參數 第一個參數:task2.getClass().getClassLoader(),使用handler對象的classloader對象來加載我們的代理對象 就是task2的類加載器 第二個參數:task2.getClass().getInterfaces(),這里為代理類提供的接口是真實對象實現的接口, 這樣代理對象就能像真實對象一樣調用接口中的所有方法 第三個參數:handler,我們將代理對象關聯到上面的ProxyHandler自定義對象上 方法執行器,幫我們目標對象執行目標方法 */ MyTask proxy = (MyTask) Proxy.newProxyInstance(task2.getClass().getClassLoader(), task2.getClass().getInterfaces(),handler); // 動態生成代理類 proxy.method1(1,2); proxy.method2(1); } }
執行結果如下
日志:執行method1前
執行業務1...
日志:執行method1后
日志:執行method2前
執行業務2...
日志:執行method2后
動態代理類的缺點
1、復雜難寫
2、java默認的動態代理 必須實現接口 否則不能動態代理
原因:首先我們看一下動態代理Proxy的類 它是哪個類?
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(proxy.getClass()); return null; } 執行結果如下:class com.sun.proxy.$Proxy0
這說明 proxy的類和我們的Task2根本不是一個類 那么proxy為什么能執行我們定義的Task2類中的方法呢?
這是因為proxy和我們的Task2實現了同一個接口 那就是MyTask 這也就是為什么我們能放心的將proxy類強制轉換為MyTask的原因
所以java jdk的動態代理必須實現接口
Spring 中AOP底層也是使用的動態代理 但是它解決了java動態代理的兩個缺點! 在我之后的隨筆中會繼續討論!