Deep agent mode

Deep agent mode

Brief description of agency mode

Selling mobile phones

  1. Xiaomi company has produced mobile phones and wants to sell them for profit.
  2. Now there are many Xiaomi homes (agents) that can help Xiaomi sell mobile phones.
  3. When selling mobile phones, Xiaomi home can provide services such as invoice, real machine demonstration and explanation to help consumers have a better time in the process of buying mobile phones.

proxy pattern

Provide a proxy for other objects to control the access of this object. (please note the difference between it and Decorator mode Decorator)

advantage

  1. Clear responsibilities (decoupling):
  • Xiaomi is responsible for the production of mobile phones, including screens, systems, chargers and so on.
  • Xiaomi home is responsible for selling mobile phones, including providing invoice services, pre-sales and after-sales services.
  1. Scalability:
  • Xiaomi company can not only produce mobile phones, but also produce Xiaomi bracelets, Xiaomi routers, Xiaomi rice cookers and so on.
  • Xiaomi home can sell not only mobile phones, but also Xiaomi bracelets, Xiaomi routers, Xiaomi rice cookers and so on.
  1. Enhancements:
  • If consumers buy mobile phones from the factory, the factory will give you a mobile phone, and you will not be able to enjoy the services provided by Xiaomi home.

shortcoming

  1. Complex system:
    Xiaomi used to be a small company that produced and sold its own mobile phones; Later, it was gradually accepted by the public, the business volume increased, and it began to be unable to do what it wanted, so it turned the production of mobile phones and the sale of mobile phones into two directions to develop, and each tube had no influence on each other; However, Xiaomi needs to coordinate the relationship between production and sales in order to obtain the maximum profit.
  2. The call chain is added and the processing speed is slow:
    Consumers go to Xiaomi home to buy a popular mobile phone. Xiaomi home tells consumers that the mobile phone has been sold out; Need to wait for the next batch; The next batch of mobile phones produced need to be sent to Xiaomi home. After Xiaomi home gets the mobile phone, it will notify consumers; Consumers can get their phones. If consumers buy directly from mobile phone manufacturers, they can get the goods as long as they have the goods, which will be very fast.

Static proxy

UML class diagram

Interface based static proxy

/**
 * Mi phones
 */
class MiPhone {
    public void call() {
        System.out.println("Can I call");
    }
    public void sendMessage() {
        System.out.println("I can text");
    }
}

/**
 * Selling mobile services
 */
interface SellMiPhone {
    public abstract MiPhone sell();
}

/**
 * Millet factory sells mobile phones
 */
class MiFactory implements SellMiPhone {

    public MiPhone sell() {
    	System.out.println("This is a MiPhone!");
        return new MiPhone();
    }

}
/**
 * Xiaomi home sells mobile phones
 */
class MiHome implements SellMiPhone {

    private MiFactory factory;

	public MiHome(MiFactory factory) {
		this.factory = factory;
	}

    public MiPhone sell() {
        System.out.println("Pre sales service");
        MiPhone miPhone = factory.sell();
        System.out.println("after-sale service");
        return miPhone;
    }

}

public class ProxyPatternDemo {

    public static void main(String[] args) {
        SellMiPhone sell1= new MiFactory();
        sell1.sell();	// The mobile phone bought in Xiaomi factory is a bare metal

        SellMiPhone sell2= new MiHome((MiFactory) sell1);
        sell2.sell();	// The mobile phones bought under the agency of Xiaomi home have pre-sales and after-sales services
    }

}

In the interface based static proxy, MiHome and mifactory are aggregation relationships; Xiaomi home maintains the relationship with Xiaomi factory.
Why is it called static? Because its type is predetermined in advance, such as MiHome in the above code.

The proxy mode can add and enhance some functions by extending the proxy class without modifying the proxy object.
It is worth noting that the proxy class and the proxied class should jointly implement an interface or inherit an abstract class (otherwise, its type cannot be determined during the call).

Interface based dynamic agent

/**
 * Mi phones
 */
class MiPhone {
    public void call() {
        System.out.println("Can I call");
    }
    public void sendMessage() {
        System.out.println("I can text");
    }
}

/**
 * Selling mobile services
 */
interface SellMiPhone {
    public abstract MiPhone sell();
}

/**
 * Millet factory sells mobile phones
 */
class MiFactory implements SellMiPhone {

    public MiPhone sell() {
        return new MiPhone();
    }

}

/**
 * Logic of Xiaomi home selling mobile phones (call processor)
 */
class MiHome implements InvocationHandler {

    private MiFactory factory;  // Factory represented by Xiaomi home

    public void setFactory(MiFactory factory) {
        this.factory = factory;
    }

    /**
     * Method call processing logic of Xiaomi home
     *
     * @param proxy Current proxy object
     * @param method Current method object
     * @param args Current method parameters
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Pre sales service");
        Object res = method.invoke(factory, args);  // When you want a mobile phone, ask the agent's factory for it
        System.out.println("after-sale service");

        return res;
    }
}

public class ProxyPatternDemo {

    public static void main(String[] args) {

        MiFactory beiJingMiFactory = new MiFactory();   // Beijing Xiaomi factory

        MiHome miHome = new MiHome();
        miHome.setFactory(beiJingMiFactory);            // This Xiaomi home sells mobile phone logic and acts as an agent for Beijing Xiaomi factory

        SellMiPhone sell = (SellMiPhone) Proxy.newProxyInstance(SellMiPhone.class.getClassLoader(),
                            new Class[]{SellMiPhone.class},
                            miHome);                    // Now Xiaomi home is buying a mobile phone
    }

}

overview:
Now we have implemented a proxy scheme through JDK dynamic proxy. We only need to write our custom proxy logic (call processor) to complete our goal.
However, the implementation of JDK dynamic agent is based on the interface, that is, the returned result can only be the interface type.
Now let's compare the advantages of static agents?

  1. Now we only need to focus on our proxy logic (enhanced functionality) without writing specific proxy class code
  2. The agent logic can be reused to represent Beijing Xiaomi factory or Tianjin Xiaomi factory

Now let's think about another problem: proxy How does the newproxyinstance method help us generate a proxy class? What are the characteristics of this proxy class? Why can only be based on interfaces? (sorry, one question leads to multiple questions)

Proxy source code analysis (JDK1.8.0_251)

package java.lang.reflect;

import java.lang.ref.WeakReference;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiFunction;
import sun.misc.ProxyGenerator;
import sun.misc.VM;
import sun.reflect.CallerSensitive;
import sun.reflect.Reflection;
import sun.reflect.misc.ReflectUtil;
import sun.security.util.SecurityConstants;

/**
 * Proxy provides static methods for creating dynamic proxy
 * classes and instances, and it is also the superclass of all
 * dynamic proxy classes created by those methods.
 * Proxy Provides static methods for creating proxy object classes and instances, and it is also the parent of all dynamic proxy classes created by these methods.
 *
 * To create a proxy for some interface {@code Foo}:
 * Example of creating proxy object for Foo interface:
 *     InvocationHandler handler = new MyInvocationHandler(...);
 *     Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);
 *     Foo f = (Foo) proxyClass.getConstructor(InvocationHandler.class).
 *                     newInstance(handler);
 * or more simply:
 * Or a simpler way:
 *     Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
 *                                          new Class<?>[] { Foo.class },
 *                                          handler);
 *
 * A dynamic proxy class (simply referred to as a proxy
 * class below) is a class that implements a list of interfaces
 * specified at runtime when the class is created, with behavior as
 * described below.
 * Dynamic proxy class (hereinafter referred to as proxy class) is a class that implements the specified interface list and has the following performance.
 *
 * A proxy interface is such an interface that is implemented
 * by a proxy class.
 * A proxy interface is an interface implemented by a proxy class
 *
 * A proxy instance< is an instance of a proxy class.
 * A proxy instance is an instance of a proxy class
 *
 * Each proxy instance has an associated <i>invocation handler</i>
 * object, which implements the interface {@link InvocationHandler}.
 * A method invocation on a proxy instance through one of its proxy
 * interfaces will be dispatched to the {@link InvocationHandler#invoke
 * invoke} method of the instance's invocation handler, passing the proxy
 * instance, a {@code java.lang.reflect.Method} object identifying
 * the method that was invoked, and an array of type {@code Object}
 * containing the arguments.  The invocation handler processes the
 * encoded method invocation as appropriate and the result that it
 * returns will be returned as the result of the method invocation on
 * the proxy instance.
 * Each proxy instance has an associated call processing object, which implements the interface InvocationHandler.
 * The method of calling the proxy instance through the proxy interface will be dispatched to InvokeHandler.
 *
 * <p>A proxy class has the following properties:
 * The proxy class has the following properties:
 * 
 * <ul>
 * <li>Proxy classes are <em>public, final, and not abstract</em> if
 * all proxy interfaces are public.</li>
 * If all proxy interfaces are public, then the proxy classes are public, final, and non abstract
 *
 * <li>Proxy classes are <em>non-public, final, and not abstract</em> if
 * any of the proxy interfaces is non-public.</li>
 * If a proxy interface is non-public, then the proxy classes are non-public, final and abstract
 *
 * <li>The unqualified name of a proxy class is unspecified.  The space
 * of class names that begin with the string {@code "$Proxy"}
 * should be, however, reserved for proxy classes.
 * The name of the Proxy class starts with $Proxy
 *
 * <li>A proxy class extends {@code java.lang.reflect.Proxy}.
 * A Proxy class inherits from Proxy
 *
 * <li>A proxy class implements exactly the interfaces specified at its
 * creation, in the same order.
 * The proxy class implements the proxy interface specified when the agent is created in the same order
 *
 * <li>If a proxy class implements a non-public interface, then it will
 * be defined in the same package as that interface.  Otherwise, the
 * package of a proxy class is also unspecified.  Note that package
 * sealing will not prevent a proxy class from being successfully defined
 * in a particular package at runtime, and neither will classes already
 * defined by the same class loader and the same package with particular
 * signers.
 * If a proxy class implements a non-public interface, it will be defined in the same package as the interface,
 * Otherwise, the package of the proxy class is unspecified. The tightness of the package, the class defined by the same class loader, and the package with the same specific signature,
 * Does not restrict the runtime definition of proxy classes.
 *
 * <li>Since a proxy class implements all of the interfaces specified at
 * its creation, invoking {@code getInterfaces} on its
 * {@code Class} object will return an array containing the same
 * list of interfaces (in the order specified at its creation), invoking
 * {@code getMethods} on its {@code Class} object will return
 * an array of {@code Method} objects that include all of the
 * methods in those interfaces, and invoking {@code getMethod} will
 * find methods in the proxy interfaces as would be expected.
 * Because a proxy Class implements all the specified interfaces when it is created, when calling the getInterfaces method of its Class object,
 * An interface array specified during creation will be returned; When calling the getMethods method of its Class object, it will return all the methods in all its interfaces
 * method; Call the getMethod method of its Class object to find the method of its proxy interface.
 *
 * <li>The {@link Proxy#isProxyClass Proxy.isProxyClass} method will
 * return true if it is passed a proxy class-- a class returned by
 * {@code Proxy.getProxyClass} or the class of an object returned by
 * {@code Proxy.newProxyInstance}-- and false otherwise.
 *  Proxy The isProxyClass method in can check whether a class is a proxy class:
 *   Via proxy Getproxyclass or proxy The object obtained by newproxyinstance returns yes
 *   Otherwise, return No
 *
 * <li>The {@code java.security.ProtectionDomain} of a proxy class
 * is the same as that of system classes loaded by the bootstrap class
 * loader, such as {@code java.lang.Object}, because the code for a
 * proxy class is generated by trusted system code.  This protection
 * domain will typically be granted
 * {@code java.security.AllPermission}.
 * Proxy classes and system classes loaded by the bootstrap classloader loader, such as Java Lang. object, with the same
 *   java.security.ProtectionDomain ;Because the bytecode of the proxy class is generated by the trusted system bytecode.
 *   This protection domain is naturally granted to Java security. AllPermission
 *
 * <li>Each proxy class has one public constructor that takes one argument,
 * an implementation of the interface {@link InvocationHandler}, to set
 * the invocation handler for a proxy instance.  Rather than having to use
 * the reflection API to access the public constructor, a proxy instance
 * can be also be created by calling the {@link Proxy#newProxyInstance
 * Proxy.newProxyInstance} method, which combines the actions of calling
 * {@link Proxy#getProxyClass Proxy.getProxyClass} with invoking the
 * constructor with an invocation handler.
 * Each proxy class has a public constructor with a parameter and an InvocationHandler implementation,
 * Used to set the invocation handler for the proxy instance
 * An instance of a proxy object does not have to use the reflection API to access the public constructor, but can also call proxy newProxyInstance
 * Method, which combines proxy Getproxyclass and calling constructor through invocation handler
 *
 * <p>A proxy instance has the following properties:
 * The proxy instance has the following properties:
 *
 * <ul>
 * <li>Given a proxy instance {@code proxy} and one of the
 * interfaces implemented by its proxy class {@code Foo}, the
 * following expression will return true:
 * <pre>
 *     {@code proxy instanceof Foo}
 * </pre>
 * and the following cast operation will succeed (rather than throwing
 * a {@code ClassCastException}):
 * <pre>
 *     {@code (Foo) proxy}
 * </pre>
 * proxy instanceof Foo And (Foo) Proxy are OK
 *
 * <li>Each proxy instance has an associated invocation handler, the one
 * that was passed to its constructor.  The static
 * {@link Proxy#getInvocationHandler Proxy.getInvocationHandler} method
 * will return the invocation handler associated with the proxy instance
 * passed as its argument.
 * A proxy object is related to an InvocationHandler, proxy getInvocationHandler(proxy) 
 * The relevant InvocationHandler will be returned
 *
 * <li>An interface method invocation on a proxy instance will be
 * encoded and dispatched to the invocation handler's {@link
 * InvocationHandler#invoke invoke} method as described in the
 * documentation for that method.
 * Indirect call
 *
 * <li>An invocation of the {@code hashCode},
 * {@code equals}, or {@code toString} methods declared in
 * {@code java.lang.Object} on a proxy instance will be encoded and
 * dispatched to the invocation handler's {@code invoke} method in
 * the same manner as interface method invocations are encoded and
 * dispatched, as described above.  The declaring class of the
 * {@code Method} object passed to {@code invoke} will be
 * {@code java.lang.Object}.  Other public methods of a proxy
 * instance inherited from {@code java.lang.Object} are not
 * overridden by a proxy class, so invocations of those methods behave
 * like they do for instances of {@code java.lang.Object}.
 * </ul>
 * Calling hashCode, equals and toString will be handled through the proxy object. Others are in Java lang.Object
 * Proxy object processing is not called when the method defined in is called
 *
 * <h3>Methods Duplicated in Multiple Proxy Interfaces</h3>
 * Duplicate methods in multi-agent interfaces 
 *
 * <p>When two or more interfaces of a proxy class contain a method with
 * the same name and parameter signature, the order of the proxy class's
 * interfaces becomes significant.  When such a <i>duplicate method</i>
 * is invoked on a proxy instance, the {@code Method} object passed
 * to the invocation handler will not necessarily be the one whose
 * declaring class is assignable from the reference type of the interface
 * that the proxy's method was invoked through.  This limitation exists
 * because the corresponding method implementation in the generated proxy
 * class cannot determine which interface it was invoked through.
 * Therefore, when a duplicate method is invoked on a proxy instance,
 * the {@code Method} object for the method in the foremost interface
 * that contains the method (either directly or inherited through a
 * superinterface) in the proxy class's list of interfaces is passed to
 * the invocation handler's {@code invoke} method, regardless of the
 * reference type through which the method invocation occurred.
 * When two or more interfaces of a proxy class contain methods with the same name and parameter signature, the order of proxy class interfaces becomes important. 
 * When this repeated method is called on the proxy instance, the method object passed to the calling handler is not necessarily the object whose declaration class can be allocated through the reference type of the interface calling the proxy method.
 * This limitation exists because the corresponding method implementation in the generated proxy class cannot determine the interface it calls.
 * Therefore, when a duplicate Method is called on a proxy instance, the Method object in the Method list of the proxy class containing the Method in the most important interface of the Method (inherited directly or through the super interface) is passed to the invoke Method of the calling handler, regardless of the reference type generated by the Method call. 
 * 
 * <p>If a proxy interface contains a method with the same name and
 * parameter signature as the {@code hashCode}, {@code equals},
 * or {@code toString} methods of {@code java.lang.Object},
 * when such a method is invoked on a proxy instance, the
 * {@code Method} object passed to the invocation handler will have
 * {@code java.lang.Object} as its declaring class.  In other words,
 * the public, non-final methods of {@code java.lang.Object}
 * logically precede all of the proxy interfaces for the determination of
 * which {@code Method} object to pass to the invocation handler.
 * If the proxy interface contains methods hashCode, equals, or toString with the same name and parameter signature, Java lang.Object ,
 * When this method is called in the proxy instance, the method passed to the calling handler object will have Java Lang. object as the declaration class.
 * In other words, Java Lang. object's public non final method Java Lang.Object logically precedes all proxy interfaces to determine which method object is passed to the calling handler.
 *
 * <p>Note also that when a duplicate method is dispatched to an
 * invocation handler, the {@code invoke} method may only throw
 * checked exception types that are assignable to one of the exception
 * types in the {@code throws} clause of the method in <i>all</i> of
 * the proxy interfaces that it can be invoked through.  If the
 * {@code invoke} method throws a checked exception that is not
 * assignable to any of the exception types declared by the method in one
 * of the proxy interfaces that it can be invoked through, then an
 * unchecked {@code UndeclaredThrowableException} will be thrown by
 * the invocation on the proxy instance.  This restriction means that not
 * all of the exception types returned by invoking
 * {@code getExceptionTypes} on the {@code Method} object
 * passed to the {@code invoke} method can necessarily be thrown
 * successfully by the {@code invoke} method.
 * Also note that when a duplicate method is dispatched to the call handler, 
 * invoke The method may only throw a check exception type that can be assigned to one exception type in the throws clause of all methods in all proxy interfaces that can be called. 
 * If the checked exception thrown by the invoke method cannot be assigned to any exception type that can be declared by calling the method in the proxy interface, select UndeclaredThrowableException to throw it through the proxy instance call. 
 * This restriction means that not all methods passed to the object invoke method on the exception type getExceptionTypes returned through the call can successfully throw the invoke method. 
 *
 * @author      Peter Jones
 * @see         InvocationHandler
 * @since       1.3
 */
public class Proxy implements java.io.Serializable {

    private static final long serialVersionUID = -2222568056686623797L;

    /** parameter types of a proxy class constructor */
    private static final Class<?>[] constructorParams =
        { InvocationHandler.class };

    /**
     * a cache of proxy classes
     */
    private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

    /**
     * the invocation handler for this proxy instance.
     * @serial
     */
    protected InvocationHandler h;

    /**
     * Prohibits instantiation.
     */
    private Proxy() {
    }

    /**
     * Constructs a new {@code Proxy} instance from a subclass
     * (typically, a dynamic proxy class) with the specified value
     * for its invocation handler.
     *
     * @param  h the invocation handler for this proxy instance
     *
     * @throws NullPointerException if the given invocation handler, {@code h},
     *         is {@code null}.
     */
    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }

    /**
     * Returns the {@code java.lang.Class} object for a proxy class
     * given a class loader and an array of interfaces.  The proxy class
     * will be defined by the specified class loader and will implement
     * all of the supplied interfaces.  If any of the given interfaces
     * is non-public, the proxy class will be non-public. If a proxy class
     * for the same permutation of interfaces has already been defined by the
     * class loader, then the existing proxy class will be returned; otherwise,
     * a proxy class for those interfaces will be generated dynamically
     * and defined by the class loader.
     *
     * <p>There are several restrictions on the parameters that may be
     * passed to {@code Proxy.getProxyClass}:
     *
     * <ul>
     * <li>All of the {@code Class} objects in the
     * {@code interfaces} array must represent interfaces, not
     * classes or primitive types.
     *
     * <li>No two elements in the {@code interfaces} array may
     * refer to identical {@code Class} objects.
     *
     * <li>All of the interface types must be visible by name through the
     * specified class loader.  In other words, for class loader
     * {@code cl} and every interface {@code i}, the following
     * expression must be true:
     * <pre>
     *     Class.forName(i.getName(), false, cl) == i
     * </pre>
     *
     * <li>All non-public interfaces must be in the same package;
     * otherwise, it would not be possible for the proxy class to
     * implement all of the interfaces, regardless of what package it is
     * defined in.
     *
     * <li>For any set of member methods of the specified interfaces
     * that have the same signature:
     * <ul>
     * <li>If the return type of any of the methods is a primitive
     * type or void, then all of the methods must have that same
     * return type.
     * <li>Otherwise, one of the methods must have a return type that
     * is assignable to all of the return types of the rest of the
     * methods.
     * </ul>
     *
     * <li>The resulting proxy class must not exceed any limits imposed
     * on classes by the virtual machine.  For example, the VM may limit
     * the number of interfaces that a class may implement to 65535; in
     * that case, the size of the {@code interfaces} array must not
     * exceed 65535.
     * </ul>
     *
     * <p>If any of these restrictions are violated,
     * {@code Proxy.getProxyClass} will throw an
     * {@code IllegalArgumentException}.  If the {@code interfaces}
     * array argument or any of its elements are {@code null}, a
     * {@code NullPointerException} will be thrown.
     *
     * <p>Note that the order of the specified proxy interfaces is
     * significant: two requests for a proxy class with the same combination
     * of interfaces but in a different order will result in two distinct
     * proxy classes.
     *
     * @param   loader the class loader to define the proxy class
     * @param   interfaces the list of interfaces for the proxy class
     *          to implement
     * @return  a proxy class that is defined in the specified class loader
     *          and that implements the specified interfaces
     * @throws  IllegalArgumentException if any of the restrictions on the
     *          parameters that may be passed to {@code getProxyClass}
     *          are violated
     * @throws  SecurityException if a security manager, <em>s</em>, is present
     *          and any of the following conditions is met:
     *          <ul>
     *             <li> the given {@code loader} is {@code null} and
     *             the caller's class loader is not {@code null} and the
     *             invocation of {@link SecurityManager#checkPermission
     *             s.checkPermission} with
     *             {@code RuntimePermission("getClassLoader")} permission
     *             denies access.</li>
     *             <li> for each proxy interface, {@code intf},
     *             the caller's class loader is not the same as or an
     *             ancestor of the class loader for {@code intf} and
     *             invocation of {@link SecurityManager#checkPackageAccess
     *             s.checkPackageAccess()} denies access to {@code intf}.</li>
     *          </ul>

     * @throws  NullPointerException if the {@code interfaces} array
     *          argument or any of its elements are {@code null}
     */
    @CallerSensitive
    public static Class<?> getProxyClass(ClassLoader loader,
                                         Class<?>... interfaces)
        throws IllegalArgumentException
    {
        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        return getProxyClass0(loader, intfs);
    }

    /*
     * Check permissions required to create a Proxy class.
     *
     * To define a proxy class, it performs the access checks as in
     * Class.forName (VM will invoke ClassLoader.checkPackageAccess):
     * 1. "getClassLoader" permission check if loader == null
     * 2. checkPackageAccess on the interfaces it implements
     *
     * To get a constructor and new instance of a proxy class, it performs
     * the package access check on the interfaces it implements
     * as in Class.getConstructor.
     *
     * If an interface is non-public, the proxy class must be defined by
     * the defining loader of the interface.  If the caller's class loader
     * is not the same as the defining loader of the interface, the VM
     * will throw IllegalAccessError when the generated proxy class is
     * being defined via the defineClass0 method.
     */
    private static void checkProxyAccess(Class<?> caller,
                                         ClassLoader loader,
                                         Class<?>... interfaces)
    {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            ClassLoader ccl = caller.getClassLoader();
            if (VM.isSystemDomainLoader(loader) && !VM.isSystemDomainLoader(ccl)) {
                sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
            }
            ReflectUtil.checkProxyPackageAccess(ccl, interfaces);
        }
    }

    /**
     * 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);
    }

    /*
     * a key used for proxy class with 0 implemented interfaces
     */
    private static final Object key0 = new Object();

    /*
     * Key1 and Key2 are optimized for the common use of dynamic proxies
     * that implement 1 or 2 interfaces.
     */

    /*
     * a key used for proxy class with 1 implemented interface
     */
    private static final class Key1 extends WeakReference<Class<?>> {
        private final int hash;

        Key1(Class<?> intf) {
            super(intf);
            this.hash = intf.hashCode();
        }

        @Override
        public int hashCode() {
            return hash;
        }

        @Override
        public boolean equals(Object obj) {
            Class<?> intf;
            return this == obj ||
                   obj != null &&
                   obj.getClass() == Key1.class &&
                   (intf = get()) != null &&
                   intf == ((Key1) obj).get();
        }
    }

    /*
     * a key used for proxy class with 2 implemented interfaces
     */
    private static final class Key2 extends WeakReference<Class<?>> {
        private final int hash;
        private final WeakReference<Class<?>> ref2;

        Key2(Class<?> intf1, Class<?> intf2) {
            super(intf1);
            hash = 31 * intf1.hashCode() + intf2.hashCode();
            ref2 = new WeakReference<Class<?>>(intf2);
        }

        @Override
        public int hashCode() {
            return hash;
        }

        @Override
        public boolean equals(Object obj) {
            Class<?> intf1, intf2;
            return this == obj ||
                   obj != null &&
                   obj.getClass() == Key2.class &&
                   (intf1 = get()) != null &&
                   intf1 == ((Key2) obj).get() &&
                   (intf2 = ref2.get()) != null &&
                   intf2 == ((Key2) obj).ref2.get();
        }
    }

    /*
     * a key used for proxy class with any number of implemented interfaces
     * (used here for 3 or more only)
     */
    private static final class KeyX {
        private final int hash;
        private final WeakReference<Class<?>>[] refs;

        @SuppressWarnings("unchecked")
        KeyX(Class<?>[] interfaces) {
            hash = Arrays.hashCode(interfaces);
            refs = (WeakReference<Class<?>>[])new WeakReference<?>[interfaces.length];
            for (int i = 0; i < interfaces.length; i++) {
                refs[i] = new WeakReference<>(interfaces[i]);
            }
        }

        @Override
        public int hashCode() {
            return hash;
        }

        @Override
        public boolean equals(Object obj) {
            return this == obj ||
                   obj != null &&
                   obj.getClass() == KeyX.class &&
                   equals(refs, ((KeyX) obj).refs);
        }

        private static boolean equals(WeakReference<Class<?>>[] refs1,
                                      WeakReference<Class<?>>[] refs2) {
            if (refs1.length != refs2.length) {
                return false;
            }
            for (int i = 0; i < refs1.length; i++) {
                Class<?> intf = refs1[i].get();
                if (intf == null || intf != refs2[i].get()) {
                    return false;
                }
            }
            return true;
        }
    }

    /**
     * A function that maps an array of interfaces to an optimal key where
     * Class objects representing interfaces are weakly referenced.
     */
    private static final class KeyFactory
        implements BiFunction<ClassLoader, Class<?>[], Object>
    {
        @Override
        public Object apply(ClassLoader classLoader, Class<?>[] interfaces) {
            switch (interfaces.length) {
                case 1: return new Key1(interfaces[0]); // the most frequent
                case 2: return new Key2(interfaces[0], interfaces[1]);
                case 0: return key0;
                default: return new KeyX(interfaces);
            }
        }
    }

    /**
     * A factory function that generates, defines and returns the proxy class given
     * the ClassLoader and array of interfaces.
     */
    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) {

            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            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());
                }
            }

            String proxyPkg = null;     // package to define proxy class in
            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.
             */
            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();
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            /*
             * Generate the specified proxy class.
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                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());
            }
        }
    }

    /**
     * Returns an instance of a proxy class for the specified interfaces
     * that dispatches method invocations to the specified invocation
     * handler.
     *
     * <p>{@code Proxy.newProxyInstance} throws
     * {@code IllegalArgumentException} for the same reasons that
     * {@code Proxy.getProxyClass} does.
     *
     * @param   loader the class loader to define the proxy class
     * @param   interfaces the list of interfaces for the proxy class
     *          to implement
     * @param   h the invocation handler to dispatch method invocations to
     * @return  a proxy instance with the specified invocation handler of a
     *          proxy class that is defined by the specified class loader
     *          and that implements the specified interfaces
     * @throws  IllegalArgumentException if any of the restrictions on the
     *          parameters that may be passed to {@code getProxyClass}
     *          are violated
     * @throws  SecurityException if a security manager, <em>s</em>, is present
     *          and any of the following conditions is met:
     *          <ul>
     *          <li> the given {@code loader} is {@code null} and
     *               the caller's class loader is not {@code null} and the
     *               invocation of {@link SecurityManager#checkPermission
     *               s.checkPermission} with
     *               {@code RuntimePermission("getClassLoader")} permission
     *               denies access;</li>
     *          <li> for each proxy interface, {@code intf},
     *               the caller's class loader is not the same as or an
     *               ancestor of the class loader for {@code intf} and
     *               invocation of {@link SecurityManager#checkPackageAccess
     *               s.checkPackageAccess()} denies access to {@code intf};</li>
     *          <li> any of the given proxy interfaces is non-public and the
     *               caller class is not in the same {@linkplain Package runtime package}
     *               as the non-public interface and the invocation of
     *               {@link SecurityManager#checkPermission s.checkPermission} with
     *               {@code ReflectPermission("newProxyInPackage.{package name}")}
     *               permission denies access.</li>
     *          </ul>
     * @throws  NullPointerException if the {@code interfaces} array
     *          argument or any of its elements are {@code null}, or
     *          if the invocation handler, {@code h}, is
     *          {@code null}
     */
    @CallerSensitive
    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 {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            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);
        } 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);
        }
    }

    private static void checkNewProxyPermission(Class<?> caller, Class<?> proxyClass) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            if (ReflectUtil.isNonPublicProxyClass(proxyClass)) {
                ClassLoader ccl = caller.getClassLoader();
                ClassLoader pcl = proxyClass.getClassLoader();

                // do permission check if the caller is in a different runtime package
                // of the proxy class
                int n = proxyClass.getName().lastIndexOf('.');
                String pkg = (n == -1) ? "" : proxyClass.getName().substring(0, n);

                n = caller.getName().lastIndexOf('.');
                String callerPkg = (n == -1) ? "" : caller.getName().substring(0, n);

                if (pcl != ccl || !pkg.equals(callerPkg)) {
                    sm.checkPermission(new ReflectPermission("newProxyInPackage." + pkg));
                }
            }
        }
    }

    /**
     * Returns true if and only if the specified class was dynamically
     * generated to be a proxy class using the {@code getProxyClass}
     * method or the {@code newProxyInstance} method.
     *
     * <p>The reliability of this method is important for the ability
     * to use it to make security decisions, so its implementation should
     * not just test if the class in question extends {@code Proxy}.
     *
     * @param   cl the class to test
     * @return  {@code true} if the class is a proxy class and
     *          {@code false} otherwise
     * @throws  NullPointerException if {@code cl} is {@code null}
     */
    public static boolean isProxyClass(Class<?> cl) {
        return Proxy.class.isAssignableFrom(cl) && proxyClassCache.containsValue(cl);
    }

    /**
     * Returns the invocation handler for the specified proxy instance.
     *
     * @param   proxy the proxy instance to return the invocation handler for
     * @return  the invocation handler for the proxy instance
     * @throws  IllegalArgumentException if the argument is not a
     *          proxy instance
     * @throws  SecurityException if a security manager, <em>s</em>, is present
     *          and the caller's class loader is not the same as or an
     *          ancestor of the class loader for the invocation handler
     *          and invocation of {@link SecurityManager#checkPackageAccess
     *          s.checkPackageAccess()} denies access to the invocation
     *          handler's class.
     */
    @CallerSensitive
    public static InvocationHandler getInvocationHandler(Object proxy)
        throws IllegalArgumentException
    {
        /*
         * Verify that the object is actually a proxy instance.
         */
        if (!isProxyClass(proxy.getClass())) {
            throw new IllegalArgumentException("not a proxy instance");
        }

        final Proxy p = (Proxy) proxy;
        final InvocationHandler ih = p.h;
        if (System.getSecurityManager() != null) {
            Class<?> ihClass = ih.getClass();
            Class<?> caller = Reflection.getCallerClass();
            if (ReflectUtil.needsPackageAccessCheck(caller.getClassLoader(),
                                                    ihClass.getClassLoader()))
            {
                ReflectUtil.checkPackageAccess(ihClass);
            }
        }

        return ih;
    }

    private static native Class<?> defineClass0(ClassLoader loader, String name,
                                                byte[] b, int off, int len);
}

How does the Proxy class help us generate Proxy objects? (two methods: Proxy.getProxyClass and Proxy.newProxyInstance)

Proxy.getProxyClass(ClassLoader loader, Class<?>... interfaces)

    @CallerSensitive
    public static Class<?> getProxyClass(ClassLoader loader,
                                         Class<?>... interfaces)
        throws IllegalArgumentException
    {
        final Class<?>[] intfs = interfaces.clone();	// Clone interface list
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);	// Verification authority, etc
        }

        return getProxyClass0(loader, intfs);
    }

According to the API documentation, the information is as follows:
Java. Java of the proxy class given the class loader and interface array Lang. class object. The class that implements the specified class is defined by the loader and will be provided by the specified class. If any given interface is non-public, the proxy class will be non-public. If the class loader has defined a proxy class with the same interface replacement, the existing proxy class will be returned; Otherwise, the proxy classes for these interfaces will be dynamically generated and defined by the class loader.
Yes, it may be passed to proxy The parameters of getproxyclass have several proxies getProxyClass :
All class objects in the interfaces array must represent interfaces, not classes or primitive types.
No two elements in the interfaces array may refer to the same class object.
All interface types must be visible through the name of the specified class loader. In other words, for class loader cl and each interface i, the following expression must be true:
Class.forName(i.getName(), false, cl) == i all non-public interfaces must be in the same package; Otherwise, the proxy class will not be able to implement all interfaces, regardless of what package is defined in it.

For any member method collection of the specified interface with the same signature:
If the return type of any method is the original type or void, all methods must have the same return type.
Otherwise, one of the methods must have a return type that can be assigned to all return types of the other methods.

The generated proxy class cannot exceed any restrictions imposed by the virtual machine on the class. For example, VM can limit the number of interfaces that a class can implement to 65535; In this case, the size of the interfaces array should not exceed 65535.
If any of these restrictions are violated, proxy Getproxyclass will throw an IllegalArgumentException. If the interfaces array parameter or any of its elements is null, a NullPointerException is thrown.
Note that the order of proxy interfaces specified is important: two requests for proxy classes with the same combination of interfaces but different orders will result in two different proxy classes.

  1. The ClassLoader parameter must be able to get the interface in the interface list, otherwise an exception will be thrown
  2. Different proxy objects will be obtained if the specified interface list order is different

Next, look at getProxyClass0(loader, intfs)

    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);
    }

This is a lazy loading implementation. When you get it once, it will be saved in the WeakCache. The next time you pass the same parameters, it will be obtained from the cache without further construction.

proxyClassCache. Was returned Get (loader, interfaces), proxyClassCache is an attribute in Proxy

    /**
     * a cache of proxy classes
     */
    private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

WeakCache is Java A class under the lang.util package provides a map of (key, sub key) - > value
Its constructor

    /**
     * Construct an instance of {@code WeakCache}
     *
     * @param subKeyFactory a function mapping a pair of
     *                      {@code (key, parameter) -> sub-key}
     * @param valueFactory  a function mapping a pair of
     *                      {@code (key, parameter) -> value}
     * @throws NullPointerException if {@code subKeyFactory} or
     *                              {@code valueFactory} is null.
     */
    public WeakCache(BiFunction<K, P, ?> subKeyFactory,
                     BiFunction<K, P, V> valueFactory) {
        this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
        this.valueFactory = Objects.requireNonNull(valueFactory);
    }

Its get method

    /**
     * Look-up the value through the cache. This always evaluates the
     * {@code subKeyFactory} function and optionally evaluates
     * {@code valueFactory} function if there is no entry in the cache for given
     * pair of (key, subKey) or the entry has already been cleared.
     *
     * @param key       possibly null key
     * @param parameter parameter used together with key to create sub-key and
     *                  value (should not be null)
     * @return the cached value (never null)
     * @throws NullPointerException if {@code parameter} passed in or
     *                              {@code sub-key} calculated by
     *                              {@code subKeyFactory} or {@code value}
     *                              calculated by {@code valueFactory} is null.
     */
    public V get(K key, P parameter) {
        //The interface required to be implemented here cannot be empty
        Objects.requireNonNull(parameter);
        //Clear expired cache
        expungeStaleEntries();
        //Wrap ClassLoader into CacheKey as the key of L1 cache
        Object cacheKey = CacheKey.valueOf(key, refQueue);
        //Get L2 cache
        ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
        //If the corresponding value is not obtained according to ClassLoader
        if (valuesMap == null) {
            //Put it in CAS mode. If it doesn't exist, put it in. Otherwise, return the original value
            ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey, 
                    valuesMap = new ConcurrentHashMap<>());
            //If oldValuesMap has a value, it indicates that the insertion failed
            if (oldValuesMap != null) {
                valuesMap = oldValuesMap;
            }
        }
        //The secondary cache key is generated according to the interface array implemented by the proxy class, which is divided into key0, key1, key2 and keyx
        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
        //Here, the value of L2 cache is obtained through subKey
        Supplier<V> supplier = valuesMap.get(subKey);
        Factory factory = null;
        //This loop provides a polling mechanism. If the condition is false, continue to retry until the condition is true
        while (true) {
            //If the value obtained through subKey is not empty
            if (supplier != null) {
                //Here, the supplier may be a Factory or a CacheValue
                //It is not judged here, but verified in the get method of the Supplier implementation class
                V value = supplier.get();
                if (value != null) {
                    return value;
                }
            }
            if (factory == null) {
                //Create a new Factory instance as the corresponding value of the subKey
                factory = new Factory(key, parameter, subKey, valuesMap);
            }
            if (supplier == null) {
                //This indicates that there is no corresponding value for the subKey, so put the factory as the value of the subKey
                supplier = valuesMap.putIfAbsent(subKey, factory);
                if (supplier == null) {
                    //This indicates that the factory was successfully put into the cache
                    supplier = factory;
                }
                //Otherwise, if another thread may modify the value during the period, it will no longer continue to assign values to the subKey, but take it out and use it directly
            } else {
                //If the value may have been modified by another thread, the original value will be replaced
                if (valuesMap.replace(subKey, supplier, factory)) {
                    //Successfully replaced factory with the new value
                    supplier = factory;
                } else {
                    //Replace failed, continue to use the original value
                    supplier = valuesMap.get(subKey);
                }
            }
        }
    }

Subkeyfactory is called here Apply (key, parameter) to obtain the subKey (here is the KeyFactory in the Proxy)
According to key, parameter, subkey and valuesmap, obtain the internal class Factory in WeakCache that implements the Supplier interface.
Its get method calls valuefactory Apply (key, parameter) gets value (here is ProxyClassFactory in Proxy),
And replace the corresponding key in valuesMap.

(I smiled wickedly when reading this. If the interviewer asks you about JDK dynamic Proxy, you can backhand ask him a question: is Proxy thread safe to create Proxy objects?)
The get method of WeakCache does not synchronize with locks, so how does it achieve thread safety? Because all its member variables that will be modified use ConcurrentMap, this class is thread safe. Therefore, it delegates its own thread safety to ConcurrentMap, and the get method reduces the synchronization code block as much as possible, which can effectively improve the performance of WeakCache.

Therefore, we should pay attention to two points here: KeyFactory and ProxyClassFactory in Proxy. ProxyClassFactory is the real place to create Proxy objects.

Proxy#KeyFactory

    /**
     * A function that maps an array of interfaces to an optimal key where
     * Class objects representing interfaces are weakly referenced.
     */
    private static final class KeyFactory
        implements BiFunction<ClassLoader, Class<?>[], Object>
    {
        @Override
        public Object apply(ClassLoader classLoader, Class<?>[] interfaces) {
            switch (interfaces.length) {
                case 1: return new Key1(interfaces[0]); // the most frequent
                case 2: return new Key2(interfaces[0], interfaces[1]);
                case 0: return key0;
                default: return new KeyX(interfaces);
            }
        }
    }

The subclass of the corresponding WeakCache is returned according to the length of the interface list.
We can see that ClassLoader is used as the key of the first level cache, so we can filter it according to the ClassLoader first, because the classes loaded by different classloaders are different. Then it uses the interface array to generate the secondary cache key. Here, it has made some optimization. Because most classes implement one or two interfaces, the secondary cache key is divided into key0, key1, key2 and keyX. Key0 to key2 respectively indicate that 0 to 2 interfaces have been implemented, and keyX indicates that 3 or more interfaces have been implemented. In fact, most of them only use key1 and key2. The generation factories of these keys are passed in the Proxy class through the constructor of WeakCache. The value of the L2 cache here is a Factory instance, and the value of the final Proxy class is obtained through the Factory of WeakCache#Factory.

WeakCache#Factory

private final class Factory implements Supplier<V> {
    //The first level cache key is generated according to ClassLoader
    private final K key;
    //Interface array implemented by proxy class
    private final P parameter;
    //The second level cache key is generated according to the interface array
    private final Object subKey;
    //L2 cache
    private final ConcurrentMap<Object, Supplier<V>> valuesMap;

    Factory(K key, P parameter, Object subKey,
            ConcurrentMap<Object, Supplier<V>> valuesMap) {
        this.key = key;
        this.parameter = parameter;
        this.subKey = subKey;
        this.valuesMap = valuesMap;
    }

    @Override
    public synchronized V get() {
        //Here again, go to the L2 cache to get the Supplier to verify whether it is the Factory itself
        Supplier<V> supplier = valuesMap.get(subKey);
        if (supplier != this) {
            //Here, verify whether the supplier is the Factory instance itself. If not, return null and let the caller continue polling and retry
            //During this period, the supplier may be replaced with CacheValue, or it may be removed from the L2 cache due to the failure of generating the proxy class
            return null;
        }
        V value = null;
        try {
            //Delegate valueFactory to generate the proxy class. Here, the proxy class will be generated through the passed ProxyClassFactory
            value = Objects.requireNonNull(valueFactory.apply(key, parameter));
        } finally {
            //If the generation of proxy class fails, the L2 cache will be deleted
            if (value == null) {
                valuesMap.remove(subKey, this);
            }
        }
        //You can only get here if the value of value is not empty
        assert value != null;
        //Wrap generated proxy classes with weak references
        CacheValue<V> cacheValue = new CacheValue<>(value);
        //Put the wrapped cacheValue into the L2 cache. This operation must be successful, or an error will be reported
        if (valuesMap.replace(subKey, this, cacheValue)) {
            //Mark the cacheValue after it is successfully put into the L2 cache
            reverseMap.put(cacheValue, Boolean.TRUE);
        } else {
            throw new AssertionError("Should not reach here");
        }
        //Finally, return the proxy class that is not wrapped by weak reference
        return value;
    }
}

You can see that its get method is synchronized using the synchronized keyword.
After the get method, first verify whether the supplier corresponding to the subKey is the factory itself. If not, it will return null, and the get method of WeakCache will continue to retry.
If it is the factory itself, it will delegate ProxyClassFactory to generate proxy classes. ProxyClassFactory is passed in when constructing WeakCache.
Therefore, here we explain why the Proxy class is finally generated by calling the internal factory of Proxy, ProxyClassFactory.
After generating the proxy class, wrap it with weak reference and put it into reverseMap. Finally, the original proxy class will be returned.

Proxy#ProxyClassFactory

    /**
     * A factory function that generates, defines and returns the proxy class given
     * the ClassLoader and array of interfaces.
     */
    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) {

            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            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());
                }
            }

            String proxyPkg = null;     // package to define proxy class in
            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.
             */
            for (Class<?> intf : interfaces) {
            	//1. Can INTF be loaded by the specified class
             	//2. Is INTF an interface
            	//3. Is INTF repeated in the array
                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();
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            /*
             * Generate the specified proxy class.
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                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());
            }
        }
    }
  1. In the code, you can see that the class name of the Proxy class generated by JDK is "$Proxy" + sequence number.
  2. If the interface is public, the proxy class is public final by default, and the generated proxy class is placed on com.com by default sun. Proxy is under this package.
  3. If the interface is non-public, the proxy class is also non-public, and the generated proxy class will be placed under the package of the corresponding interface.
  4. If the interfaces are not public and they are not in the same package, an error will be reported.

What really works is:

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);

ProxyGenerator.generateProxyClass

private byte[] generateClassFile() {
    //The first step is to assemble all the methods into ProxyMethod objects
    //First, generate proxy methods such as toString, hashcode and equals for the proxy class
    addProxyMethod(hashCodeMethod, Object.class);
    addProxyMethod(equalsMethod, Object.class);
    addProxyMethod(toStringMethod, Object.class);
    //Traverse each method of each interface and generate ProxyMethod object for it
    for (int i = 0; i < interfaces.length; i++) {
        Method[] methods = interfaces[i].getMethods();
        for (int j = 0; j < methods.length; j++) {
            addProxyMethod(methods[j], interfaces[i]);
        }
    }
    //Verify that the methods returned by the proxy have the same value for the compatible methods
    for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
        checkReturnTypes(sigmethods);
    }

    //The second step is to assemble all the field information and method information of the class file to be generated
    try {
        //Add constructor method
        methods.add(generateConstructor());
        //Traversing the proxy method in the cache
        for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
            for (ProxyMethod pm : sigmethods) {
                //Add the static field of the proxy class, for example: private static Method m1;
                fields.add(new FieldInfo(pm.methodFieldName,
                        "Ljava/lang/reflect/Method;", ACC_PRIVATE | ACC_STATIC));
                //Add proxy method of proxy class
                methods.add(pm.generateMethod());
            }
        }
        //Add static field initialization method of proxy class
        methods.add(generateStaticInitializer());
    } catch (IOException e) {
        throw new InternalError("unexpected I/O Exception");
    }

    //Validation method and field set cannot be greater than 65535
    if (methods.size() > 65535) {
        throw new IllegalArgumentException("method limit exceeded");
    }
    if (fields.size() > 65535) {
        throw new IllegalArgumentException("field limit exceeded");
    }

    //The third step is to write the final class file
    //Verify that the fully qualified name of the proxy class exists in the constant pool
    cp.getClass(dotToSlash(className));
    //Verify that the fully qualified name of the parent class of the proxy class exists in the constant pool, and the parent class name is: "java/lang/reflect/Proxy"
    cp.getClass(superclassName);
    //Verify that the constant pool has the fully qualified name of the proxy class interface
    for (int i = 0; i < interfaces.length; i++) {
        cp.getClass(dotToSlash(interfaces[i].getName()));
    }
    //Next, start writing files. Set the constant pool to read-only
    cp.setReadOnly();

    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    DataOutputStream dout = new DataOutputStream(bout);
    try {
        //1. Write magic number
        dout.writeInt(0xCAFEBABE);
        //2. Write the minor version number
        dout.writeShort(CLASSFILE_MINOR_VERSION);
        //3. Write in the main version number
        dout.writeShort(CLASSFILE_MAJOR_VERSION);
        //4. Write to constant pool
        cp.write(dout);
        //5. Write access modifier
        dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);
        //6. Write class index
        dout.writeShort(cp.getClass(dotToSlash(className)));
        //7. Write the parent class index, and the generated Proxy classes inherit from Proxy
        dout.writeShort(cp.getClass(superclassName));
        //8. Write interface count value
        dout.writeShort(interfaces.length);
        //9. Write interface set
        for (int i = 0; i < interfaces.length; i++) {
            dout.writeShort(cp.getClass(dotToSlash(interfaces[i].getName())));
        }
        //10. Write field count value
        dout.writeShort(fields.size());
        //11. Write field set
        for (FieldInfo f : fields) {
            f.write(dout);
        }
        //12. Write method count value
        dout.writeShort(methods.size());
        //13. Write method set
        for (MethodInfo m : methods) {
            m.write(dout);
        }
        //14. Write the attribute count value. The proxy class file has no attribute, so it is 0
        dout.writeShort(0);
    } catch (IOException e) {
        throw new InternalError("unexpected I/O Exception");
    }
    //Convert to binary array output
    return bout.toByteArray();
}

Proxy#defineClass0

private static native Class<?> defineClass0(ClassLoader loader, String name,
                                                byte[] b, int off, int len);

This method is a static method of Proxy, which is implemented by other languages through JNI.
So far, we have got the proxy class Class object, and then call the constructor instantiated with its parameter InvocationHandler to get the instance of the proxy object.

Next, let's see how the newProxyInstance method does:

    @CallerSensitive
    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 {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            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);
        } 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);
        }
    }

Similarly, first obtain the Class object of the proxy Class, then obtain the constructor and instantiate it.

extend

We can get the bytecode (ProxyGenerator.generateProxyClass), write it into the file and decompile it (someone else's is used here):

public final class proxy extends Proxy implements MyService {
    private static Method m1;
    private static Method m4;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public proxy(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 void test01() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    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 test02(String var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    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"));
            m4 = Class.forName("com.dmz.proxy.target.MyService").getMethod("test01");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.dmz.proxy.target.MyService").getMethod("test02", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

Obtain the following information:

  1. As described in the API of Proxy, the Proxy class inherits from Proxy and implements the interface.
  2. As described in the Proxy API, the equals, hashCode and toString of the Proxy class call InvocationHandler for processing.

If you output proxy in the invoke method of the custom InvocationHandler, you will get a stack overflow exception. Why?
Because you're calling system out. Println (proxy) actually calls system out. println(proxy.toString())
proxy.toString() calls the invoke method of the custom InvocationHandler again
invoke called proxy again toString() …

Class based dynamic agent

Through "inheritance", you can inherit all the exposed methods of the parent class, and then you can override these methods. When overriding, you can enhance these methods. This is the idea of cglib. According to the principle of Richter substitution (LSP), where the parent class needs to appear, the child class can appear, so the proxy implemented by cglib can also be used normally.

package org;

import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.*;

import java.lang.reflect.Method;

/**
 * Mi phones
 */
class MiPhone {
    public void call() {
        System.out.println("Can I call");
    }

    public void sendMessage() {
        System.out.println("I can text");
    }
}

/**
 * Millet factory sells mobile phones
 */
class MiFactory {

    public MiPhone sell() {
        return new MiPhone();
    }

}

public class ProxyPatternDemo {

    public static void main(String[] args) {
        // Open debugging mode and output proxy class bytecode
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "C:\\");

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(MiFactory.class);    // The parent class of the proxy is set

//        /**
//         * Can proxy interface
//         */
//        enhancer.setInterfaces();

//        /**
//         * CallbackFilter and Callbacks can be used to control which method uses which callback
//         */
//        enhancer.setCallbackFilter();
//        enhancer.setCallbacks();

//        /**
//         * For simplicity and performance, additional
//         *  specialized callback types, such as {@link LazyLoader} are also available.
//         *
//         * API Provides a callback for LazyLoader
//         */
//        enhancer.setCallback(new LazyLoader() {
//            @Override
//            public Object loadObject() throws Exception {
//                return null;
//            }
//        });

        /**
         * API Provides a callback to the MethodInterceptor
         */
        enhancer.setCallback(new MethodInterceptor() {
            /**
             * There is an inheritance relationship between the proxy class and the proxied class. Pay attention to the dead cycle when using
             *
             * @param obj
             *              Proxy object
             * @param method
             *              Referenced by the method of the proxy class
             * @param args
             *              Method parameter list
             * @param proxy
             *              Method reference of proxy class; This parameter has two methods, invoke and invokeSuper
             *              invoke Is to call the method of proxy object (class), which will call the logic of intercept (recursion)
             *              invokeSuper Is to call the method of the parent class of the proxy object and the method of the proxy object (class)
             * @return
             * @throws Throwable
             */
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                System.out.println("Pre sales service");
                Object res = proxy.invokeSuper(obj, args);
                System.out.println("after-sale service");
                return res;
            }
        });

//        /**
//         * API The callback of InvocationHandler is provided, which is similar to the InvocationHandler in JDKProxy
//         */
//        enhancer.setCallback(new MyInvocationHandler(new MiFactory()));

        MiFactory superFactory = (MiFactory) enhancer.create();
        superFactory.sell();
    }

}

class MyInvocationHandler implements InvocationHandler {

    private Object target;

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

    /**
     *
     * @param proxy
     *             Proxy object (pay attention to loop problem)
     * @param method
     *             Referenced by proxy class method
     * @param args
     *             Method parameter list
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Pre sales service");
        Object res = method.invoke(target, args);
        System.out.println("after-sale service");
        return res;
    }
}

Here is a point to note:

When the method calling the proxy object is the final method of the parent class (proxied class), the method of the parent class is called directly.
CGLIB will generate three classes. Please analyze the source code here when you have time

Differences between JDK dynamic agent and CGLIB dynamic agent

  1. JDK dynamic agent is mainly based on interface, and CGLIB is based on class and interface
  2. From the perspective of performance: give priority to JDK dynamic agent when JDK dynamic agent can be used (others say)
  3. They can be applied to AOP and Spring framework. The bottom layer is dynamically generated bytecode.

Easter egg

The difference between forward proxy and reverse proxy
Application of agent mode
Spring AOP source code

Keywords: Java

Added by orlandinho on Tue, 08 Mar 2022 00:41:16 +0200