JDK 代理模式實際應用再分析

背景

java 中的代理模式分為靜態(tài)代理、動態(tài)代理,今天 我們再次重溫下 定義及基本代碼邏輯,并加入實際業(yè)務中的一種應用場景。

靜態(tài)動態(tài)概念何來?從對象生成的時機而來,編譯器生成即靜態(tài)代理,運行期動態(tài)生成即動態(tài)代理。

1、靜態(tài)代理:編譯期間生成委托別人實現(xiàn)的代碼。

我們以老板和秘書的關(guān)系為例,整一段代碼如下:

#主題類
public interface Subject {

    void meet();
}
#老板類
public class Boss implements Subject {
    @Override
    public void meet() {
        System.out.println("Boss is meeting……");
    }
}
#秘書類
public class Secretary implements Subject {

    private Subject boss;

    public Secretary(Boss boss) {
        this.boss = boss;
    }

    @Override
    public void meet() {
        System.out.println("Before meeting you should make an appointment");
        boss.meet();
        System.out.println("After meeting you should not stay any longer");
    }
}
#客戶端調(diào)用
public class Client {

    public static void main(String[] args) {

        Boss boss = new Boss();

        Secretary secretary = new Secretary(boss);

        secretary.meet();

    }
}

秘書代理了老板的之前和之后的所有工作。老板只負責開會的時候過去就好了??赡苌罘矫嬉藏撠熈恕?/p>

所有的類 都是編譯期間生成,從上述例子可以看出,代理模式可以通過不修改被代理對象的前提下,實現(xiàn)其功能的增加和擴展。同時注意代理類跟被代理類需要實現(xiàn)同一個接口或者繼承同一個父類。對應上述例子可以理解為 老板和秘書必須在同一家公司,否則沒有什么權(quán)利和義務做這些。

2、動態(tài)代理:運行期間動態(tài)生成委托別人實現(xiàn)的代碼

與靜態(tài)代理的區(qū)別就是,秘書類 不再實現(xiàn)共同的接口,而是實現(xiàn)了一個java 反射包中的 InvocationHandler 類。代碼如下:

#Subject
public interface Subject {
    void meet();
}
#Boss
public class Boss implements Subject {
    @Override
    public void meet() {
        System.out.println("Boss is meeting now, please do not disturb");
    }
}
#Secretary
public class Secretary implements InvocationHandler {

    private Boss boss;

    public Secretary(Boss boss) {
        this.boss = boss;
    }

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

        System.out.println("Before meeting you should make an appointment");
        method.invoke(boss, args);
        System.out.println("After meeting you should not stay any longer");
        return null;
    }
}
#Client
public class Client {
    public static void main(String[] args) {

        Boss boss = new Boss();

        InvocationHandler handler = new Secretary(boss);

        Subject dynamicProxy = (Subject) Proxy.newProxyInstance(Boss.class.getClassLoader(), Boss.class.getInterfaces(),
                handler);

        dynamicProxy.meet();

    }
}

動態(tài)代理涉及到一個非常重要的類 Proxy ,正是通過 Proxy 的靜態(tài)方法 newProxyInstance 才會動態(tài)創(chuàng)建代理。

Proxy

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

下面講解它的 3 個參數(shù)意義。

  • loader 自然是類加載器
  • interfaces 代碼要用來代理的接口
  • h 一個 InvocationHandler 對象

其中 interfaces 通過 實現(xiàn)類的 class.getInterfaces() 方法獲得,也可以直接 new class<?>[]{Interface.class},這點很重要?。。。。『竺鏁玫?/p>

InvocationHandler

public interface InvocationHandler {

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

InvocationHandler 是一個接口,官方文檔解釋說,每個代理的實例都有一個與之關(guān)聯(lián)的 InvocationHandler 實現(xiàn)類,如果代理的方法被調(diào)用,那么代理便會通知和轉(zhuǎn)發(fā)給內(nèi)部的 InvocationHandler 實現(xiàn)類,由它決定處理。

需要深究其中代碼的 可以查看 jdk 源碼。

3、代理的作用

日志記錄,性能統(tǒng)計,安全控制,事務處理,異常處理等等。

4、作者實際項目中的一種應用

業(yè)務場景:

項目中的 RPC 調(diào)用,需要通過 HTTP 方式調(diào)用,通過 代理 引入的接口,來反射出 方法的 參數(shù)類型及 實際入?yún)?,從?支持 http 的調(diào)用。

怎么會有這么操蛋的方式,還是 由于公司技術(shù)棧太亂,太爛,各種惡心的方式調(diào)用,哎,不討論原因 既然出現(xiàn)這種情況,就想辦法實現(xiàn)把。

對于不同類型的業(yè)務功能做代理 其實 InvocationHandler 中 invoke 的實現(xiàn)都不相同,但是都是為了完成某一類型的業(yè)務功能。

HTTP 對遠程方法的調(diào)用,只要反射出 方法簽名以及方法調(diào)用的實際參數(shù)就可以了。

talk is cheep , show me the code !!!

public class ProxyHandler implements InvocationHandler {

    private Class<?> proxyInterface;

    public ProxyHandler(Class<?> clazz) {
        this.proxyInterface = clazz;
    }

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

        System.out.println("代理方法 : " + method.getName());

        Class<?>[] parameterTypes = method.getParameterTypes();
        for (Class<?> parameterType : parameterTypes) {
            System.out.println(parameterType.getName());
        }

        for (Object arg : args) {
            System.out.println(arg);
        }

        //component request method and params
        //invoke remote service......

        return null;
    }
}

public class ProxyFactory {

    private ProxyFactory() {
    }

    public static <T> T getProxy(Class<?> clazz) {

        ProxyHandler handler = new ProxyHandler(clazz);

        return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class<?>[] { clazz }, handler);
    }

}


public class Client {
    public static void main(String[] args) {

        PolicyService service = (PolicyService) ProxyFactory.getProxy(PolicyService.class);

        service.savePolicy("88888", "simon");

        UserService userService = (UserService) ProxyFactory.getProxy(UserService.class);

        userService.save("candy", 9999L);

        userService.save("xiaolong", 888);

    }
}

接口定義如下:
public interface UserService {

    void save(String userName, Long userId);

    void save(String userName, Integer userId);
}

public interface PolicyService {

    void savePolicy(String policyNo, String insurantName);

}

整體思路就是:通過代理 來實現(xiàn)接口的實例化,然后在 invoke 方法中 對方法名稱 方法參數(shù)進行攔截,最后實現(xiàn)遠程的 HTTP 調(diào)用。

附:代理模式姐妹篇

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容