你必須會的 JDK 動態代理和 CGLIB 動態代理

咱們在閱讀一些 Java 框架的源碼時,基本上常會看到使用動態代理機制,它能夠無感的對既有代碼進行方法的加強,使得代碼擁有更好的拓展性。
經過從靜態代理、JDK 動態代理、CGLIB 動態代理來進行本文的分析。java

靜態代理

靜態代理就是在程序運行以前,代理類字節碼.class就已編譯好,一般一個靜態代理類也只代理一個目標類,代理類和目標類都實現相同的接口。
接下來就先經過 demo 進行分析什麼是靜態代理,當前建立一個 Animal 接口,裏面包含call函數。數組

package top.ytao.demo.proxy;

/**
 * Created by YangTao
 */
public interface Animal {

    void call();

}

建立目標類 Cat,同時實現 Animal 接口,下面是 Cat 發出叫聲的實現。緩存

package top.ytao.demo.proxy;

/**
 * Created by YangTao
 */
public class Cat implements Animal {

    @Override
    public void call() {
        System.out.println("喵喵喵 ~");
    }
}

因爲 Cat 叫以前是由於肚子餓了,因此咱們須要在目標對象方法Cat#call以前說明是飢餓,這是使用靜態代理實現貓飢餓而後發出叫聲。bash

package top.ytao.demo.proxy.jdk;

import top.ytao.demo.proxy.Animal;

/**
 * Created by YangTao
 */
public class StaticProxyAnimal implements Animal {

    private Animal impl;

    public StaticProxyAnimal(Animal impl) {
        this.impl = impl;
    }

    @Override
    public void call() {
        System.out.println("貓飢餓");
        impl.call();
    }
}

經過調用靜態代理實現貓飢餓和叫行爲。app

public class Main {

    @Test
    public void staticProxy(){
        Animal staticProxy = new StaticProxyAnimal(new Cat());
        staticProxy.call();
    }
}

執行結果框架

代理類、目標類、接口之間關係如圖:jvm

以上內容能夠看到代理類中經過持有目標類對象,而後經過調用目標類的方法,實現靜態代理。
靜態代理雖然實現了代理,但在一些狀況下存在比較明顯不足之處:ide

  1. 當咱們在 Animal 接口中增長方法,這時不只實現類 Cat 須要新增該方法的實現,同時,因爲代理類實現了 Animal 接口,因此代理類也必須實現 Animal 新增的方法,這對項目規模較大時,在維護上就不太友好了。
  2. 代理類實現Animal#call是針對 Cat 目標類的對象進行設置的,若是再須要添加 Dog 目標類的代理,那就必須再針對 Dog 類實現一個對應的代理類,這樣就使得代理類的重用型不友好,而且過多的代理類對維護上也是比較繁瑣。

上面問題,在 JDk 動態代理中就獲得了較友好的解決。函數

JDK 動態代理

動態代理類與靜態代理類最主要不一樣的是,代理類的字節碼不是在程序運行前生成的,而是在程序運行時再虛擬機中程序自動建立的。
繼續用上面 Cat 類和 Animal 接口實現 JDK 動態代理。源碼分析

實現 InvocationHandler 接口

JDK 動態代理類必須實現反射包中的 java.lang.reflect.InvocationHandler 接口,在此接口中只有一個 invoker 方法:

InvocationHandler#invoker中必須調用目標類被代理的方法,不然沒法作到代理的實現。下面爲實現 InvocationHandler 的代碼。

/**
 * Created by YangTao
 */
public class TargetInvoker implements InvocationHandler {
    // 代理中持有的目標類
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("jdk 代理執行前");
        Object result = method.invoke(target, args);
        System.out.println("jdk 代理執行後");
        return result;
    }
}

在實現InvocationHandler#invoker時,該方法裏有三個參數:

  • proxy 代理目標對象的代理對象,它是真實的代理對象。
  • method 執行目標類的方法
  • args 執行目標類的方法的參數

建立 JDK 動態代理類

建立 JDK 動態代理類實例一樣也是使用反射包中的 java.lang.reflect.Proxy 類進行建立。經過調用Proxy#newProxyInstance靜態方法進行建立。

/**
 *
 * Created by YangTao
 */
public class DynamicProxyAnimal {

    public static Object getProxy(Object target) throws Exception {
        Object proxy = Proxy.newProxyInstance(
                target.getClass().getClassLoader(), // 指定目標類的類加載
                target.getClass().getInterfaces(),  // 代理須要實現的接口,可指定多個,這是一個數組
                new TargetInvoker(target)   // 代理對象處理器
        );
        return proxy;
    }

}

Proxy#newProxyInstance中的三個參數(ClassLoader loader、Class<?>[] interfaces、InvocationHandler h):

  • loader 加載代理對象的類加載器
  • interfaces 代理對象實現的接口,與目標對象實現一樣的接口
  • h 處理代理對象邏輯的處理器,即上面的 InvocationHandler 實現類。

最後實現執行 DynamicProxyAnimal 動態代理:

public class Main {

    @Test
    public void dynamicProxy() throws Exception {
        Cat cat = new Cat();
        Animal proxy = (Animal) DynamicProxyAnimal.getProxy(cat);
        proxy.call();
    }
}

執行結果:

經過上面的代碼,有兩個問題:代理類是怎麼建立的和代理類怎麼調用方法的?

分析

Proxy#newProxyInstance入口進行源碼分析:

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);
    }

    // 查找或生成指定的代理類
    Class<?> cl = getProxyClass0(loader, intfs);

    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);
    }
}

newProxyInstance 方法裏面獲取到代理類,若是類的做用不能訪問,使其能被訪問到,最後實例化代理類。這段代碼中最爲核心的是獲取代理類的getProxyClass0方法。

private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

private static Class<?> getProxyClass0(ClassLoader loader,
                                       Class<?>... interfaces) {
    // 實現類的接口不能超過 65535 個
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }

    // 獲取代理類
    return proxyClassCache.get(loader, interfaces);
}

若是 proxyClassCache 緩存中存在指定的代理類,則從緩存直接獲取;若是不存在,則經過 ProxyClassFactory 建立代理類。
至於爲何接口最大爲 65535,這個是由字節碼文件結構和 Java 虛擬機規定的,具體能夠經過研究字節碼文件瞭解。

進入到proxyClassCache#get,獲取代理類:

繼續進入Factory#get查看,

最後到ProxyClassFactory#apply,這裏實現了代理類的建立。

private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>>{
    // 全部代理類名稱都已此前綴命名
    private static final String proxyClassNamePrefix = "$Proxy";

    // 代理類名的編號
    private static final AtomicLong nextUniqueNumber = new AtomicLong();

    @Override
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

        Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
        for (Class<?> intf : interfaces) {
            
            // 校驗代理和目標對象是否實現同一接口
            Class<?> interfaceClass = null;
            try {
                interfaceClass = Class.forName(intf.getName(), false, loader);
            } catch (ClassNotFoundException e) {
            }
            if (interfaceClass != intf) {
                throw new IllegalArgumentException(
                    intf + " is not visible from class loader");
            }
            
            // 校驗 interfaceClass 是否爲接口
            if (!interfaceClass.isInterface()) {
                throw new IllegalArgumentException(
                    interfaceClass.getName() + " is not an interface");
            }
            
            // 判斷當前 interfaceClass 是否被重複
            if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                throw new IllegalArgumentException(
                    "repeated interface: " + interfaceClass.getName());
            }
        }

        // 代理類的包名
        String proxyPkg = null;     
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

        // 記錄非 public 修飾符代理接口的包,使生成的代理類與它在同一個包下
        for (Class<?> intf : interfaces) {
            int flags = intf.getModifiers();
            if (!Modifier.isPublic(flags)) {
                accessFlags = Modifier.FINAL;
                // 獲取接口類名
                String name = intf.getName();
                // 去掉接口的名稱,獲取所在包的包名
                int n = name.lastIndexOf('.');
                String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                if (proxyPkg == null) {
                    proxyPkg = pkg;
                } else if (!pkg.equals(proxyPkg)) {
                    throw new IllegalArgumentException(
                        "non-public interfaces from different packages");
                }
            }
        }

        if (proxyPkg == null) {
            // 若是接口類是 public 修飾,則用 com.sun.proxy 包名
            proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
        }

        // 建立代理類名稱
        long num = nextUniqueNumber.getAndIncrement();
        String proxyName = proxyPkg + proxyClassNamePrefix + num;

        // 生成代理類字節碼文件
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName, interfaces, accessFlags);
        try {
            // 加載字節碼,生成指定代理對象
            return defineClass0(loader, proxyName,
                                proxyClassFile, 0, proxyClassFile.length);
        } catch (ClassFormatError e) {
            throw new IllegalArgumentException(e.toString());
        }
    }
}

