Spring AOP底層原理之動態代理模式


什么是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動態代理的兩個缺點!  在我之后的隨筆中會繼續討論!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM