設計模式 – 動態代理原理及模仿JDK Proxy 寫一個屬於自己的動態代理
- 2019 年 10 月 15 日
- 筆記
本篇文章程式碼內容較多,講的可能會有些粗糙,大家可以選擇性閱讀。
本篇文章的目的是簡單的分析動態代理的原理及模仿
JDK Proxy
手寫一個動態代理以及對幾種代理做一個總結。
對於代理模式的介紹和講解,網上已經有很多優質的文章,我這裡就不會再過多的介紹了,這裡推薦幾篇優質的文章作為參考:
另外,我的 github 倉庫對應目錄中也有相關的基礎示例程式碼:https://github.com/eamonzzz/java-advanced…
JDK Proxy 動態代理
動態代理的概念這裡就不再闡述了;動態代理相對於靜態代理來說,它的功能更加強大,隨著業務的擴展,適應性更強。
在說動態代理原理之前,我們還是來看看動態代理的一般使用。
使用
本篇文章的使用示例,是以一個最為簡單的代理模式的程式碼為例,相信大家在學習或了解代理模式的時候都有看到或者接觸過這些程式碼。
- 先創建一個
Subject
主體抽象介面:
/** * @author eamon.zhang * @date 2019-10-09 下午4:06 */ public interface Subject { void request(); }
- 再創建一個真實的主體
RealSubject
來處理我們的真實的邏輯:
/** * @author eamon.zhang * @date 2019-10-09 下午4:06 */ public class RealSubject implements Subject { @Override public void request() { System.out.println("真實處理邏輯!"); } }
- 在不修改
RealSubject
類的情況下,如果我們要實現在執行RealSubject
類中request()
方法之前或之後執行一段邏輯的話,該怎麼實現呢?這就得創建一個代理類,來達到增強原有程式碼的目的。所以現在創建一個 JDK 動態代理類RealSubjectJDKDynamicProxy
:
/** * @author eamon.zhang * @date 2019-10-09 下午4:08 */ public class RealSubjectJDKDynamicProxy implements InvocationHandler { // 被代理對象的引用 private Object target; // 通過構造器傳入對象引用 public RealSubjectJDKDynamicProxy(Object target) { this.target = target; } // 獲得 JDK 動態代理創建的代理對象 public Object getInstance() { Class<?> clazz = target.getClass(); return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this); } @Override public Object invoke(Object o, Method method, Object[] objects) throws Throwable { before(); // 代理執行被代理對象的相應方法 Object invoke = method.invoke(target, objects); after(); return invoke; } private void before() { System.out.println("前置增強!"); } private void after() { System.out.println("後置增強!"); } }
- 測試程式碼:
@Test public void test(){ Subject realSubject = new RealSubject(); RealSubjectJDKDynamicProxy proxy = new RealSubjectJDKDynamicProxy(realSubject); Subject instance = (Subject) proxy.getInstance(); instance.request(); System.out.println(realSubject.getClass()); System.out.println(instance.getClass()); }
- 測試結果
前置增強! 真實處理邏輯! 後置增強! class com.eamon.javadesignpatterns.proxy.dynamic.jdk.RealSubject class com.sun.proxy.$Proxy8
從結果來看,上面的程式碼已經達到了我們的增強的目的。
原理分析
不知道大家有沒有注意到上面的測試程式碼中,最後兩行我將代理之前和代理之後的class
對象給列印了出來;並且發現,這兩個對象並非同一個,最重要的是,經過代理之後的對象的Subject
是com.sun.proxy.$Proxy8
而不是com.eamon.javadesignpatterns.proxy.dynamic.jdk.RealSubject
或者com.eamon.javadesignpatterns.proxy.dynamic.jdk.Subject
,那麼這個instance
到底是從哪裡來?帶著這個疑問,我們來通過 JDK Proxy 源碼來分析一下:
我們跟進RealSubjectJDKDynamicProxy
類中的Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
方法:
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 { ... 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; } }); } return cons.newInstance(new Object[]{h}); } catch (IllegalAccessException|InstantiationException e) { throw new InternalError(e.toString(), e); } ... }
發現在newProxyInstance
方法中調用了getProxyClass0(loader, intfs)
方法,我們跟進去這個方法看一下:
/** * Generate a proxy class. Must call the checkProxyAccess method * to perform permission checks before calling this. */ private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) { if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } // If the proxy class defined by the given loader implementing // the given interfaces exists, this will simply return the cached copy; // otherwise, it will create the proxy class via the ProxyClassFactory return proxyClassCache.get(loader, interfaces); }
程式碼邏輯很簡單,做了兩個事情:
- 檢查類的介面數量是否超過
65535
,介面個數用 2 個byte
存儲,最大支援65535
個。 - 從
proxyClassCache
快取中去取,從注釋中可知,如果快取沒有就會調用ProxyClassFactory
去創建。
我們現在就來簡單分析一下proxyClassCache.get(loader, interfaces)
裡面的邏輯:
public V get(K key, P parameter) { Objects.requireNonNull(parameter); expungeStaleEntries(); Object cacheKey = CacheKey.valueOf(key, refQueue); // lazily install the 2nd level valuesMap for the particular cacheKey ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey); if (valuesMap == null) { ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey, valuesMap = new ConcurrentHashMap<>()); if (oldValuesMap != null) { valuesMap = oldValuesMap; } } // create subKey and retrieve the possible Supplier<V> stored by that // subKey from valuesMap Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter)); Supplier<V> supplier = valuesMap.get(subKey); Factory factory = null; // 這裡是一個 while(true) while (true) { // 如果創建 factory(這裡指ProxyClassFactory) 成功,就調用 factory.get()方法 if (supplier != null) { // supplier might be a Factory or a CacheValue<V> instance // V value = supplier.get(); if (value != null) { return value; } } // else no supplier in cache // or a supplier that returned null (could be a cleared CacheValue // or a Factory that wasn't successful in installing the CacheValue) // lazily construct a Factory if (factory == null) { factory = new Factory(key, parameter, subKey, valuesMap); } if (supplier == null) { supplier = valuesMap.putIfAbsent(subKey, factory); if (supplier == null) { // successfully installed Factory supplier = factory; } // else retry with winning supplier } else { if (valuesMap.replace(subKey, supplier, factory)) { // successfully replaced // cleared CacheEntry / unsuccessful Factory // with our Factory supplier = factory; } else { // retry with current supplier supplier = valuesMap.get(subKey); } } } }
程式碼可能有點長,其實邏輯就是為了調用ProxyClassFactory.apply()
去生成代理類。我們從while(true)
處將程式碼分割成兩個部分來看:
- 前半部分,是從快取中去取
ProxyClassFactory
,如果創建成功了,則可以取到(快取中的 key 這裡不分析了) - 然後看
while(true)
程式碼塊中的邏輯,if (supplier != null)
這個判斷,如果快取中創建了ProxyClassFactory
就會執行supplier.get()
並且終止循環;如果沒有,則會執行new Factory(key, parameter, subKey, valuesMap);
去創建factory
,然後將其放入快取supplier
中,然後繼續循環,這個時候就會執行if (supplier != null)
程式碼塊中的邏輯,我們再來分析一下這個程式碼塊裡面的程式碼:
if (supplier != null) { // supplier might be a Factory or a CacheValue<V> instance V value = supplier.get(); if (value != null) { return value; } }
跟進 supplier.get()
方法去看一下,我們從上面的分析可以知道這裡的supplier
其實就是一個Factory
,所以我們看Factory
的實現,重點看get()
方法:
private final class Factory implements Supplier<V> { ... @Override public synchronized V get() { // serialize access ... // create new value V value = null; try { value = Objects.requireNonNull(valueFactory.apply(key, parameter)); } finally { if (value == null) { // remove us on failure valuesMap.remove(subKey, this); } } // the only path to reach here is with non-null value assert value != null; // wrap value with CacheValue (WeakReference) CacheValue<V> cacheValue = new CacheValue<>(value); // put into reverseMap reverseMap.put(cacheValue, Boolean.TRUE); // try replacing us with CacheValue (this should always succeed) if (!valuesMap.replace(subKey, this, cacheValue)) { throw new AssertionError("Should not reach here"); } // successfully replaced us with new CacheValue -> return the value // wrapped by it return value; } }
我們注意到,程式碼中的重點是在Objects.requireNonNull(valueFactory.apply(key, parameter));
,那這個程式碼中的valueFactory
是什麼呢?我們在Proxy
中,來看一下proxyClassCache
的定義
private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
WeakCache
中第二個參數是new ProxyClassFactory()
,再來看一下對應的構造器:
public WeakCache(BiFunction<K, P, ?> subKeyFactory, BiFunction<K, P, V> valueFactory) { this.subKeyFactory = Objects.requireNonNull(subKeyFactory); this.valueFactory = Objects.requireNonNull(valueFactory); }
這時候明白了嗎?其實 valueFactory
就是ProxyClassFactory()
明白了這一點,就來分析一下valueFactory.apply(key, parameter)
到底執行了什麼?我們直接看ProxyClassFactory
的程式碼
private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>> { // prefix for all proxy class names private static final String proxyClassNamePrefix = "$Proxy"; // next number to use for generation of unique proxy class names private static final AtomicLong nextUniqueNumber = new AtomicLong(); @Override public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { ... /* * Generate the specified proxy class. */ byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); try { return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { throw new IllegalArgumentException(e.toString()); } } }
縱觀全覽,不難分析,程式碼中其實就是在創建$Proxy
這個中間代理類,其中byte[] proxyClassFile
是程式碼塊中組裝完成之後的類的位元組碼文件數據,通過ProxyGenerator.generateProxyClass()
生成;然後通過classloader
動態載入位元組碼,並生成動態代理類的Class
實例,並返回。
我們再跟進ProxyGenerator.generateProxyClass()
方法,來看看在生成代理類過程中的處理邏輯,看重點程式碼:。
public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) { ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2); final byte[] var4 = var3.generateClassFile(); ... return var4; }
可以發現其程式碼調用了var3.generateClassFile()
去生成Class
文件,所以我們跟進generateClassFile()
方法,看重點內容:
private byte[] generateClassFile() { this.addProxyMethod(hashCodeMethod, Object.class); this.addProxyMethod(equalsMethod, Object.class); this.addProxyMethod(toStringMethod, Object.class); Class[] var1 = this.interfaces; int var2 = var1.length; int var3; Class var4; for(var3 = 0; var3 < var2; ++var3) { var4 = var1[var3]; Method[] var5 = var4.getMethods(); int var6 = var5.length; for(int var7 = 0; var7 < var6; ++var7) { Method var8 = var5[var7]; this.addProxyMethod(var8, var4); } } ... }
程式碼有點長,這裡就不全部展開了,有興趣的朋友可以跟進去詳細看一下。從程式碼中我們大致可以看出來,在生成代理類的過程中,還添加了hashCode、equals、toString
這三個方法,然後後面的邏輯就是將代理對象中的所有介面進行迭代,將其所有的方法都重新生成代理方法;然後生成位元組碼。
最後再將代理類載入到JVM
中。
看一下JDK Proxy
生成的代理類$Proxy
我們通過下面這段程式碼,將$Proxy
文件輸出到文件:
@Test public void test1(){ System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); RealSubject realSubject = new RealSubject(); RealSubjectJDKDynamicProxy proxy = new RealSubjectJDKDynamicProxy(realSubject); Subject instance = (Subject) proxy.getInstance(); try { byte[] proxychar= ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Subject.class}); OutputStream outputStream = new FileOutputStream("/Users/eamon.zhang/IdeaProjects/own/java-advanced/01.DesignPatterns/design-patterns/"+instance.getClass().getSimpleName()+".class"); outputStream.write(proxychar); outputStream.flush(); outputStream.close(); } catch (IOException e) { e.printStackTrace(); } instance.request(); System.out.println(instance.getClass()); }
通過IDEA
工具查看$Proxy0
,印證一下我們之前的分析:
public final class $Proxy0 extends Proxy implements Subject { private static Method m1; private static Method m2; private static Method m3; private static Method m0; public $Proxy0(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final void request() 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 { try { return (Integer)super.h.invoke(this, m0, (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")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m3 = Class.forName("com.eamon.javadesignpatterns.proxy.dynamic.jdk.Subject").getMethod("request"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
總結
總結一下JDK Proxy
的實現步驟:
- 拿到被代理對象的引用,並獲取它的所有介面(通過反射)
JDK Proxy
類重新生成一個新的類,同時新的類要實現被代理類的所有實現的介面,還有hashCode、equals、toString
這三個方法- 動態生成
Java
程式碼,把新加的業務邏輯方法由一定的邏輯程式碼去調用(在程式碼中體現) - 編譯新生成的
Java
程式碼的.class
文件 - 重新載入到
JVM
中運行
模擬手寫 JDK Proxy
在明白了上面的原理之後,其實我們就可以嘗試手動來實現一個JDK Proxy
:
我們參照JDK Proxy
實現原理分析一下需要動手編寫哪些內容:
- 首先我們需要有一個代理類
MimeProxy
- 然後從代理類出發,需要有
newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this)
這一個方法,方法參數為:(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
,所以我們需要創建一個ClassLoader
、InvocationHandler
;
下面來一步一步創建:
- 先創建
MimeClassLoader
類,繼承自ClassLoader
,並重寫findClass()
方法:
/** * @author eamon.zhang * @date 2019-10-10 下午2:47 */ public class MimeClassLoader extends ClassLoader { private Object target; public MimeClassLoader(Object target) { this.target = target; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { String classname = target.getClass().getPackage().getName() + "." + name; String filePath = MimeClassLoader.class.getResource("").getPath() + name + ".class"; try { URI uri = new URI("file:///" + filePath); Path path = Paths.get(uri); File file = path.toFile(); if (file.exists()) { byte[] fileBytes = Files.readAllBytes(path); return defineClass(classname, fileBytes, 0, fileBytes.length); } } catch (Exception e) { e.printStackTrace(); } return null; } }
- 創建
MimeInvocationHandler
類:
/** * @author eamon.zhang * @date 2019-10-10 下午2:46 */ public interface MimeInvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
- 創建
MimeProxy
類,這個類就是用來組裝成代理類,並載入到JVM
,然後返回這個代理對象:
/** * @author eamon.zhang * @date 2019-10-10 下午3:08 */ public class MimeProxy { private static final String ln = "rn"; private static final String semi = ";"; private static Map<Class, Class> mappings = new HashMap<Class, Class>(); static { mappings.put(int.class, Integer.class); } public static Object newProxyInstance(MimeClassLoader loader, Class<?>[] interfaces, MimeInvocationHandler h) throws IllegalArgumentException { try { // 1. 動態生成 .java 文件 String src = generateSrc(interfaces); // System.out.println(src); // 2. java 文件輸出到磁碟 String filePath = MimeProxy.class.getResource("").getPath(); // System.out.println(filePath); File f = new File(filePath + "$Proxy8.java"); // f.deleteOnExit(); FileWriter fw = new FileWriter(f); fw.write(src); fw.flush(); fw.close(); // 3. 把 java 文件編譯成 .class 文件 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager sjfm = compiler.getStandardFileManager(null, null, null); Iterable<? extends JavaFileObject> iterable = sjfm.getJavaFileObjects(f); JavaCompiler.CompilationTask task = compiler.getTask(null, sjfm, null, null, null, iterable); task.call(); sjfm.close(); // 4. 把.class 文件載入到jvm Class<?> proxyClass = loader.findClass("$Proxy8"); Constructor<?> c = proxyClass.getConstructor(MimeInvocationHandler.class); f.delete(); // 5. 返回位元組碼重組以後的新的代理對象 return c.newInstance(h); } catch (Exception e) { e.printStackTrace(); } return null; } /** * 生成 代理類 * * @param interfaces * @return */ private static String generateSrc(Class<?>[] interfaces) { // 這裡使用 StringBuffer 執行緒安全 StringBuffer sb = new StringBuffer(); sb.append("package ").append(interfaces[0].getPackage().getName()).append(semi).append(ln); sb.append("import ").append(interfaces[0].getName()).append(semi).append(ln); sb.append("import java.lang.reflect.*;").append(ln); sb.append("import ").append(interfaces[0].getPackage().getName()).append(".mimeproxy.MimeInvocationHandler;").append(ln); sb.append("public class $Proxy8 implements ").append(interfaces[0].getSimpleName()).append(" {").append(ln); sb.append("MimeInvocationHandler h;" + ln); sb.append("public $Proxy8(MimeInvocationHandler h) {").append(ln); sb.append("this.h = h;").append(ln); sb.append("}").append(ln); for (Method method : interfaces[0].getMethods()) { Class<?>[] params = method.getParameterTypes(); StringBuffer paramNames = new StringBuffer(); StringBuffer paramValues = new StringBuffer(); StringBuffer paramClasses = new StringBuffer(); for (Class<?> clazz : params) { String type = clazz.getName(); String paramName = toLowerFirstCase(clazz.getSimpleName()); paramNames.append(type).append(" ").append(paramName); paramValues.append(paramName); paramClasses.append(clazz.getName()).append(".class"); for (int i = 0; i < params.length; i++) { paramNames.append(","); paramValues.append(","); paramClasses.append(","); } } sb.append("public ").append(method.getReturnType().getName()).append(" ").append(method.getName()) .append("(").append(paramNames.toString()).append(") {").append(ln); sb.append("try {").append(ln); // Method m = interfaces[0].getName().class.getMethod(method.getName()),new Class[]{paramClasses.toString()}); sb.append("Method m = ").append(interfaces[0].getName()).append(".class.getMethod("") .append(method.getName()).append("", new Class[]{").append(paramClasses.toString()).append("});") .append(ln); // return this.h.invoke(this, m, new Object[]{paramValues}, method.getReturnType()); sb.append(hasReturnValue(method.getReturnType()) ? "return " : "") .append(getCaseCode("this.h.invoke(this,m,new Object[]{" + paramValues + "})", method.getReturnType())) .append(";") .append(ln); sb.append("} catch (Error _ex) {}").append(ln); sb.append("catch (Throwable e) {").append(ln); sb.append("throw new UndeclaredThrowableException(e);").append(ln); sb.append("}"); sb.append(getReturnEmptyCode(method.getReturnType())).append(ln); sb.append("}"); } sb.append("}").append(ln); return sb.toString(); } /** * 獲取返回值類型 * * @param returnClass * @return */ private static String getReturnEmptyCode(Class<?> returnClass) { if (mappings.containsKey(returnClass)) { return "return 0;"; } else if (returnClass == void.class) { return ""; } else { return "return null;"; } } /** * 拼接 invocationHandler 執行程式碼 * * @param code * @param returnClass * @return */ private static String getCaseCode(String code, Class<?> returnClass) { if (mappings.containsKey(returnClass)) { return "((" + mappings.get(returnClass).getName() + ")" + code + ")." + returnClass.getSimpleName() + "Value()"; } return code; } /** * 判斷是否有返回值 * * @param clazz * @return */ private static boolean hasReturnValue(Class<?> clazz) { return clazz != void.class; } /** * 首字母轉換為小寫 * * @param src * @return */ private static String toLowerFirstCase(String src) { char[] chars = src.toCharArray(); chars[0] += 32; return String.valueOf(chars); } }
這樣子就編寫了一個屬於自己的動態代理,當然,代理方法還不完善,只是針對本示例進行了編寫,有興趣的朋友可以試試將其改為更通用的程式碼。
CGlib 動態代理
下面來看一下 CGlib 的動態代理的使用
使用
先創建RealSubject
類,注意,這個類不用實現任何介面:
/** * @author eamon.zhang * @date 2019-10-09 下午4:22 */ public class RealSubject { public void request(){ System.out.println("真實處理邏輯!"); } }
然後創建RealSubjectCglibDynamicProxy
代理類,它必須實現MethodInterceptor
介面:
/** * @author eamon.zhang * @date 2019-10-09 下午4:23 */ public class RealSubjectCglibDynamicProxy implements MethodInterceptor { public Object getInstance(Class<?> clazz) { // 通過CGLIB動態代理獲取代理對象的過程 Enhancer enhancer = new Enhancer(); // 要把哪個設置為即將生成的新類父類 enhancer.setSuperclass(clazz); // 設置回調對象 enhancer.setCallback(this); // 創建代理對象 return enhancer.create(); } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { before(); Object invokeSuper = proxy.invokeSuper(obj, args); after(); return invokeSuper; } private void before() { System.out.println("前置增強!"); } private void after() { System.out.println("後置增強!"); } }
這樣,一個簡單的CGlib
動態代理實現就完成了,我們現在來創建測試程式碼:
@Test public void test(){ RealSubjectCglibDynamicProxy proxy = new RealSubjectCglibDynamicProxy(); RealSubject instance = (RealSubject) proxy.getInstance(RealSubject.class); instance.request(); }
測試結果:
前置增強! 真實處理邏輯! 後置增強!
原理分析
不管是JDK Proxy
還是CGlib
,他們的核心內容都是去創建代理類,所以我們只要去了解其創建代理類的過程就 OK 了。
從上面簡單的使用示例可以知道,要使用 CGlib 動態代理,代理類必須要實現MethodInterceptor
(方法攔截器),MethodInterceptor
介面源碼如下:
/** * General-purpose {@link Enhancer} callback which provides for "around advice". * @author Juozas Baliuka <a href="mailto:[email protected]">[email protected]</a> * @version $Id: MethodInterceptor.java,v 1.8 2004/06/24 21:15:20 herbyderby Exp $ */ public interface MethodInterceptor extends Callback { /** * All generated proxied methods call this method instead of the original method. * The original method may either be invoked by normal reflection using the Method object, * or by using the MethodProxy (faster). * @param obj "this", the enhanced object * @param method intercepted Method * @param args argument array; primitive types are wrapped * @param proxy used to invoke super (non-intercepted method); may be called * as many times as needed * @throws Throwable any exception may be thrown; if so, super method will not be invoked * @return any value compatible with the signature of the proxied method. Method returning void will ignore this value. * @see MethodProxy */ public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable; }
介面中只有一個intercept
方法,其中傳入的參數:
obj
表示增強的對象,即實現這個介面類的一個對象;method
表示要被攔截的方法;args
表示方法參數;proxy
表示要觸發父類的方法對象;
在創建代理對象的邏輯getInstance(Class<?> clazz)
中,調用了enhancer.create()
方法,我們跟進源碼看一下:
/** * Generate a new class if necessary and uses the specified * callbacks (if any) to create a new object instance. * Uses the no-arg constructor of the superclass. * @return a new instance */ public Object create() { classOnly = false; argumentTypes = null; return createHelper(); }
源碼注釋內容翻譯:如有必要,生成一個新類,並使用指定的回調(如果有)來創建一個新的對象實例。 使用的父類的參數的構造方法來實例化父類。
它的核心內容是在createHelper();
方法中:
private Object createHelper() { preValidate(); Object key = KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null, ReflectUtils.getNames(interfaces), filter == ALL_ZERO ? null : new WeakCacheKey<CallbackFilter>(filter), callbackTypes, useFactory, interceptDuringConstruction, serialVersionUID); this.currentKey = key; Object result = super.create(key); return result; }
preValidate()
方法的作用是,前置校驗,校驗callbackTypes、filter
是否為空,以及為空時的處理。
然後通過KEY_FACTORY.newInstance()
方法創建EnhancerKey
對象,並將其作為super.create(key)
方法的參數傳入,我們來看一下這個create()
方法,發現它是Enhancer
類的父類AbstractClassGenerator
中的一個方法:
protected Object create(Object key) { try { ClassLoader loader = getClassLoader(); Map<ClassLoader, ClassLoaderData> cache = CACHE; ClassLoaderData data = cache.get(loader); if (data == null) { synchronized (AbstractClassGenerator.class) { cache = CACHE; data = cache.get(loader); if (data == null) { Map<ClassLoader, ClassLoaderData> newCache = new WeakHashMap<ClassLoader, ClassLoaderData>(cache); data = new ClassLoaderData(loader); newCache.put(loader, data); CACHE = newCache; } } } this.key = key; Object obj = data.get(this, getUseCache()); if (obj instanceof Class) { return firstInstance((Class) obj); } return nextInstance(obj); } catch (RuntimeException e) { throw e; } catch (Error e) { throw e; } catch (Exception e) { throw new CodeGenerationException(e); } }
這個方法在最後調用了 nextInstance(obj)
方法,它對應的實現,是在Enhancer
類中:
protected Object nextInstance(Object instance) { EnhancerFactoryData data = (EnhancerFactoryData) instance; if (classOnly) { return data.generatedClass; } Class[] argumentTypes = this.argumentTypes; Object[] arguments = this.arguments; if (argumentTypes == null) { argumentTypes = Constants.EMPTY_CLASS_ARRAY; arguments = null; } return data.newInstance(argumentTypes, arguments, callbacks); }
這裡又調用了data.newInstance(argumentTypes, arguments, callbacks)
方法,第一個參數為代理對象的構造器類型,第二個為代理對象構造方法參數,第三個為對應回調對象。源碼如下:
public Object newInstance(Class[] argumentTypes, Object[] arguments, Callback[] callbacks) { setThreadCallbacks(callbacks); try { // Explicit reference equality is added here just in case Arrays.equals does not have one if (primaryConstructorArgTypes == argumentTypes || Arrays.equals(primaryConstructorArgTypes, argumentTypes)) { // If we have relevant Constructor instance at hand, just call it // This skips "get constructors" machinery return ReflectUtils.newInstance(primaryConstructor, arguments); } // Take a slow path if observing unexpected argument types return ReflectUtils.newInstance(generatedClass, argumentTypes, arguments); } finally { // clear thread callbacks to allow them to be gc'd setThreadCallbacks(null); } }
我們發現這裡面的邏輯的意思就是,根據傳進來的參數,通過反射來生成對象,我們可以利用cglib
的代理類可以將記憶體中的 class
文件寫入本地磁碟:
@Test public void test1(){ //利用 cglib 的代理類可以將記憶體中的 class 文件寫入本地磁碟 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/eamon.zhang/Documents/cglib"); RealSubjectCglibDynamicProxy proxy = new RealSubjectCglibDynamicProxy(); RealSubject instance = (RealSubject) proxy.getInstance(RealSubject.class); instance.request(); }
執行之後,在對應的目錄中可以看到生成了下圖中這三個.class
文件:
通過調試跟蹤,我們發現 RealSubject$$EnhancerByCGLIB$$5389cdca
就是 CGLib
生成的代理類,繼承了 RealSubject
類。通過IDEA
查看該源碼:
public class RealSubject$$EnhancerByCGLIB$$5389cdca extends RealSubject implements Factory { ... static void CGLIB$STATICHOOK1() { CGLIB$THREAD_CALLBACKS = new ThreadLocal(); CGLIB$emptyArgs = new Object[0]; Class var0 = Class.forName("com.eamon.javadesignpatterns.proxy.dynamic.cglib.RealSubject$$EnhancerByCGLIB$$5389cdca"); Class var1; CGLIB$request$0$Method = ReflectUtils.findMethods(new String[]{"request", "()V"}, (var1 = Class.forName("com.eamon.javadesignpatterns.proxy.dynamic.cglib.RealSubject")).getDeclaredMethods())[0]; CGLIB$request$0$Proxy = MethodProxy.create(var1, var0, "()V", "request", "CGLIB$request$0"); Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods()); CGLIB$equals$1$Method = var10000[0]; CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1"); CGLIB$toString$2$Method = var10000[1]; CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2"); CGLIB$hashCode$3$Method = var10000[2]; CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3"); CGLIB$clone$4$Method = var10000[3]; CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4"); } final void CGLIB$request$0() { super.request(); } public final void request() { MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; if (var10000 == null) { CGLIB$BIND_CALLBACKS(this); var10000 = this.CGLIB$CALLBACK_0; } if (var10000 != null) { var10000.intercept(this, CGLIB$request$0$Method, CGLIB$emptyArgs, CGLIB$request$0$Proxy); } else { super.request(); } } ... }
我們通過代理類的源碼可以看到,代理類會獲得所有在父類繼承來的方法,並且會有 MethodProxy
與之對應,比如 Method CGLIB$request$0$Method
、MethodProxy CGLIB$request$0$Proxy
這些方法在代理類的 reuqest()
中都有調用。
調用過程: 代理對象調用 this.request()
方法 -> 調用攔截器 -> methodProxy.invokeSuper
-> CGLIB$request$0()
-> 被代理對象 request()
方法。 此時,我們發現攔截器 MethodInterceptor
中就是由 MethodProxy
的 invokeSuper
方法調用代理方法的。
MethodProxy 非常關鍵,我們分析一下它具體做了什麼:
public class MethodProxy { private Signature sig1; private Signature sig2; private CreateInfo createInfo; private final Object initLock = new Object(); private volatile FastClassInfo fastClassInfo; /** * For internal use by {@link Enhancer} only; see the {@link net.sf.cglib.reflect.FastMethod} class * for similar functionality. */ public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) { MethodProxy proxy = new MethodProxy(); proxy.sig1 = new Signature(name1, desc); proxy.sig2 = new Signature(name2, desc); proxy.createInfo = new CreateInfo(c1, c2); return proxy; } ... private static class CreateInfo { Class c1; Class c2; NamingPolicy namingPolicy; GeneratorStrategy strategy; boolean attemptLoad; public CreateInfo(Class c1, Class c2) { this.c1 = c1; this.c2 = c2; AbstractClassGenerator fromEnhancer = AbstractClassGenerator.getCurrent(); if (fromEnhancer != null) { namingPolicy = fromEnhancer.getNamingPolicy(); strategy = fromEnhancer.getStrategy(); attemptLoad = fromEnhancer.getAttemptLoad(); } } } ...
繼續看invokeSuper()
方法:
public Object invokeSuper(Object obj, Object[] args) throws Throwable { try { init(); FastClassInfo fci = fastClassInfo; return fci.f2.invoke(fci.i2, obj, args); } catch (InvocationTargetException e) { throw e.getTargetException(); } } private static class FastClassInfo { FastClass f1; FastClass f2; int i1; int i2; }
上面程式碼調用過程就是獲取到代理類對應的 FastClass
,並執行了代理方法。還記得之前生成三個 class
文件嗎?RealSubject$$EnhancerByCGLIB$$5389cdca$$FastClassByCGLIB$$57b94d72.class
就是代理類的 FastClass
,RealSubject$$FastClassByCGLIB$$ed23432.class
就是被代理類的FastClass
。
CGLib
動態代理執行代理方法效率之所以比 JDK
的高是因為 Cglib
採用了 FastClass
機 制,它的原理簡單來說就是:
- 為代理類和被代理類各生成一個 Class,這個 Class 會為代理類或被代理類的方法分配一個
index(int 類型)
。這個index
當做一個入參,FastClass
就可以直接定位要調用的方法直接進行調用,這樣省去了反射調用,所以調用效率比JDK
動態代理通過反射調用高。
至此,Cglib 動態代理的原理我們就基本搞清楚了,如果對程式碼細節有興趣的小夥伴可以再自行深入研究。
JDK Proxy 與 CGlib 比較
JDK
動態代理是實現了被代理對象的介面,CGLib
是繼承了被代理對象。JDK
和CGLib
都是在運行期生成位元組碼,JDK
是直接寫Class
位元組碼,CGLib
使用ASM
框架寫Class
位元組碼,Cglib
代理實現更複雜,生成代理類 比JDK
效率低。JDK
調用代理方法,是通過反射機制調用,CGLib
是通過FastClass
機制直接調用方法,CGLib
執行效率 更高
代理模式與 Spring
Spring 中的代理選擇原則
- 當
Bean
有實現介面時,Spring
就會用JDK
的動態代理 - 當
Bean
沒有實現介面時,Spring
選擇CGLib
。 Spring
可以通過配置強制使用CGLib
,只需在Spring
的配置文件中加入如下程式碼:
<aop:aspectj-autoproxy proxy-target-class="true"/>
參考資料:https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html
總結
靜態代理和動態的本質區別
- 靜態代理只能通過手動完成代理操作,如果被代理類增加新的方法,代理類需要同步新增,違背開閉原則。
- 動態代理採用在運行時動態生成程式碼的方式,取消了對被代理類的擴展限制,遵循開閉原則。
- 若動態代理要對目標類的增強邏輯擴展,結合策略模式,只需要新增策略類便可完成,無需修改代理類的程式碼。
代理模式的優缺點
優點
- 代理模式能將代理對象與真實被調用的目標對象分離。
- 一定程度上降低了系統的耦合度,擴展性好。
- 可以起到保護目標對象的作用。
- 可以對目標對象的功能增強
缺點
- 代理模式會造成系統設計中類的數量增加。
- 在客戶端和目標對象增加一個代理對象,會造成請求處理速度變慢。
- 增加了系統的複雜度。
本篇文章的源碼目錄:https://github.com/eamonzzz/java-advanced/tree/master/01.DesignPatterns/design-patterns/src/main/java/com/eamon/javadesignpatterns/proxy
測試類源碼目錄:https://github.com/eamonzzz/java-advanced/tree/master/01.DesignPatterns/design-patterns/src/test/java/com/eamon/javadesignpatterns/proxy
歡迎大家 star 源碼,共同進步,我會按照 git 上的大綱在學習的同時,記錄文章與源碼~
部落客剛開始寫部落格不久,文中若有錯誤或者有任何的建議,請在留言中指出,向大家學習~
本文由部落格一文多發平台 OpenWrite 發布!