In contact with SpringAOP, we will be impressed by the magic of this function, want to know the mystery, how the bottom of the implementation. So, we will through the search engine, know a strange noun: dynamic proxy, and slowly know the dynamic proxy has a variety of implementation methods, such as JDK dynamic proxy, Cglib and so on. Today I will talk briefly about JDK dynamic proxies.

Simple application of JDK dynamic proxy

Let’s start with the simplest example:

First we need to define an interface:

public interface UserService {
    void query();
}
Copy the code

Then implement this interface:

public class UserServiceImpl implements UserService {
    public void query() {
        System.out.println("Query user information"); }}Copy the code

Define a class that implements InvocationHandler:

public class MyInvocationHandler implements InvocationHandler {

    Object target;

    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Enter Invoke");
        method.invoke(target);
        System.out.println("Invoke executed");
        returnnull; }}Copy the code

Then there is the Main method:

public class Main { public static void main(String[] args) { MyInvocationHandler myInvocationHandler = new MyInvocationHandler(new UserServiceImpl()); Object o = Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{UserService.class} , myInvocationHandler); ((UserService)o).query(); }}Copy the code

Run:

As you can see, everything is fine, the enhanced logic has been successfully executed, and the target method has been executed.

Three questions

Although this is the simplest example, you must have many doubts like me when you first learn it: first, you don’t know why you need to pass in an interface, second, you don’t know why the JDK dynamic proxy can only proxy interfaces, and third, you don’t know the function of the class loader. Also, the code is more complex.

These three doubts puzzled me for a long time, until I followed the blog, hand lugged a castrated version of the JDK dynamic proxy, and briefly looked at the final JDK generated code and source code.

Write a neutered version of the JDK dynamic proxy

The invoke method of MyInvocationHandler takes three parameters. The first parameter is the proxy class, the second parameter is the method, and the third parameter is the parameter needed to execute the method. Methods implement two logic inside, one to enhance the logic, and one to execute the target method. We can’t help but wonder if we could automatically generate a class that calls the Invoke method in MyInvocationHandler to implement dynamic proxies.

This is indeed a bold and crazy idea, but it can be done in the following steps:

  1. Concatenate the code of the proxy class
  2. Output. Java files
  3. Compile.java files into.class files
  4. Load the.class file
  5. Create and return a proxy class object

For convenience, regardless of return values and arguments, I write a castrated MockInvocationHandler class modeled after the existing MyInvocationHandler:

public class MockInvocationHandler {

    private Object targetObject;

    public MockInvocationHandler(Object targetObject) {
        this.targetObject = targetObject;

    }

    public void invoke(Method targetMethod) {
        try {
            System.out.println("Enter Invoke");
            targetMethod.invoke(targetObject, null);
            System.out.println("End invoke"); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); }}}Copy the code

To call the Invoke method in MockInvocationHandler, the generated proxy class might or might not look like this:

public class $ProxyImplements interface that requires the agent {MockInvocationHandler h; public$Proxy (MockInvocationHandler h ) {this.h = h; }
	 public void query(){try{//method= this.h.invoke(method); }catch(Exception ex){} } }Copy the code

Ok, now for the manual work, just paste the code:

public class MockProxy {

    final static String ENTER = "\n";
    final static String TAB = "\t";

    public static Object newProxyInstance(Class interfaceClass,MockInvocationHandler h) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("package com.codebear;");
        stringBuilder.append(ENTER);
        stringBuilder.append("import java.lang.reflect.*;");
        stringBuilder.append(ENTER);
        stringBuilder.append("public class $Proxy implements " + interfaceClass.getName() + "{");
        stringBuilder.append(ENTER);
        stringBuilder.append(TAB);
        stringBuilder.append(" MockInvocationHandler h;");
        stringBuilder.append(ENTER);
        stringBuilder.append(TAB);
        stringBuilder.append(" public $Proxy (MockInvocationHandler h ) {this.h = h; }");
        stringBuilder.append(ENTER);
        stringBuilder.append(TAB);
        for (Method method : interfaceClass.getMethods()) {
            stringBuilder.append(" public void " + method.getName() + "() {");
            stringBuilder.append(ENTER);
            stringBuilder.append(TAB);
            stringBuilder.append(" try{ ");
            stringBuilder.append(ENTER);
            stringBuilder.append(TAB);
            stringBuilder.append(TAB);
            stringBuilder.append(" Method method = " + interfaceClass.getName() + ".class.getMethod(\"" + method.getName() + "\");");
            stringBuilder.append(ENTER);
            stringBuilder.append(TAB);
            stringBuilder.append(TAB);
            stringBuilder.append(" this.h.invoke(method);");
            stringBuilder.append(ENTER);
            stringBuilder.append(TAB);
            stringBuilder.append(TAB);
            stringBuilder.append("}catch(Exception ex){}");
            stringBuilder.append(ENTER);
            stringBuilder.append(TAB);
            stringBuilder.append("}");
            stringBuilder.append(ENTER);
            stringBuilder.append("}");
        }
        String content = stringBuilder.toString();

        try {
            String filePath = "D:\\com\\codebear\\$Proxy.java";
            File file = new File(filePath);

            File fileParent = file.getParentFile();
            if(! fileParent.exists()) { fileParent.mkdirs(); } FileWriter fileWriter = new FileWriter(file); fileWriter.write(content); fileWriter.flush(); fileWriter.close(); JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fileManager = compiler.getStandardFileManager (null, null, null); Iterable iterable = fileManager.getJavaFileObjects(filePath); JavaCompiler.CompilationTask task = compiler.getTask (null, fileManager, null, null, null, iterable); task.call(); fileManager.close(); URLClassLoader classLoader = new URLClassLoader(new URL[]{new URL("file:D:\\\\")}); Class<? > clazz = classLoader.loadClass("com.codebear.$Proxy"); Constructor<? > constructor = clazz.getConstructor(MockInvocationHandler.class);return constructor.newInstance(h);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        returnnull; }}Copy the code

Then test it out:

public class Main { public static void main(String[] args) { MockInvocationHandler mockInvocationHandler=new MockInvocationHandler(new UserServiceImpl()); UserService userService = (UserService)MockProxy. newProxyInstance(UserService.class, mockInvocationHandler); userService.query(); }}Copy the code

Running results:

Well, without consideration of performance, maintainability, or security, our emasculated version of dynamic proxy is complete. The code is not too difficult, but more reflective and patient.

A brief analysis of the JDK source code

Source code based on JDK1.8

public static Object newProxyInstance(ClassLoader loader, Class<? >[] interfaces, InvocationHandler h) throws IllegalArgumentException { Objects.requireNonNull(h); final Class<? >[] intfs = interfaces.clone(); / / security verification final SecurityManager sm = System. GetSecurityManager ();if(sm ! = null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } /* * get the proxy Class */ Class<? > cl = getProxyClass0(loader, intfs); try {if(sm ! = null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } final Constructor<? > cons = cl.getConstructor(constructorParams); Final InvocationHandler ih = h; // If the constructor is not public, you need to modify the access permissions to make it accessibleif(! Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Voidrun() {
                        cons.setAccessible(true);
                        returnnull; }}); }returncons.newInstance(new Object[]{h}); // Create an object with a constructor, The incoming InvocationHandler object} the catch (IllegalAccessException | InstantiationException e) {throw new InternalError (e. oString (),  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); }}Copy the code

Taking a quick look at the source code, we can quickly move on to the getProxyClass0 method. That’s what we need to worry about.

private static Class<? > getProxyClass0(ClassLoader loader, Class<? >... Interfaces) {// An error is reported when the interface is larger than 65535if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        return proxyClassCache.get(loader, interfaces);
    }
Copy the code

This method doesn’t do anything, but it’s easy to see that JDK dynamic proxies are cached by using the proxyClassCache. The method we need to focus on is in get.

public V get(K key, P parameter) { Objects.requireNonNull(parameter); expungeStaleEntries(); CacheKey = cacheKey.valueof (key, refQueue); cacheKey = cacheKey.valueof (key, refQueue); ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey); ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey); // If valuesMap is empty, create a new ConcurrentHashMap. // Key is the generated cacheKey and push the new ConcurrentHashMap to the mapif (valuesMap == null) {
            ConcurrentMap<Object, Supplier<V>> oldValuesMap
                = map.putIfAbsent(cacheKey,
                                  valuesMap = new ConcurrentHashMap<>());
            if(oldValuesMap ! = null) { valuesMap = oldValuesMap; }} // Key is the class loader, parameter is the class itself, SubKey = objects.requirenonNULL (subkeyFactory.apply (key, parameter)); Supplier<V> supplier = valuesMap.get(subKey); Factory factory = null;while (true) {
            if(supplier ! = null) {// If there is a cache, call get directly and return, if there is no cache, continue to execute the following code, // becausewhile (true// The get method calls the static inner class Factory in WeakCahce V value = suppler.get (); // The static inner class Factory V value = suppler.get ();if(value ! = null) {returnvalue; }} // When factory is null, a Factory object is createdif (factory == null) {
                factory = new Factory(key, parameter, subKey, valuesMap);
            }

            if (supplier == null) {
                supplier = valuesMap.putIfAbsent(subKey, factory);
                if(supplier == null) {// When there is no proxy class cache, it runs here, assigns the supplier object to the Factory, and the next loop, supplier is not empty and can call get to return it. // This Factory is located in the WeakCahce class, which is a static internal class supplier = Factory; }}else {
                if (valuesMap.replace(subKey, supplier, factory)) {
                    supplier = factory;
                } else{ supplier = valuesMap.get(subKey); }}}}Copy the code

Here the code is more complex, simple to say:

  • The JDK dynamic proxy uses two layers of maps for caching, the first layer being the classloader and the second layer being the classloader + itself
  • When there is a cache, call GET directly and return. Otherwise, continue with the following code to assign to supplier. Since while (true), it will get here a second time and return with get(). The core is supplene.get (), which calls the static inner class Factory get() in WeakCahce, which is the method to get the proxy class.

Let’s look at the component.get () method:

 value = Objects.requireNonNull(valueFactory.apply(key, parameter));
Copy the code

But what is valueFactory? We can look at its definition:

 private final BiFunction<K, P, V> valueFactory;
Copy the code

Let’s take a look at its WeakCahce construction method:

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

We must have called this constructor somewhere, as defined in the Proxy class:

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

Is proxyClassCache familiar? Yes, it is used in getProxyClass0, where a WeakCache object is created and a constructor with two arguments is called. The second argument is a ProxyClassFactory object. BiFunction<K, P, V> valueFactory is the second parameter in WeakCache, and then the value is assigned to final valueFactory. Valuefactory.apply so you end up calling the apply method in ProxyClassFactory. The key is:

byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); // Generate the binary array of the proxy Class try {// Internal is a native tag method, which is implemented in C or C++return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                throw new IllegalArgumentException(e.toString());
            }
Copy the code

GenerateProxyClass generates a binary array of proxy classes inside the generateProxyClass method. You can click on it and see for yourself. We won’t go further here, because our goal is to find generateProxyClass and write a method of our own. GenerateProxyClass, output the returned byte[] to the.class file, and use idea decompile to see what the resulting proxy class looks like:

 byte[] $proxies = ProxyGenerator.generateProxyClass("$Proxy", new Class[]{UserService.class});
        File file=new File("D:\\$Proxy.class");
        FileOutputStream outputStream = null;
        try {
            outputStream = new FileOutputStream(file);
            try {
                outputStream.write($proxies);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
Copy the code

$proxy. class = $proxy. class = $proxy. class = $proxy. class = $proxy. class = $proxy. class = $proxy. class

// Public Final Class inherits Proxy class$Proxy extends Proxy implements UserService {
    public $Proxy(InvocationHandler var1) throws { super(var1); } public final void query() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); }}}Copy the code

This code is not very familiar, very close to our own handwritten dynamic proxy generated proxy class.

hand

Well, first of all, I wrote a castrated version of the dynamic proxy, and then a simple look at the JDK dynamic proxy source code, also look at the JDK dynamic proxy generated proxy class. In this way, we can solve the above three puzzles:

  1. Class loaders are required internally in the JDK to act as cache keys via class loading and to generate classes
  2. Why you need an interface: Because the generated proxy class needs to implement this interface
  3. Why JDK dynamic proxies can only Proxy interfaces: Because the generated Proxy class already inherits from Proxy classes, Java is single-inherited, so there is no way to inherit from another class.

Some bloggers might point out the difference between cglib and JDK dynamic proxies. Cglib does this by manipulating bytecodes, but JDK dynamic proxies also manipulate bytecodes.

After this analysis, I believe you have a new understanding of the JDK dynamic proxy.