您好,登錄后才能下訂單哦!
本篇內容介紹了“web中動態代理模式是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
代理模式的定義:代理模式給某一個對象提供一個代理對象,并由代理對象控制對原對象的引用。通俗的來講代理模式就是我們生活中常見的中介。
代理模式的分類:代理模式分為靜態代理和動態代理
以簡單的事務處理為例
interface public interface UserDao{ void save(); } 實現類 public class UserDaoImpl implements UserDao{ public void save(){//保存} } 事務處理代理類 public class TransactionHandler implements UserDao{ private UserDaoImpl userDao;//目標代理對象 public TransactionHandler(UserDao userDao){ this.useDao = userDao; } public void save(){ //開啟事務 userDao.save(); //結束事務 } }
靜態代理很簡單,但是有一個缺點,如果要對很多類、方法進行代理只能一個一個寫多個代理類,無法做到代碼重用的目的
以JDK動態代理實現為例
//事務處理代理類 public class TransactionHandler implements InvocationHandler{ private Object target;//目標代理對象 public TransactionHandler(Object target){ this.target= target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //開啟事務 Object result = method.invoke(target,args); //結束事務 return result } } //調用方法 public class Main { public static void main(String[] args) { Object target = new UserDaoImpl(); TransactionHandler handler = new TransactionHandler(target); UserDao userDao = (UserDao)Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler); userDao.save(); }
在這里有兩個疑問,我們在后面慢慢解開它們的面紗
1.如何生成代理類?2.如何執行代理方法?
我們先看Proxy.newProxyInstance方法原碼
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { ..... Class<?> cl = getProxyClass0(loader, intfs);//獲取或生成代理類 .... final Constructor<?> cons = cl.getConstructor(constructorParams);//得到參數類型是InvocationHandler.class構造函數 .... return cons.newInstance(new Object[]{h});//生成代理實例 .... }
這個方法主要就做三件事
1.獲取或生成代理類
2.得到參數類型是InvocationHandler.class構造函數
3.生成代理實例
我們現在來看看它是如何生成代理類的。看getProxyClass0源碼
private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) { .... return proxyClassCache.get(loader, interfaces);//proxyClassCache->WeakCache } public V get(K key, P parameter) { .... Object cacheKey = CacheKey.valueOf(key, refQueue);//將ClassLoader包裝成CacheKey, 作為一級緩存的key ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);//獲得classload緩存 if (valuesMap == null) { ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey, valuesMap = new ConcurrentHashMap<>());//以CAS方式放入, 如果不存在則put,否則返回舊值 //問題:為什么要用二級緩存?為什么要map.putIfAbsent? } Supplier<V> supplier = valuesMap.get(subKey); while (true) { if (supplier != null) { V value = supplier.get();//得到代理類 if (value != null) { return value;//取到了直接返回 } } //如果為null,生成代理類工廠 if (factory == null) { factory = new Factory(key, parameter, subKey, valuesMap); } if (supplier == null) { supplier = valuesMap.putIfAbsent(subKey, factory); if (supplier == null) { supplier = factory;//如果沒有舊值,將新值賦給引用對象,循環退出 }//如果替換失敗,說明已經有值,重新循環 }else{//其它線程修改了值, 那么就將原先的值替換 if (valuesMap.replace(subKey, supplier, factory)) { supplier = factory; } else { supplier = valuesMap.get(subKey);//替換失敗, 繼續使用原先的值 } } } }
這個方法主要就是看緩存里是否存在代理工廠類,如果存在直接調用get()返回,這里緩存用的是WeakCache,新生代回收時就會被回收,不會占用內存
如果緩存中沒有就通過new Factory生成一個代理工廠。這里有一些線程安全方面的處理。
這里返回的是Supperlier.get(),現在看這個方法中做了些什么事情
private final class Factory implements Supplier<V> { .... public synchronized V get() { Supplier<V> supplier = valuesMap.get(subKey); if (supplier != this) {//在這里驗證supplier是否是Factory實例本身,因為可能被其它線程修改了 return null; } V value = null; try{ value = Objects.requireNonNull(valueFactory.apply(key, parameter)); //交給ProxyClassFactory去生成代理類 }finally{ if (value == null) {//如果生成代理類失敗, 就將這個二級緩存刪除 valuesMap.remove(subKey, this); } } .... return value; } }
這個方法比較簡單主要是交給valueFactory.apply生成返回,valueFactory是ProxyClassFactory類
我們再看看這個方法里在做什么
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { .... int accessFlags = Modifier.PUBLIC | Modifier.FINAL;//生成代理類默認是public final String proxyName = proxyPkg + proxyClassNamePrefix + num;//包名+前綴+序號 //用ProxyGenerator來生成字節碼 byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName,interfaces, accessFlags); try{ //根據字節碼生成代理類 return defineClass0(loader, proxyName, proxyClassFile,0, proxyClassFile.length); } catch (ClassFormatError e) {....} }
主要是一些規范定義,然后根據ProxyGenerator.generateProxyClass生成字節碼
再這個方法里做了些什么
public static byte[] generateProxyClass(String var0, Class<?>[] var1) { return generateProxyClass(var0, var1, 49); } 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; } private byte[] generateClassFile() { //首先為代理類生成toString, hashCode, equals等代理方法 addProxyMethod(hashCodeMethod, Object.class); addProxyMethod(equalsMethod, Object.class); addProxyMethod(toStringMethod, Object.class); //遍歷每一個接口的每一個方法, 并且為其生成ProxyMethod對象 for (int i = 0; i < interfaces.length; i++) { Method[] methods = interfaces[i].getMethods(); for (int j = 0; j < methods.length; j++) { addProxyMethod(methods[j], interfaces[i]); } } ....//將類的屬性寫入bout流中 return bout.toByteArray(); }
可以看出主要是分多步將被代理類的方法屬性等寫到字節流中
生成的代理類就如下
public class Proxy0 extends Proxy implements UserDao { //第一步, 生成構造器 protected Proxy0(InvocationHandler h) { super(h); } //第二步, 生成靜態域 private static Method m1; //hashCode方法 private static Method m2; //equals方法 private static Method m3; //toString方法 private static Method m4; //... //第三步, 生成代理方法 @Override public int hashCode() { try { return (int) h.invoke(this, m1, null); } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } @Override public boolean equals(Object obj) { } @Override public String toString() { } @Override public void save(User user) { try { //構造參數數組, 如果有多個參數往后面添加就行了 Object[] args = new Object[] {user}; h.invoke(this, m4, args);//代理類增強方法 } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } //第四步, 生成靜態初始化方法 static { try { Class c1 = Class.forName(Object.class.getName()); Class c2 = Class.forName(UserDao.class.getName()); m1 = c1.getMethod("hashCode", null); m2 = c1.getMethod("equals", new Class[]{Object.class}); m3 = c1.getMethod("toString", null); m4 = c2.getMethod("save", new Class[]{User.class}); //... } catch (Exception e) { e.printStackTrace(); } } }
先看純CGLIB是如何使用
//攔截類 public class Interceptor implements MethodInterceptor { public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { //invokeSuper調用原方法,invoke對原類實例生效,調用代理方法 //前置處理 Object retVal = proxy.invokeSuper(obj, args); //后置處理 return retVal; } } public static void main(String[] args) { //實例化一個增強器,也就是cglib中的一個class generator Enhancer eh = new Enhancer(); //設置目標類 eh.setSuperclass(Target.class); // 設置攔截對象 eh.setCallback(new Interceptor()); // 生成代理類并返回一個實例 Target t = (Target) eh.create(); t.method(); }
具體如何生成代理類的源碼就不展開了,大體思路和JDK一致,最后生成代理類的方式有所不同。
先看生成的代理類
public class Target$$EnhancerByCGLIB$$788444a0 extends Target implements Factory { ....//equal tostring就不寫了,拿一個典型 final void CGLIB$g$0()//invokeSuper調用這個方法 { super.g(); } public final void g()//invoke調用這個方法 { MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0; if (tmp4_1 == null) { CGLIB$BIND_CALLBACKS(this); tmp4_1 = this.CGLIB$CALLBACK_0; } if (this.CGLIB$CALLBACK_0 != null) { tmp4_1.intercept(this, CGLIB$g$0$Method, CGLIB$emptyArgs, CGLIB$g$0$Proxy); } else{ super.g(); } } }
可以看出和JDK不同的是JDK是implements 原接口,CGLIB是extends原類,而且會生成兩個對應的方法,供不同的調用。
CGLIB與JDK動態代理的區別 1、CGLIB代理類不需要實現接口,但是也不能是final型 2、CGLIB回調方法不再通過method.invoke反射來處理,采用效率更高的FastClass機制 FastClass機制就是對一個類的方法建立索引,通過索引來直接調用相應的方法
例如:
//先通過獲得方法索引 public int getIndex(String signature){ switch(signature.hashCode()){ case 3078479: return 1; case 3108270: return 2; } return -1; } //然后通過索引得到方法 public Object invoke(int index, Object o, Object[] ol){ Test t = (Test) o; switch(index){ case 1: t.f(); return null; case 2: t.g(); return null; } return null; }
Spring Aop默認使用JDK動態代理,除非指定使用CGLIB動態代理,但對接口類只能使用JDK動態代理 具體代碼在DefaultAopProxyFactory
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } if (targetClass.isInterface()) { return new JdkDynamicAopProxy(config); } if (!cglibAvailable) { throw new AopConfigException( "Cannot proxy target class because CGLIB2 is not available. " + "Add CGLIB to the class path or specify proxy interfaces."); } return CglibProxyFactory.createCglibProxy(config); } else { return new JdkDynamicAopProxy(config); } }
以JdkDynamicAopProxy為例我們來看spring是如何對方法進行有選擇性的增強的
1、生成代理類
@Override public Object getProxy(ClassLoader classLoader) { if (logger.isDebugEnabled()) { logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource()); } Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised); findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);//hanler傳入的是自己 }
可以看出增強的方法不是我們自己定義的邏輯,而是JdkDynamicAopProxy 那么JdkDynamicAopProxy 必然有一個invoke方法,再看這個方法怎么處理
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { ..... //獲取攔截鏈 List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); if (chain.isEmpty()) { //直接調原方法返回 retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args); }else{ invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); //執行攔截鏈 retVal = invocation.proceed(); } .... }
“web中動態代理模式是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。