如果想增強一個方法的功能,無非就是直接在方法體內直接修改。但這也無非給一些有代碼潔癖人士一絲絲不悅!於是乎我們即不想在原來的代碼里修改,又不想把原有的代碼重新寫一次,那麼前輩們就發明瞭代理. 註意:本文以 JdkProxy 為基礎展開所有描述! 參與對象 那麼一個代理過程參與的對象有以下幾項: 目標 ...
如果想增強一個方法的功能,無非就是直接在方法體內直接修改。但這也無非給一些有代碼潔癖人士一絲絲不悅!於是乎我們即不想在原來的代碼里修改,又不想把原有的代碼重新寫一次,那麼前輩們就發明瞭代理.
註意:本文以 JdkProxy 為基礎展開所有描述!
參與對象
那麼一個代理過程參與的對象有以下幾項:
- 目標介面
- 目標類(Target)
- 代理基類(Proxy)
- 生成的代理類
- 調用處理程式(InvocationHandler)
目標介面。
至於為什麼要用介面,這是JdkProxy的理論知識。文章結束後你也會明白!
public interface Fight {
/**
* 射擊
*/
void shot();
/**
* 炸彈
*/
void bomb();
}
目標類
就是需要被代理的類!
public class BeautifulCountryTarget implements Fight {
private static final Logger LOGGER = LoggerFactory.getLogger(BeautifulCountryTarget.class);
/**
* 射擊
*/
@Override
public void shot() {
LOGGER.debug("M4 shot ---> big goose!");
}
/**
* 炸彈
*/
@Override
public void bomb() {
LOGGER.debug("HIMARS fire ---> big goose!");
}
}
調試和輸出結果
public class JdkProxy {
private static final Logger LOGGER = LoggerFactory.getLogger(JdkProxy.class);
/**
* 通過 Proxy.create 生成的對象是代理對象,基於 介面的代理對象,那麼有以下幾點是需要註意
*
* 1:生成的 代理對象 是實現了 目標介面
* 2:生成的 代理對象 與 代理目標 是兄弟關係 (都實現了同一個目標介面)
*
*/
public static void main(String[] args) throws InterruptedException {
// jdkProxy
jdkProxyTest();
}
public static void jdkProxyTest() throws InterruptedException {
//類載入器,負責把生成的class($Proxy???)文件載入到JVM
ClassLoader loader = JdkProxy.class.getClassLoader();
//需要代理的目標(對象)
BeautifulCountryTarget target = new BeautifulCountryTarget();
//增強目標方法的處理程式
InvocationHandler handler = (proxy, method, args) -> {
LOGGER.debug("大哥你在旁邊看著喝Coffee!,武器開給,我來打!");
//方法調用,(目標對象,參數)
return method.invoke(target, args);
};
//創建代理人
Fight w_k_l_Fight = (Fight)Proxy.newProxyInstance(
loader,
new Class[]{Fight.class},
handler
);
//列印下類路徑,方便使用 arthas 進行反張譯
LOGGER.debug("proxy<w_k_l_Fight> class {}", w_k_l_Fight.getClass());
//代理人調用方法
w_k_l_Fight.shot();
w_k_l_Fight.bomb();
}
}
列印出
19:22:41.839 [main] DEBUG com.java.coffeetime.aop.JdkProxy -- proxy<w_k_l_Fight> class class jdk.proxy1.$Proxy0
19:22:41.842 [main] DEBUG com.java.coffeetime.aop.JdkProxy -- 大哥你在旁邊看著喝Coffee!,武器開給,我來打!
19:22:41.842 [main] DEBUG com.java.coffeetime.aop.BeautifulCountryTarget -- M4 shot ---> big goose!
19:22:41.842 [main] DEBUG com.java.coffeetime.aop.JdkProxy -- 大哥你在旁邊看著喝Coffee!,武器開給,我來打!
19:22:41.842 [main] DEBUG com.java.coffeetime.aop.BeautifulCountryTarget -- HIMARS fire ---> big goose!
可以看出代理生效了,-_-!
那麼通過 Proxy.create函數生成的是一位元組碼文件(至於怎麼生成,太高端沒去研究),它被 loader 載入到JVM,通過 debug只看了類名$Proxy0
模擬手寫代理類
既然是系統自己生成的,那麼我們自己可以自己寫一個,不用系統生成的~。由理論知識可以寫出以下 代理類
/**
* 模擬 通過 Proxy.create 出來的 $Proxy?? 類,
*
*/
public class SimulateProxy extends Proxy implements Fight {
protected SimulateProxy(InvocationHandler h) {
super(h);
}
/**
* 射擊
*/
@Override
public void shot() {
try {
// 代理的方法
Method pMethod = Fight.class.getMethod("shot");
//調用增強處理器
super.h.invoke(this, pMethod, null);
} catch (Throwable e) {
e.printStackTrace();
}
}
/**
* 炸彈
*/
@Override
public void bomb() {
try {
// 代理的方法
Method pMethod = Fight.class.getMethod("bomb");
//調用增強處理器
super.h.invoke(this, pMethod, null);
} catch (Throwable e) {
e.printStackTrace();
}
}
}
調試輸出
public class JdkProxy {
private static final Logger LOGGER = LoggerFactory.getLogger(JdkProxy.class);
/**
* 通過 Proxy.create 生成的對象是代理對象,基於 介面的代理對象,那麼有以下幾點是需要註意
*
* 1:生成的 代理對象 是實現了 目標介面
* 2:生成的 代理對象 與 代理目標 是兄弟關係 (都實現了同一個目標介面)
*
*/
public static void main(String[] args) throws InterruptedException {
// 模擬代理類
simulateProxyTest();
}
public static void simulateProxyTest() {
//需要代理的目標(對象)
BeautifulCountryTarget target = new BeautifulCountryTarget();
//增強目標方法的函數
InvocationHandler handler = (proxy, method, args) -> {
LOGGER.debug("大哥你在旁邊看著喝Coffee!,武器開給,我來打!");
return method.invoke(target, args);
};
//創建代理人 這裡換成自己手寫的
Fight w_k_l_Fight = new SimulateProxy(handler);
//代理人調用方法
w_k_l_Fight.shot();
w_k_l_Fight.bomb();
}
}
同樣輸出
19:38:23.950 [main] DEBUG com.java.coffeetime.aop.JdkProxy -- 大哥你在旁邊看著喝Coffee!,武器開給,我來打!
19:38:23.951 [main] DEBUG com.java.coffeetime.aop.BeautifulCountryTarget -- M4 shot ---> big goose!
19:38:23.951 [main] DEBUG com.java.coffeetime.aop.JdkProxy -- 大哥你在旁邊看著喝Coffee!,武器開給,我來打!
19:38:23.951 [main] DEBUG com.java.coffeetime.aop.BeautifulCountryTarget -- HIMARS fire ---> big goose!
總結
我們先來看下系統生成的$Proxy0
是什麼樣。
現在通過在main
方法添加 System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
可以把系統生成的代理class文件寫到目錄里root/jdk/proxy1/$Proxy0.class
。我這裡直接貼出來,方便和上下文對比。
public final class $Proxy0 extends Proxy implements Fight {
private static final Method m0;
private static final Method m1;
private static final Method m2;
private static final Method m3;
private static final Method m4;
public $Proxy0(InvocationHandler param1) {
super(var1);
}
public final int hashCode() {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final boolean equals(Object var1) {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() {
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 void shot() {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void bomb() {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.java.coffeetime.aop.Fight").getMethod("shot");
m4 = Class.forName("com.java.coffeetime.aop.Fight").getMethod("bomb");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
private static Lookup proxyClassLookup(Lookup var0) throws IllegalAccessException {
if (var0.lookupClass() == Proxy.class && var0.hasFullPrivilegeAccess()) {
return MethodHandles.lookup();
} else {
throw new IllegalAccessException(var0.toString());
}
}
}
那麼對比下系統生成的和自己手寫的代理類區別 SimulateProxy和 $Proxy0,可以看出系統生成的代理類比我們手寫的比較優雅一些!
- 利用靜態方法把需要代理的目標方法在載入階段就初始化了,而不需要每次調用的時候去通過反射獲取到.
- 多了
equals
,toString
,hashCode
,有意思的是代理類和目標類 equals 是 ture, 但是不同的實例!19:49:59.622 [main] DEBUG com.java.coffeetime.aop.JdkProxy -- w_k_l_Fight equals target ? true 19:49:59.622 [main] DEBUG com.java.coffeetime.aop.JdkProxy -- w_k_l_Fight == target ? false
- 通過生成的代理類可以看出,的確是實現了 目標介面(和目標類一樣),和代理類是兄弟關係,但又勝似兄弟!
- 對異常的處理
本文來自博客園,作者:烏托拉賽文,轉載請註明原文鏈接:https://www.cnblogs.com/m78-seven/p/17311107.html