動(dòng)態(tài)代理
代理模式
代理模式強(qiáng)調(diào)在對(duì)被代理對(duì)象的控制。代理模式知識(shí)點(diǎn)不做贅述。
靜態(tài)代理,代理類的代碼是在編譯期間就已經(jīng)確定好的。
動(dòng)態(tài)代理,代理類的代碼編譯期間是沒(méi)有的,只有在運(yùn)行期間才能確定。
簡(jiǎn)單回顧下靜態(tài)代理相關(guān)代碼,以統(tǒng)計(jì)方法耗時(shí)為例。
//聲明接口類
public interface IDoSth {
void doSth(String s);
}
//聲明實(shí)際類
public class DoSth implements IDoSth{
@Override
public void doSth(String s) {
System.out.println("doSth:" + s);
}
}
//聲明靜態(tài)代理類,可對(duì)被代理的對(duì)象進(jìn)行定制化處理,此處將 xx 掉包
public class ProxyDoSth implements IDoSth{
private IDoSth instance;
public ProxyDoSth(IDoSth ins) {
this.instance = ins;
}
@Override
public void doSth(String s) {
long start = System.currentTimeMillis();
instance.doSth(s);
System.out.println(System.currentTimeMillis()-start);
}
}
動(dòng)態(tài)代理相關(guān)語(yǔ)法
// 動(dòng)態(tài)代理相關(guān)代碼
IDoSth ins = new DoSth();
IDoSth proxy = (IDoSth) Proxy.newProxyInstance(Test.class.getClassLoader(), ins.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
Object res = method.invoke(ins, args);
System.out.println("method: " + method.getName() + " costTime : "
+ (System.currentTimeMillis() - start));
return res;
}
});
proxy.doSth("做飯");
// 打印 proxy 對(duì)象相關(guān)類和接口信息
System.out.println(proxy.getClass());
System.out.println(proxy.getClass().getSuperclass());
for (Class cls : proxy.getClass().getInterfaces()) {
System.out.println(cls);
}
}
//輸出結(jié)果
doSth:做飯
method: doSth costTime : 1 // 方法耗時(shí)
class com.sun.proxy.$Proxy0 //proxy 類的類名
class java.lang.reflect.Proxy //proxy 類父類
interface jjava.dynamicProxy.IDoSth //proxy 類實(shí)現(xiàn)的接口
由 proxy 對(duì)象可知,其類名com.sun.proxy.$Proxy0 ,該類父類為 java.lang.reflect.Proxy且實(shí)現(xiàn)了 IDoSth 接口。
這里面有兩個(gè)關(guān)鍵類,一個(gè)是 Proxy,一個(gè)是InvocationHandler。Proxy 主要負(fù)責(zé)代理類的生成,InvocationHandler 主要負(fù)責(zé)用戶自定義實(shí)現(xiàn)代理類的功能。
動(dòng)態(tài)代理的本質(zhì)
動(dòng)態(tài)代理沒(méi)那么玄乎,僅僅是在程序運(yùn)行期間生成了代理類的代碼而已。那這份代碼長(zhǎng)什么樣子呢?
我們將動(dòng)態(tài)生成的 Proxy 類的代碼保存在class 文件中,這一步代碼如下
//$Proxy0 為生成的代理類的名字,path 為保存的文件名,實(shí)際上就是將byte 數(shù)組保存到本地,newProxyInstance() 方法內(nèi)部就是通過(guò)這種方式生成的 代理類的 byte[]
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", ins.getClass().getInterfaces());
String path = "/${projectPath}/jjava/dynamicProxy/$Proxy0.class";
try(FileOutputStream fos = new FileOutputStream(path)) {
fos.write(classFile);
fos.flush();
System.out.println("cls 文件寫入成功");
} catch (Exception e) {
System.out.println("cls 文件寫入失敗");
e.printStackTrace();
}
生成的 $Proxy0 類的代碼如下
public final class $Proxy0 extends Proxy implements IDoSth {
private static Method m1; //java.lang.Object#equals()
private static Method m2; //java.lang.Object#toString()
private static Method m3; //jjava.dynamicProxy.IDoSth#doSth()
private static Method m0; //java.lang.Object#hashcode()
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
// 通過(guò) proxy.doSth(xx) 時(shí),本質(zhì)上調(diào)的是這個(gè)方法,內(nèi)部是通過(guò)調(diào)用 InvocationHandler 中的 invoke 方法實(shí)現(xiàn)的。
public final void doSth(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);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("jjava.dynamicProxy.IDoSth").getMethod("doSth", Class.forName("java.lang.String"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
通過(guò)類信息,可以和簽名的log信息互相驗(yàn)證。同時(shí) 由于所有動(dòng)態(tài)代理產(chǎn)生的類都是Proxy 的子類,所以我們?cè)谑褂脮r(shí),只能用 interfaces 中的任一接口承接,而不能用自定義的其他類型,否則會(huì)報(bào) ClassCastException。
可以看到 $Proxy0類的構(gòu)造函數(shù),包括一個(gè) InvocationHandler 類型的參數(shù),并將該參數(shù)傳給了父類的函數(shù),該闡述就是我們調(diào)用Proxy.newProxyInstance() 方法傳入的 h。
我們?cè)谡{(diào)用代理類的方法時(shí),如 proxy.doSth(),本質(zhì)上是通過(guò)super.h.invoke(this, m3, new Object[]{var1}); 實(shí)現(xiàn)的。h為我們傳入的 InvocationHandler 的實(shí)例,m3 為一個(gè)Method 對(duì)象,最終是通過(guò)調(diào)用 m3.invoke(target,args ) 實(shí)現(xiàn)被代理對(duì)象的方法調(diào)用。m3 是一個(gè) Method 類的實(shí)例。與此同時(shí),代理類 $Proxy0 除了實(shí)現(xiàn)所有接口類的方法外,還自動(dòng)幫我們添加了Object 類 的3個(gè)方法,分別是 toString()、hashcode()、equals()。并且在代理類的類初始化階段對(duì)這幾個(gè)Method 實(shí)例進(jìn)行賦值。
動(dòng)態(tài)代理類的名字規(guī)則
類全路徑名名 = pkgName + className
包名主要主要取決于接口是否是public 的。如果要代理的接口全都是public 的,則類的全路徑名是 com.sun.proxy,否則就是非public 接口的包名。
類名為 {n},n為當(dāng)前已經(jīng)創(chuàng)建的動(dòng)態(tài)代理類的個(gè)數(shù),由于有緩存的邏輯,只有不同數(shù)組接口的代理,才會(huì)創(chuàng)建新的類。n從0開(kāi)始,在同進(jìn)程內(nèi)依次遞增。源碼如下:
Proxy$ProxyClassFactory#apply()
/*
* Record the package of a non-public proxy interface so that the
* proxy class will be defined in the same package. Verify that
* all non-public proxy interfaces are in the same package.
*/
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) {
// if no non-public proxy interfaces, use com.sun.proxy package
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";// 默認(rèn)值 com.sun.proxy.
}
/*
* Choose a name for the proxy class to generate.
*/
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num; //proxyClassNamePrefix 為“$Proxy”
個(gè)人理解
動(dòng)態(tài)代理是對(duì)接口的代理,而不是對(duì)類的代理。沒(méi)有接口則無(wú)從代理。
jvm由于不支持運(yùn)行時(shí)修改一個(gè)類或修改方法,如修改方法內(nèi)容,添加方法等。動(dòng)態(tài)代理由于是在運(yùn)行期間生產(chǎn)了新的代理類去擴(kuò)展功能,也算是對(duì)此種不足提供了一種支持。
動(dòng)態(tài)代理是 AOP 思想的一種實(shí)現(xiàn),完美的實(shí)現(xiàn)了一個(gè)切面。方便我們?cè)诿總€(gè)方法執(zhí)行前后定制功能。最常見(jiàn)的就是統(tǒng)計(jì)每個(gè)方法的執(zhí)行耗時(shí),在方法執(zhí)行前記錄時(shí)間戳,方法執(zhí)行完后再記錄一次。
靜態(tài)代理也可以做切面編程,只不過(guò)當(dāng)使用靜態(tài)代理時(shí),手動(dòng)編寫代理接口類的成本太大。相對(duì)于靜態(tài)代理,動(dòng)態(tài)代理是在此層面上做了一層抽象和封裝。定制化需求只需要在 InvocationHandler 的 invoke() 方法中添加就可以了。而且 invoke() 方法傳入的參數(shù)是 一個(gè) object,意味著任何類的實(shí)例都可以被代理(只要實(shí)現(xiàn)相應(yīng)接口),大大增強(qiáng)了InvocationHandler 子類復(fù)用性。
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
很多 android 和 java 框架都應(yīng)用了動(dòng)態(tài)代理技術(shù)。如 Spring,retrofit等。