動(dòng)態(tài)代理-Java-含源碼解析

什么是代理模式:代理模式是一種結(jié)構(gòu)型設(shè)計(jì)模式,主要用于給某一個(gè)對(duì)象提供代理對(duì)象,并由代理對(duì)象控制對(duì)真實(shí)對(duì)象的訪問。

主要應(yīng)用:Spring AOP、日志、用戶鑒權(quán)、Hibernate數(shù)據(jù)查詢、測試框架的后端mock、RPC遠(yuǎn)程調(diào)用、Java注解對(duì)象獲取、、全局性異常處理、性能監(jiān)控,甚至事務(wù)處理等。

代理模式角色:主要分為調(diào)用方、代理對(duì)象,業(yè)務(wù)對(duì)象、抽象接口

  • 抽象接口:定義對(duì)外提供的方法(功能)。
  • 業(yè)務(wù)對(duì)象:實(shí)現(xiàn)抽象接口所定義的具體功能。
  • 代理對(duì)象:實(shí)現(xiàn)抽象接口,封裝業(yè)務(wù)對(duì)象,控制對(duì)象的訪問,并提供給調(diào)用方使用。

代理模式的優(yōu)點(diǎn):

  • 可以使真是角色的操作更純粹,不用去關(guān)注一些公共業(yè)務(wù)
  • 公共業(yè)務(wù)就交給代理角色,實(shí)現(xiàn)了業(yè)務(wù)的分工
  • 公共業(yè)務(wù)發(fā)生擴(kuò)展的時(shí)候,方便集中管理

缺點(diǎn):

  • 一個(gè)真實(shí)角色就會(huì)產(chǎn)生一個(gè)代理角色,代碼量會(huì)翻倍

代理模式按照代理類的創(chuàng)建時(shí)機(jī)可分為靜態(tài)代理與動(dòng)態(tài)代理:

  • 靜態(tài)代理:程序運(yùn)行前就已經(jīng)有代理類的字節(jié)碼
  • 動(dòng)態(tài)代理:在程序運(yùn)行期間動(dòng)態(tài)生成代理類,在Java中JDK動(dòng)態(tài)代理與Cglib動(dòng)態(tài)代理。

下面用一個(gè)案例來分析靜態(tài)代理、JDK動(dòng)態(tài)代理與Cglib動(dòng)態(tài)代理的實(shí)現(xiàn)。

編寫一個(gè)日志打印功能的代理:如下UserDao是一個(gè)抽象接口,定義了獲取、設(shè)置用戶信息的方法,UserDaoImpl是具體的實(shí)現(xiàn)類。

public interface UserDao {
    public void setUserInfo(String userInfo);
    public String getUserInfo();
}
public class UserDaoImpl implements UserDao{
    private String userInfo;
    @Override
    public void setUserInfo(String userInfo) {
        this.userInfo = userInfo;
        System.out.println("插入信息:" + userInfo);
    }
    @Override
    public String getUserInfo() {
        System.out.println("獲取信息:" + userInfo);
        return userInfo;
    }
}

首先從靜態(tài)代理開始。

靜態(tài)代理

靜態(tài)代理需要手工編碼創(chuàng)建代理類,這里定義為LogStaticProxy,代理類實(shí)現(xiàn)了UserDao接口,并持有代理類對(duì)象的引用,在具體業(yè)務(wù)方法前后執(zhí)行了日志打印的代碼,如下。

public class LogStaticProxy implements UserDao{
    UserDao userDao;
    public LogStaticProxy(UserDao userDao){
        this.userDao = userDao;
    }

    @Override
    public void setUserInfo(String userInfo) {
        before();
        this.userDao.setUserInfo(userInfo);
        after();
    }

    @Override
    public String getUserInfo() {
        before();
        String rst = this.userDao.getUserInfo();
        after();
        return rst;
    }
    
    private void before(){
        System.out.println("log before" + (new Date().toString()));
    }

    private void after(){
        System.out.println("log after" + (new Date().toString()));
    }
}

測試類:

public class TestClient1 {
    public static void main(String[] args) {
        UserDao userDao = new UserDaoImpl();
        UserDao proxy = new LogStaticProxy(userDao);
        proxy.setUserInfo("我是static proxy");
        System.out.println(proxy.getUserInfo());
        // 輸出
        // log beforeTue Jun 14 20:10:06 CST 2022
        // 插入信息:我是static proxy
        // log afterTue Jun 14 20:10:06 CST 2022
        // log beforeTue Jun 14 20:10:06 CST 2022
        // 獲取信息:我是static proxy
        // log afterTue Jun 14 20:10:06 CST 2022
        // 我是static proxy
    }
}

靜態(tài)代理很好的實(shí)現(xiàn)了與業(yè)務(wù)無關(guān)的代碼的抽離,但是其還有不足之處:

  1. 當(dāng)代理的類很多時(shí),有兩種實(shí)現(xiàn)方式
    • 要求代理類實(shí)現(xiàn)每一個(gè)類的接口,這樣會(huì)導(dǎo)致代理類過于臃腫
    • 對(duì)于每一個(gè)類都生成一個(gè)代理類,這樣產(chǎn)生過多的代理類
  2. 當(dāng)被代理類增加、刪除、修改接口時(shí),代理類也需要做對(duì)應(yīng)的改造

一種改進(jìn)的方法就是使用動(dòng)態(tài)代理。使用動(dòng)態(tài)代理,無需在開發(fā)階段編寫代理類,只需要編寫代理要執(zhí)行的操作即可,代理類實(shí)在運(yùn)行時(shí)動(dòng)態(tài)生成的。

JDK動(dòng)態(tài)代理

使用方法如下,首先需要編寫一個(gè)用于執(zhí)行代理操作的類LogJdkHandler,這個(gè)類需要實(shí)現(xiàn)java.lang.reflect.InvocationHandler接口,并且持有代理對(duì)象的引用。用戶使用代理類調(diào)用接口方法時(shí),會(huì)觸發(fā)LogJdkHandler所實(shí)現(xiàn)的invoke方法。真正原有對(duì)象的方法是通過反射的方式調(diào)用的,即method.invoke(target, args)。

public class LogJdkHandler implements InvocationHandler{
    private Object target;

    public LogJdkHandler(Object userDao) {
        this.target = userDao;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // proxy是動(dòng)態(tài)生成的代理類的對(duì)象
        // method是被觸發(fā)的方法
        // args是方法入?yún)⒘斜?        before();
        Object result = method.invoke(target, args);
        after();
        return result;
    }
    
    private void before(){
        System.out.println("log before" + (new Date().toString()));
    }

    private void after(){
        System.out.println("log after" + (new Date().toString()));
    }
}

然后就可以通過java.lang.reflect.Proxy的靜態(tài)方法newProxyInstance創(chuàng)建目標(biāo)對(duì)象的代理對(duì)象,具體方法如下:

public class TestClient2 {
    public static void main(String[] args) {
        // 1. 創(chuàng)建代理對(duì)象
        UserDao userDao = new UserDaoImpl();
        // 2. 創(chuàng)建代理的處理類
        LogJdkHandler handler = new LogJdkHandler(userDao);
        // 3. 使用Proxy.newProxyInstance生成代理對(duì)象
        UserDao proxy = (UserDao)Proxy.newProxyInstance(
            userDao.getClass().getClassLoader(), 
            userDao.getClass().getInterfaces(), 
            handler);
        
        proxy.setUserInfo("我是jdk proxy");
        System.out.println(proxy.getUserInfo());
        // 輸出:
        // 插入信息:我是jdk proxy
        // log afterTue Jun 14 20:45:20 CST 2022
        // log beforeTue Jun 14 20:45:20 CST 2022
        // 獲取信息:我是jdk proxy
        // log afterTue Jun 14 20:45:20 CST 2022
        // 我是jdk proxy
    }
}

JDK動(dòng)態(tài)代理的實(shí)現(xiàn)原理

在測試類中,使用的是Proxy.newProxyInstance創(chuàng)建的代理對(duì)象,點(diǎn)擊進(jìn)入該方法,可以看到代理類的創(chuàng)建過程。newProxyInstance方法的入?yún)⒁来问谴韺?duì)象的類加載器ClassLoader loader,代理對(duì)象實(shí)現(xiàn)的接口列表Class<?>[] interfaces、以及我們自己實(shí)現(xiàn)的InvocationHandler h。

 public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

最主要的是下面兩行代碼:

Class<?> cl = getProxyClass0(loader, intfs);

首先去獲取生成的代理類類實(shí)例Class<?> clgetProxyClass0(loader, intfs)方法會(huì)首先判斷該類實(shí)例是否在內(nèi)存中,是則直接獲取,如果不在,則進(jìn)行生成。然后獲取類實(shí)例的構(gòu)造函數(shù)。

final Constructor<?> cons = cl.getConstructor(constructorParams);

可以看到,cl.getConstructor代碼獲取的是參數(shù)類型為的constructorParams構(gòu)造函數(shù),該類型在Proxy類中有定義:

private static final Class<?>[] constructorParams =
        { InvocationHandler.class };

該類型即為我們實(shí)現(xiàn)的接口InvocationHandler。最后通過反射調(diào)用構(gòu)造函數(shù)來創(chuàng)建類實(shí)例,其入?yún)⒓礊镻roxy.newInstance傳入的第三個(gè)參數(shù)h,即我們創(chuàng)建的LogJdkHandler對(duì)象:

return cons.newInstance(new Object[]{h});

代理得的動(dòng)態(tài)創(chuàng)建過程以及將生成的代理類放入到內(nèi)存中的方法這里先不展開(感興趣可以點(diǎn)擊閱讀JDK動(dòng)態(tài)代理代理類創(chuàng)建源碼淺析)。為了方便理解,可以先看一下生成的代理類結(jié)構(gòu):

在測試類生成代理前運(yùn)行如下代碼,就可以將動(dòng)態(tài)生成的代理類的class文件保存到工程com\sun\proxy目錄下的,類似于$Proxy0.class的文件。

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

反編譯$Proxy0.class,得到如下代碼

package com.sun.proxy;

import com.maomao.patterndesign.p1_proxy.UserDao;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements UserDao {
  private static Method m1;
  
  private static Method m2;
  
  private static Method m3;
  
  private static Method m4;
  
  private static Method m0;
  
  public $Proxy0(InvocationHandler paramInvocationHandler) {
    super(paramInvocationHandler);
  }
  
  public final boolean equals(Object paramObject) {
    try {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  
  public final String toString() {
    try {
      return (String)this.h.invoke(this, m2, null);
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  
  public final String getUserInfo() {
    try {
      return (String)this.h.invoke(this, m3, null);
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  
  public final void setUserInfo(String paramString) {
    try {
      this.h.invoke(this, m4, new Object[] { paramString });
      return;
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  
  public final int hashCode() {
    try {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  
  static {
    try {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m3 = Class.forName("com.maomao.patterndesign.p1_proxy.UserDao").getMethod("getUserInfo", new Class[0]);
      m4 = Class.forName("com.maomao.patterndesign.p1_proxy.UserDao").getMethod("setUserInfo", new Class[] { Class.forName("java.lang.String") });
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    } catch (NoSuchMethodException noSuchMethodException) {
      throw new NoSuchMethodError(noSuchMethodException.getMessage());
    } catch (ClassNotFoundException classNotFoundException) {
      throw new NoClassDefFoundError(classNotFoundException.getMessage());
    } 
  }
}

反編譯后可以看到生成的代理類的結(jié)構(gòu)如下,生成的代理類名稱為$Proxy0。$Proxy0繼承了Proxy類并且實(shí)現(xiàn)了UserDao接口定義的方法。$Proxy0擁有一個(gè)有參構(gòu)造函數(shù),入?yún)⑹?code>InvocationHandler(即我們自定義的攔截方法),并在構(gòu)造函數(shù)調(diào)用父類的構(gòu)造函數(shù)super(paramInvocationHandler);。展開Proxy類,可以看到類中定義了成員字段,該字段用于保留攔截器對(duì)象的引用。

    /**
     * the invocation handler for this proxy instance.
     * @serial
     */
    protected InvocationHandler h;

代理類除了實(shí)現(xiàn)UserDao的方法外(setUserInfo,getUserInfo),還重寫了hashCode()、toString以及equals方法。在每個(gè)重寫的方法中都有類似的調(diào)用邏輯,以setUserInfo為例:

  public final void setUserInfo(String paramString) {
    try {
      this.h.invoke(this, m4, new Object[] { paramString });
      return;
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }

可以看到,當(dāng)用戶調(diào)用代理類對(duì)象的setUserInfo方法后,代理類對(duì)象會(huì)執(zhí)行this.h.invoke(this, m4, new Object[] { paramString });語句,該語句就是調(diào)用了我么實(shí)現(xiàn)的InvocationHandler的invoke方法,即我們?cè)跀r截器中重寫的方法。該方法的入?yún)⒁来问谴眍悓?duì)象自身的引用,觸發(fā)的方法對(duì)象、參數(shù)列表。

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // proxy是動(dòng)態(tài)生成的代理類的對(duì)象
        // method是被觸發(fā)的方法
        // args是方法入?yún)⒘斜?        before();
        Object result = method.invoke(target, args);
        after();
        return result;
    }

這里有一個(gè)值得注意的地方是,在invoke方法中執(zhí)行真正的業(yè)務(wù)代碼,是通過反射調(diào)用的,即method.invoke(target, args);。傳入的第一個(gè)參數(shù)是我們代理的對(duì)象引用,而不是入?yún)?code>proxy,如果傳入proxy的話,會(huì)繼續(xù)觸發(fā)代理類的方法,而代理類的方法會(huì)執(zhí)行this.h.invoke,因此會(huì)陷入無盡循環(huán)。

Cglib動(dòng)態(tài)代理

首先需要?jiǎng)?chuàng)建一個(gè)方法攔截器類LogMethodInterceptor,用于編寫代理的功能。該方法攔截器類需要實(shí)現(xiàn)Cglib的MethodInterceptor接口,該接口定義了一個(gè)intercept方法,入?yún)⑷缦拢?/p>

  • Object:由CGlib創(chuàng)建的被代理的對(duì)象
  • method:觸發(fā)的方法
  • args:方法入?yún)?/li>
  • methodProxy:增強(qiáng)的方法
public class LogMethodInterceptor implements MethodInterceptor{

    @Override
    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        before();
        Object rst = methodProxy.invokeSuper(object, args);
        after();
        return rst;
    }
    private void before(){
        System.out.println("log before" + (new Date().toString()));
    }

    private void after(){
        System.out.println("log after" + (new Date().toString()));
    }
}

之后就可以使用Cglib來創(chuàng)建代理對(duì)象了:

public class TestClient3 {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();

        enhancer.setSuperclass(UserDaoImpl.class);
        LogMethodInterceptor interceptor = new LogMethodInterceptor();
        enhancer.setCallback(interceptor);
        UserDao userDao = (UserDao) enhancer.create();

        userDao.setUserInfo("我是cglib");

        System.out.println(userDao.getUserInfo());
        // log beforeTue Jun 14 21:13:15 CST 2022
        // 插入信息:我是cglib
        // log afterTue Jun 14 21:13:15 CST 2022
        // log beforeTue Jun 14 21:13:15 CST 2022
        // 獲取信息:我是cglib
        // log afterTue Jun 14 21:13:15 CST 2022
        // 我是cglib
    }
}

首先創(chuàng)建Enhancer對(duì)象enhancer,設(shè)置要增強(qiáng)的類以及攔截方法類,然后就能通過調(diào)用enhancer.create()方法創(chuàng)建被代理的類的對(duì)象了。

參考

Java 動(dòng)態(tài)代理詳解
代理模式應(yīng)用場景
代理模式詳解

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

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

  • 概念: 由于某些原因需要給某對(duì)象提供一個(gè)代理以控制該對(duì)象的訪問。這時(shí),訪問對(duì)象不適合或者不能直接引用目標(biāo)對(duì)象,代理...
    JW2015閱讀 467評(píng)論 0 0
  • 代理模式 定義 為實(shí)際對(duì)象提供一個(gè)代理,以控制對(duì)實(shí)際對(duì)象的訪問。代理類負(fù)責(zé)為委托類預(yù)處理消息,過濾消息并轉(zhuǎn)發(fā)消息,...
    CoderBao閱讀 1,267評(píng)論 0 0
  • 代理模式 為其他對(duì)象提供一個(gè)代理,以控制這個(gè)對(duì)象的訪問,在客戶端與目標(biāo)對(duì)象之間起到中介作用,屬于結(jié)構(gòu)型模式 作用 ...
    Jiek閱讀 659評(píng)論 0 0
  • 代理模式: 為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪問。代理模式主要有靜態(tài)代理,動(dòng)態(tài)代理,Cglib代理三種; ...
    iarchitect閱讀 278評(píng)論 0 0
  • 在代理模式(Proxy Pattern)中,一個(gè)類代表另一個(gè)類的功能。這種類型的設(shè)計(jì)模式屬于結(jié)構(gòu)型模式。 在代理模...
    隨遇而安_90d2閱讀 463評(píng)論 0 1

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