1. 程式人生 > 其它 >Spring 動態代理 簡單講解

Spring 動態代理 簡單講解

技術標籤:java學習-Springjava反射springaop動態代理

動態代理

java.lang.reflect.Proxy

通過"Proxy"類提供的一個newProxyInstance方法用來建立一個物件的代理物件

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 

newProxyInstance方法用來返回一個代理物件,這個方法總共有3個引數:

  1. ClassLoader loader 指明生成代理物件使用哪個類裝載器 【指定 代理物件 】
  2. Class<?>[] interfaces 指明生成哪個物件的代理物件,通過介面指定. 【指定 被代理物件----使用介面指定,這應就能使用這個介面的所有實現類】
  3. InvocationHandler h 指明產生的這個代理物件要做什麼事情。【這是一個介面,通過實現它的 invoke 方法,來指定動態代理要做什麼事情】

所以我們只需要呼叫newProxyInstance方法就可以得到某一個物件的代理物件了。

注意:在java中規定,要想產生一個物件的代理物件,那麼這個物件必須要有一個介面,原因不難分析,如果是指定一個類而不是介面,就只能代理一個類(它的子類),那就一個類,動態代理了個寂寞呀。指定介面就能代理這個介面所有的實現類

出租房子事例:

定義介面——租房子

抽象行為,出租房子

public interface Rent {
    public void rent();
}

定義實現類——房東

這裡定義了三個實現類,對應了三個房東

public class Host implements Rent {
    @Override
    public void rent() {
        System.out.println("曉軍東莞好地段大量房屋出租");
    }
}
public class Host2 implements Rent{
    @Override
    public
void rent() { System.out.println("凱文大量出租深圳樓盤!"); } } public class Host3 implements Rent{ @Override public void rent() { System.out.println("老van黃牛二手轉租,凱文曉軍 的大房子"); } }

實現代理類

代理類乾的事情,在這裡是 房屋中介 幫房東去 乾的事情

// 使用反射匯入的一些方法
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class MyProxy{
	// 定義一個 出租房子的介面物件
    private Rent rent;
	// setter 方法
    public void setRent(Rent rent) {
        this.rent = rent;
    }
	// 獲取一個代理物件
    public Object getProxy() {
        // 使用 Proxy.newProxyInstance(...) 方法獲取代理物件
        return Proxy.newProxyInstance( this.getClass().getClassLoader(), rent.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        seeHost();
                        Object result = method.invoke(rent, args);
                        face();
                        return result;
                    }
                }
        );
    }
	// 代理幫房東干的事情
    public void seeHost(){
        System.out.println("看房");
    }
	// 代理幫自己乾的事情
    public void face(){
        System.out.println("收中介費");
    }
}
Proxy.newProxyInstance(…) 的 引數解析說明:
  1. this.getClass().getClassLoader() 獲取代理類自身的類載入器

  2. rent.getClass().getInterfaces() 獲取代理物件的介面(雖然rent本來就是介面引用,但是在使用的時候,會把 介面物件引用 rent 指向它的實現類,所以需要這樣子來獲取介面)

  3. new InvocationHandler() 這是一個匿名內部類,它沒有類名,預設是抽象類和介面 的實現類 (所以匿名內部類在實現呼叫介面上非常方便)

     @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        seeHost();
        Object result = method.invoke(rent, args);
        face();
        return result;
    }
    

    在匿名內部類中,實現了 介面 InvocationHandler 的 invoke 方法,我們來看一下它的引數

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    

    proxy 被代理物件,method 被代理物件的方法,args 方法的引數列表。

    **看房–seeHost() 和 收中介費 face() **

    seeHost();
    Object result = method.invoke(rent, args);
    face();
    

    因為是內部類,所以可以訪問 外部類所有 的 私有的 private 或者 公有 public 的 屬性 和 方法。所以在執行被代理物件的方法的前後,可以直接使用方法名,執行代理類內部實現的一些 代理方法

    被代理的方法

    method.invoke(rent, args);
    

    相當於 proxy.method(args); 這裡是反射的呼叫方式。

為什麼要用反射來寫這個方法呢?而不是直接寫?

因為這個重寫了 InvocationHandler 介面的 invoke 方法後,呼叫被代理物件的 所有方法都將被 代理類所代理 ,這個概念有點類似於我們之前使用到的過濾器,即 invoke方法攔截到了 代理物件的 所有方法呼叫,並像過濾器一樣給它前後進行了一些 代理操作。

就像我們之前用過濾器 把所有請求體的 字元編碼 都改成 UTF-8 來防止亂碼一樣。

測試類

public class Client {
    public static void main(String[] args) {
        Host host = new Host();
//        Proxy proxy = new Proxy(host);
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        pih.setRent(host);
        Rent proxy = (Rent) pih.getProxy();
        proxy.rent();
    }
}

可能你會問為什麼不用 有參構造的 方式 來使程式碼更加 簡潔,因為 在 Spring 中 使用 Setter 注入 會 遠比 使用 構造器 注入 多

// 有參構造 注入代理物件 host
ProxyInvocationHandler pih = new ProxyInvocationHandler(host);
// 無參構造 + setter 方法 注入代理物件 host
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setRent(host);

習慣性使用容器的 Setter 注入方法

獲取代理物件 proxy

Rent proxy = (Rent) pih.getProxy();

使用 代理物件 proxy 呼叫 被代理物件 host 的方法

proxy.rent();

通過代理物件執行的方法,都會通過我們在 介面 InvocationHandler 中 實現的 invoke 方法 去呼叫,結果相當於

proxy.seeHost();
host.rent();
proxy.face();

執行結果截圖:

在這裡插入圖片描述

拓展

在這裡插入圖片描述

invoke 方法是通過反射去呼叫的,因此你能在 invoke 方法中通過 反射,類名,方法名,屬性名,方法引數的值,方法的註解(註解值),方法引數列表的值 等等,獲取所有 反射能獲取到的 資訊(類的全部資訊)