Java 靜態(tài)代理
靜態(tài)代理通常用于對原有業(yè)務(wù)邏輯的擴(kuò)充。比如持有二方包的某個類,并調(diào)用了其中的某些方法。然后出于某種原因,比如記錄日志、打印方法執(zhí)行時間,但是又不好將這些邏輯寫入二方包的方法里。所以可以創(chuàng)建一個代理類實現(xiàn)和二方方法相同的方法,通過讓代理類持有真實對象,然后在原代碼中調(diào)用代理類方法,來達(dá)到添加我們需要業(yè)務(wù)邏輯的目的。
這其實也就是代理模式的一種實現(xiàn),通過對真實對象的封裝,來實現(xiàn)擴(kuò)展性。
一個典型的代理模式通常有三個角色,這里稱之為**代理三要素**
共同接口
- public interface Action {
- public void doSomething();
- }
真實對象
- public class RealObject implements Action{
- public void doSomething() {
- System.out.println('do something');
- }
- }
代理對象
- public class Proxy implements Action {
- private Action realObject;
- public Proxy(Action realObject) {
- this.realObject = realObject;
- }
- public void doSomething() {
- System.out.println('proxy do');
- realObject.doSomething();
- }
- }
運(yùn)行代碼
- Proxy proxy = new Proxy(new RealObject());
- proxy.doSomething();
這種代理模式也最為簡單,就是通過proxy持有realObject的引用,并進(jìn)行一層封裝。
靜態(tài)代理的優(yōu)點(diǎn)和缺點(diǎn)
先看看代理模式的優(yōu)點(diǎn): 擴(kuò)展原功能,不侵入原代碼。
再看看這種代理模式的缺點(diǎn):
假如有這樣一個需求,有十個不同的RealObject,同時我們要去代理的方法是不同的,比要代理方法:doSomething、doAnotherThing、doTwoAnotherThing,添加代理前,原代碼可能是這樣的:
- realObject.doSomething();
- realObject1.doAnotherThing();
- realObject2.doTwoAnother();
為了解決這個問題,我們有方案一:
為這些方法創(chuàng)建不同的代理類,代理后的代碼是這樣的:
- proxy.doSomething();
- proxy1.doAnotherThing();
- proxy2.doTwoAnother();
當(dāng)然,也有方案二:
通過創(chuàng)建一個proxy,持有不同的realObject,實現(xiàn)Action1、Action2、Action3接口,來讓代碼變成這樣:
- proxy.doSomething();
- proxy.doAnotherThing();
- proxy.doTwoAnother();
于是你的代理模型會變成這樣:
毫無疑問,僅僅為了擴(kuò)展同樣的功能,在方案一種,我們會重復(fù)創(chuàng)建多個邏輯相同,僅僅RealObject引用不同的Proxy。
而在方案二中,會導(dǎo)致proxy的膨脹,而且這種膨脹往往是無意義的。此外,假如方法簽名是相同的,更需要在調(diào)用的時候引入額外的判斷邏輯。
java 動態(tài)代理
搞清楚靜態(tài)代理的缺點(diǎn)十分重要,因為動態(tài)代理的目的就是為了解決靜態(tài)代理的缺點(diǎn)。通過使用動態(tài)代理,我們可以通過在運(yùn)行時,動態(tài)生成一個持有RealObject、并實現(xiàn)代理接口的Proxy,同時注入我們相同的擴(kuò)展邏輯。哪怕你要代理的RealObject是不同的對象,甚至代理不同的方法,都可以動過動態(tài)代理,來擴(kuò)展功能。
簡單理解,動態(tài)代理就是我們上面提到的方案一,只不過這些proxy的創(chuàng)建都是自動的并且是在運(yùn)行期生成的。
動態(tài)代理基本用法
使用動態(tài)代理,需要將要擴(kuò)展的功能寫在一個InvocationHandler 實現(xiàn)類里:
- public class DynamicProxyHandler implements InvocationHandler {
- private Object realObject;
- public DynamicProxyHandler(Object realObject) {
- this.realObject = realObject;
- }
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- //代理擴(kuò)展邏輯
- System.out.println('proxy do');
- return method.invoke(realObject, args);
- }
- }
這個Handler中的invoke方法中實現(xiàn)了代理類要擴(kuò)展的公共功能。
到這里,需要先看一下這個handler的用法:
- public static void main(String[] args) {
- RealObject realObject = new RealObject();
- Action proxy = (Action) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Action.class}, new DynamicProxyHandler(realObject));
- proxy.doSomething();
- }
Proxy.newProxyInstance 傳入的是一個ClassLoader, 一個代理接口,和我們定義的handler,返回的是一個Proxy的實例。
仔細(xì)體會這個過程,其實有點(diǎn)類似我們在靜態(tài)代理中提到的方案一,生成了一個包含我們擴(kuò)展功能,持有RealObject引用,實現(xiàn)Action接口的代理實例Proxy。只不過這個Proxy不是我們自己寫的,而是java幫我們生成的,有沒有一點(diǎn)動態(tài)的味道。
讓我們再回顧一下代理三要素:真實對象:RealObject,代理接口:Action,代理實例:Proxy
上面的代碼實含義也就是,輸入 RealObject、Action,返回一個Proxy。妥妥的代理模式。
綜上,動態(tài)生成+代理模式,也就是動態(tài)代理。
網(wǎng)上搜了不少文章,到了這里,接下來就是和cglib等動態(tài)代理實現(xiàn)方法做一下橫向比較。本文不做橫向比較,為了不偏離主題,接下來做縱向挖掘。
看一下源碼
道理清楚了,但是這篇文章題目是搞懂,所以來看一下這個Proxy是如何自動被生成的。入口就在newProxyInstance方法,核心代碼如下:
- private static final Class?>[] constructorParams =
- { InvocationHandler.class };
- public static Object newProxyInstance(ClassLoader loader,
- Class?>[] interfaces,
- InvocationHandler h)
- throws IllegalArgumentException
- {
- Class?> cl = getProxyClass0(loader, intfs);
- ...
- final Constructor?> cons = cl.getConstructor(constructorParams);
- if (!Modifier.isPublic(cl.getModifiers())) {
- AccessController.doPrivileged(new PrivilegedAction() {
- public Void run() {
- cons.setAccessible(true);
- return null;
- }
- });
- }
- return cons.newInstance(new Object[]{h});
- }
整體流程就是:
1、生成代理類Proxy的Class對象。
2、如果Class作用域為私有,通過 setAccessible 支持訪問
3、獲取Proxy Class構(gòu)造函數(shù),創(chuàng)建Proxy代理實例。
生成Proxy的Class文件
生成Class對象的方法中,先是通過傳進(jìn)來的ClassLoader參數(shù)和Class[] 數(shù)組作為組成鍵,維護(hù)了一個對于Proxy的Class對象的緩存。這樣需要相同Proxy的Class對象時,只需要創(chuàng)建一次。
第一次創(chuàng)建該Class文件時,為了線程安全,方法進(jìn)行了大量的處理,最后會來到ProxyClassFactory的apply方法中,經(jīng)過以下流程:
1、校驗傳入的接口是否由傳入的ClassLoader加載的。
2、校驗傳入是否是接口的Class對象。
3、校驗是否傳入重復(fù)的接口。
4、拼裝代理類包名和類名,生成.class 文件的字節(jié)碼。
5、調(diào)用native方法,傳入字節(jié)碼,生成Class對象。
- proxyPkg = ReflectUtil.PROXY_PACKAGE + '.';
- long num = nextUniqueNumber.getAndIncrement();
- String proxyName = proxyPkg + proxyClassNamePrefix + num;
- byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
- proxyName, interfaces, accessFlags);
- return defineClass0(loader, proxyName,
- proxyClassFile, 0, proxyClassFile.length);
看一下第四步生成.class文件字節(jié)碼的過程,主要分為兩個階段:
- addProxyMethod(hashCodeMethod, Object.class);
- addProxyMethod(equalsMethod, Object.class);
- addProxyMethod(toStringMethod, Object.class);
- for (int i = 0; i < interfaces.length;="" i++)="">
- Method[] methods = interfaces[i].getMethods();
- for (int j = 0; j < methods.length;="" j++)="">
- addProxyMethod(methods[j], interfaces[i]);
- }
- }
- methods.add(this.generateConstructor());
- for (List sigmethods : proxyMethods.values()) {
- for (ProxyMethod pm : sigmethods) {
- fields.add(new FieldInfo(pm.methodFieldName,
- 'Ljava/lang/reflect/Method;', ACC_PRIVATE | ACC_STATIC));
- methods.add(pm.generateMethod());
- }
- }
- methods.add(generateStaticInitializer());
第一個階段的代碼比較清晰,主要就是添加各種Method,比如toString()、equals,以及傳入的代理接口中的方法。再添加一下構(gòu)造方法以及靜態(tài)初始化方法。這要構(gòu)成了一個對象,存儲生成Proxy的Class的一些信息。
到了這里,已經(jīng)把要構(gòu)造的Proxy的方法基本定義完成了,接下來就要生成這個.class文件了。
- ByteArrayOutputStream bout = new ByteArrayOutputStream();
- DataOutputStream dout = new DataOutputStream(bout);
- dout.writeInt(0xCAFEBABE);
- ...
- dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);
- ...
- return bout.toByteArray();
看到這個CAFEBABE,就清楚第二階段的內(nèi)容了。CAFEBABE是Class文件的魔數(shù),關(guān)于Class文件這個咖啡寶貝的魔數(shù),相信做Java的人都知道。沒錯,第二階段就是生成字節(jié)碼。按JVM規(guī)范,寫入Class文件中包括權(quán)限控制、方法表、字段表等內(nèi)容,生成符合規(guī)范的Class文件。最后返回對應(yīng)的字節(jié)碼。
字節(jié)碼生成以后,通過調(diào)用native方法defineClass解析字節(jié)碼,就生成了Proxy的Class對象。
Proxy構(gòu)造方法
看一下Proxy的構(gòu)造方法字節(jié)碼生成部分:
- MethodInfo minfo = new MethodInfo('', '(Ljava/lang/reflect/InvocationHandler;)V',ACC_PUBLIC);
- DataOutputStream out = new DataOutputStream(minfo.code);
- code_aload(0, out);
- code_aload(1, out);
- out.writeByte(opc_invokespecial);
- out.writeShort(cp.getMethodRef(superclassName,'', '(Ljava/lang/reflect/InvocationHandler;)V'));
- ...
關(guān)鍵在于,生成了一個參數(shù)為InvocationHandler的構(gòu)造方法,code加載的是jvm方法區(qū)中的代碼,然后通過invokespecial指令調(diào)用了父類構(gòu)造方法。
查看生成的Class文件
上面利用字節(jié)碼生成技術(shù)產(chǎn)生Class文件的過程,看起來可能比較晦澀,其實我們可以查看這個產(chǎn)生的Proxy到底是個什么樣子。
注意ProxyGenerator中有這樣一個邏輯:
- if(saveGeneratedFiles) {
- ...
- FileOutputStream file = new FileOutputStream(dotToSlash(name) + '.class');
- file.write(classFile);
- ...
- }
再看一下saveGeneratedFiles這個變量:
- private final static boolean saveGeneratedFiles =
- java.security.AccessController.doPrivileged(
- new GetBooleanAction('sun.misc.ProxyGenerator.saveGeneratedFiles'))
- .booleanValue();
這是一個final類型的變量,通過GetBooleanAction方法讀取系統(tǒng)變量,獲取系統(tǒng)設(shè)置。默認(rèn)這個值是false,稍微看一下System這個類的源碼,發(fā)現(xiàn)有可以設(shè)置系統(tǒng)變量的Api,然后在程序的main 函數(shù)設(shè)置一下這個變量:
System.getProperties().setProperty('sun.misc.ProxyGenerator.saveGeneratedFiles', 'true');
這個時候,再跑一遍程序,就可以看到生成的Proxy的Class文件了,直接雙擊利用 ide 反編譯。
- package com.sun.proxy;
- 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 Action {
- private static Method m1;
- private static Method m3;
- private static Method m2;
- private static Method m0;
- public $Proxy0(InvocationHandler var1) throws {
- super(var1);
- }
- public final void doSomething() throws {
- try {
- super.h.invoke(this, m3, (Object[])null);
- } catch (RuntimeException | Error var2) {
- throw var2;
- } catch (Throwable var3) {
- throw new UndeclaredThrowableException(var3);
- }
- }
- ...
- static {
- try {
- ...
- m3 = Class.forName('Action').getMethod('doSomething', new Class[0]);
- } catch (NoSuchMethodException var2) {
- throw new NoSuchMethodError(var2.getMessage());
- } catch (ClassNotFoundException var3) {
- throw new NoClassDefFoundError(var3.getMessage());
- }
- }
- }
省略一些無關(guān)代碼,可以看到兩個重要的方法。
一個就是我們的代理方法doSomething、另一個就是構(gòu)造方法。
這個$Proxy0 繼承 Proxy并調(diào)用了父類的構(gòu)造方法,回憶一下上文提到的invokeSpecial,怎么樣,對上了吧。
看一下Proxy中這個構(gòu)造方法:
- protected Proxy(InvocationHandler h) {
- Objects.requireNonNull(h);
- this.h = h;
- }
在看一下$Proxy0 的代理方法:
super.h.invoke(this, m3, (Object[])null);
再來回顧一下生成Proxy實例的過程:
- private static final Class?>[] constructorParams =
- { InvocationHandler.class };
- ...
- final Constructor?> cons = cl.getConstructor(constructorParams);
- ...
- return cons.newInstance(new Object[]{h});
其實newInstance生成Proxy實例時,通過$Proxy0的Class對象,選擇了這個InvocationHandler為參數(shù)的構(gòu)造方法,傳入我們定義的InvocationHandler并生成了一個 Proxy0的實例!InvocationHandler 里有realObject的邏輯以及我們的擴(kuò)展邏輯,當(dāng)我們調(diào)用Proxy0的doSomething方法時,就會調(diào)用到我們InvocationHandler 里 實現(xiàn)的invoke方法。
對上面這個過程,做一張圖總結(jié)一下: