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

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

靜態代理

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

<code>package top.ytao.demo.proxy;

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

void call();

}/<code>

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

<code>package top.ytao.demo.proxy;

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

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

由於 Cat 叫之前是因為肚子餓了,所以我們需要在目標對象方法Cat#call之前說明是飢餓,這是使用靜態代理實現貓飢餓然後發出叫聲。

<code>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();
}
}/<code>

通過調用靜態代理實現貓飢餓和叫行為。

<code>public class Main {

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

執行結果

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


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

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


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

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

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

JDK 動態代理

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

實現 InvocationHandler 接口

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

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

在InvocationHandler#invoker中必須調用目標類被代理的方法,否則無法做到代理的實現。下面為實現 InvocationHandler 的代碼。

<code>/**
* 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;
}
}/<code>

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

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

創建 JDK 動態代理類

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

<code>/**
*
* 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;
}

}/<code>

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

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

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

<code>public class Main {

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

執行結果:

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


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

分析

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

<code>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);
}
}/<void>/<code>

newProxyInstance 方法裡面獲取到代理類,如果類的作用不能訪問,使其能被訪問到,最後實例化代理類。這段代碼中最為核心的是獲取代理類的getProxyClass0方法。

<code>private static final WeakCache<classloader>[], 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);
}/<classloader>/<code>

如果 proxyClassCache 緩存中存在指定的代理類,則從緩存直接獲取;如果不存在,則通過 ProxyClassFactory 創建代理類。 至於為什麼接口最大為 65535,這個是由字節碼文件結構和 Java 虛擬機規定的,具體可以通過研究字節碼文件瞭解。

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

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


繼續進入Factory#get查看,

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

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

<code>private static final class ProxyClassFactory implements BiFunction<classloader>[], 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());
}
}

}/<class>/<classloader>/<code>

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


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


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

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

所以通過設置將生成的代理類字節碼保存到本地。

<code>-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true/<code>

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

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


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

CGLIB 動態代理

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

實現 MethodInterceptor 接口

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

<code>/**
* 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;
}
}/<code>

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

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

創建 CGLIB 動態代理類

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

<code>/**
* 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();
}

}/<code>

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

<code>public class Main {

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

執行結果:

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


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

<code>/**
* 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;
}
}/<code>

然後在 Cat 中增加 hobby 方法,因為 CGLIB 代理無需實現接口,可以直接代理普通類,所以不需再 Animal 接口中增加方法:

<code>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 ~");
}
}/<code>

實現回調過濾器 CallbackFilter

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

return 0;
}
}/<code>

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

<code>/**
* 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();
}

}/<code>

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

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


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

<code>System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "F:\\\\\\xxx");/<code>

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

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


通過反編譯可看到

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


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

最後

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

動態代理在 Java 開發中是非常常見的,在日誌,監控,事務中都有著廣泛的應用,同時在大多主流框架中的核心組件中也是少不了使用的,掌握其要點,不管是開發還是閱讀其他框架源碼時,都是必須的。


作者:ytao
鏈接:https://juejin.im/post/5e8ad506e51d4547165507b2


分享到:


相關文章: