Detailed explanation of JDK dynamic agent steps of source code analysis

  • 2021-09-11 20:14:01
  • OfStack

Dynamic proxy step

1. Create a class that implements the interface InvocationHandler, which must implement the invoke method

2. Create proxy classes and interfaces

3. Static method through Proxy

Static method through Proxy


ProxyObject proxyObject = new ProxyObject();
    InvocationHandler invocationHandler = new DynamicProxy(proxyObject);
    ClassLoader classLoader = proxyObject.getClass().getClassLoader();
    ProxyObjectInterface proxy = (IRoom) Proxy.newProxyInstance(classLoader,new Class[]
    {ProxyObjectInterface.class},invocationHandler);
    proxy.execute();

    public class DynamicProxy implements InvocationHandler {
    private Object object;

    public DynamicProxy(Object object){
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(object,args);
        return result;
    }
}

Create a proxy newProxyInstance


public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        // Inspection h Is not empty, h Throw anomaly for null 
        Objects.requireNonNull(h);
        // Class object copy of interface 1 Portions 
        final Class<?>[] intfs = interfaces.clone();
        // Go on 1 Some security checks 
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }
        /*
         * Look up or generate the designated proxy class.
         *   Query (already in the cache) or generate the specified proxy class class Object. 
         */
        Class<?> cl = getProxyClass0(loader, intfs);
        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
            // Get the constructor of the proxy class object, and the parameters of this constructor are determined by the constructorParams Specify 
            // Parameter constructorParames Is a constant value: 
            private static final Class<?>[] constructorParams = { InvocationHandler.class };
            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;
                    }
                });
            }
            // The proxy object is generated here, and the passed parameters new Object[]{h} Talk later 
            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);
        }
    }

First, the h is judged to be empty.
The core of this code is to get the Class object of proxy class through getProxyClass0 (loader, intfs), and then get the construction method through Class object, and then create proxy object. Look at the getProxyClass0 method in the next step. From 1, we can see that the interface class is obtained from the interface first, and when the number of interfaces exceeds 65535, an exception is reported.


// This method is also Proxy Methods under the class 
    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
        // If the proxy class is specified by the class loader loader Defines and implements the given interface interfaces , 
        // Then the cached proxy class object is returned, otherwise, the proxy class object is used ProxyClassFactory Create a proxy class. 
        return proxyClassCache.get(loader, interfaces);
    }

proxyClassCache is a weak reference cache
When you see proxyClassCache here, you will know that it means cache when you have Cache, which just echoes the previous Look up or generate the designated proxy class. Query (already in the cache) or generate an class object for the specified proxy class.

Before entering the get method, let's look at what proxyClassCache is. High-energy warning, the code ahead may look messy, but we only need to pay attention to the key points.


private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
//K Representative key The type of, P Represents the type of the parameter, V Representative value The type of. 
// WeakCache<ClassLoader, Class<?>[], Class<?>>  proxyClassCache   Description proxyClassCache The stored value is Class<?> Object, which is exactly the proxy class object we need. 
final class WeakCache<K, P, V> {
    private final ReferenceQueue<K> refQueue
        = new ReferenceQueue<>();
    // the key type is Object for supporting null key
    private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map
        = new ConcurrentHashMap<>();
    private final ConcurrentMap<Supplier<V>, Boolean> reverseMap
        = new ConcurrentHashMap<>();
    private final BiFunction<K, P, ?> subKeyFactory;
    private final BiFunction<K, P, V> valueFactory;
    public WeakCache(BiFunction<K, P, ?> subKeyFactory,
                     BiFunction<K, P, V> valueFactory) {
        this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
        this.valueFactory = Objects.requireNonNull(valueFactory);
    }

Where the map variable is the core variable to implement caching, it is a dual Map structure: (key, sub-key)- > value. Where key is the wrapped object of the incoming Classloader, and sub-key is generated by KeyFactory () passed by the WeakCache constructor. value is the object that produces the proxy class, which is generated by ProxyClassFactory (), a descendant of the WeakCache constructor. As follows, review 1:

proxyClassCache is an object of the WeakCache class that calls proxyClassCache. get (loader, interfaces); You can get a cached proxy class or create a proxy class (in the case of no cache).

Explain that get is a method in WeakCache. Look at the definition of WeakCache class first (only the definition and constructor of variables are given here), and continue to look at its get ();


//K And P Is WeakCache Generics in the definition, key Is a class loader, parameter Is an array of interface classes 
public V get(K key, P parameter) {
        // Check parameter Not empty 
        Objects.requireNonNull(parameter);
         // Clear invalid cache 
        expungeStaleEntries();
        // cacheKey Is (key, sub-key) -> value In 1 Grade key , 
        Object cacheKey = CacheKey.valueOf(key, refQueue);
        // lazily install the 2nd level valuesMap for the particular cacheKey
        // According to 1 Grade key Get  ConcurrentMap<Object, Supplier<V>> Object. If it does not exist before, create a new 1 A ConcurrentMap<Object, Supplier<V>> And cacheKey ( 1 Grade key ) 1 Lifting and placing map Medium. 
        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
        // This is the part that calls the build sub-key The code of, we have already seen how to generate it above 
        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
        // Pass sub-key Get supplier
        Supplier<V> supplier = valuesMap.get(subKey);
        //supplier Actually, this is it factory
        Factory factory = null;

        while (true) {
            // If there is a cache supplier  , then go directly through get Method, get the proxy class object, return, and it's over. 1 Temporal analysis get Method. 
            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
            // The purpose of all the following code is: If there is no supplier Is created 1 A Factory Object, put factory Secure assignment of objects in a multithreaded environment supplier . 
            // Because it was in while ( true ), go back to the above after the assignment is successful get Method, the return ends. 
            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);
                }
            }
        }
    }

