Java設計模式-代理模式

代理模式

使用代理模式建立表明(representative)對象,讓代理對象控制某對象的訪問,被代理的對象能夠是遠程的對象,建立開銷大的對象或須要安全控制的對象。——[Head First 設計模式]html

簡單的講就是 :爲服務對象提供代理,經過代理控制對服務對象的訪問範圍java

  • 生活中的場景
  • 代理模式的好處
  • 技術上的應用
  • 分類
  • 靜態代理
  • JDK動態代理
  • Cglib動態代理
  • 小結

生活中代理模式的場景

設計模式-代理模式

思考一下,其實在咱們生活中不乏出現代理模式的場景:git

  1. 滴滴出行;出門搭車並不須要直接聯繫某個司機,而是經過滴滴下單,滴滴就會幫咱們將乘車需求通知到附近的司機;
  2. 淘寶購物;相信大多數人會在淘寶或其餘電商平臺上購物,而做爲用戶,咱們能夠經過淘寶就能夠了解到商品的具體詳情,而不須要到線下店瞭解;
  3. 鏈家租房;生活中找租房一般會選擇中介代理,好比鏈家,這樣一來,用戶不須要四處奔波就能夠經過代理獲取到租房信息;

爲何須要代理呢?由於代理最大的好處就是方便,上面例子,體現代理的第一個做用:服務透明,屏蔽具體實現,用戶沒法感知真正的服務提供方;可是經過代理就能夠實現本身的需求;github

代理模式的好處

  1. 擴展,邏輯解耦,屏蔽真實實現,更換被代理對象,使用無感知;
  2. 保護,非直接調用,只提供本該提供的特定服務,防止越界訪問;
  3. 簡化,職責專注,被代理者只負責業務邏輯,不關心其餘事項;

保護的理解:好比鏈家,只提供房東的房屋信息,對於房東的其餘信息,客戶無從知道,從而起到了保護做用
簡化的理解:好比滴滴,對於司機來講,只負責正真的運輸,不關心開車之外的事情,好比,找客源,講價錢等等,簡化了整個流程。編程

技術上的應用

代理模式最典型的的應用就是AOP;講到AOP,相信你們腦海裏,都會浮現出不少關鍵字:面向切面編程、靜態代理、JDK動態代理,CGLIB;設計模式

AOP(Aspect Orient Programming),面向切面編程,用於在系統各個模塊中的業務流程中織入通用的處理邏輯,好比事務管理、操做日誌、緩存、異常處理等等。api

有關AOP的介紹後續會用心的篇章來說解,本文主要講解代理的實現。緩存

分類

從功能的角度劃分,能夠分爲:遠程代理、虛擬代理、保護代理、智能引用代理;
按實現的角度劃分,則有靜態代理和動態代理;動態代理還能夠分爲JDK動態代理和CGLIB動態代理安全

這裏寫圖片描述

在瞭解具體實現以前,先詳細看一下上圖;
首先是Service提供了服務接口,ServiceImpl和Proxy都必須實現Service接口;經過實現同個接口,使得上層調用能夠像ServiceImpl同樣使用Proxy的服務;
Proxy持有Service的引用,以便後續將請求轉發給ServiceImpl;
固然除了接口,基於繼承的方式也是能夠實現代理模式,好比Cglib動態代理;
瞭解以上內容,已經大概瞭解了代理模式的具體實現。oracle

靜態代理

假設咱們須要提供一個提供新增用戶的服務接口給上層業務使用;
咱們首先要先制定好接口UserService,將接口 addUser(String user) 開放給上層業務;

/**
 * 開放代理的接口
 */
public interface UserService {
    public void addUser(String user);
}

接下來,編寫具體實現的服務類 UserServiceImpl.java

/**
 * 實際業務邏輯
 */
public class UserServiceImpl implements UserService{
    @Override
    public void addUser(String user) {
        System.out.println(String.format("add user:%s success", user));
    }
}

測試使用靜態代理

/**
 * 代理模式測試類
 */
public class ProxyTest {
    /**
     * 測試靜態代理
     */
    @Test
    public void testStaticProxy(){
        UserService userService = new UserServiceImpl();
        StaticProxy staticProxy = new StaticProxy(userService);
        staticProxy.addUser("xupeng.zhang");
    }
}

運行上述代碼輸出結果:
begin to execute static proxy to add user......
add user:xupeng.zhang success
execute static proxy to add user end. cost 0 ms**

採用靜態代理,咱們須要在代理類StaticProxy中傳入UserService的引用;
同時,代理類須要同時實現UserService的接口;只是簡單的將請求轉發給UserServiceImpl對象;

/**
 * 靜態代理類
 */
public class StaticProxy implements UserService{
    private UserService userService;

    public StaticProxy(UserService userService) {
        this.userService = userService;
    }

    @Override
    public void addUser(String user) {
        //統計耗時
        Long executeTime = System.currentTimeMillis();
        System.out.println("begin to execute static proxy to add user......");
        userService.addUser(user);
        System.out.println(String.format("execute static proxy to add user end. cost %s ms", System.currentTimeMillis() - executeTime));
    }
}

以上即是靜態代理類的實現方式,比較簡單;可是若是UserService新增一個接口,那麼從UserService到Proxy,都要從新實現一下,並且統計耗時的代碼邏輯每一個方法都要寫一份。
有沒有什麼辦法能夠減小這種重複性的工做呢?固然有,那就是接下來要講的動態代理;

JDK動態代理

一樣須要實現 UserService 接口,須要持有 UserService的引用;
所不一樣的是,須要JDK幫咱們動態生成代理對象;

具體作法以下:

  1. 實現InvocationHandler接口,代理類對象的執行最後會轉發到invoke方法;
  2. 經過JDK的動態代理獲取UserService的代理對象;
/**
 * 實際執行處理類
 */
public class RealInvocationHandler implements InvocationHandler{
    private Object target;

    public RealInvocationHandler(Object target) {
        this.target = target;
    }

    /**
     * 最終代理對象會將請求轉發到invoke方法執行
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Long executeTime = System.currentTimeMillis();
        System.out.println("begin to execute method in dynamic proxy...");
        Object result = method.invoke(target, args);
        System.out.println(String.format("execute method by dynamic proxy end, cost %s ms", System.currentTimeMillis() - executeTime));
        return result;
    }

    /**
     * 基於反射實現動態代理對象生成
     * @param <T>
     * @return
     */
    public <T> T getProxy(){
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), this);
    }
}

測試使用JDK動態代理

/**
 * 代理模式測試類
 */
public class ProxyTest {
    /**
     * 測試JDK動態代理
     */
    @Test
    public void testJDKDynamicProxy(){
        UserService userService = new UserServiceImpl();
        RealInvocationHandler realInvocationHandler = new RealInvocationHandler(userService);
        UserService userProxy = realInvocationHandler.getProxy();
        userProxy.addUser("xupeng.zhang");
    }
}

運行上述代碼輸出結果:
begin to execute method in dynamic proxy...
add user:xupeng.zhang success
execute method by dynamic proxy end, cost 0 ms
Process finished with exit code 0**

上述代碼中,咱們並無手寫過代理類,JDK基於反射動態幫咱們生成了代理類:

/**
     * 基於反射實現動態代理對象生成
     * @param <T>
     * @return
     */
    public <T> T getProxy(){
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), this);//this即 RealInvocationHandler對象
    }

若是進去newProxyInstance看源碼的話,無非就如下三句關鍵代碼:

1. Class<?> cl = getProxyClass0(loader, intfs); //獲取代理類對象
 2. final Constructor<?> cons = cl.getConstructor(constructorParams);//獲取代理類的構造方法
 3. return cons.newInstance(new Object[]{h});//構造方法生成代理對象並返回

其中:
loader,指定代理對象的類加載器;
intfs 即 interfaces,代理對象須要實現的接口,能夠同時指定多個接口;
h 即 RealInvocationHandler對象,方法調用的實際處理者,代理對象的方法調用都會轉發到invoke方法。

繼續查看代理類的相關信息,你會發現JDK的動態代理竟然如此奇妙:

/**
 * 代理模式測試類
 */
public class ProxyTest {
    @Test
    public void testGetJDKProxyClassInfo(){
        UserService userService = new UserServiceImpl();
        RealInvocationHandler realInvocationHandler = new RealInvocationHandler(userService);
        UserService userProxy = realInvocationHandler.getProxy();
        System.out.println(userProxy.getClass().getName());
        System.out.println(userProxy.getClass().getSuperclass());
        System.out.println(userProxy.getClass().getInterfaces()[0].getName());
        ProxyGeneratorUtils.saveProxyClass("D:/JdkProxy.class", userProxy.getClass());
    }
}

運行上述代碼輸出結果:
com.sun.proxy.$Proxy2
class java.lang.reflect.Proxy
com.orig.design.proxy.UserService**

代理類的類型是com.sun.proxy.$Proxy2;且繼承自Proxy.java
此外還實現了UserService接口;
而咱們將UserSercieProxy.class的字節碼文件反編譯會發現如下關鍵代碼:

public final void addUser(String var1) throws  {
     try {
         super.h.invoke(this, m3, new Object[]{var1});
     } catch (RuntimeException | Error var3) {
         throw var3;
     } catch (Throwable var4) {
         throw new UndeclaredThrowableException(var4);
     }
 }

最終addUser會將請求轉發給 RealInvocationHandler的 invoke方法執行;invoke方法調用method.invoke執行服務類對應的方法;

CGLIB動態代理

JDK生成的代理類已經繼承了Proxy類,Java不支持多重繼承,所以沒法實現繼承式的動態代理;這個時候就能夠採用Cglib動態代理來實現;
原理上跟JDK動態代理其實大同小異:
cglib會動態生成代理類,它只關注本身的父類,經過繼承得到父類非final的接口;以後將請求轉發給MethodInterceptor的intercept方法去作實際處理;

具體作法以下:

  1. 實現MethodInterceptor接口,intercept方法用於後續實際請求處理;
  2. 經過Cglib的Enhancer對象構造代理對象,須要告知enhance繼承的父類;

具體實現代碼以下:

/**
 * cglib動態代理
 * 和JDK動態代理不一樣
 * 是基於繼承實現
 */
public class RealInterceptor implements MethodInterceptor{
    private Enhancer enhancer = new Enhancer();

    /**
     * 具體執行的回調方法
     * @param o
     * @param method
     * @param args
     * @param methodProxy
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        Long currentTime = System.currentTimeMillis();
        System.out.println("begin to execute cglib proxy to add user");
        System.out.println(String.format("method: %s", method.getName()));
        Object result = methodProxy.invokeSuper(o, args);//注意這裏是invokeSuper
        System.out.println(String.format("execute cglib proxy end, cost: %s ms", System.currentTimeMillis()-currentTime));
        return result;
    }

    /**
     * 設置代理的父類
     * 設置回調方法
     * @param tClass
     * @param <T>
     * @return
     */
    public <T> T newProxyInstance(Class<T> tClass){
        enhancer.setSuperclass(tClass);
        enhancer.setCallback(this);
        return (T) enhancer.create();
    }
}

運行上述代碼輸出結果:
begin to execute cglib proxy to add user
method: addUser
add user:xupeng.zhang success
execute cglib proxy end, cost: 22 ms**

經過上述結果能夠看出,Cglib性能上相對於JDK動態代理稍差;一樣的代理的實現,Cglib的實現比JDK動態代理的實現要耗時;

一樣的,咱們針對代理類的相關信息進行挖掘:

/**
 * 代理模式測試類
 */
public class ProxyTest {
    /**
     * 獲取cglib代理類信息
     */
    @Test
    public void testGetCglibProxyClassInfo() throws Exception {
        RealInterceptor realInterceptor = new RealInterceptor();
        UserRoleService userRoleProxy = realInterceptor.newProxyInstance(UserRoleService.class);
        System.out.println(userRoleProxy.getClass().getName());
        System.out.println(userRoleProxy.getClass().getSuperclass());
        System.out.println(userRoleProxy.getClass().getInterfaces()[0].getName());
        ProxyGeneratorUtils.saveCglibProxyClass("D:/CglibProxy.class",realInterceptor.getEnhancer());
    }
}

運行上述代碼輸出結果:
com.orig.design.proxy.UserServiceImpl$$EnhancerByCGLIB$$591ab16e
class com.orig.design.proxy.UserServiceImpl
net.sf.cglib.proxy.Factory**

Cglib生成的代理類關鍵代碼以下:

public class UserRoleService$$EnhancerByCGLIB$$8ba328ed extends UserRoleService implements Factory {
    .......省略部分代碼
    public final void addUserRole(String var1, String var2) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (this.CGLIB$CALLBACK_0 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            var10000.intercept(this, CGLIB$addUserRole$0$Method, new Object[]{var1, var2}, CGLIB$addUserRole$0$Proxy);
        } else {
            super.addUserRole(var1, var2);
        }
    }
}

Cglib動態代理類直接繼承了UserRoleService,並重寫了addUserRole方法;
此外,對原有的方法作了加強處理,若是Enhancer有設置設置回調方法,則會執行對應的回調方法interceptor;

/**
     * 設置代理的父類
     * 設置回調方法
     * @param tClass
     * @param <T>
     * @return
     */
    public <T> T newProxyInstance(Class<T> tClass){
        enhancer.setSuperclass(tClass);//設置繼承的父類
        enhancer.setCallback(this);//設置回調函數,入參是Callback類型
        return (T) enhancer.create();
    }

/**回調方法**/
public interface MethodInterceptor extends Callback {
    Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
}

小結

  1. 動態代理原理:(1)動態生成字節碼;(2)經過反射調用真實實現類方法
  2. JDK原生動態代理基於接口實現;Cglib動態代理基於繼承方式實現;
  3. 動態代理相對靜態代理的好處在於靈活,強大,不須要對每一個接口進行代理邏輯的開發;
  4. 固然,增長了代理,無形中會增長調用鏈,意味着下降處理速度;實現代理也須要額外的開發工做,增長了實現成本。

本文相關的代碼Demo已經上傳到github上:github

相關文章
相關標籤/搜索