This article code content is more, say may be a little rough, you can read selectively.

The purpose of this article is to simply analyze the principle of dynamic Proxy and imitate JDK Proxy handwritten dynamic Proxy and several agents to do a summary.

For the introduction and explanation of the agent model, there are already many high-quality articles on the Internet, I will not introduce too much, here recommend several high-quality articles as a reference:

  1. Explain the agency model to your girlfriend
  2. Easy to learn, Java proxy pattern and dynamic proxy

In addition, I also have the basic sample code in the corresponding directory of my Github repository: github.com/eamonzzz/ja…

JDK Proxy Dynamic Proxy

The concept of dynamic proxy is not explained here; Dynamic proxies are more powerful than static proxies and more adaptable as services expand.

Before we talk about the principle of dynamic proxies, let’s look at the general use of dynamic proxies.

use

This article uses an example of the simplest proxy pattern code as an example, I believe that you have seen or touched these codes when learning or understanding the proxy pattern.

  1. So let’s create oneSubjectPrincipal Abstract interface:
/**
 * @author eamon.zhang
 * @date 2019-10-09 下午4:06
 */
public interface Subject {
    void request();
}Copy the code

  1. Create a real bodyRealSubjectTo deal with our real logic:
/** * @author * @date 2019-10-09 PM */ public implements Subject {@override public Void request() {system.out.println (" real processing logic!" ); }}Copy the code

  1. Without changingRealSubjectClass case if we want to implement in executionRealSubjectIn the classrequest()How to implement a piece of logic before or after a method? This creates a proxy class to enhance the original code. So now create a JDK dynamic proxy classRealSubjectJDKDynamicProxy
/ * * * @ author eamon. Zhang 4:08 * * @ the date 2019-10-09 afternoon/public class RealSubjectJDKDynamicProxy implements InvocationHandler {// Private Object target; / / by the constructor incoming Object reference public RealSubjectJDKDynamicProxy Object (target) {this. Target = target; 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 () {system.out.println (); ); }}Copy the code

  1. Test code:
@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());
}Copy the code

  1. The test results
Front-facing enhancement! Real processing logic! Afterboost! class com.eamon.javadesignpatterns.proxy.dynamic.jdk.RealSubject class com.sun.proxy.$Proxy8Copy the code

As a result, the code above has achieved our enhancement purpose.

The principle of analysis

I don’t know if you noticed in the test code above, the last two lines I printed out the class object before and after the proxy; And discovered that these two objects are not the same, and most importantly, After the proxy object of the Subject is com. Sun. Proxy. $Proxy8 not com. Eamon. Javadesignpatterns. Proxy. Dynamic. The JDK. RealSubject or com. Eamon. Javadesi Gnpatterns. Proxy. Dynamic. JDK. Subject, then this instance exactly where it came from? With this question, let’s use JDK Proxy source code to analyze it:

We follow up RealSubjectJDKDynamicProxy class Proxy newProxyInstance (clazz. GetClassLoader (), clazz. GetInterfaces (), this); Methods:

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); }... }Copy the code

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); }Copy the code

The code logic is simple and does two things:

  1. Check whether the number of interfaces of the class exceeds65535, the number of interfaces is 2byteStorage, maximum support65535A.
  2. fromproxyClassCacheFrom the comment, it will be called if the cache does not have oneProxyClassFactoryTo create.

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; // Call factory. Get () if (supplier!) 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); }}}}Copy the code

The code may be a bit long, but the logic is to call proxyClassFactory.apply () to generate the proxy class. From while(true) we split the code into two parts:

  1. So the first half, we get it from the cacheProxyClassFactoryIf the key is created successfully, it can be retrieved from the cache.
  2. Then watchwhile(true)The logic in the code block,if (supplier ! = null)This judgment, if created in the cacheProxyClassFactoryWill performsupplier.get()And terminate the loop; If not, it will be executednew Factory(key, parameter, subKey, valuesMap);To createfactory, and then put it into the cachesupplier, then continue the loop, at which point it will executeif (supplier ! = null)The logic in the code block, let’s analyze the code inside the code block again:
if (supplier ! = null) { // supplier might be a Factory or a CacheValue<V> instance V value = supplier.get(); if (value ! = null) { return value; }}Copy the code

Follow up with the supplier. Get () method. We know from the above analysis that the supplier is actually a Factory, so we look at the implementation of Factory, focusing on the get() method:

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; }}Copy the code

We notice that the emphasis in the code is on objects.requirenonNULL (valueFactory.apply(key, parameter)); What is valueFactory in this code? Let’s look at the definition of proxyClassCache in Proxy

private static final WeakCache<ClassLoader, Class<? >[], Class<? >> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());Copy the code

WeakCache second parameter is new ProxyClassFactory(), let’s look at the corresponding constructor:

public WeakCache(BiFunction<K, P, ? > subKeyFactory, BiFunction<K, P, V> valueFactory) { this.subKeyFactory = Objects.requireNonNull(subKeyFactory); this.valueFactory = Objects.requireNonNull(valueFactory); }Copy the code

Does that make sense? ProxyClassFactory()

What does valueFactory.apply(key, parameter) do? Let’s look directly at the ProxyClassFactory code

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()); }}}Copy the code

Throughout overview, it’s not hard to analyze, in the code is in the middle of the create $Proxy the Proxy class, one byte [] proxyClassFile is assembled in a code block after class bytecode file data, through ProxyGenerator. GenerateProxyClass () generation; The bytecode is then dynamically loaded through the Classloader, and a Class instance of the dynamic proxy Class is generated and returned.

. We’ll follow up ProxyGenerator generateProxyClass () method, to see the processing logic in the process of generating the proxy class, look at the key code:.

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; }Copy the code

GenerateClassFile (); generateClassFile(); 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); }}... }Copy the code

The code is a bit long, so I won’t expand it all here. Interested friends can follow me to have a look in detail. Add hashCode, Equals, and toString to generate the proxy class. Then the logic is to iterate through all the interfaces of the proxy object and regenerate all its methods. Bytecode is then generated.

Finally, the proxy classes are loaded into the JVM.

Look at theJDK ProxyGenerated proxy classes$Proxy

We output the $Proxy file to a file with the following code:

@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.getClas s().getSimpleName()+".class"); outputStream.write(proxychar); outputStream.flush(); outputStream.close(); } catch (IOException e) { e.printStackTrace(); } instance.request(); System.out.println(instance.getClass()); }Copy the code

$Proxy0 $Proxy0 $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()); }}}Copy the code

conclusion

To summarize the steps for implementing JDK Proxy:

  1. Get a reference to the proxied object and get all its interfaces (via reflection)
  2. JDK ProxyClass regenerates a new class that implements all the interfaces implemented by the proxied class, andHashCode, equals, toStringThese three methods
  3. Dynamically generatedJavaCode, the newly added business logic method by a certain logic code to call (reflected in the code)
  4. Compile the newly generatedJavaThe code of.classfile
  5. Reload toJVMRunning in the

Emulates handwritten JDK Proxy

With this in mind, we can actually try to implement a JDK Proxy manually:

We refer to the implementation principle of JDK Proxy to analyze what we need to write by hand:

  • First we need to have a proxy classMimeProxy
  • And then from the proxy class, you need to havenewProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this)In this method, the method parameters are:(ClassLoader loader,Class[] interfaces,InvocationHandler h), so we need to create oneClassLoader,InvocationHandler;

Let’s create it step by step:

  1. First createMimeClassLoaderClass, inherited fromClassLoaderAnd writefindClass()Methods:
/** * @author extends ClassLoader {private Object} /** * @author 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; }}Copy the code

  1. createMimeInvocationHandlerClass:
/**
 * @author eamon.zhang
 * @date 2019-10-10 下午2:46
 */
public interface MimeInvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable;
}Copy the code

  1. createMimeProxyClass, which is used to assemble proxy classes and load them intoJVMAnd return the proxy object:
/** * @author eamon.zhang * @date 2019-10-10 11:00pm */ public class MimeProxy {private static Final String ln = "\r\n";  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. Dynamically generate.java file String SRC = generateSrc(interfaces); // System.out.println(src); / / 2. Java file output to disk String filePath = MimeProxy. Class. The 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. Compile Java files into. Class files 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. Load the. Class file into the 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; } /** * generateSrc private static String interfaces (Class<? >[] interfaces) {// here StringBuffer thread-safe 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(); } /** * Get the return value type ** @param returnClass * @return */ private static String getremptycode (Class<? > returnClass) { if (mappings.containsKey(returnClass)) { return "return 0;" ; } else if (returnClass == void.class) { return ""; } else { return "return null;" ; }} /** * concatenate invocationHandler to execute code ** @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; } private static String toLowerFirstCase(String SRC) {char[] chars = * private static String toLowerFirstCase(String SRC) {char[] chars = src.toCharArray(); chars[0] += 32; return String.valueOf(chars); }}Copy the code

In this way, you write a dynamic proxy of your own, of course, the proxy method is not perfect, but for this example is written, interested friends can try to change it to more general code.

CGlib dynamic proxy

Let’s look at the use of CGlib’s dynamic proxy

use

Start by creating the RealSubject class. Note that this class does not implement any interface:

/** * @author eamon.zhang * @date 2019-10-09 PM 4:22 */ public class RealSubject {public void request(){ System.out.println(" Real processing logic! ") ); }}Copy the code

Then create RealSubjectCglibDynamicProxy proxy class, it must implement the MethodInterceptor interface:

/ * * * @ author eamon. Zhang 4:23 * * @ the date 2019-10-09 afternoon/public class RealSubjectCglibDynamicProxy implements MethodInterceptor { public Object getInstance(Class<? > clazz) {Enhancer Enhancer = new Enhancer(); // which to set as the parent of the new class to be generated, enhancer.setsuperclass (clazz); // Set the callback object, 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 () {system.out.println (); ); }}Copy the code

Now that a simple CGlib dynamic proxy implementation is complete, let’s create the test code:

@Test
public void test(){
    RealSubjectCglibDynamicProxy proxy = new RealSubjectCglibDynamicProxy();
    RealSubject instance = (RealSubject) proxy.getInstance(RealSubject.class);
    instance.request();
}Copy the code

Test results:

Front-facing enhancement! Real processing logic! Afterboost!Copy the code

The principle of analysis

The core of both JDK Proxy and CGlib is to create Proxy classes, so we just need to understand the process of creating Proxy classes.

As you can see from the simple example above, to use CGlib dynamic proxy, the proxy class must implement a MethodInterceptor. The source code for the MethodInterceptor interface is as follows:

/** * 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 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; }Copy the code

There is only one Intercept method in the interface, passing in the argument:

  1. objRepresents an enhanced object, that is, an object that implements this interface class;
  2. methodRepresents the method to be intercepted;
  3. argsRepresent method parameters;
  4. proxyRepresents the method object to fire the parent class;

In the logic getInstance(Class clazz) that creates the proxy object, the enhancer.create() method is called.

/**
 * 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();
}Copy the code

If necessary, generate a new class and use the specified callback (if any) to create a new object instance. Instantiate the parent class using the constructor of the arguments to the parent class.

The core of this is createHelper(); Method:

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; }Copy the code

The preValidate() method prevalidates whether the callbackTypes and filter are null and how to handle them if they are null.

We then create an EnhancerKey object using key_factory.newinstance () and pass it as a parameter to the super.create(key) method. Let’s look at the create() method. It is found to be a method in the Enhancer class’s parent, 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); }}Copy the code

This method finally calls the nextInstance(obj) method, whose corresponding implementation is in the Enhancer class:

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);
}Copy the code

The data.newinstance (argumentTypes, arguments, callbacks) method is called again, with the first argument being the constructor type of the proxy object, the second being the constructor parameter of the proxy object, and the third being the corresponding callback object. The source code is as follows:

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); }}Copy the code

We can use cglib’s proxy class to write an in-memory class file to a local disk:

@test public void test1(){// Use cglib's proxy class to write in-memory class files to local disks System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/eamon.zhang/Documents/cglib"); RealSubjectCglibDynamicProxy proxy = new RealSubjectCglibDynamicProxy(); RealSubject instance = (RealSubject) proxy.getInstance(RealSubject.class); instance.request(); }Copy the code

After executing, you can see the three.class files in the corresponding directory as shown below:

Through the debug trace, we found that the RealSubject? EnhancerByCGLIB? The 5389CDCA is a proxy class generated by CGLib that inherits from the RealSubject class. View the source code through 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(); }}... }Copy the code

As you can see from the source code of the proxy class, the proxy class gets all the methods that are inherited from the parent class, and there’s a MethodProxy that corresponds to that, Methods such as Method CGLIB$request$0$Method and MethodProxy CGLIB$request$0$Proxy are called in the Proxy class reuqest().

InvokeSuper -> CGLIB$request$0() -> Propped object request() method. At this point, we see that the proxy method is called by the invokeSuper method in MethodProxy in the MethodInterceptor.

MethodProxy is key, so let’s look at what it does:

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(); }}}...Copy the code

Moving on to the invokeSuper() method:

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;
}Copy the code

The above code calls the FastClass of the proxy class and executes the proxy method. Remember generating three class files earlier? RealSubject? EnhancerByCGLIB? 5389cdca? FastClassByCGLIB? 57b94d72.class is the FastClass of the proxy class, RealSubject? FastClassByCGLIB? Ed23432. class is the FastClass of the proxied class.

The CGLib dynamic proxy executes the proxy method more efficiently than the JDK because CGLib uses the FastClass machine.

  • Generate a Class for the proxy Class and a proxied Class, and this Class allocates one for the proxy Class or proxied Class’s methodsThe index (int). thisindexAs an input parameter,FastClassYou can directly locate the method to be called and call it directly, which eliminates the need for reflection calls, so the call efficiency ratioJDKDynamic proxy calls high through reflection.

The principle of Cglib dynamic proxy is basically clear. If you are interested in the details of the code, you can further study it.

Compare JDK Proxy with CGlib

  1. JDKDynamic proxy isimplementationThe interface of the proxied object,CGLibinheritanceThe proxied object.
  2. JDKCGLibBoth generate bytecode at run time,JDKIs to write directlyClassBytecode,CGLibuseASMFramework to writeClassBytecode,CglibProxy implementations are more complex,Generating proxy classesJDKEfficiency is low.
  3. JDKThe proxy method is called by reflection,CGLibIs through theFastClassMechanisms call methods directly,CGLib Execution efficiencyhigher

The proxy pattern versus Spring

Proxy selection principles in Spring

  1. whenBeanWhen there is an implementation interface,SpringWill useJDKDynamic proxy of
  2. whenBeanWhen no interface is implemented,SpringchooseCGLib.
  3. SpringIt can be used forcibly through configurationCGLib, just inSpringAdd the following code to the config file:
<aop:aspectj-autoproxy proxy-target-class="true"/>Copy the code

Reference data: https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html

conclusion

Essential difference between static proxy and dynamic proxy

  1. A static proxy can only be manually performed. If a new method is added to the proxy class, the proxy class must be added at the same time, which violates the open/close principle.
  2. Dynamic proxy adopts the method of dynamically generating code at run time, which removes the limitation of extending the proxied class and follows the open and closed principle.
  3. If the dynamic proxy wants to extend the enhanced logic of the target class, combined with the policy mode, it only needs to add the policy class to complete, without modifying the code of the proxy class.

Advantages and disadvantages of the proxy model

advantages

  1. The proxy pattern separates the proxy object from the actual target object being invoked.
  2. The coupling degree of the system is reduced to a certain extent, and the expansibility is good.
  3. It can protect the target object.
  4. You can enhance the functionality of the target object

disadvantages

  1. The proxy pattern increases the number of classes in the system design.
  2. Adding a proxy object to both the client and the target object slows down request processing.
  3. Increased system complexity.

The source directory for this article: https://github.com/eamonzzz/java-advanced/tree/master/01.DesignPatterns/design-patterns/src/main/java/com/eamon/javadesi gnpatterns/proxy

Test class source directory: https://github.com/eamonzzz/java-advanced/tree/master/01.DesignPatterns/design-patterns/src/test/java/com/eamon/javadesi gnpatterns/proxy

Welcome everyone star source code, common progress, I will learn in accordance with the git outline at the same time, record the article and source ~

If you have any mistakes or suggestions, please point them out in the comments and learn from us

This article is published by OpenWrite!