So let's look at the get method in the Factory class. Next, look at get () of supplier


public synchronized V get() { // serialize access
            // re-check
            Supplier<V> supplier = valuesMap.get(subKey);
            // Re-check the results supplier Is the current object 
            if (supplier != this) {
                // something changed while we were waiting:
                // might be that we were replaced by a CacheValue
                // or were removed because of failure ->
                // return null to signal WeakCache.get() to retry
                // the loop
                return null;
            }
            // else still us (supplier == this)
            // create new value
            V value = null;
            try {
                 // This is where the proxy class calls valueFactory Generated 
                 //valueFactory Is what we passed in  new ProxyClassFactory()
                //1 Will we analyze ProxyClassFactory() Adj. apply Method 
                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)
            // Put value Wrapped as a weak reference 
            CacheValue<V> cacheValue = new CacheValue<>(value);

            // put into reverseMap
            // reverseMap Is used to realize the validity of cache 
            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;
        }
    }

To the apply method of ProxyClassFactory, where the proxy class is generated.

First look at the definition of proxyClassCache WeakCache < ClassLoader, Class < ? > [], Class < ? > > In the generics, the first represents the loader K, the second represents the interface class P, and the third is the generated proxy class V. The generation of V is generated by ProxyClassFactory. Call its apply ();


// Here's BiFunction<T, U, R> Is a functional interface, which can be understood as using T , U Two types as parameters, and get R Return value of type 
private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>
    {
        // prefix for all proxy class names
        // Prefix of all proxy class names 
        private static final String proxyClassNamePrefix = "$Proxy";
        // next number to use for generation of unique proxy class names
        // Counters used to generate proxy class names 
        private static final AtomicLong nextUniqueNumber = new AtomicLong();
        @Override
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            // Verify the proxy interface without looking at 
            for (Class<?> intf : interfaces) {
                /*
                 * Verify that the class loader resolves the name of this
                 * interface to the same Class object.
                 */
                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");
                }
                /*
                 * Verify that the Class object actually represents an
                 * interface.
                 */
                if (!interfaceClass.isInterface()) {
                    throw new IllegalArgumentException(
                        interfaceClass.getName() + " is not an interface");
                }
                /*
                 * Verify that this interface is not a duplicate.
                 */
                if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                    throw new IllegalArgumentException(
                        "repeated interface: " + interfaceClass.getName());
                }
            }
            // Package name of the generated proxy class  
            String proxyPkg = null;     // package to define proxy class in
            // Proxy class access control character : public ,final
            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
            /*
             * Record the package of a non-public proxy interface so that the
             * proxy class will be defined in the same package.  Verify that
             * all non-public proxy interfaces are in the same package.
             */
            // Verify that all non-public interfaces are in the same 1 Within a package; Public ones don't need to be dealt with 
            // Logic for generating package name and class name, and the package name defaults to com.sun.proxy , 
            //  The class name defaults to $Proxy  Plus 1 Self-increasing integer value 
            // If the proxy class is  non-public proxy interface  The interface with the proxy class is used 1 Sample package name 
            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) {
                // if no non-public proxy interfaces, use com.sun.proxy package
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }

            /*
             * Choose a name for the proxy class to generate.
             */
            long num = nextUniqueNumber.getAndIncrement();
            // The fully qualified name of the proxy class, such as com.sun.proxy.$Proxy0.calss
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            /*
             * Generate the specified proxy class.
             */
            // The core part, which generates the bytecode of the proxy class 
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                // Load the proxy class into the JVM At this point, the dynamic agent process basically ends 
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                /*
                 * A ClassFormatError here means that (barring bugs in the
                 * proxy class generation code) there was some other
                 * invalid aspect of the arguments supplied to the proxy
                 * class creation (such as virtual machine limitations
                 * exceeded).
                 */
                throw new IllegalArgumentException(e.toString());
            }
        }
    }

