A brief introduction to dynamic proxy

1. What is dynamic proxy?

A design pattern for dynamically generating agent objects through reflection.

2. How to distinguish static proxy from dynamic proxy?

  • Static proxy: The proxy class already exists before the program runs.
  • Dynamic proxy: before the program runs, the proxy class does not exist. During the running, the proxy class is dynamically generated.

3. Why use dynamic proxies?

Because a static proxy class can only serve one type of target objects, when there are many target objects, there will be more proxy classes and more code.

Using dynamic proxy to dynamically generate proxy objects can avoid this situation.

Dynamic proxy principle

Take a look at the UML diagram of the dynamic proxy:

Dynamic proxy JDK VS CGLIB

1, through the

Proxy.newProxyInstance(ClassLoader loader, Class<? >[] interfaces, InvocationHandler h);Copy the code

Reflection generates proxy class objects.

Call dynamic proxy class object method, callback

h.invoke(thisObject proxy, Method method, Object[] args); // The invoke() method overridden in the InvocationHandler implementation class is finally calledCopy the code

3. Finally, pass

method.invoke(Object obj, Object... args);
Copy the code

Calls a method of the target object.

Three, dynamic proxy actual combat

1. Use steps

(1) Declare the abstract interface of the target object.

(2) Declare the call handler class (implementing the InvocationHandler interface).

(3) Generate the target object class (realize the abstract interface of the target object. Here, because each Proxy class inherits the Proxy class and Java’s single inheritance feature, the Proxy class can only be created for the interface, but not for the class, which will be explained later).

(4) Generate proxy class objects.

(5) Through the proxy class object, call the target object method.

2. Actual code

// (1) Declare the target object's abstract interface
public interface ISubject {
    void buy(a);
}

// (2) declare the call handler class (implement the InvocationHandler interface)
public class InvocationHandlerImpl implements InvocationHandler{
    private Object mRealObject;

    public InvocationHandlerImpl(Object realObject) {
        mRealObject = realObject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("proxy invoke, proxy = " + proxy.getClass() + ", realObject = " + mRealObject.getClass());
        Object result = method.invoke(mRealObject, args);
        returnresult; }}// (3.1) Implement target object class 1
public class Buyer1 implements ISubject {
    @Override
    public void buy(a) {
        System.out.println("buyer1 buy"); }}// (3.2) Implement target object class 2
public class Buyer2 implements ISubject {
    @Override
    public void buy(a) {
        System.out.println("buyer2 buy"); }}public class DynamicProxyDemo {
    public static void main(String[] args) {
        // Generate $Proxy0 class file in the project directory
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles"."true");

        Buyer1 buyer1 = new Buyer1(); // Create an object for the target object
        InvocationHandlerImpl invocationHandlerImpl1 = new InvocationHandlerImpl(buyer1); // Create the call handler class object
        
        // (4) Generate a proxy class object
        ISubject buyer1Proxy = (ISubject) Proxy.newProxyInstance(buyer1.getClass().getClassLoader(), buyer1.getClass().getInterfaces(), invocationHandlerImpl1);
        
        // (5) Call the target object's method through the proxy class object
        buyer1Proxy.buy();

        System.out.println("Target Object 1:" + buyer1.getClass());
        System.out.println("Proxy object 1:" + buyer1Proxy.getClass());

        Buyer2 buyer2 = new Buyer2();
        InvocationHandlerImpl invocationHandlerImpl2 = new InvocationHandlerImpl(buyer2);
        ISubject buyer2Proxy = (ISubject) Proxy.newProxyInstance(buyer2.getClass().getClassLoader(), buyer2.getClass().getInterfaces(), invocationHandlerImpl2);
        buyer2Proxy.buy();

        System.out.println("Target Object 2:" + buyer2.getClass());
        System.out.println("Proxy object 2:"+ buyer2Proxy.getClass()); }}Copy the code

Let’s run the Demo and see what it looks like:

Invoke, proxy = class com.sun.proxy.$Proxy0, realObject = class com.trampc.proxy.buyer1 Buyer1 buy Class com.trampcr.proxy.buyer1 class com.sun.proxy.$Proxy0 proxy invoke, proxy = class com.sun.proxy.$Proxy0, RealObject = class com.trampc.proxy.buyer2 Buyer2 Buy target object 2: class com.trampc.proxy.buyer2 class com.sun.proxy.$Proxy0Copy the code

$Proxy0: com.sun.proxy.$Proxy0: com.sun.proxy.$Proxy0: com.sun.proxy.

Attentive students may have seen more than one line of code is a special code, the role of the line of code is the sun. The misc. ProxyGenerator. SaveGeneratedFiles this variable assignment is true, this variable is true, The $Proxy0 class file will be generated in the project directory (since the ProxyGenerator class that generated the proxy classes is in the sun.misc package and cannot be called in Android Studio, So here is a call to the Demo written in Intellij) :

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles"."true");
Copy the code

After running, a com.sun.proxy package will appear in the SRC sibling of the project. This package contains the dynamically generated proxy class $Proxy0.

package com.sun.proxy;

$Proxy0 inherits from Proxy by default, so you can create Proxy classes only for ISubject, not for classes.
public final class $Proxy0 extends Proxy implements ISubject {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw newUndeclaredThrowableException(var4); }}public final String toString(a) throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}public final void buy(a) throws  {
        try {
            // m3 = Class.forName("com.trampcr.proxy.ISubject").getMethod("buy", new Class[0]);
            // The method that accesses the target object through the proxy class
            // We will eventually call back to invoke() of our overwritten InvocationHandler implementation class via super.h.invoke().
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}public final int hashCode(a) throws  {
        try {
            return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}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.trampcr.proxy.ISubject").getMethod("buy".new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode".new Class[0]);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw newNoClassDefFoundError(var3.getMessage()); }}}Copy the code

This gives us some idea of the use of dynamic proxies and dynamically generated proxy classes, but we need to take a closer look at the source code for how proxy objects are dynamically generated.

Dynamic agent source code analysis

Dynamic Proxy objects are generated through proxy.newProxyInstance ().

The source code analysis here is divided into two versions: JDK 1.7 and JDK 1.8.

JDK 1.7

// loader: generates the class loader for the proxy object (it must be the same class loader as the target object)
// interfaces: The interface implemented by the target object, which the proxy class also needs to implement
// h: implementation object of InvocationHandler. When the dynamic proxy object calls the target object method, the final callback is H.invoke ().
public static Object newProxyInstance(ClassLoader loader, Class
       [] interfaces, InvocationHandler h)
        throws IllegalArgumentException {...// Select * from ()
    
    // create dynamic proxy classes using loader and interfacesClass<? > cl = getProxyClass0(loader, interfaces);try {
        // Get the constructor of the dynamic proxy class by reflection (type invocationHandler. class)
        finalConstructor<? > cons = cl.getConstructor(constructorParams);finalInvocationHandler ih = h; .// create an instance of the dynamic proxy class by using the constructor of the dynamic proxy class and calling the handler object
            returnnewInstance(cons, ih); . }catch (NoSuchMethodException e) {
        throw newInternalError(e.toString()); }}// loader: generates the class loader for the proxy object
// interfaces: interfaces implemented by the target object
private staticClass<? > getProxyClass0(ClassLoader loader, Class<? >... interfaces) { ... Class<? > proxyClass =null;

    // An array of interface names, used to collect interface names as the proxy class cache key
    String[] interfaceNames = new String[interfaces.length];

    // A collection of interfaces used to check for duplicate interfacesSet<Class<? >> interfaceSet =new HashSet<>();

    // Iterate over the interface implemented by the target object
    for (int i = 0; i < interfaces.length; i++) {
        // Get the interface nameString interfaceName = interfaces[i].getName(); Class<? > interfaceClass =null;
        try {
            // Load the interface implemented by the target class into memory by reflection
            interfaceClass = Class.forName(interfaceName, false, loader);
        } catch (ClassNotFoundException e) {
        }
        if(interfaceClass ! = interfaces[i]) {throw new IllegalArgumentException(
                interfaces[i] + " is not visible from class loader"); }...// If the interface is duplicated, an exception is thrown
        if (interfaceSet.contains(interfaceClass)) {
            throw new IllegalArgumentException("repeated interface: " + interfaceClass.getName());
        }
        interfaceSet.add(interfaceClass);
        interfaceNames[i] = interfaceName;
    }

    // Convert an array of interface names to a list of interface names
    List<String> key = Arrays.asList(interfaceNames);

    // Get or create a proxy class cache by Classloader
    Map<List<String>, Object> cache;
    
    // Map a ClassLoader to the proxy class cache of that ClassLoader
    // private static Map<ClassLoader, Map<List<String>, Object>> loaderToCache = new WeakHashMap<>();
    
    synchronized (loaderToCache) {
        cache = loaderToCache.get(loader);
        if (cache == null) {
            cache = newHashMap<>(); loaderToCache.put(loader, cache); }}synchronized (cache) {
        do {
            Object value = cache.get(key);
            if (value instanceofReference) { proxyClass = (Class<? >) ((Reference) value).get(); }if(proxyClass ! =null) {
                return proxyClass;
            } else if (value == pendingGenerationMarker) {
                // The proxy class is being created, wait, and notifyAll() is executed when the proxy class is created
                try {
                    cache.wait();
                } catch (InterruptedException e) {
                }
                continue;
            } else {
                // If the proxy class is empty, add a pendingGenerationMarker to the proxy class cache to indicate that the proxy class is being created
                cache.put(key, pendingGenerationMarker);
                break; }}while (true); // This is an infinite loop until the proxy class is not empty
    }

    // Generate proxy class logic
    try {
        String proxyPkg = null;

        // Iterate over the interface's access modifier. If non-public, the proxy class package name is the package name of the interface
        for (int i = 0; i < interfaces.length; i++) {
            int flags = interfaces[i].getModifiers();
            if(! Modifier.isPublic(flags)) { String name = interfaces[i].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 the interfaces are public, use com.sun.proxy as the package name, as seen in the $Proxy0 class
            proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
        }

        {
            long num;
            synchronized (nextUniqueNumberLock) {
                num = nextUniqueNumber++;
            }
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            // Create the bytecode of the proxy class based on the proxy class full path and interface
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);
            try {
                // Generate the proxy class based on the bytecode of the proxy class
                proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                throw newIllegalArgumentException(e.toString()); }}// A collection of all proxy classes created
        // private static Map
      
       , Void> proxyClasses = Collections.synchronizedMap(new WeakHashMap
       
        , Void>());
       >
      >
        proxyClasses.put(proxyClass, null);
    } finally {
        synchronized (cache) {
            if(proxyClass ! =null) {
                // Create the proxy class and store it in the proxy class cache
                cache.put(key, newWeakReference<Class<? >>(proxyClass)); }else {
                // Otherwise, clear the previously stored pendingGenerationMarker markercache.remove(key); } cache.notifyAll(); }}return proxyClass;
}
Copy the code

Source code is a bit much, summary of the dynamic generation of proxy class object process:

1. Create dynamic proxy classes using loaders and interfaces (first, bytecode of the proxy class is created based on the full path and interface of the proxy class, and second, bytecode generation of the proxy class).

Get the constructor of the dynamic proxy class (parameter type invocationHandler. class) through reflection.

3. Create an instance of a dynamic proxy class through its constructor and call handler object.

Finally, the dynamically created proxy class can see the code posted above: $Proxy0.

JDK 1.8

// loader: generates the class loader for the proxy object (it must be the same class loader as the target object)
// interfaces: The interface implemented by the target object, which the proxy class also needs to implement
// h: implementation object of InvocationHandler. When the dynamic proxy object calls the target object method, the final callback is H.invoke ().
public static Object newProxyInstance(ClassLoader loader, Class
       [] interfaces, InvocationHandler h) throws IllegalArgumentException {..// clone the interface implemented by the incoming target object
    finalClass<? >[] intfs = interfaces.clone(); .1Create the dynamic proxy Class Class<? > cl = getProxyClass0(loader, intfs);try{...// Get the constructor of the dynamic proxy class by reflection (type invocationHandler. class)
        finalConstructor<? > cons = cl.getConstructor(constructorParams); .// create an instance of the dynamic proxy class by using the constructor of the dynamic proxy class and calling the handler object
        return cons.newInstance(newObject[]{h}); }... }private staticClass<? > getProxyClass0(ClassLoader loader, Class<? >... interfaces) { ...// Proxy class cache
    // private static final WeakCache
      
       [], Class
       > proxyClassCache =
      ,>
    // new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
    
    // Get the proxy class from the cache based on the loader and interfaces passed in, or create the proxy class via ProxyClassFactory if there is no cache
    return proxyClassCache.get(loader, interfaces);
}

public V get(K key, P parameter) {...// private final ReferenceQueue<K> refQueue = new ReferenceQueue<>();
    Object cacheKey = CacheKey.valueOf(key, refQueue); // Change key to CacheKey

    // The map key is of Object type, and the key value can be null
    // private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map = new ConcurrentHashMap<>();
    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; }}New KeyFactory() is passed in, keyfactory.apply () later.
    Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
    Supplier<V> supplier = valuesMap.get(subKey);
    Factory factory = null;

    // This is an infinite loop that breaks out if value is not null
    The key point here is supplene.get (), where the proxy class is created using ProxyClassFactory
    while (true) {
        if(supplier ! =null) {
            // Supplier may be a Factory or CacheValue
      
        instance
      
            V value = supplier.get();
            if(value ! =null) {
                returnvalue; }}if (factory == null) {
            factory = new Factory(key, parameter, subKey, valuesMap);
        }

        if (supplier == null) {
            supplier = valuesMap.putIfAbsent(subKey, factory);
            if (supplier == null) { supplier = factory; }}else {
            if (valuesMap.replace(subKey, supplier, factory)) {
                supplier = factory;
            } else{ supplier = valuesMap.get(subKey); }}}}SubKey (keyfactory.apply ());
// This method creates subkeys based on the number of interfaces
public Object apply(ClassLoader classLoader, Class
       [] interfaces) {
    switch (interfaces.length) {
        case 1: return new Key1(interfaces[0]);
        case 2: return new Key2(interfaces[0], interfaces[1]);
        
        // private static final Object key0 = new Object();
        case 0: return key0;
        default: return newKeyX(interfaces); }}Weakcache.factory.get () == weakcache.factory.get ()
public synchronized V get(a) {
            // Double check
            Supplier<V> supplier = valuesMap.get(subKey);
            if(supplier ! =this) {
                // There may be several reasons for this:
                // 1, replaced by CacheValue
                // 2, removed due to failure
                return null;
            }

            V value = null;
            try {
                ValueFactory = ProxyClassFactory
                // The code that actually creates the proxy class is in proxyClassFactory.apply ()
                value = Objects.requireNonNull(valueFactory.apply(key, parameter));
            } finally {
                if (value == null) {
                    valuesMap.remove(subKey, this); }}assertvalue ! =null;

            CacheValue<V> cacheValue = new CacheValue<>(value);

            if (valuesMap.replace(subKey, this, cacheValue)) {
                reverseMap.put(cacheValue, Boolean.TRUE);
            } else {
                throw new AssertionError("Should not reach here");
            }

            return value;
        }

/ / key point 3, Proxy. ProxyClassFactory. The apply () : to create a Proxy class
// The code for this method is familiar, almost identical to the getProxyClass0() method in JDK 1.7
publicClass<? > apply(ClassLoader loader, Class<? >[] interfaces) { Map<Class<? >, Boolean> interfaceSet =new IdentityHashMap<>(interfaces.length);
    for(Class<? > intf : interfaces) { Class<? > interfaceClass =null;
        try {
            interfaceClass = Class.forName(intf.getName(), false, loader);
        } catch (ClassNotFoundException e) {
        }
        
        ...
    }

    String proxyPkg = null;
    int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

    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) {
        proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
    }

    long num = nextUniqueNumber.getAndIncrement();
    String proxyName = proxyPkg + proxyClassNamePrefix + num;

    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
    try {
        return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
    } catch (ClassFormatError e) {
        throw newIllegalArgumentException(e.toString()); }}Copy the code

After analyzing the source code in JDK 1.7 and JDK 1.8, we found that the method of generating proxy class is the same. The difference is mainly reflected in the caching method. JDK 1.8 has made performance optimization, which is significantly faster than 1.7.

5. Dynamic proxy application in Android

1. Android uses dynamic proxy in cross-process communication

The Activity startup process, for example, hides the use of a remote proxy.

2. The Create () method in Retrofit fetches interface objects through dynamic proxies.

These scenes may not be comprehensive enough, but you can add them in the comments section. I’ll add them later when I see new scenes.

Reference:

Java dynamic proxies for common technology points

Proxy Pattern: Dynamic Proxy – the most easy-to-understand design Pattern resolution