JDK Dynamic proxy

The CORE of the JDK dynamic Proxy is two classes: InvocationHandler and Proxy

Take a chestnut

To facilitate understanding, let’s first look at an example: We want to realize such a function: when using UserService, we only need to pay attention to the realization of our own core business logic, and the printing of the log function is completed by the public service of the system. Start by defining an interface to a business class: userService.java

package com.proxy;
/ * * *@author: create by lengzefu
 * @description: com.proxy
 * @date: the 2020-09-15 * /
public interface UserService {
    void login(a);
}
Copy the code

Implement this interface:

package com.proxy;

/ * * *@author: create by lengzefu
 * @description: com.proxy
 * @date: the 2020-09-15 * /
public class UserServiceImpl implements UserService {
    @Override
    public void login(a) {
        System.out.println("User login..."); }}Copy the code

Define an InvocationHandler implementation class that has the public log we want to add

package com.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;

/ * * *@author: create by lengzefu
 * @description: com.proxy
 * @date: the 2020-09-15 * /
public class LogHandler implements InvocationHandler {
	// The propped object, in this case the UserServiceImpl object
    Object target;

	// Constructor, which will be passed in by the proxy object
    public LogHandler(Object target) {
        this.target = target;
    }

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

    private void before(a) {
        System.out.println("Method call start time :" + new Date());
    }

    private void after(a) {
        System.out.println("Method call end time :" + newDate()); }}Copy the code

Now how do you call it on the client

/ * * *@Classname Client
 * @Date 2020/9/12 2:40
 * @Autor lengxuezhang
 */
public class Client {
    public static void main(String[] args) {
        // Set the variable to hold the dynamic proxy class. The default name is $Proxy0
        // System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        // 1. Create the proxied object, the implementation class of the UserService interface
        UserService userService = new UserServiceImpl();
        // 2. Obtain the corresponding ClassLoader
        ClassLoader classLoader = userService.getClass().getClassLoader();
        // 3. Obtain the Class of all interfaces. UserServiceImpl implements only one interface, UserService
        Class[] interfaces = userService.getClass().getInterfaces();
        // 4. Create a call request handler that will be passed to the proxy class to handle all method calls on the proxy object
        // Create a custom log handler and pass in the actual execution object userServiceImpl
        InvocationHandler logHandler = new LogHandler(userService);
        /* 5. Create a proxy object based on the information provided above. During this process, A.jdk dynamically creates a bytecode b equivalent to a.class file in memory based on the parameters passed in. Then convert it to the corresponding class according to the corresponding bytecode, c. Then call newInstance() to create the proxy instance */
        UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, logHandler);

        // Call the proxy method
        proxy.login();
        // Save the proxy class generated by JDK dynamic proxy. Save the class name as UserServiceProxy
        //ProxyUtils.generateClassFile(userServiceImpl.getClass(), "UserServiceProxy");}}Copy the code

Start step-by-step analysis of the source code

Source code analysis

1.Proxy.newProxyInstance( ClassLoader loader, Class[] interfaces, InvocationHandler h)

NewProxyInstance (ClassLoader loader, Class[] interfaces, InvocationHandler H) generates a Proxy object

  • ClassLoader loader: classloader, which is used to load proxy classes. The test results are the same whether the userService class loader is passed or the logHandler class loader is passed.
  • Class[] interfaces: A collection of interfaces implemented by the proxied class. With it, the proxy class can implement all the interfaces of the proxied class.
  • InvocationHandler H: Handler object, cannot be null.

Let’s look at the implementation of the method:

