微信公眾號:bugstack蟲洞棧
沉淀、分享、成長,專注于原創(chuàng)專題案例,以最易學習編程的方式分享知識,讓自己和他人都能有所收獲。目前已完成的專題有;Netty4.x實戰(zhàn)專題案例、用Java實現JVM、基于JavaAgent的全鏈路監(jiān)控、手寫RPC框架、架構設計專題案例[Ing]等。歡迎?Star和使用,你用劍🗡、我用刀🔪,好的代碼都很燒😏,望你不吝出招💨!
前言介紹
在Java中動態(tài)代理是非常重要也是非常有用的一個技術點,如果沒有動態(tài)代理技術幾乎也就不會有各種優(yōu)秀框架的出現,包括Spring。
其實在動態(tài)代理的使用中,除了我們平時用的Spring還有很多中間件和服務都用了動態(tài)代理,例如;
- RPC通信框架Dubbo,在通信的時候由服務端提供一個接口描述信息的Jar,調用端進行引用,之后在調用端引用后生成了對應的代理類,當執(zhí)行方法調用的時候,實際需要走到代理類向服務提供端發(fā)送請求信息,直至內容回傳。
- 另外在使用Mybatis時候可以知道只需要定義一個接口,不需要實現具體方法就可以調用到Mapper中定義的數據庫操作信息了。這樣極大的簡化了代碼的開發(fā),又增強了效率。
- 最后不知道你自己是否嘗試過開發(fā)一些基于代理類的框架,以此來優(yōu)化業(yè)務代碼。也就是將業(yè)務代碼中非業(yè)務邏輯又通用性的功能抽離出來,開發(fā)為獨立的組件。推薦個案例,方便知道代理類的應用:手寫RPC框架第三章《RPC中間件》
代理方式
動態(tài)代理可以使用Jdk方式也可以使用CGLB,他們的區(qū)別,如下;
類型 | 機制 | 回調方式 | 適用場景 | 效率 |
---|
JDK | 委托機制,代理類和目標類都實現了同樣的接口,InvocationHandler持有目標類,代理類委托InvocationHandler去調用目標類的原始方法 | 反射 | 目標類是接口類 | 效率瓶頸在反射調用稍慢 |
CGLIB | 繼承機制,代理類繼承了目標類并重寫了目標方法,通過回調函數MethodInterceptor調用父類方法執(zhí)行原始邏輯 | 通過FastClass方法索引調用 | 非接口類,非final類,非final方法 | 第一次調用因為要生成多個Class對象較JDK方式慢,多次調用因為有方法索引較反射方式快,如果方法過多switch case過多其效率還需測試 |
案例工程
itstack-demo-test
└── src
├── main
│ └── java
│ └── org.itstack.demo
│ ├── proxy
│ │└── cglib
│ │ └── CglibProxy.java
│ ├── jdk
│ │├── reflect
│ ││ ├── JDKInvocationHandler.java
│ ││ └── JDKProxy.java
│ │ └── util
│ │ └── ClassLoaderUtils.java
│ └── service
│ ├── IUserService.java
│ └── UserService.java
└── test
└── java
└── org.itstack.demo.test
└── ApiTest.java
基礎接口和方法便于驗證
service/IUserService.java
public interface IUserService {
String queryUserNameById(String userId);
}
service/UserService.java
public class UserService implements IUserService {
public String queryUserNameById(String userId) {
return "hi user " + userId;
}
}
JDK動態(tài)代理
reflect/JDKInvocationHandler.java & 代理類反射調用
- 實現InvocationHandler.invoke,用于方法增強{監(jiān)控、執(zhí)行其他業(yè)務邏輯、遠程調用等}
- 如果有需要額外的參數可以提供構造方法
public class JDKInvocationHandler implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName());
return "我被JDKProxy代理了";
}
}
reflect/JDKProxy.java & 定義一個代理類獲取的服務
- Proxy.newProxyInstance 來實際生成代理類,過程如下;
- Class<?> cl = getProxyClass0(loader, intfs); 查找或生成指定的代理類
- proxyClassCache.get(loader, interfaces); 代理類的緩存中獲取
- subKeyFactory.apply(key, parameter) 繼續(xù)下一層
- byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags); 生成代理類的字節(jié)碼
public class JDKProxy {
public static <T> T getProxy(Class<T> interfaceClass) throws Exception {
InvocationHandler handler = new JDKInvocationHandler();
ClassLoader classLoader = ClassLoaderUtils.getCurrentClassLoader();
T result = (T) Proxy.newProxyInstance(classLoader, new Class[]{interfaceClass}, handler);
return result;
}
}
ApiTest.test_proxy_jdk() & 執(zhí)行調用并輸出反射類的字節(jié)碼
- 代理后調用方法驗證
- 通過使用ProxyGenerator.generateProxyClass獲取實際的字節(jié)碼,查看代理類的內容
@Test
public void test_proxy_jdk() throws Exception {
IUserService proxy = (IUserService) JDKProxy.getProxy(ClassLoaderUtils.forName("org.itstack.demo.service.IUserService"));
String userName = proxy.queryUserNameById("10001");
System.out.println(userName);
String name = "ProxyUserService";
byte[] data = ProxyGenerator.generateProxyClass(name, new Class[]{IUserService.class});
// 輸出類字節(jié)碼
FileOutputStream out = null;
try {
out = new FileOutputStream(name + ".class");
System.out.println((new File("")).getAbsolutePath());
out.write(data);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != out) try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
輸出結果
queryUserNameById
我被JDKProxy代理了
將生成的代理類進行反編譯jd-gui
部分內容抽取,可以看到比較核心的方法,也就是我們在調用的時候走到了這里
public final String queryUserNameById(String paramString)
throws
{
try
{
return (String)this.h.invoke(this, m3, new Object[] { paramString });
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m3 = Class.forName("org.itstack.demo.service.IUserService").getMethod("queryUserNameById", new Class[] { Class.forName("java.lang.String") });
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
CGLIB動態(tài)代理
cglib/CglibProxy.java
- 提供構造方法,生成CGLIB的代理類,回調this
- intercept可以進行方法的增強,處理相關業(yè)務邏輯
- CGLIB是通過ASM來操作字節(jié)碼生成類
public class CglibProxy implements MethodInterceptor {
public Object newInstall(Object object) {
return Enhancer.create(object.getClass(), this);
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("我被CglibProxy代理了");
return methodProxy.invokeSuper(o, objects);
}
}
ApiTest.test_proxy_cglib() & 調用代理類
@Test
public void test_proxy_cglib() {
CglibProxy cglibProxy = new CglibProxy();
UserService userService = (UserService) cglibProxy.newInstall(new UserService());
String userName = userService.queryUserNameById("10001");
System.out.println(userName);
}
輸出結果
我被CglibProxy代理了
hi user 10001
綜上總結
- 在我們實際使用中兩種方式都用所有使用,也可以依照不同的訴求進行選擇
- 往往動態(tài)代理會和注解共同使用,代理類拿到以后獲取方法的注解,并做相應的業(yè)務操作
- 有時候你是否會遇到增加AOP不生效,因為有時候有些類是被代理操作的,并沒有執(zhí)行你的自定義注解也就是切面