以上就是建立字節碼流程,經過檢查接口的屬性,決定代理類字節碼文件生成的包名及名稱規則,而後加載字節碼獲取代理實例。操做生成字節碼文件在ProxyGenerator#generateProxyClass中生成具體的字節碼文件,字節碼操做這裏不作詳細講解。
生成的字節碼文件,咱們能夠經過保存本地進行反編譯查看類信息,保存生成的字節碼文件能夠經過兩種方式:設置jvm參數或將生成 byte[] 寫入文件。

上圖的ProxyGenerator#generateProxyClass方法可知,是經過 saveGeneratedFiles 屬性值控制,該屬性的值來源:

private static final boolean saveGeneratedFiles = ((Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"))).booleanValue();

因此經過設置將生成的代理類字節碼保存到本地。

-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

反編譯查看生成的代理類:

生成的代理類繼承了 Proxy 和實現了 Animal 接口,調用call方法,是經過調用 Proxy 持有的 InvocationHandler 實現TargetInvoker#invoker的執行。

CGLIB 動態代理

CGLIB 動態代理的實現機制是生成目標類的子類,經過調用父類(目標類)的方法實現,在調用父類方法時再代理中進行加強。

實現 MethodInterceptor 接口

相比於 JDK 動態代理的實現,CGLIB 動態代理不須要實現與目標類同樣的接口,而是經過方法攔截的方式實現代理,代碼實現以下,首先方法攔截接口 net.sf.cglib.proxy.MethodInterceptor。

/**
 * Created by YangTao
 */
public class TargetInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("CGLIB 調用前");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("CGLIB 調用後");
        return result;
    }
}

經過方法攔截接口調用目標類的方法,而後在該被攔截的方法進行加強處理,實現方法攔截器接口的 intercept 方法裏面有四個參數:

  • obj 代理類對象
  • method 當前被代理攔截的方法
  • args 攔截方法的參數
  • proxy 代理類對應目標類的代理方法

建立 CGLIB 動態代理類

建立 CGLIB 動態代理類使用 net.sf.cglib.proxy.Enhancer 類進行建立,它是 CGLIB 動態代理中的核心類,首先建立個簡單的代理類:

/**
 * Created by YangTao
 */
public class CglibProxy {

    public static Object getProxy(Class<?> clazz){
        Enhancer enhancer = new Enhancer();
        // 設置類加載
        enhancer.setClassLoader(clazz.getClassLoader());
        // 設置被代理類
        enhancer.setSuperclass(clazz);
        // 設置方法攔截器
        enhancer.setCallback(new TargetInterceptor());
        // 建立代理類
        return enhancer.create();
    }

}

設置被代理類的信息和代理類攔截的方法的回調執行邏輯,就能夠實現一個代理類。
實現 CGLIB 動態代理調用:

public class Main {

    @Test
    public void dynamicProxy() throws Exception {
        Animal cat = (Animal) CglibProxy.getProxy(Cat.class);
        cat.call();
    }
}

執行結果:

CGLIB 動態代理簡單應用就這樣實現,可是 Enhancer 在使用過程當中,經常使用且有特點功能還有回調過濾器 CallbackFilter 的使用,它在攔截目標對象的方法時,能夠有選擇性的執行方法攔截,也就是選擇被代理方法的加強處理。使用該功能須要實現 net.sf.cglib.proxy.CallbackFilter 接口。
如今增長一個方法攔截的實現:

/**
 * Created by YangTao
 */
public class TargetInterceptor2 implements MethodInterceptor {

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("CGLIB 調用前 TargetInterceptor2");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("CGLIB 調用後 TargetInterceptor2");
        return result;
    }
}

而後在 Cat 中增長 hobby 方法,由於 CGLIB 代理無需實現接口,能夠直接代理普通類,因此不需再 Animal 接口中增長方法:

package top.ytao.demo.proxy;

/**
 * Created by YangTao
 */
public class Cat implements Animal {

    @Override
    public void call() {
        System.out.println("喵喵喵 ~");
    }
    
    public void hobby(){
        System.out.println("fish ~");
    }
}

實現回調過濾器 CallbackFilter

/**
 * Created by YangTao
 */
public class TargetCallbackFilter implements CallbackFilter {
    @Override
    public int accept(Method method) {
        if ("hobby".equals(method.getName()))
            return 1;
        else
            return 0;
    }
}

爲演示調用不一樣的方法攔截器,在 Enhancer 設置中,使用Enhancer#setCallbacks設置多個方法攔截器,參數是一個數組,TargetCallbackFilter#accept返回的數字即爲該數組的索引,決定調用的回調選擇器。

/**
 * Created by YangTao
 */
public class CglibProxy {

    public static Object getProxy(Class<?> clazz){
        Enhancer enhancer = new Enhancer();
        enhancer.setClassLoader(clazz.getClassLoader());
        enhancer.setSuperclass(clazz);
        enhancer.setCallbacks(new Callback[]{new TargetInterceptor(), new TargetInterceptor2()});
        enhancer.setCallbackFilter(new TargetCallbackFilter());
        return enhancer.create();
    }

}

按代碼實現邏輯,call 方法會調用 TargetInterceptor 類,hobby 類會調用 TargetInterceptor2 類,執行結果:

CGLIB 的實現原理是經過設置被代理的類信息到 Enhancer 中,而後利用配置信息在Enhancer#create生成代理類對象。生成類是使用 ASM 進行生成,本文不作重點分析。若是不關注 ASM 的操做原理,只看 CGLIB 的處理原理仍是比較容易讀懂。這裏主要看生成後的代理類字節碼文件,經過設置

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "F:\\xxx");

可保存生成的字節到 F:\xxx 文件夾中

經過反編譯可看到

代理類繼承了目標類 Cat,同時將兩個方法攔截器加載到了代理類中,經過 Callbacks 下標做爲變量名後綴進行區分,最後調用指定的方法攔截器中的 intercept 實現代理的最終的執行結果。
這裏須要注意的是 CGLIB 動態代理不能代理 final 修飾的類和方法。

最後

經過反編譯生成的 JDK 代理類和 CGLIB 代理類,咱們能夠看到它們兩種不一樣機制的實現:
JDK 動態代理是經過實現目標類的接口,而後將目標類在構造動態代理時做爲參數傳入,使代理對象持有目標對象,再經過代理對象的 InvocationHandler 實現動態代理的操做。
CGLIB 動態代理是經過配置目標類信息,而後利用 ASM 字節碼框架進行生成目標類的子類。當調用代理方法時,經過攔截方法的方式實現代理的操做。
總的來講,JDK 動態代理利用接口實現代理,CGLIB 動態代理利用繼承的方式實現代理。

動態代理在 Java 開發中是很是常見的,在日誌,監控,事務中都有着普遍的應用,同時在大多主流框架中的核心組件中也是少不了使用的,掌握其要點,不論是開發仍是閱讀其餘框架源碼時,都是必須的。

我的博客: https://ytao.top
關注公衆號 【ytao】,更多原創好文
個人公衆號

相關文章
相關標籤/搜索