Then call getMethod (), adding equals (), hashcode (), toString (), and so on. Then iterate through the methods of all interfaces and add them to the proxy class. Finally, these methods are sorted.


private static List<Method> getMethods(Class<?>[] interfaces) {
        List<Method> result = new ArrayList<Method>();
        try {
            result.add(Object.class.getMethod("equals", Object.class));
            result.add(Object.class.getMethod("hashCode", EmptyArray.CLASS));
            result.add(Object.class.getMethod("toString", EmptyArray.CLASS));
        } catch (NoSuchMethodException e) {
            throw new AssertionError();
        }

        getMethodsRecursive(interfaces, result);
        return result;
    }
private static void getMethodsRecursive(Class<?>[] interfaces, List<Method> methods) {
        for (Class<?> i : interfaces) {
            getMethodsRecursive(i.getInterfaces(), methods);
            Collections.addAll(methods, i.getDeclaredMethods());
        }
    }

Finally, the correlation proxy class is output


package com.zhb.jdk.proxy;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Proxy;

import com.zhb.jdk.dynamicProxy.HelloworldImpl;

import sun.misc.ProxyGenerator;

/**
 * @author ZHB
 * @date 2018 Year 8 Month 31 Sunday afternoon 11:35:07
 * @todo TODO
 */
public class DynamicProxyTest {

    public static void main(String[] args) {

        IUserService target = new UserServiceImpl();
        MyInvocationHandler handler = new MyInvocationHandler(target);
        // No. 1 1 The parameter is the class loader that specifies the proxy class (we pass in the class loader of the current test class) 
        // No. 1 2 Parameters are the interfaces that the proxy class needs to implement (we pass in the interface implemented by the proxy class, so that the generated proxy class and the proxy class implement the same interface) 
        // No. 1 3 The parameters are invocation handler That handles calls to methods. Here we pass in our own implementation handler
        IUserService proxyObject = (IUserService) Proxy.newProxyInstance(DynamicProxyTest.class.getClassLoader(),
                target.getClass().getInterfaces(), handler);
        proxyObject.add(" Old grain ");

        String path = "D:/$Proxy0.class";
        byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", HelloworldImpl.class.getInterfaces());
        FileOutputStream out = null;

        try {
            out = new FileOutputStream(path);
            out.write(classFile);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}
// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://kpdus.tripod.com/jad.html
// Decompiler options: packimports(3) fieldsfirst ansi space

import com.zhb.jdk.proxy.IUserService;
import java.lang.reflect.*;

public final class $Proxy0 extends Proxy
    implements IUserService
{

    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;
    // The constructor of the proxy class whose parameters are InvocationHandler Example, 
    //Proxy.newInstance Method is created by using this constructor to create a proxy instance 
    public $Proxy0(InvocationHandler invocationhandler)
    {
        super(invocationhandler);
    }
     // Object Class in the 3 Methods, equals , toString ,  hashCode
    public final boolean equals(Object obj)
    {
        try
        {
            return ((Boolean)super.h.invoke(this, m1, new Object[] {
                obj
            })).booleanValue();
        }
        catch (Error ) { }
        catch (Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final String toString()
    {
        try
        {
            return (String)super.h.invoke(this, m2, null);
        }
        catch (Error ) { }
        catch (Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }
    // Interface proxy method 
    public final void add(String s)
    {
        try
        {
            // invocation handler Adj.  invoke Method is called here 
            super.h.invoke(this, m3, new Object[] {
                s
            });
            return;
        }
        catch (Error ) { }
        catch (Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final int hashCode()
    {
        try
        {
            //  Called here invoke Method. 
            return ((Integer)super.h.invoke(this, m0, null)).intValue();
        }
        catch (Error ) { }
        catch (Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    //  Static code blocks perform on variables 1 Some initialization work 
    static 
    {
        try
        {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
                Class.forName("java.lang.Object")
            });
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m3 = Class.forName("com.zhb.jdk.proxy.IUserService").getMethod("add", new Class[] {
                Class.forName("java.lang.String")
            });
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
        }
        catch (NoSuchMethodException nosuchmethodexception)
        {
            throw new NoSuchMethodError(nosuchmethodexception.getMessage());
        }
        catch (ClassNotFoundException classnotfoundexception)
        {
            throw new NoClassDefFoundError(classnotfoundexception.getMessage());
        }
    }
}

Above is JDK Dynamic Agent Steps Detailed Explanation (Source Code Analysis) details, more information about JDK Dynamic Agent Please pay attention to this site other related articles!


Related articles: