I. What is the Agency Model
Definition of proxy pattern: The proxy pattern provides a proxy object for an object, and the proxy object controls the reference of the original object. Generally speaking, the agency model is a common intermediary in our life.
Classification of Agent Patterns: Agent Patterns are divided into Static Agents and Dynamic Agents
II. Static Agent
Take simple transaction processing as an example
interface public interface UserDao{ void save(); } //Implementation class public class UserDaoImpl implements UserDao{ public void save(){//Preservation} } //Transaction Agent Class public class TransactionHandler implements UserDao{ private UserDaoImpl userDao;//Target proxy object public TransactionHandler(UserDao userDao){ this.useDao = userDao; } public void save(){ //Open a transaction userDao.save(); //Closing the transaction } }
Static proxy is very simple, but it has one disadvantage. If you want to proxy many classes and methods, you can only write more than one proxy class one by one, so you can not achieve the purpose of code reuse.
III. Dynamic Agent
Take the implementation of JDK dynamic proxy as an example
//Transaction Agent Class public class TransactionHandler implements InvocationHandler{ private Object target;//Target proxy object public TransactionHandler(Object target){ this.target= target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //Open a transaction Object result = method.invoke(target,args); //Closing the transaction return result } } //Call method 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(); }
Here are two questions. We'll slowly unveil them later.
1. How to generate proxy classes? 2. How to execute the proxy method?
IV. Principle of JDK Dynamic Agent
Let's first look at the Proxy.newProxyInstance method source code
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { ..... Class<?> cl = getProxyClass0(loader, intfs);//Get or generate proxy classes .... final Constructor<?> cons = cl.getConstructor(constructorParams);//Obtain that the parameter type is the InvocationHandler.class constructor .... return cons.newInstance(new Object[]{h});//Generate proxy instances .... }
This method mainly does three things.
1. Getting or generating proxy classes
2. Obtain that the parameter type is InvocationHandler.class constructor
3. Generating proxy instances
Now let's see how it generates proxy classes. Look at the getProxyClass0 source code
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);//Wrap ClassLoader as a CacheKey as a first-level cached key ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);//Get the classload cache if (valuesMap == null) { ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey, valuesMap = new ConcurrentHashMap<>());//Place it in CAS mode, put it if it does not exist, or return the old value //Question: Why use secondary caching? Why map.putIfAbsent? } Supplier<V> supplier = valuesMap.get(subKey); while (true) { if (supplier != null) { V value = supplier.get();//Get the proxy class if (value != null) { return value;//Get it straight back } } //If null, generate a proxy factory if (factory == null) { factory = new Factory(key, parameter, subKey, valuesMap); } if (supplier == null) { supplier = valuesMap.putIfAbsent(subKey, factory); if (supplier == null) { supplier = factory;//If there is no old value, assign the new value to the reference object and exit iteratively }//If the replacement fails, it indicates that the value is already there, and it is recycled. }else{//If other threads change the value, they replace the original value. if (valuesMap.replace(subKey, supplier, factory)) { supplier = factory; } else { supplier = valuesMap.get(subKey);//Replacement failed, continue to use the original value } } } }
This method mainly depends on whether there is a proxy factory class in the cache. If there is a direct call to get() to return, the cache uses WeakCache, which will be reclaimed when the new generation reclaims and will not occupy memory.
If not in the cache, a proxy factory is generated through the new Factory. Here are some threading security aspects.
Here's Supperlier.get(), and now let's see what's done in this method.
private final class Factory implements Supplier<V> { .... public synchronized V get() { Supplier<V> supplier = valuesMap.get(subKey); if (supplier != this) {//Verify here that supplier is the Factory instance itself, because it may have been modified by other threads. return null; } V value = null; try{ value = Objects.requireNonNull(valueFactory.apply(key, parameter)); //Give ProxyClassFactory to generate proxy classes }finally{ if (value == null) {//If the generation of the proxy class fails, delete the secondary cache valuesMap.remove(subKey, this); } } .... return value; } }
This method is relatively simple, mainly to the valueFactory.apply generated return, valueFactory is the ProxyClassFactory class.
Let's see what's going on in this method.
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { .... int accessFlags = Modifier.PUBLIC | Modifier.FINAL;//Generating proxy classes defaults to public final String proxyName = proxyPkg + proxyClassNamePrefix + num;//Package name + prefix + serial number //Generating bytecode with ProxyGenerator byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName,interfaces, accessFlags); try{ //Generating proxy classes from bytecodes return defineClass0(loader, proxyName, proxyClassFile,0, proxyClassFile.length); } catch (ClassFormatError e) {....} }
Mainly some specification definitions, and then generate bytecode according to ProxyGenerator.generateProxyClass
What has been done in this method?
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() { //First, toString, hashCode, equals and other proxy methods are generated for the proxy class. addProxyMethod(hashCodeMethod, Object.class); addProxyMethod(equalsMethod, Object.class); addProxyMethod(toStringMethod, Object.class); //Traversing through each method of each interface and generating ProxyMethod objects for it 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]); } } ....//Write class attributes into the bout stream return bout.toByteArray(); }
It can be seen that the method attributes of the proxy class are written to the byte stream in several steps.
The generated proxy class is as follows
public class Proxy0 extends Proxy implements UserDao { //The first step is to generate the constructor protected Proxy0(InvocationHandler h) { super(h); } //The second step is to generate static fields private static Method m1; //hashCode method private static Method m2; //equals method private static Method m3; //toString method private static Method m4; //... //The third step is to generate the proxy method @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 { //Construct an array of parameters, if you have more than one parameter to add later Object[] args = new Object[] {user}; h.invoke(this, m4, args);//Enhancement of Agent Classes } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } //The fourth step is to generate a static initialization method 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(); } } }
V. CGLIB Dynamic Agent
First look at how pure CGLIB is used
//Interception class public class Interceptor implements MethodInterceptor { public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { //invokeSuper calls the original method, invoke takes effect on the instance of the original class, and invokes the proxy method. //pre-processing Object retVal = proxy.invokeSuper(obj, args); //Postprocessing return retVal; } } public static void main(String[] args) { //Instantiate an enhancer, a class generator in cglib Enhancer eh = new Enhancer(); //Setting target classes eh.setSuperclass(Target.class); // Setting Interceptor Objects eh.setCallback(new Interceptor()); // Generate the proxy class and return an instance Target t = (Target) eh.create(); t.method(); }
How to generate the source code of the proxy class is not expanded. The general idea is the same as JDK. Finally, the way to generate the proxy class is different.
Look at the generated proxy class first
public class Target$$EnhancerByCGLIB$$788444a0 extends Target implements Factory { ....//equal tostring is not written. Take a typical example. final void CGLIB$g$0()//invokeSuper calls this method { super.g(); } public final void g()//invoke calls this method { 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(); } } }
It can be seen that unlike JDK, JDK is the original interface of implements, CGLIB is the original class of extends, and two corresponding methods are generated for different calls.
The difference between CGLIB and JDK dynamic proxy 1. CGLIB proxy class does not need to implement interfaces, but it can not be final type 2. CGLIB callback method can no longer be processed by method.invoke reflection. FastClass mechanism with higher efficiency is to index a class's method and call the corresponding method directly by index. Method
For example:
//Get the method index first public int getIndex(String signature){ switch(signature.hashCode()){ case 3078479: return 1; case 3108270: return 2; } return -1; } //Then the method is obtained by indexing. 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
Spring Aop uses JDK dynamic proxy by default, unless CGLIB dynamic proxy is specified, but only JDK dynamic proxy code can be used for interface classes in 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); } }
Take Jdk DynamicAopProxy as an example to see how spring selectively enhances methods
1. Generating proxy classes
@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 introduced himself }
It can be seen that the method of enhancement is not the logic defined by ourselves, but JdkDynamicAopProxy. Then JdkDynamicAopProxy must have an invoke method, and see how this method is handled.
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { ..... //Acquisition of Interception Chain List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); if (chain.isEmpty()) { //Direct tuning method return retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args); }else{ invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); //Execution Interception Chain retVal = invocation.proceed(); } .... }
Appendix
JDK source: https://www.cnblogs.com/liuyun1995/p/8144628.html
CGLIB source: https://www.cnblogs.com/cruze/p/3843996.html
Class loading problem: http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html#jvms-5.3