咱們在閱讀一些 Java 框架的源碼時,基本上常會看到使用動態代理機制,它能夠無感的對既有代碼進行方法的加強,使得代碼擁有更好的拓展性。
經過從靜態代理、JDK 動態代理、CGLIB 動態代理來進行本文的分析。java
靜態代理就是在程序運行以前,代理類字節碼.class
就已編譯好,一般一個靜態代理類也只代理一個目標類,代理類和目標類都實現相同的接口。
接下來就先經過 demo 進行分析什麼是靜態代理,當前建立一個 Animal 接口,裏面包含call
函數。數組
package top.ytao.demo.proxy; /** * Created by YangTao */ public interface Animal { void call(); }
建立目標類 Cat,同時實現 Animal 接口,下面是 Cat 發出叫聲的實現。緩存
package top.ytao.demo.proxy; /** * Created by YangTao */ public class Cat implements Animal { @Override public void call() { System.out.println("喵喵喵 ~"); } }
因爲 Cat 叫以前是由於肚子餓了,因此咱們須要在目標對象方法Cat#call
以前說明是飢餓,這是使用靜態代理實現貓飢餓而後發出叫聲。bash
package top.ytao.demo.proxy.jdk; import top.ytao.demo.proxy.Animal; /** * Created by YangTao */ public class StaticProxyAnimal implements Animal { private Animal impl; public StaticProxyAnimal(Animal impl) { this.impl = impl; } @Override public void call() { System.out.println("貓飢餓"); impl.call(); } }
經過調用靜態代理實現貓飢餓和叫行爲。app
public class Main { @Test public void staticProxy(){ Animal staticProxy = new StaticProxyAnimal(new Cat()); staticProxy.call(); } }
執行結果框架
代理類、目標類、接口之間關係如圖:jvm
以上內容能夠看到代理類中經過持有目標類對象,而後經過調用目標類的方法,實現靜態代理。
靜態代理雖然實現了代理,但在一些狀況下存在比較明顯不足之處:ide
Animal#call
是針對 Cat 目標類的對象進行設置的,若是再須要添加 Dog 目標類的代理,那就必須再針對 Dog 類實現一個對應的代理類,這樣就使得代理類的重用型不友好,而且過多的代理類對維護上也是比較繁瑣。上面問題,在 JDk 動態代理中就獲得了較友好的解決。函數
動態代理類與靜態代理類最主要不一樣的是,代理類的字節碼不是在程序運行前生成的,而是在程序運行時再虛擬機中程序自動建立的。
繼續用上面 Cat 類和 Animal 接口實現 JDK 動態代理。源碼分析
JDK 動態代理類必須實現反射包中的 java.lang.reflect.InvocationHandler 接口,在此接口中只有一個 invoker 方法:
在InvocationHandler#invoker
中必須調用目標類被代理的方法,不然沒法作到代理的實現。下面爲實現 InvocationHandler 的代碼。
/** * Created by YangTao */ public class TargetInvoker implements InvocationHandler { // 代理中持有的目標類 private Object target; public TargetInvoker(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("jdk 代理執行前"); Object result = method.invoke(target, args); System.out.println("jdk 代理執行後"); return result; } }
在實現InvocationHandler#invoker
時,該方法裏有三個參數:
建立 JDK 動態代理類實例一樣也是使用反射包中的 java.lang.reflect.Proxy 類進行建立。經過調用Proxy#newProxyInstance
靜態方法進行建立。
/** * * Created by YangTao */ public class DynamicProxyAnimal { public static Object getProxy(Object target) throws Exception { Object proxy = Proxy.newProxyInstance( target.getClass().getClassLoader(), // 指定目標類的類加載 target.getClass().getInterfaces(), // 代理須要實現的接口,可指定多個,這是一個數組 new TargetInvoker(target) // 代理對象處理器 ); return proxy; } }
Proxy#newProxyInstance
中的三個參數(ClassLoader loader、Class<?>[] interfaces、InvocationHandler h):
最後實現執行 DynamicProxyAnimal 動態代理:
public class Main { @Test public void dynamicProxy() throws Exception { Cat cat = new Cat(); Animal proxy = (Animal) DynamicProxyAnimal.getProxy(cat); proxy.call(); } }
執行結果:
經過上面的代碼,有兩個問題:代理類是怎麼建立的和代理類怎麼調用方法的?
從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); } // 查找或生成指定的代理類 Class<?> cl = getProxyClass0(loader, intfs); 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; } }); } // 建立代理類實例化 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); } }
newProxyInstance 方法裏面獲取到代理類,若是類的做用不能訪問,使其能被訪問到,最後實例化代理類。這段代碼中最爲核心的是獲取代理類的getProxyClass0
方法。
private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory()); private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) { // 實現類的接口不能超過 65535 個 if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } // 獲取代理類 return proxyClassCache.get(loader, interfaces); }
若是 proxyClassCache 緩存中存在指定的代理類,則從緩存直接獲取;若是不存在,則經過 ProxyClassFactory 建立代理類。
至於爲何接口最大爲 65535,這個是由字節碼文件結構和 Java 虛擬機規定的,具體能夠經過研究字節碼文件瞭解。
進入到proxyClassCache#get
,獲取代理類:
繼續進入Factory#get
查看,
最後到ProxyClassFactory#apply
,這裏實現了代理類的建立。
private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>>{ // 全部代理類名稱都已此前綴命名 private static final String proxyClassNamePrefix = "$Proxy"; // 代理類名的編號 private static final AtomicLong nextUniqueNumber = new AtomicLong(); @Override public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); for (Class<?> intf : interfaces) { // 校驗代理和目標對象是否實現同一接口 Class<?> interfaceClass = null; try { interfaceClass = Class.forName(intf.getName(), false, loader); } catch (ClassNotFoundException e) { } if (interfaceClass != intf) { throw new IllegalArgumentException( intf + " is not visible from class loader"); } // 校驗 interfaceClass 是否爲接口 if (!interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } // 判斷當前 interfaceClass 是否被重複 if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) { throw new IllegalArgumentException( "repeated interface: " + interfaceClass.getName()); } } // 代理類的包名 String proxyPkg = null; int accessFlags = Modifier.PUBLIC | Modifier.FINAL; // 記錄非 public 修飾符代理接口的包,使生成的代理類與它在同一個包下 for (Class<?> intf : interfaces) { int flags = intf.getModifiers(); if (!Modifier.isPublic(flags)) { accessFlags = Modifier.FINAL; // 獲取接口類名 String name = intf.getName(); // 去掉接口的名稱,獲取所在包的包名 int n = name.lastIndexOf('.'); String pkg = ((n == -1) ? "" : name.substring(0, n + 1)); if (proxyPkg == null) { proxyPkg = pkg; } else if (!pkg.equals(proxyPkg)) { throw new IllegalArgumentException( "non-public interfaces from different packages"); } } } if (proxyPkg == null) { // 若是接口類是 public 修飾,則用 com.sun.proxy 包名 proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; } // 建立代理類名稱 long num = nextUniqueNumber.getAndIncrement(); String proxyName = proxyPkg + proxyClassNamePrefix + num; // 生成代理類字節碼文件 byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); try { // 加載字節碼,生成指定代理對象 return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { throw new IllegalArgumentException(e.toString()); } } }
以上就是建立字節碼流程,經過檢查接口的屬性,決定代理類字節碼文件生成的包名及名稱規則,而後加載字節碼獲取代理實例。操做生成字節碼文件在ProxyGenerator#generateProxyClass
中生成具體的字節碼文件,字節碼操做這裏不作詳細講解。
生成的字節碼文件,咱們能夠經過保存本地進行反編譯查看類信息,保存生成的字節碼文件能夠經過兩種方式:設置jvm參數或將生成 byte[] 寫入文件。
上圖的ProxyGenerator#generateProxyClass
方法可知,是經過 saveGeneratedFiles 屬性值控制,該屬性的值來源:
private static final boolean saveGeneratedFiles = ((Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"))).booleanValue();
因此經過設置將生成的代理類字節碼保存到本地。
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
反編譯查看生成的代理類:
生成的代理類繼承了 Proxy 和實現了 Animal 接口,調用call
方法,是經過調用 Proxy 持有的 InvocationHandler 實現TargetInvoker#invoker
的執行。
CGLIB 動態代理的實現機制是生成目標類的子類,經過調用父類(目標類)的方法實現,在調用父類方法時再代理中進行加強。
相比於 JDK 動態代理的實現,CGLIB 動態代理不須要實現與目標類同樣的接口,而是經過方法攔截的方式實現代理,代碼實現以下,首先方法攔截接口 net.sf.cglib.proxy.MethodInterceptor。
/** * Created by YangTao */ public class TargetInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("CGLIB 調用前"); Object result = proxy.invokeSuper(obj, args); System.out.println("CGLIB 調用後"); return result; } }
經過方法攔截接口調用目標類的方法,而後在該被攔截的方法進行加強處理,實現方法攔截器接口的 intercept 方法裏面有四個參數:
建立 CGLIB 動態代理類使用 net.sf.cglib.proxy.Enhancer 類進行建立,它是 CGLIB 動態代理中的核心類,首先建立個簡單的代理類:
/** * Created by YangTao */ public class CglibProxy { public static Object getProxy(Class<?> clazz){ Enhancer enhancer = new Enhancer(); // 設置類加載 enhancer.setClassLoader(clazz.getClassLoader()); // 設置被代理類 enhancer.setSuperclass(clazz); // 設置方法攔截器 enhancer.setCallback(new TargetInterceptor()); // 建立代理類 return enhancer.create(); } }
設置被代理類的信息和代理類攔截的方法的回調執行邏輯,就能夠實現一個代理類。
實現 CGLIB 動態代理調用:
public class Main { @Test public void dynamicProxy() throws Exception { Animal cat = (Animal) CglibProxy.getProxy(Cat.class); cat.call(); } }
執行結果:
CGLIB 動態代理簡單應用就這樣實現,可是 Enhancer 在使用過程當中,經常使用且有特點功能還有回調過濾器 CallbackFilter 的使用,它在攔截目標對象的方法時,能夠有選擇性的執行方法攔截,也就是選擇被代理方法的加強處理。使用該功能須要實現 net.sf.cglib.proxy.CallbackFilter 接口。
如今增長一個方法攔截的實現:
/** * Created by YangTao */ public class TargetInterceptor2 implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("CGLIB 調用前 TargetInterceptor2"); Object result = proxy.invokeSuper(obj, args); System.out.println("CGLIB 調用後 TargetInterceptor2"); return result; } }
而後在 Cat 中增長 hobby 方法,由於 CGLIB 代理無需實現接口,能夠直接代理普通類,因此不需再 Animal 接口中增長方法:
package top.ytao.demo.proxy; /** * Created by YangTao */ public class Cat implements Animal { @Override public void call() { System.out.println("喵喵喵 ~"); } public void hobby(){ System.out.println("fish ~"); } }
實現回調過濾器 CallbackFilter
/** * Created by YangTao */ public class TargetCallbackFilter implements CallbackFilter { @Override public int accept(Method method) { if ("hobby".equals(method.getName())) return 1; else return 0; } }
爲演示調用不一樣的方法攔截器,在 Enhancer 設置中,使用Enhancer#setCallbacks
設置多個方法攔截器,參數是一個數組,TargetCallbackFilter#accept
返回的數字即爲該數組的索引,決定調用的回調選擇器。
/** * Created by YangTao */ public class CglibProxy { public static Object getProxy(Class<?> clazz){ Enhancer enhancer = new Enhancer(); enhancer.setClassLoader(clazz.getClassLoader()); enhancer.setSuperclass(clazz); enhancer.setCallbacks(new Callback[]{new TargetInterceptor(), new TargetInterceptor2()}); enhancer.setCallbackFilter(new TargetCallbackFilter()); return enhancer.create(); } }
按代碼實現邏輯,call 方法會調用 TargetInterceptor 類,hobby 類會調用 TargetInterceptor2 類,執行結果:
CGLIB 的實現原理是經過設置被代理的類信息到 Enhancer 中,而後利用配置信息在Enhancer#create
生成代理類對象。生成類是使用 ASM 進行生成,本文不作重點分析。若是不關注 ASM 的操做原理,只看 CGLIB 的處理原理仍是比較容易讀懂。這裏主要看生成後的代理類字節碼文件,經過設置
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "F:\\xxx");
可保存生成的字節到 F:\xxx 文件夾中
經過反編譯可看到
代理類繼承了目標類 Cat,同時將兩個方法攔截器加載到了代理類中,經過 Callbacks 下標做爲變量名後綴進行區分,最後調用指定的方法攔截器中的 intercept 實現代理的最終的執行結果。
這裏須要注意的是 CGLIB 動態代理不能代理 final 修飾的類和方法。
經過反編譯生成的 JDK 代理類和 CGLIB 代理類,咱們能夠看到它們兩種不一樣機制的實現:
JDK 動態代理是經過實現目標類的接口,而後將目標類在構造動態代理時做爲參數傳入,使代理對象持有目標對象,再經過代理對象的 InvocationHandler 實現動態代理的操做。
CGLIB 動態代理是經過配置目標類信息,而後利用 ASM 字節碼框架進行生成目標類的子類。當調用代理方法時,經過攔截方法的方式實現代理的操做。
總的來講,JDK 動態代理利用接口實現代理,CGLIB 動態代理利用繼承的方式實現代理。
動態代理在 Java 開發中是很是常見的,在日誌,監控,事務中都有着普遍的應用,同時在大多主流框架中的核心組件中也是少不了使用的,掌握其要點,不論是開發仍是閱讀其餘框架源碼時,都是必須的。
我的博客: https://ytao.top
關注公衆號 【ytao】,更多原創好文