動態(tài)代理
代理模式是設(shè)計模式中非常重要的一種類型屑咳,而設(shè)計模式又是編程中非常重要的知識點萨赁,特別是在業(yè)務系統(tǒng)的重構(gòu)中,更是有舉足輕重的地位兆龙。代理模式從類型上來說杖爽,可以分為靜態(tài)代理和動態(tài)代理兩種類型。
在解釋動態(tài)代理之前我們先理解一下靜態(tài)代理:
首先你要明白靜態(tài)代理的作用
我們有一個字體提供類紫皇,有多種實現(xiàn)(從磁盤慰安,從網(wǎng)絡(luò),從系統(tǒng))
public interface FontProvider {
Font getFont(String name);
}
public abstract class ProviderFactory {
public static FontProvider getFontProvider() {
return new FontProviderFromDisk();
}
}
public class Main() {
public static void main(String[] args) {
FontProvider fontProvider = ProviderFactory.getFontProvider();
Font font = fontProvider.getFont("微軟雅黑");
......
}
}
現(xiàn)在我們希望給他加上一個緩存功能聪铺,我們可以用靜態(tài)代理來完成
public class CachedFontProvider implements FontProvider {
private FontProvider fontProvider;
private Map<String, Font> cached;
public CachedFontProvider(FontProvider fontProvider) {
this.fontProvider = fontProvider;
}
public Font getFont(String name) {
Font font = cached.get(name);
if (font == null) {
font = fontProvider.getFont(name);
cached.put(name, font);
}
return font;
}
}
/* 對工廠類進行相應修改化焕,代碼使用處不必進行任何修改。
這也是面向接口編程以及工廠模式的一個好處 */
public abstract class ProviderFactory {
public static FontProvider getFontProvider() {
return new CachedFontProvider(new FontProviderFromDisk());
}
}
當然铃剔,我們直接修改FontProviderFromDisk類也可以實現(xiàn)目的撒桨,但是我們還有FontProviderFromNet, FontProviderFromSystem等多種實現(xiàn)類,一一修改太過繁瑣且易出錯键兜。況且將來還可能添加日志凤类,權(quán)限檢查,異常處理等功能顯然用代理類更好一點普气。
然而今天的重點是:我們都知道牛逼轟轟的Spring AOP的實現(xiàn)的一種方式是使用JDK的動態(tài)代理(另一種是cglib)谜疤,大部分人也會用jdk的動態(tài)代理,不過沒有研究過jdk的動態(tài)代理到底是怎么實現(xiàn)的。今天就來揭開他的神秘面紗夷磕;
1. 原理源碼剖析
首先我們先來講一下JDK動態(tài)代理的實現(xiàn)原理
1.拿到被代理對象的引用苇侵,然后獲取他的接口
2.JDK代理重新生成一個類,同時實現(xiàn)我們給的代理對象所實現(xiàn)的接口
3.把被代理對象的引用拿到了
4.重新動態(tài)生成一個class字節(jié)碼
5.然后編譯
然后先實現(xiàn)一個動態(tài)代理企锌,代碼很簡單了榆浓,就是實現(xiàn)
java.lang.reflect.InvocationHandler
接口,并使用
java.lang.reflect.Proxy.newProxyInstance()
方法生成代理對象
/**
* @author mark
* @date 2018/3/30
*/
public class JdkInvocationHandler implements InvocationHandler {
private ProductService target;
public Object getInstance(ProductService target){
this.target = target;
Class clazz = this.target.getClass();
// 參數(shù)1:被代理類的類加載器 參數(shù)2:被代理類的接口 參數(shù)3
return Proxy.newProxyInstance(clazz.getClassLoader(),
clazz.getInterfaces(),
this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
String currentDate = simpleDateFormat.format(new Date());
System.out.println("日期【"+currentDate + "】添加了一款產(chǎn)品");
return method.invoke(this.target,args);
}
}
被代理接口和實現(xiàn)
/**
* 模仿產(chǎn)品Service
* @author mark
* @date 2018-03-30
*/
public interface ProductService {
/**
* 添加產(chǎn)品
* @param productName
*/
void addProduct(String productName);
}
/**
* @author mark
* @date 2018/3/30
*/
public class ProductServiceImpl implements ProductService{
public void addProduct(String productName) {
System.out.println("正在添加"+productName);
}
}
測試類
public class Test {
public static void main(String[] args) throws Exception {
ProductService productService = new ProductServiceImpl();
ProductService proxy = (ProductService) new JdkInvocationHandler().getInstance(productService);
proxy.addProduct("iphone");
// 這里我們將jdk生成的代理類輸出了出來撕攒,方便后面分析使用
byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{productService.getClass()});
FileOutputStream os = new FileOutputStream("Proxy0.class");
os.write(bytes);
os.close();
}
}
結(jié)果輸出
日期【2018-03-30】添加了一款產(chǎn)品
正在添加iphone
Process finished with exit code 0
上面我們實現(xiàn)動態(tài)動態(tài)代理的時候輸出了代理類的字節(jié)碼文件陡鹃,現(xiàn)在來看一下字節(jié)碼文件反編譯過后的內(nèi)容
import com.gwf.jdkproxy.ProductServiceImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
// 繼承了Proxy類
public final class $Proxy0 extends Proxy implements ProductServiceImpl {
private static Method m1;
private static Method m8;
private static Method m2;
private static Method m3;
private static Method m5;
private static Method m4;
private static Method m7;
private static Method m9;
private static Method m0;
private static Method m6;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
....
....
/**
* 這里是代理類實現(xiàn)的被代理對象的接口的相同方法
*/
public final void addProduct(String var1) throws {
try {
// super.h 對應的是父類的h變量,他就是Proxy.nexInstance方法中的InvocationHandler參數(shù)
// 所以這里實際上就是使用了我們自己寫的InvocationHandler實現(xiàn)類的invoke方法
super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final Class getClass() throws {
try {
return (Class)super.h.invoke(this, m7, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
....
....
// 在靜態(tài)構(gòu)造塊中抖坪,代理類通過反射獲取了被代理類的詳細信息萍鲸,比如各種方法
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m8 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("notify");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("addProduct", Class.forName("java.lang.String"));
m5 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("wait", Long.TYPE);
m4 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("wait", Long.TYPE, Integer.TYPE);
m7 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("getClass");
m9 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("notifyAll");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m6 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("wait");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
補充一下上面代母注釋中的super.h
protected InvocationHandler h;
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
// 這個方法是Proxy的newProxyInstance方法,主要就是生成了上面的動態(tài)字節(jié)碼文件
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
// 重點看這里擦俐,將我們傳來的InvocationHandler參數(shù)穿給了構(gòu)造函數(shù)
return cons.newInstance(new Object[]{h});
} 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);
}
}
以上就是jdk動態(tài)代理的內(nèi)部實現(xiàn)過程脊阴,最后再次將上面的原理聲明一遍,強化記憶
1.拿到被代理對象的引用蚯瞧,然后獲取他的接口 (Proxy.getInstance方法)
2.JDK代理重新生成一個類嘿期,同時實現(xiàn)我們給的代理對象所實現(xiàn)的接口 (上面的反編譯文件中實現(xiàn)了同樣的接口)
3.把被代理對象的引用拿到了(上面被代理對象中在靜態(tài)代碼塊中通過反射獲取到的信息,以及我們實現(xiàn)的JdkInvocationHandler中的target)
4.重新動態(tài)生成一個class字節(jié)碼
5.然后編譯
2.自己手寫一個動態(tài)代理
(聲明:本代碼只用作實例埋合,很多細節(jié)沒有考慮進去备徐,比如,多接口的代理類甚颂,Object類的其他默認方法的代理蜜猾,為確保原汁原味,一些模板引擎和commons工具類也沒有使用振诬;覺得不足的老鐵們可以隨意完善蹭睡,記得評論區(qū)留言完善方法哦)
我們使用jdk代理的類名和方法名定義,已經(jīng)執(zhí)行思路赶么,但是所有的實現(xiàn)都自己來寫肩豁;
首先先定義出類結(jié)構(gòu)
/**
* 自定義類加載器
* @author gaowenfeng
* @date 2018/3/30
*/
public class MyClassLoader extends ClassLoader {
/**
* 通過類名稱加載類字節(jié)碼文件到JVM中
* @param name 類名
* @return 類的Class獨享
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
return super.findClass(name);
}
}
/**
* @desc 自己實現(xiàn)的代理類,用來生成字節(jié)碼文件禽绪,并動態(tài)加載到JVM中
* @author gaowenfeng
* @date 2018/3/30
*/
public class MyProxy {
/**
* 生成代理對象
* @param loader 類加載器蓖救,用于加載被代理類的類文件
* @param interfaces 被代理類的接口
* @param h 自定義的InvocationHandler接口,用于具體代理方法的執(zhí)行
* @return 返回被代理后的代理對象
* @throws IllegalArgumentException
*/
public static Object newProxyInstance(MyClassLoader loader,
Class<?>[] interfaces,
MyInvocationHandler h)
throws IllegalArgumentException{
/**
* 1.生成代理類的源代碼
* 2.將生成的源代碼輸出到磁盤,保存為.java文件
* 3.編譯源代碼印屁,并生成.java文件
* 4.將class文件中的內(nèi)容循捺,動態(tài)加載到JVM中
* 5.返回被代理后的代理對象
*/
return null;
}
}
/**
* 自定義類加載器
* @author gaowenfeng
* @date 2018/3/30
*/
public class MyClassLoader extends ClassLoader {
/**
* 通過類名稱加載類字節(jié)碼文件到JVM中
* @param name 類名
* @return 類的Class獨享
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
return super.findClass(name);
}
}
/**
* @author gaowenfeng
* @date 2018/3/30
*/
public class CustomInvocationHandler implements MyInvocationHandler {
private ProductService target;
public Object getInstance(ProductService target){
this.target = target;
Class clazz = this.target.getClass();
// 參數(shù)1:被代理類的類加載器 參數(shù)2:被代理類的接口 參數(shù)3
// 這里的MyClassLoader先用new的方式保證編譯不報錯,后面會修改
return MyProxy.newProxyInstance(new MyClassLoader(),
clazz.getInterfaces(),
this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
String currentDate = simpleDateFormat.format(new Date());
System.out.println("日期【"+currentDate + "】添加了一款產(chǎn)品");
return method.invoke(this.target,args);
}
}
接下來我們來按照步驟一步一步的完善我們的類
生成代理類的源文件
/**
* 生成代理類的源代碼
* @return
*/
private static String genSesource(Class<?> interfaces){
StringBuilder src = new StringBuilder();
src.append("package com.gwf.custom;").append(ln)
.append("import java.lang.reflect.Method;").append(ln)
.append("public class $Proxy0 implements ").append(interfaces.getName()).append("{").append(ln)
.append("private MyInvocationHandler h;").append(ln)
.append("public $Proxy0(MyInvocationHandler h){").append(ln)
.append("this.h=h;").append(ln)
.append("}").append(ln);
for(Method method:interfaces.getMethods()){
src.append("public ").append(method.getReturnType()).append(" ").append(method.getName()).append("() {").append(ln)
.append("try {").append(ln)
.append("Method m = ").append(interfaces.getName()).append(".class.getMethod(\"").append(method.getName()).append("\");").append(ln)
.append("this.h.invoke(this, m, new Object[]{});").append(ln)
.append("}catch (Throwable e){").append(ln)
.append("e.printStackTrace();").append(ln)
.append("}").append(ln)
.append("}").append(ln);
}
src.append("}");
return src.toString();
}
2.將源文件保存到本地
// 1.生成代理類的源代碼
String src = genSesource(interfaces);
// 2.將生成的源代碼輸出到磁盤雄人,保存為.java文件
String path = MyProxy.class.getResource("").getPath();
File file = new File(path+"$Proxy0.java");
FileWriter fw = new FileWriter(file);
fw.write(src);
fw.close();
3.編譯源代碼从橘,并生成.java文件
// 3.編譯源代碼念赶,并生成.java文件
// 獲取java編譯器
JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
// 標注java文件管理器,用來獲取java字節(jié)碼文件
StandardJavaFileManager manager = javaCompiler.getStandardFileManager(null,null,null);
Iterable iterable = manager.getJavaFileObjects(file);
// 創(chuàng)建task恰力,通過java字節(jié)碼文件將類信息加載到JVM中
JavaCompiler.CompilationTask task = javaCompiler.getTask(null,manager,null,null,null,iterable);
// 開始執(zhí)行task
task.call();
// 關(guān)閉管理器
manager.close();
4.將class文件中的內(nèi)容叉谜,動態(tài)加載到JVM中
public class MyClassLoader extends ClassLoader {
private String baseDir;
public MyClassLoader(){
this.baseDir = MyClassLoader.class.getResource("").getPath();
}
/**
* 通過類名稱加載類字節(jié)碼文件到JVM中
* @param name 類名
* @return 類的Class獨享
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 獲取類名
String className = MyClassLoader.class.getPackage().getName()+"."+name;
if(null == baseDir) {
throw new ClassNotFoundException();
}
// 獲取類文件
File file = new File(baseDir,name+".class");
if(!file.exists()){
throw new ClassNotFoundException();
}
// 將類文件轉(zhuǎn)換為字節(jié)數(shù)組
try(
FileInputStream in = new FileInputStream(file);
ByteArrayOutputStream out = new ByteArrayOutputStream();
){
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer))!=-1){
out.write(buffer,0,len);
}
// 調(diào)用父類方法生成class實例
return defineClass(className,out.toByteArray(),0,out.size());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
5.返回被代理后的代理對象
Constructor c = proxyClass.getConstructor(MyInvocationHandler.class);
return c.newInstance(h);
最后看一下總體的MyProxy類 的 newProxyInstance方法
public static Object newProxyInstance(MyClassLoader loader,
Class<?> interfaces,
MyInvocationHandler h)
throws IllegalArgumentException{
/**
* 1.生成代理類的源代碼
* 2.將生成的源代碼輸出到磁盤,保存為.java文件
* 3.編譯源代碼踩萎,并生成.java文件
* 4.將class文件中的內(nèi)容停局,動態(tài)加載到JVM中
* 5.返回被代理后的代理對象
*/
try {
// 1.生成代理類的源代碼
String src = genSesource(interfaces);
// 2.將生成的源代碼輸出到磁盤,保存為.java文件
String path = MyProxy.class.getResource("").getPath();
File file = new File(path+"$Proxy0.java");
FileWriter fw = new FileWriter(file);
fw.write(src);
fw.close();
// 3.編譯源代碼香府,并生成.java文件
JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager manager = javaCompiler.getStandardFileManager(null,null,null);
Iterable iterable = manager.getJavaFileObjects(file);
JavaCompiler.CompilationTask task = javaCompiler.getTask(null,manager,null,null,null,iterable);
task.call();
manager.close();
// 4.將class文件中的內(nèi)容董栽,動態(tài)加載到JVM中
Class proxyClass = loader.findClass("$Proxy0");
// 5.返回被代理后的代理對象
Constructor c = proxyClass.getConstructor(MyInvocationHandler.class);
return c.newInstance(h);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
激動人心的時刻:測試運行
public class CustomClient {
public static void main(String[] args){
ProductService productService = new ProductServiceImpl();
ProductService proxy = (ProductService) new CustomInvocationHandler().getInstance(productService);
proxy.addProduct();
}
}
運行結(jié)果
日期【2018-03-30】添加了一款產(chǎn)品
正在添加iphone
Process finished with exit code 0
總結(jié):以上通過理解jdk動態(tài)代理的原理,自己手寫了一個動態(tài)代理企孩,里面涉及到的重點主要是代理類字節(jié)碼的生成(這里采用通過反射強行生成源文件并編譯的方法锭碳,其實應該可以直接生成字節(jié)碼文件的,有興趣的同學可以嘗試)和將生成的類動態(tài)加載到JVM中(本次試驗由于測試勿璃,比較簡單擒抛,直接將類名硬編碼到了系統(tǒng)里,正常應該是自動加載)补疑,雖然還不完善歧沪,但是對于理解原理應該是有很多幫助了,歡迎同學們評論區(qū)留言評論給出更好的建議
在互聯(lián)網(wǎng)公司面試中癣丧,架構(gòu)的底層一定是面試官會問問的問題槽畔,針對面試官一般會提到的問題,我錄制了一些分布式胁编,微服務,性能優(yōu)化等技術(shù)點底層原理的錄像視頻鳞尔,加群895244712
可以免費獲取這些錄像嬉橙,里面還有些分布式,微服務寥假,性能優(yōu)化市框,spring,MyBatis的等源碼知識點的錄像視頻糕韧。