This article is participating in “Java Theme Month – Java Development Practice”, for details: juejin.cn/post/696826…

This is the 7th day of my participation in Gwen Challenge

Dynamic proxy step

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

  2. Create proxied classes and interfaces

  3. Static method by Proxy

Static method by 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);
        returnresult; }}Copy the code

Create an agent newProxyInstance

    public static Object newProxyInstance(ClassLoader loader, Class
       [] interfaces, InvocationHandler h)
        throws IllegalArgumentException
    {
        // check that h is not null
        Objects.requireNonNull(h);
        // Make a copy of the interface class object
        finalClass<? >[] intfs = interfaces.clone();// Perform some security checks
        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 {
            if(sm ! =null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
            // Get the constructor of the proxy class object, whose arguments are specified by constructorParams
            // constructorParames are constant values:
			private static finalClass<? >[] constructorParams = { InvocationHandler.class };finalConstructor<? > cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;
            if(! Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run(a) {
                        cons.setAccessible(true);
                        return null; }}); }New Object[]{h}
            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 newInternalError(t.toString(), t); }}catch (NoSuchMethodException e) {
            throw newInternalError(e.toString(), e); }}Copy the code
  1. First, void processing is performed on H.

GetProxyClass0 (loader, INTFS) to get the Class object of the proxy Class, and then get the constructor from the Class object, and then create the proxy object. The next step is the getProxyClass0 method. According to 1, the interface obtains the interface class first. When the number of interfaces exceeds 65535, an exception is reported.

    // This method is also a Proxy method
    private staticClass<? > 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 defined by the specified class loader and implements the given interface,
        // Return the cached proxy class object, otherwise use ProxyClassFactory to create the proxy class.
        return proxyClassCache.get(loader, interfaces);
    }
Copy the code
  1. ProxyClassCache is a cache of weak references

Look up or generate the designated proxy class. Query (already in the cache) or generate a class object for the specified proxy class.

Before we get into the get method, what is proxyClassCache? High alert, the code ahead may look messy, but we just need to focus on the main point.

private static finalWeakCache<ClassLoader, Class<? >[], Class<? >> proxyClassCache =new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
Copy the code
//K indicates the type of key, P indicates the type of parameter, and V indicates the type of value.
// WeakCache
      
       [], Class
       > proxyClassCache Indicates that the value of proxyClassCache is Class
        object, which is exactly what 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 finalBiFunction<K, P, ? > subKeyFactory;private final BiFunction<K, P, V> valueFactory;
    public WeakCache(BiFunction
       
         subKeyFactory, BiFunction
        
          valueFactory)
        ,>
       ,> {
        this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
        this.valueFactory = Objects.requireNonNull(valueFactory);
    }
Copy the code

The map variable is the core variable to achieve cache, which is a double map structure: (key, sub-key) -> value. Where key is the object wrapped by the Classloader passed in, sub-key is generated by KeyFactory() passed in by WeakCache constructor. Value is the object that generates the proxy class, which is generated by the ProxyClassFactory() passed on by the WeakCache constructor. Here, to recap:

ProxyClassCache is a WeakCache object that calls proxyClassCache. Get (loader, interfaces); You can get cached proxy classes or create proxy classes if there is no caching.

It indicates that WeakCache has the method get. Let’s take a look at the definition of WeakCache class (here we only give the definition and constructor of variables first), and continue to look at its get ();

//K and P are generics in WeakCache definition, key is class loader, parameter is interface class array
public V get(K key, P parameter) {
        // Check that parameter is not empty
        Objects.requireNonNull(parameter);
         // Clear the invalid cache
        expungeStaleEntries();
        // cacheKey is (key, sub-key) -> value;
        Object cacheKey = CacheKey.valueOf(key, refQueue);
        // lazily install the 2nd level valuesMap for the particular cacheKey
        // Get the ConcurrentMap
      
       > Object based on the primary key. If one does not already exist, create a New ConcurrentMap
       > in the map along with the 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
        // This is the code that generates the sub-key
        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
        // Get supplier via sub-key
        Supplier<V> supplier = valuesMap.get(subKey);
        // Supplier is actually the factory
        Factory factory = null;

        while (true) {
            // If there is any supplier in the cache, we get the supplier directly, return it, and we are done.
            if(supplier ! =null) {
                // supplier might be a Factory or a CacheValue<V> instance
                V value = supplier.get();
                if(value ! =null) {
                    returnvalue; }}// 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 to create a Factory object if no supplier exists in the cache, which is safely assigned to the supplier in a multi-threaded environment.
            // While (true), the assignment is successful and the return is complete.
            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 suppliersupplier = valuesMap.get(subKey); }}}}Copy the code

So let’s look at the get method in the Factory class. Now look at the supplier get()

        public synchronized V get(a) { // serialize access
            // re-check
            Supplier<V> supplier = valuesMap.get(subKey);
            // Re-check that the 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 is generated by calling valueFactory
                 //valueFactory is the new ProxyClassFactory() we passed
                // We will analyze the apply method of ProxyClassFactory() later
                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
            assertvalue ! =null;

            // wrap value with CacheValue (WeakReference)
            // Wrap value as a weak reference
            CacheValue<V> cacheValue = new CacheValue<>(value);

            // put into reverseMap
            // reverseMap is used to implement cache validity
            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
            returnvalue; }}Copy the code

Come to the Apply method at ProxyClassFactory, where the proxy classes are generated. WeakCache<ClassLoader, Class[], Class>, the first one in the generic type represents loader K, the second represents interface Class P, and the third is the generated proxyClass V. V is generated by ProxyClassFactory. Call its apply ();

 BiFunction
      
        BiFunction
       
         BiFunction
        
          BiFunction
        ,>
       ,>
      ,>
private static final class ProxyClassFactory
        implements BiFunction<ClassLoader.Class<? > [].Class<? >>{
        // prefix for all proxy class names
        // The prefix of all proxy class names
        private static final String proxyClassNamePrefix = "$Proxy";
        // next number to use for generation of unique proxy class names
        // The counter used to generate the proxy class name
        private static final AtomicLong nextUniqueNumber = new AtomicLong();
        @Override
        publicClass<? > apply(ClassLoader loader, Class<? >[] interfaces) { Map<Class<? >, Boolean> interfaceSet =new IdentityHashMap<>(interfaces.length);
            // Verify the proxy interface
            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()); }}// The package name of the generated proxy class
            String proxyPkg = null;     // package to define proxy class in
            // Proxy class access control characters: 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 package; Public ones don't need to be handled
            // Generate logic for package name and class name, default package name is com.sun.proxy,
			// The class name defaults to $Proxy with an incremented integer value
            // If the proxied class is a non-public proxy interface, use the same package name as the proxied class interface
            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();
            // Fully qualified name of the proxy class, such as com.sun.proxy.$proxy0.calss
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            /* * Generate the specified proxy class. */
            // Generate the bytecode of the proxy class
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                // Load the proxy class into the JVM, and the dynamic proxy process is almost complete
                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 newIllegalArgumentException(e.toString()); }}}Copy the code

Then call getMethod (), adding equals(), hashCode (),toString(), etc. The methods of all interfaces are then iterated over and added to the proxy class. Finally, sort these methods.

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
       
         methods)
        {
        for (Class<?> i : interfaces) {
            getMethodsRecursive(i.getInterfaces(), methods);
            Collections.addAll(methods, i.getDeclaredMethods());
        }
    }
Copy the code

Finally, the related 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
 * @dateAugust 31, 2018 11:35:07 PM *@todo TODO
 */
public class DynamicProxyTest {

    public static void main(String[] args) {

        IUserService target = new UserServiceImpl();
        MyInvocationHandler handler = new MyInvocationHandler(target);
        // The first argument is to specify the classloader for the proxy class (we pass in the classloader for the current test class)
        // The second argument is the interface 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 proxied class implement the same interface)
        The third argument is the Invocation Handler, which handles the invocation of the method. Here we pass in our own implementation handler
        IUserService proxyObject = (IUserService) Proxy.newProxyInstance(DynamicProxyTest.class.getClassLoader(),
                target.getClass().getInterfaces(), handler);
        proxyObject.add("Chen 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(); }}}}Copy the code
Decomcompiled by Jad V1.5.8 E2. 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 takes an instance of InvocationHandler,
    The proxy. newInstance method creates a Proxy instance through this constructor
    public $Proxy0(InvocationHandler invocationhandler)
    {
        super(invocationhandler);
    }
     // Three methods in the Object class, 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 newUndeclaredThrowableException(throwable); }}public final String toString(a)
    {
        try
        {
            return (String)super.h.invoke(this, m2, null);
        }
        catch (Error ) { }
        catch (Throwable throwable)
        {
            throw newUndeclaredThrowableException(throwable); }}// Interface proxy method
    public final void add(String s)
    {
        try
        {
            The invocation Handler's invoke method is called here
            super.h.invoke(this, m3, new Object[] {
                s
            });
            return;
        }
        catch (Error ) { }
        catch (Throwable throwable)
        {
            throw newUndeclaredThrowableException(throwable); }}public final int hashCode(a)
    {
        try
        {
            // The invoke method is called here.
            return ((Integer)super.h.invoke(this, m0, null)).intValue();
        }
        catch (Error ) { }
        catch (Throwable throwable)
        {
            throw newUndeclaredThrowableException(throwable); }}// Static code blocks do some initialization of variables
    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 newNoClassDefFoundError(classnotfoundexception.getMessage()); }}}Copy the code