為了引出動態代理,我們看看一個案列!
廣東廣州,早上9:00,一位靚仔穿著人字拖、提著鳥籠,走進了早茶店。沒錯,這就是廣州典型的包租公!名下幾棟樓,只收租為生,沒工作,這人身真是無趣至極!
這裡就得出一個問題:收租不算工作?好吧,其實正真的包租公不會自己去收租,都是委託給中介去做。為什麼呢?這其中可以說牽扯到安全、隱私等等。想一下,假如包租公自己收租,當下租客很多,其他包租公就不爽了,乾脆找人去搗亂,比如只問不租,浪費包租公時間。當然不僅僅是這樣…
好的,租房中介就出來了,租客看房、談價、籤合同、交付鑰匙等等都讓中介(代理)去做,房東就坐等收錢就行了。
程式碼中使用輸出語句代替真正的業務!
首先,我們定義一個房東介面,和一個房東實現類。(為什麼需要介面?後面代理類是根據介面得到的,而且房東類不應該暴露出去的)
public interface ILandlordService {
void deliver();
}
public class LandlordServiceImpl implements ILandlordService {
public void deliver() {
try {
System.out.println("告知房東出租成功,房東收錢");
} catch (Exception e) {
e.printStackTrace();
System.out.println("出現異常,終止");
}
}
}
接下來建立一個類,實現java.lang.reflect.InvocationHandler介面,該類用來對原來的方法增強。(注意包,別導錯)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class TransactionInvocationHandler implements InvocationHandler {
// 需要被代理的物件(房東)
private Object target;
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
/*
實現介面需要重寫的方法
proxy:代理物件
method:房東介面中被呼叫的方法
args:房東介面中被呼叫的方法需要的引數
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object retVal = null;
try {
System.out.println("交付前與中介的交談");
// 房東收錢
retVal = method.invoke(target, args);
System.out.println("租房成功");
System.out.println("給鑰匙");
} catch (Exception e) {
e.printStackTrace();
System.out.println("出現異常,終止交付");
}
return retVal;
}
}
最後寫測試類(租客)
import org.junit.Test;
import java.lang.reflect.Proxy;
public class TenantTest {
@Test
public void test() {
//這兩個建立物件和設定值應在spring中設定,讓spring建立、設值
// 建立一個房東物件
ILandlordService landlordService = new LandlordServiceImpl();
// 建立增強類物件
TransactionInvocationHandler transactionInvocationHandler = new TransactionInvocationHandler();
// 把房東物件設定到增強類物件中使用
transactionInvocationHandler.setTarget(landlordService);
/*
使用強轉得到房東類代理物件(中介)
newProxyInstance方法需要三個引數
ClassLoader loader:類載入器,直接使用本類的類載入器(系統類載入器)
Class<?>[] interfaces:介面陣列
InvocationHandler h:增強類
*/
ILandlordService proxy = (ILandlordService) Proxy.newProxyInstance(
this.getClass().getClassLoader(),
// 這裡landlordService物件暫時在測試類中建立,
// 使用spring後,注入到TransactionInvocationHandler(增強類)的欄位(target)中,
// 直接到增強類中獲取,這樣測試類(租客)就不知道實現類了(房東)
landlordService.getClass().getInterfaces(),
transactionInvocationHandler
);
// 呼叫代理物件的deliver(),會執行增強類的invoke方法,引數method為代理物件呼叫的方法
proxy.deliver();
}
}
執行結果:
交付前與中介的交談
告知房東出租成功,房東收錢
租房成功
給鑰匙
此時很多人就疑惑了,怎麼建立代理類的?又怎麼會呼叫增強類的invoke方法的?那就接下去看看原理吧!建議截圖看,程式碼有點多!
測試類中的newProxyInstance方法,點進去
ILandlordService proxy = (ILandlordService) Proxy.newProxyInstance(
this.getClass().getClassLoader(),
landlordService.getClass().getInterfaces(),
transactionInvocationHandler
);
到proxy類的newProxyInstance方法,省去不必要的程式碼
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
/*
* Look up or generate the designated proxy class and its constructor.
* 查詢或生成 指定的代理類和它的建構函式
*/
/*
獲取生成類的構造器
loader:類載入器
interfaces:介面陣列
*/
Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
return newProxyInstance(caller, cons, h);
}
往newProxyInstance方法中走,省去不必要的程式碼,try-catch也省去了
private static Object newProxyInstance(Class<?> caller,
Constructor<?> cons,
InvocationHandler h) {
// caller有關安全的,這裡忽略
checkNewProxyPermission(caller, cons.getDeclaringClass());
/*
反射呼叫構造器建立這個類的物件
cons:代理類構造器
h:增強類
*/
return cons.newInstance(new Object[]{h});
}
使用以下程式碼獲取代理類位元組碼檔案
ProxyGenerator 在 JDK 11 已經不是公有的了,我這裡的原始碼是JKD 11
下面獲取代理類位元組碼用的是JDK 8
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
public class DynamicProxyClassGenerator {
public static void main(String[] args) throws Exception {
// 實現類的class物件
generateClassFile(LandlordServiceImpl.class, "LandlordServiceProxy");
}
public static void generateClassFile(Class<?> targetClass, String proxyName) throws Exception {
// 根據類資訊和提供的代理類名稱,生成位元組碼
byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, targetClass.getInterfaces());
String path = targetClass.getResource(".").getPath();
System.out.println(path);
FileOutputStream out = null;
// 保留到硬碟中
out = new FileOutputStream(path + proxyName + ".class");
out.write(classFile);
out.close();
}
}
代理類生成的class檔案是這樣的,deliver()方法是根據我們定義的類得來的,省去幾個Object類得到的方法內容
呼叫有參構造器建立物件
import com.hao.service.ILandlordService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class LandlordServiceProxy extends Proxy implements ILandlordService {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
// 建立物件時呼叫的方法
public LandlordServiceProxy(InvocationHandler var1) throws {
// 呼叫父類別(proxy類)構造器
super(var1);
}
public final boolean equals(Object var1) throws {
}
public final String toString() throws {
}
public final void deliver() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
}
static {
try {
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.hao.service.ILandlordService").getMethod("deliver");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
// 把增強類設定到proxy類的欄位上
this.h = h;
}
protected InvocationHandler h;
最終Proxy.newProxyInstance()返回的是代理類(中介)
ILandlordService proxy = (ILandlordService) Proxy.newProxyInstance(
);
proxy.deliver();
然後我們呼叫deliver()方法,進入的是代理類的deliver()方法
public final void deliver() throws {
try {
// 呼叫父類別的h欄位(增強類)中的invoke方法,m3在本類static{}中獲取
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
m3在static{}中
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
// 通過反射得到deliver方法
m3 = Class.forName("com.hao.service.ILandlordService").getMethod("deliver");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
可以看出,呼叫代理類最終會呼叫增強類的invoke方法。看懂了嗎?三連唄!!!