在接觸SpringAOP的時(shí)候,大家一定會(huì)被這神奇的功能所折服飞涂,想知道其中的奧秘旦部,底層到底是如何實(shí)現(xiàn)的祈搜。于是,大家會(huì)通過(guò)搜索引擎士八,知道了一個(gè)陌生的名詞:動(dòng)態(tài)代理容燕,慢慢的又知道了動(dòng)態(tài)代理有多種實(shí)現(xiàn)方式,比如 JDK動(dòng)態(tài)代理婚度,Cglib 等等蘸秘。今天我就來(lái)簡(jiǎn)單說(shuō)說(shuō)JDK動(dòng)態(tài)代理。
JDK動(dòng)態(tài)代理的簡(jiǎn)單應(yīng)用
我們還是從一個(gè)最簡(jiǎn)單的例子著手:
首先我們需要定義一個(gè)接口:
public interface UserService {
void query();
}
然后實(shí)現(xiàn)這個(gè)接口:
public class UserServiceImpl implements UserService {
public void query() {
System.out.println("查詢用戶信息");
}
}
定義一個(gè)類蝗茁,需要實(shí)現(xiàn)InvocationHandler:
public class MyInvocationHandler implements InvocationHandler {
Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("進(jìn)入了invoke");
method.invoke(target);
System.out.println("執(zhí)行了invoke");
return null;
}
}
然后就是Main方法了:
public class Main {
public static void main(String[] args) {
MyInvocationHandler myInvocationHandler = new MyInvocationHandler(new UserServiceImpl());
Object o = Proxy.newProxyInstance(Main.class.getClassLoader(),
new Class[]{UserService.class}
, myInvocationHandler);
((UserService)o).query();
}
}
運(yùn)行:
可以看到醋虏,一切正常,成功的執(zhí)行了增強(qiáng)的邏輯哮翘,也執(zhí)行了目標(biāo)方法颈嚼。
三個(gè)疑惑
雖然說(shuō)這是最簡(jiǎn)單的一個(gè)例子了,但是在初學(xué)的時(shí)候饭寺,大家肯定和我一樣阻课,有不少疑惑:一是不知道為什么需要傳入接口,二是不知道為什么JDK動(dòng)態(tài)代理只能代理接口艰匙,三是不知道類加載器的作用限煞。還有,就是代碼比較復(fù)雜员凝。
這三個(gè)疑惑困擾我很久署驻,直到我跟著博客,自己手?jǐn)]一個(gè)閹割版的JDK動(dòng)態(tài)代理健霹,并且簡(jiǎn)單的看了下JDK最終生成的代碼以及源碼才明白硕舆。
寫一個(gè)閹割版的JDK動(dòng)態(tài)代理
我們先來(lái)分析下MyInvocationHandler類中的invoke方法,方法有三個(gè)參數(shù)骤公,第一個(gè)參數(shù)是代理類抚官,第二個(gè)參數(shù)是方法,第三個(gè)參數(shù)是 執(zhí)行方法需要用到的參數(shù)阶捆。方法內(nèi)部實(shí)現(xiàn)了兩個(gè)邏輯凌节,一個(gè)是增強(qiáng)邏輯 ,一個(gè)是執(zhí)行目標(biāo)方法洒试。我們不禁的想倍奢,如果我們可以自動(dòng)生成一個(gè)類,去調(diào)用MyInvocationHandler中的invoke方法是不是就可以實(shí)現(xiàn)動(dòng)態(tài)代理了垒棋。
人有多大膽卒煞,地有多大產(chǎn),這的確是一個(gè)大膽瘋狂的想法叼架,但是這確實(shí)可以辦到畔裕,主要有如下幾個(gè)步驟:
- 拼接代理類的代碼
- 輸出.java文件
- 編譯.java文件成.class文件
- 裝載.class文件
- 創(chuàng)建并返回代理類對(duì)象
為了方便衣撬,就不考慮返回值和帶參的情況了,我仿照現(xiàn)有的MyInvocationHandler 寫了一個(gè)閹割版的MockInvocationHandler類:
public class MockInvocationHandler {
private Object targetObject;
public MockInvocationHandler(Object targetObject) {
this.targetObject = targetObject;
}
public void invoke(Method targetMethod) {
try {
System.out.println("進(jìn)入了invoke");
targetMethod.invoke(targetObject, null);
System.out.println("結(jié)束了invoke");
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
要調(diào)用到MockInvocationHandler 中的invoke方法扮饶,生成的代理類大概可能也許長(zhǎng)這個(gè)樣子:
public class $Proxy implements 需要代理的接口{
MockInvocationHandler h;
public $Proxy (MockInvocationHandler h ) {this.h = h; }
public void query(){
try{
//method=需要的執(zhí)行方法
this.h.invoke(method);
}catch(Exception ex){}
}
}
好了具练,接下來(lái)就是體力活了,直接貼上代碼:
public class MockProxy {
final static String ENTER = "\n";
final static String TAB = "\t";
public static Object newProxyInstance(Class interfaceClass,MockInvocationHandler h) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("package com.codebear;");
stringBuilder.append(ENTER);
stringBuilder.append("import java.lang.reflect.*;");
stringBuilder.append(ENTER);
stringBuilder.append("public class $Proxy implements " + interfaceClass.getName() + "{");
stringBuilder.append(ENTER);
stringBuilder.append(TAB);
stringBuilder.append(" MockInvocationHandler h;");
stringBuilder.append(ENTER);
stringBuilder.append(TAB);
stringBuilder.append(" public $Proxy (MockInvocationHandler h ) {this.h = h; }");
stringBuilder.append(ENTER);
stringBuilder.append(TAB);
for (Method method : interfaceClass.getMethods()) {
stringBuilder.append(" public void " + method.getName() + "(){");
stringBuilder.append(ENTER);
stringBuilder.append(TAB);
stringBuilder.append(" try{ ");
stringBuilder.append(ENTER);
stringBuilder.append(TAB);
stringBuilder.append(TAB);
stringBuilder.append(" Method method = " + interfaceClass.getName() + ".class.getMethod(\"" + method.getName() + "\");");
stringBuilder.append(ENTER);
stringBuilder.append(TAB);
stringBuilder.append(TAB);
stringBuilder.append(" this.h.invoke(method);");
stringBuilder.append(ENTER);
stringBuilder.append(TAB);
stringBuilder.append(TAB);
stringBuilder.append("}catch(Exception ex){}");
stringBuilder.append(ENTER);
stringBuilder.append(TAB);
stringBuilder.append("}");
stringBuilder.append(ENTER);
stringBuilder.append("}");
}
String content = stringBuilder.toString();
try {
String filePath = "D:\\com\\codebear\\$Proxy.java";
File file = new File(filePath);
File fileParent = file.getParentFile();
if (!fileParent.exists()) {
fileParent.mkdirs();
}
FileWriter fileWriter = new FileWriter(file);
fileWriter.write(content);
fileWriter.flush();
fileWriter.close();
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager
(null, null, null);
Iterable iterable = fileManager.getJavaFileObjects(filePath);
JavaCompiler.CompilationTask task = compiler.getTask
(null, fileManager, null, null, null, iterable);
task.call();
fileManager.close();
URLClassLoader classLoader = new URLClassLoader(new URL[]{new URL("file:D:\\\\")});
Class<?> clazz = classLoader.loadClass("com.codebear.$Proxy");
Constructor<?> constructor = clazz.getConstructor(MockInvocationHandler.class);
return constructor.newInstance(h);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
}
然后測(cè)試一下:
public class Main {
public static void main(String[] args) {
MockInvocationHandler mockInvocationHandler=new MockInvocationHandler(new UserServiceImpl());
UserService userService = (UserService)MockProxy.
newProxyInstance(UserService.class, mockInvocationHandler);
userService.query();
}
}
運(yùn)行結(jié)果:
好了甜无,在不考慮性能扛点,可維護(hù)性,安全性的情況下岂丘,我們閹割版的動(dòng)態(tài)代理就完成了陵究。代碼難度不是很大,就是比較考驗(yàn)反射和耐心奥帘。
簡(jiǎn)單分析下JDK源碼
源碼基于JDK1.8
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
//安全驗(yàn)證
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* 得到代理類
*/
Class<?> cl = getProxyClass0(loader, intfs);
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);//獲得構(gòu)造方法
final InvocationHandler ih = h;
//如果構(gòu)造器器不是公共的畔乙,需要修改訪問(wèn)權(quán)限,使其可以訪問(wèn)
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});//通過(guò)構(gòu)造方法翩概,創(chuàng)建對(duì)象,傳入InvocationHandler 對(duì)象
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
簡(jiǎn)單的看下源碼返咱,我們一下子就能把目光移動(dòng)到getProxyClass0方法了钥庇,這才是我們需要關(guān)心的,我們點(diǎn)進(jìn)去:
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
//當(dāng)接口大于65535報(bào)錯(cuò)
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
return proxyClassCache.get(loader, interfaces);
}
這方法可以說(shuō)什么事情也沒(méi)干咖摹,但是通過(guò)最后的proxyClassCache.get可以很容易的知道JDK的動(dòng)態(tài)代理是用了緩存的评姨,我們需要關(guān)注的方法在get里面,繼續(xù)點(diǎn)進(jìn)去:
public V get(K key, P parameter) {
Objects.requireNonNull(parameter);
expungeStaleEntries();
//通過(guò)上游方法萤晴,可以知道key是類加載器吐句,這里是通過(guò)類加載器可以獲得第一層key
Object cacheKey = CacheKey.valueOf(key, refQueue);
//我們查看map的定義,可以看到map變量是一個(gè)兩層的ConcurrentMap
ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);//通過(guò)第一層key嘗試獲取數(shù)據(jù)
//如果valuesMap 為空店读,就新建一個(gè)ConcurrentHashMap嗦枢,
//key就是生成出來(lái)的cacheKey,并把這個(gè)新建的ConcurrentHashMap推到map
if (valuesMap == null) {
ConcurrentMap<Object, Supplier<V>> oldValuesMap
= map.putIfAbsent(cacheKey,
valuesMap = new ConcurrentHashMap<>());
if (oldValuesMap != null) {
valuesMap = oldValuesMap;
}
}
//通過(guò)上游方法可以知道key是類加載器屯断,parameter是類本身文虏,這里是通過(guò)類加載器和類本身獲得第二層key
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
Supplier<V> supplier = valuesMap.get(subKey);
Factory factory = null;
while (true) {
if (supplier != null) {
//如果有緩存,直接調(diào)用get方法后返回殖演,當(dāng)沒(méi)有緩存氧秘,會(huì)繼續(xù)執(zhí)行后面的代碼,
//由于while (true)趴久,會(huì)第二次跑到這里丸相,再get返回出去,
//其中g(shù)et方法調(diào)用的是WeakCahce中的靜態(tài)內(nèi)部類Factory的get方法
V value = supplier.get();
if (value != null) {
return value;
}
}
//當(dāng)factory為空彼棍,會(huì)創(chuàng)建Factory對(duì)象
if (factory == null) {
factory = new Factory(key, parameter, subKey, valuesMap);
}
if (supplier == null) {
supplier = valuesMap.putIfAbsent(subKey, factory);
if (supplier == null) {
//當(dāng)沒(méi)有代理類緩存的時(shí)候灭忠,會(huì)運(yùn)行到這里膳算,把Factory的對(duì)象賦值給supplier ,
//進(jìn)行下一次循環(huán)更舞,supplier就不為空了畦幢,可以調(diào)用get方法返回出去了,
//這個(gè)Factory位于WeakCahce類中缆蝉,是一個(gè)靜態(tài)內(nèi)部類
supplier = factory;
}
} else {
if (valuesMap.replace(subKey, supplier, factory)) {
supplier = factory;
} else {
supplier = valuesMap.get(subKey);
}
}
}
}
這里面的代碼比較復(fù)雜宇葱,簡(jiǎn)單的來(lái)說(shuō):
- JDK動(dòng)態(tài)代理是用了兩層的map去緩存,第一個(gè)層是類加載器刊头,第二層是 類加載器+本身
- 當(dāng)有緩存黍瞧,直接調(diào)用get并且返回,反之繼續(xù)執(zhí)行下面的代碼原杂,為supplier進(jìn)行賦值印颤,由于while (true),會(huì)第二次跑到這里穿肄,再調(diào)用get()返回出去年局。核心在于supplier.get(),它調(diào)用的是WeakCahce中的靜態(tài)內(nèi)部類Factory的get()咸产,里面就是 獲取代理類的方法了矢否。
讓我們看下supplier.get()方法:
value = Objects.requireNonNull(valueFactory.apply(key, parameter));
核心在于這一句話,但是valueFactory是什么脑溢?我們可以查看它的定義:
private final BiFunction<K, P, V> valueFactory;
我們?cè)倏聪滤腤eakCahce構(gòu)造方法:
public WeakCache(BiFunction<K, P, ?> subKeyFactory,
BiFunction<K, P, V> valueFactory) {
this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
this.valueFactory = Objects.requireNonNull(valueFactory);
}
我們肯定在哪邊調(diào)用過(guò)這個(gè)構(gòu)造方法了僵朗,在Proxy類中有這樣的定義:
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
這個(gè)proxyClassCache有沒(méi)有很熟悉, 是的屑彻,它就在getProxyClass0方法中用到了验庙,這里創(chuàng)建了WeakCache對(duì)象,并且調(diào)用了帶兩個(gè)參數(shù)的構(gòu)造方法社牲,第二個(gè)參數(shù)是ProxyClassFactory對(duì)象粪薛,也就對(duì)應(yīng)了WeakCache中第二個(gè)參數(shù)BiFunction<K, P, V> valueFactory,然后把值賦值給了final valueFactory搏恤,valueFactory.apply所以最終會(huì)調(diào)用ProxyClassFactory中的apply方法汗菜。關(guān)鍵在于:
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);//生成代理類的二進(jìn)制數(shù)組
try {
//內(nèi)部是native標(biāo)記的方法,是用C或者C++實(shí)現(xiàn)的挑社,這里不深究
//方法內(nèi)部就是通過(guò)類加載器和上面生成的代理類的二進(jìn)制數(shù)組等數(shù)據(jù)陨界,經(jīng)過(guò)處理,成為Class
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
generateProxyClass方法內(nèi)部生成了代理類的二進(jìn)制數(shù)組痛阻,具體是怎么生成的菌瘪,大家可以點(diǎn)進(jìn)去自己看看,這里就不再繼續(xù)往下了,因?yàn)槲覀兊哪繕?biāo)就是找到generateProxyClass方法俏扩,然后自己寫一個(gè)方法糜工,去執(zhí)行g(shù)enerateProxyClass,把返回的byte[]輸出到.class文件录淡,利用idea的反編譯功能捌木,看看最終生成出來(lái)的代理類是什么樣子的:
byte[] $proxies = ProxyGenerator.generateProxyClass("$Proxy", new Class[]{UserService.class});
File file=new File("D:\\$Proxy.class");
FileOutputStream outputStream = null;
try {
outputStream = new FileOutputStream(file);
try {
outputStream.write($proxies);
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
運(yùn)行,發(fā)現(xiàn)D盤出現(xiàn)了$Proxy.class文件嫉戚,我們把它拖到idea里面刨裆,看看它的真面目,因?yàn)樯傻拇a還是比較長(zhǎng)的彬檀,我這里只把核心代碼貼出來(lái):
//繼承了Proxy類
public final class $Proxy extends Proxy implements UserService {
public $Proxy(InvocationHandler var1) throws {
super(var1);
}
public final void query() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
}
這代碼有沒(méi)有很熟悉帆啃,很接近我們自己手寫動(dòng)態(tài)代理生成的代理類。
解開疑惑
好了窍帝,先是自己手寫了一個(gè)閹割版的動(dòng)態(tài)代理努潘,然后簡(jiǎn)單的看了下JDK動(dòng)態(tài)代理源碼,也看了下JDK動(dòng)態(tài)代理生成的代理類坤学。這樣疯坤,就可以解開上面的三個(gè)疑惑了:
- 類加載器是干嘛的:其一:JDK內(nèi)部需要通過(guò)類加載作為緩存的key 其二:需要類加載器生成class
- 為什么需要接口:因?yàn)樯傻拇眍愋枰獙?shí)現(xiàn)這個(gè)接口
- 為什么JDK動(dòng)態(tài)代理只能代理接口:因?yàn)樯傻拇眍愐呀?jīng)繼承了Proxy類,Java是單繼承的深浮,所以沒(méi)法再繼承另外一個(gè)類了压怠。
有一些博客上可能會(huì)說(shuō)cglib和JDK動(dòng)態(tài)代理的區(qū)別,cglib是通過(guò)操作字節(jié)碼去完成代理的略号,其實(shí)JDK動(dòng)態(tài)代理也操作了字節(jié)碼。
經(jīng)過(guò)這么一分析洋闽,相信大家對(duì)JDK動(dòng)態(tài)代理有了一個(gè)新的認(rèn)識(shí)玄柠。