代理模式是設計模式中很是重要的一種類型,而設計模式又是編程中很是重要的知識點,特別是在業務系統的重構中,更是有舉足輕重的地位。代理模式從類型上來講,能夠分爲靜態代理和動態代理兩種類型。java
在解釋動態代理以前咱們先理解一下靜態代理:面試
咱們有一個字體提供類,有多種實現(從磁盤,從網絡,從系統)spring
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("微軟雅黑"); ...... } }
如今咱們但願給他加上一個緩存功能,咱們能夠用靜態代理來完成編程
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類也能夠實現目的,可是咱們還有FontProviderFromNet, FontProviderFromSystem等多種實現類,一一修改太過繁瑣且易出錯。何況未來還可能添加日誌,權限檢查,異常處理等功能顯然用代理類更好一點。設計模式
然而今天的重點是:咱們都知道牛逼轟轟的Spring AOP的實現的一種方式是使用JDK的動態代理(另外一種是cglib),大部分人也會用jdk的動態代理,不過沒有研究過jdk的動態代理究竟是怎麼實現的。今天就來揭開他的神祕面紗;數組
1.拿到被代理對象的引用,而後獲取他的接口
2.JDK代理從新生成一個類,同時實現咱們給的代理對象所實現的接口
3.把被代理對象的引用拿到了
4.從新動態生成一個class字節碼
5.而後編譯緩存
而後先實現一個動態代理,代碼很簡單了,就是實現
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(); // 參數1:被代理類的類加載器 參數2:被代理類的接口 參數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 + "】添加了一款產品"); return method.invoke(this.target,args); } }
被代理接口和實現網絡
/** * 模仿產品Service * @author mark * @date 2018-03-30 */ public interface ProductService { /** * 添加產品 * @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); } }
測試類app
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(); } }
結果輸出
日期【2018-03-30】添加了一款產品 正在添加iphone Process finished with exit code 0
上面咱們實現動態動態代理的時候輸出了代理類的字節碼文件,如今來看一下字節碼文件反編譯事後的內容
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); } .... .... /** * 這裏是代理類實現的被代理對象的接口的相同方法 */ public final void addProduct(String var1) throws { try { // super.h 對應的是父類的h變量,他就是Proxy.nexInstance方法中的InvocationHandler參數 // 因此這裏實際上就是使用了咱們本身寫的InvocationHandler實現類的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); } } .... .... // 在靜態構造塊中,代理類經過反射獲取了被代理類的詳細信息,好比各類方法 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方法,主要就是生成了上面的動態字節碼文件 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參數穿給了構造函數 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動態代理的內部實現過程,最後再次將上面的原理聲明一遍,強化記憶
1.拿到被代理對象的引用,而後獲取他的接口 (Proxy.getInstance方法)
2.JDK代理從新生成一個類,同時實現咱們給的代理對象所實現的接口 (上面的反編譯文件中實現了一樣的接口)
3.把被代理對象的引用拿到了(上面被代理對象中在靜態代碼塊中經過反射獲取到的信息,以及咱們實現的JdkInvocationHandler中的target)
4.從新動態生成一個class字節碼
5.而後編譯
(聲明:本代碼只用做實例,不少細節沒有考慮進去,好比,多接口的代理類,Object類的其餘默認方法的代理,爲確保原汁原味,一些模板引擎和commons工具類也沒有使用;以爲不足的老鐵們能夠隨意完善,記得評論區留言完善方法哦)
咱們使用jdk代理的類名和方法名定義,已經執行思路,可是全部的實現都本身來寫;
首先先定義出類結構
/** * 自定義類加載器 * @author gaowenfeng * @date 2018/3/30 */ public class MyClassLoader extends ClassLoader { /** * 經過類名稱加載類字節碼文件到JVM中 * @param name 類名 * @return 類的Class獨享 * @throws ClassNotFoundException */ @Override protected Class<?> findClass(String name) throws ClassNotFoundException { return super.findClass(name); } }
/** * @desc 本身實現的代理類,用來生成字節碼文件,並動態加載到JVM中 * @author gaowenfeng * @date 2018/3/30 */ public class MyProxy { /** * 生成代理對象 * @param loader 類加載器,用於加載被代理類的類文件 * @param interfaces 被代理類的接口 * @param h 自定義的InvocationHandler接口,用於具體代理方法的執行 * @return 返回被代理後的代理對象 * @throws IllegalArgumentException */ public static Object newProxyInstance(MyClassLoader loader, Class<?>[] interfaces, MyInvocationHandler h) throws IllegalArgumentException{ /** * 1.生成代理類的源代碼 * 2.將生成的源代碼輸出到磁盤,保存爲.java文件 * 3.編譯源代碼,並生成.java文件 * 4.將class文件中的內容,動態加載到JVM中 * 5.返回被代理後的代理對象 */ return null; } }
/** * 自定義類加載器 * @author gaowenfeng * @date 2018/3/30 */ public class MyClassLoader extends ClassLoader { /** * 經過類名稱加載類字節碼文件到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(); // 參數1:被代理類的類加載器 參數2:被代理類的接口 參數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 + "】添加了一款產品"); 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字節碼文件 StandardJavaFileManager manager = javaCompiler.getStandardFileManager(null,null,null); Iterable iterable = manager.getJavaFileObjects(file); // 建立task,經過java字節碼文件將類信息加載到JVM中 JavaCompiler.CompilationTask task = javaCompiler.getTask(null,manager,null,null,null,iterable); // 開始執行task task.call(); // 關閉管理器 manager.close();
4.將class文件中的內容,動態加載到JVM中
public class MyClassLoader extends ClassLoader { private String baseDir; public MyClassLoader(){ this.baseDir = MyClassLoader.class.getResource("").getPath(); } /** * 經過類名稱加載類字節碼文件到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(); } // 將類文件轉換爲字節數組 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); } // 調用父類方法生成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文件中的內容,動態加載到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文件中的內容,動態加載到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(); } }
運行結果
日期【2018-03-30】添加了一款產品 正在添加iphone Process finished with exit code 0
總結:以上經過理解jdk動態代理的原理,本身手寫了一個動態代理,裏面涉及到的重點主要是代理類字節碼的生成(這裏採用經過反射強行生成源文件並編譯的方法,其實應該能夠直接生成字節碼文件的,有興趣的同窗能夠嘗試)和將生成的類動態加載到JVM中(本次試驗因爲測試,比較簡單,直接將類名硬編碼到了系統裏,正常應該是自動加載),雖然還不完善,可是對於理解原理應該是有不少幫助了,歡迎同窗們評論區留言評論給出更好的建議
在互聯網公司面試中,動態代理必定是面試官會問的問題,針對面試官通常會提到的問題,我錄製了一些分佈式,微服務,性能優化等技術點底層原理的錄像視頻,加羣895244712
能夠免費獲取這些錄像,裏面還有些分佈式,微服務,性能優化,spring,MyBatis的等源碼知識點的錄像視頻。