 public static Object newProxyInstance(ClassLoader loader, Class
       [] interfaces, InvocationHandler h)
        throws IllegalArgumentException
    {
	    // Verify that the Handler object cannot be empty
        Objects.requireNonNull(h);
		// Make a copy of the Class object of the interface. (The interface is also an object, which is an object with Class name)
        finalClass<? >[] intfs = interfaces.clone();// Safety check is not the point
        final SecurityManager sm = System.getSecurityManager();
        if(sm ! =null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /* * Look up or generate the Designated proxy class. * Look up the designated proxy class object. * /Class<? > cl = getProxyClass0(loader, intfs);/* * Invoke its constructor with the Designated Invocation Handler. * Invoke its constructor with the Designated Invocation Handler. Call its (whose?) The constructor */
        try {
            if(sm ! =null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
			// Return the public constructor for constructorParams
			// constructorParames constructorParames = private static final Class
      [] constructorParams = { InvocationHandler.class };
            finalConstructor<? > cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;
            if(! Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run(a) {
                        cons.setAccessible(true);
                        return null; }}); }/ / here to generate the proxy object, rounding on here: https://www.cnblogs.com/ferryman/p/12089210.html
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw newInternalError(t.toString(), t); }}catch (NoSuchMethodException e) {
            throw newInternalError(e.toString(), e); }}Copy the code

GetProxyClass0 (Loader, intfs); getProxyClass0(Loader, intfs); Next look at the method getProxyClass0.

2.getProxyClass0

// This method is also in the Proxy class
   private staticClass<? > getProxyClass0(ClassLoader loader, Class<? >... interfaces) {if (interfaces.length > 65535) {
           throw new IllegalArgumentException("interface limit exceeded");
       }
 
       // If the proxy class defined by the given loader implementing
       // the given interfaces exists, this will simply return the cached copy;
       // otherwise, it will create the proxy class via the ProxyClassFactory
       // If the proxy class is defined by the specified classloader and implements the given interfaces,
       // Then return the cached proxy class object, otherwise use ProxyClassFactory to create the proxy class.
       return proxyClassCache.get(loader, interfaces);
   }
Copy the code

3. proxyClassCache.get

Look up or generate the Designated proxy class in proxyClassCache. Query (already in the cache) or generate a class object for the specified proxy class. ProxyClassCache is a Class of WeakCache object. Call proxyClassCache. Get (loader, interfaces); You can either get a cached proxy class or create a proxy class (without caching). Let’s look at the definition of WeakCache class (here only give the variable definition and constructor) :

//K represents the type of key, P represents the type of parameter, and V represents the type of value.
// WeakCache
      
       [], Class
       > proxyClassCache indicates that proxyClassCache stores the value Class
        object, which is exactly the proxy object we need.
      ,>
final class WeakCache<K.P.V> {
 
   private final ReferenceQueue<K> refQueue
       = new ReferenceQueue<>();
   // the key type is Object for supporting null key
   private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map
       = new ConcurrentHashMap<>();
   private final ConcurrentMap<Supplier<V>, Boolean> reverseMap
       = new ConcurrentHashMap<>();
   private finalBiFunction<K, P, ? > subKeyFactory;private final BiFunction<K, P, V> valueFactory;
 
 
   public WeakCache(BiFunction
       
         subKeyFactory, BiFunction
        
          valueFactory)
        ,>
       ,> {
       this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
       this.valueFactory = Objects.requireNonNull(valueFactory);
   }
Copy the code

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

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

The KeyFactory code that generates the sub-key is as follows. We won’t go into this, as long as it generates the sub-key based on the passed ClassLoader and interface class.

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 newKeyX(interfaces); }}}Copy the code

Obtain an Supplier

> object, then call the get method on this object, and finally get the Class object of the proxy Class.
>

ProxyClassCache. Get (Loader, interfaces); This code. Get is the method in WeakCache. The source code is as follows:

//K and P are generics in WeakCache definition. Key is the class loader and parameter is the interface class array
public V get(K key, P parameter) {
       // Check that parameter is not null
       Objects.requireNonNull(parameter);
        // Clear the invalid cache
       expungeStaleEntries();
       // cacheKey is the level 1 key in (key, sub-key) -> value,
       Object cacheKey = CacheKey.valueOf(key, refQueue);
 
       // lazily install the 2nd level valuesMap for the particular cacheKey
       // Obtain the ConcurrentMap
      
       > Object based on the level 1 key. If it does not already exist, create a new ConcurrentMap
       
        > and place it in the map with the cacheKey (level 1 key).
       ,>
      ,>
        ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
       if (valuesMap == null) {
           ConcurrentMap<Object, Supplier<V>> oldValuesMap
               = map.putIfAbsent(cacheKey,
                                 valuesMap = new ConcurrentHashMap<>());
           if(oldValuesMap ! =null) { valuesMap = oldValuesMap; }}// create subKey and retrieve the possible Supplier<V> stored by that
       // subKey from valuesMap
       // This part of the code is called to generate sub-key, we have seen above how to generate
       Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
       // Obtain an supplier through the sub-key
       Supplier<V> supplier = valuesMap.get(subKey);
       // Supplier is actually the factory
       Factory factory = null;
 
       while (true) {
           // If there is an supplier in the cache, then get the proxy object and return it.
            if(supplier ! =null) {
               // supplier might be a Factory or a CacheValue<V> instance
               V value = supplier.get();
               if(value ! =null) {
                   returnvalue; }}// else no supplier in cache
           // or a supplier that returned null (could be a cleared CacheValue
           // or a Factory that wasn't successful in installing the CacheValue)
           // lazily construct a Factory
           // The purpose of all the code below is to create a Factory object if there is no supplier in the cache and safely assign the Factory object to the supplier in a multi-threaded environment.
            }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
           if (factory == null) {
               factory = new Factory(key, parameter, subKey, valuesMap);
           }
			
			// Thread safety?
           if (supplier == null) {
               supplier = valuesMap.putIfAbsent(subKey, factory);
               if (supplier == null) {
                   // successfully installed Factory
                   supplier = factory;
               }
               // else retry with winning supplier
           } else {
               if (valuesMap.replace(subKey, supplier, factory)) {
                   // successfully replaced
                   // cleared CacheEntry / unsuccessful Factory
                   // with our Factory
                   supplier = factory;
               } else {
                   // retry with current suppliersupplier = valuesMap.get(subKey); }}}}Copy the code

4.Factory.get

An supplier is a factory, so next let’s look at the GET method of the Factory class (factory is an inner class of WeakCache).

public synchronized V get(a) { // serialize access
    // re-checkSupplier<V> supplier = valuesMap.get(subKey); / Recheck whether the obtained supplier is the current objectif(supplier ! =this) {
               // something changed while we were waiting:
               // might be that we were replaced by a CacheValue
               // or were removed because of failure ->
               // return null to signal WeakCache.get() to retry
               // the loop
               return null;
           }
           // else still us (supplier == this)
 
           // create new value
           V value = null;
           try {
                // This is where the proxy class is generated by calling valueFactory
                //valueFactory is what we pass in new ProxyClassFactory()
               // We will analyze the apply method of ProxyClassFactory()
               value = Objects.requireNonNull(valueFactory.apply(key, parameter));
           } finally {
               if (value == null) { // remove us on failure
                   valuesMap.remove(subKey, this); }}// the only path to reach here is with non-null value
           assertvalue ! =null;
 
           // wrap value with CacheValue (WeakReference)
           // Wrap value as a weak reference
           CacheValue<V> cacheValue = new CacheValue<>(value);
 
           // put into reverseMap
           ReverseMap is used to achieve cache validity
           reverseMap.put(cacheValue, Boolean.TRUE);
 
           // try replacing us with CacheValue (this should always succeed)
           if(! valuesMap.replace(subKey,this, cacheValue)) {
               throw new AssertionError("Should not reach here");
           }
 
           // successfully replaced us with new CacheValue -> return the value
           // wrapped by it
           returnvalue; }}Copy the code

5.ProxyClassFactory.apply

Finally, go to the apply method of the ProxyClassFactory, where the proxy class is generated.

// BiFunction
      
        = BiFunction
       
         = BiFunction
       ,>
      ,>
private static final class ProxyClassFactory
       implements BiFunction<ClassLoader.Class<? > [].Class<? >>{
       // prefix for all proxy class names
       // The prefix of all proxy class names
       private static final String proxyClassNamePrefix = "$Proxy";
       
       // next number to use for generation of unique proxy class names
       // The counter used to generate the proxy class name
       private static final AtomicLong nextUniqueNumber = new AtomicLong();
 
       @Override
       publicClass<? > apply(ClassLoader loader, Class<? >[] interfaces) { Map<Class<? >, Boolean> interfaceSet =new IdentityHashMap<>(interfaces.length);
            // Verify the proxy interface without looking at it
           for(Class<? > intf : interfaces) {/* * Verify that the class loader resolves the name of this * interface to the same Class object. */Class<? > interfaceClass =null;
               try {
                   interfaceClass = Class.forName(intf.getName(), false, loader);
               } catch (ClassNotFoundException e) {
               }
               if(interfaceClass ! = intf) {throw new IllegalArgumentException(
                       intf + " is not visible from class loader");
               }
               /* * Verify that the Class object actually represents an * interface. */
               if(! interfaceClass.isInterface()) {throw new IllegalArgumentException(
                       interfaceClass.getName() + " is not an interface");
               }
               /* * Verify that this interface is not a duplicate. */
               if(interfaceSet.put(interfaceClass, Boolean.TRUE) ! =null) {
                   throw new IllegalArgumentException(
                       "repeated interface: "+ interfaceClass.getName()); }}// The package name of the generated proxy class
           String proxyPkg = null;     // package to define proxy class in
           // Proxy class access control: public,final
           int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
 
           /* * Record the package of a non-public proxy interface so that the * proxy class will be defined in the same package. Verify that * all non-public proxy interfaces are in the same package. */
           // Verify that all non-public interfaces are in the same package; Public ones don't need to be dealt with
           // Generate the logic for the package name and class name. The package name defaults to com.sun.proxy and the class name defaults to $proxy plus an increasing integer value
            // If the proxy class is a non-public proxy interface, use the same package name as the proxy class interface
           for(Class<? > intf : interfaces) {int flags = intf.getModifiers();
               if(! Modifier.isPublic(flags)) { accessFlags = Modifier.FINAL; String name = intf.getName();int n = name.lastIndexOf('. ');
                   String pkg = ((n == -1)?"" : name.substring(0, n + 1));
                   if (proxyPkg == null) {
                       proxyPkg = pkg;
                   } else if(! pkg.equals(proxyPkg)) {throw new IllegalArgumentException(
                           "non-public interfaces from different packages"); }}}if (proxyPkg == null) {
               // if no non-public proxy interfaces, use com.sun.proxy package
               proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
           }
 
           /* * Choose a name for the proxy class to generate. */
           long num = nextUniqueNumber.getAndIncrement();
           // The fully qualified name of the proxy class, such as com.sun.proxy.$proxy0.calss
           String proxyName = proxyPkg + proxyClassNamePrefix + num;
 
           /* * Generate the specified proxy class. */
           // Core to generate the bytecode of the proxy class
           byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
               proxyName, interfaces, accessFlags);
           try {
               // Load the proxy class into the JVM, and the dynamic proxy process is almost complete
               return defineClass0(loader, proxyName,
                                   proxyClassFile, 0, proxyClassFile.length);
           } catch (ClassFormatError e) {
               /* * A ClassFormatError here means that (barring bugs in the * proxy class generation code) there was some other * invalid aspect of the arguments supplied to the proxy * class creation (such as virtual machine limitations * exceeded). * /
               throw newIllegalArgumentException(e.toString()); }}}Copy the code

6. Bytecode file analysis

I’ve actually done the analysis here, but in a more probing way, I decided to take a look at what the DYNAMIC proxy bytecode generated by the JDK was, so I saved the bytecode to a class file on disk. The code is as follows:

package com.sun.proxy;

import com.leng.proxy.dynamic.UserService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements UserService {

   private static Method m1;
   private static Method m2;
   private static Method m3;
   private static Method m0;

   // The constructor of the proxy class takes an InvovationHandler instance as an argument
   // The proxy. newInstance method uses this constructor to create Proxy instances
   public $Proxy0(InvocationHandler var1) throws  {
      super(var1);
   }

   Equals (); toString (); hashCode ()
   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); }}// Interface proxy method
   public final void login(a) throws  {
      try {
         // Invocation Handler's Invoke method is invoked here
         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.leng.proxy.dynamic.UserService").getMethod("login".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

The last

  • If you feel there is a harvest, three consecutive support;
  • If there are mistakes in the article, please comment and point out, also welcome to reprint, reprint please indicate the source;
  • Personal VX: Listener27, exchange technology, interview, study materials, help the first-line Internet manufacturers in the promotion, etc