编程知识 cdmana.com

Design pattern learning -- Analysis of Java Dynamic agent principle

One 、JDK Dynamic agent execution process

We talked about that in the last one JDK Simple use of dynamic agents , Today, let's study its principle .

First, let's recall the code in the last article :

public class Main {
    public static void main(String[] args)
    {
        IPaymentService paymentService = new WatchPaymentService();
        PaymentIH paymentIH = new PaymentIH(paymentService);
        IPaymentService proxy = (IPaymentService) Proxy.newProxyInstance(
                paymentService.getClass().getClassLoader(),
                new Class[] {IPaymentService.class}, paymentIH);
        proxy.pay();
    }
}

We go through Proxy.newProxyInstance Method creates a proxy object , We go through Debug Look at this proxy What is it :

We see proxy The class is $Proxy0, Obviously, this is an automatically generated class , We use the following tool class to save this dynamic class and have a look :

public class ProxyUtils {
​
    /**
     *  Save the binary bytecode of the dynamic class to the hard disk , The default is clazz Under the table of contents 
     * params: clazz  Classes that need to generate dynamic proxy classes 
     * proxyName:  The name of the dynamically generated proxy class 
     */
    public static void generateClassFile(Class clazz, String proxyName) {
        //  Based on the class information and the supplied proxy class name , Generated bytecode 
        byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
        String paths = clazz.getResource(".").getPath();
        System.out.println(paths);
        FileOutputStream out = null;
        try {
            // Save to hard drive 
            out = new FileOutputStream(paths + proxyName + ".class");
            out.write(classFile);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

stay main Method to execute the following code , We can be in target Of classes Find the generated dynamic class .

ProxyUtils.generateClassFile(paymentService.getClass(), "PaymentServiceProxy");

We go through IDEA See the contents of this dynamic class as follows :

public final class PaymentServiceProxy extends Proxy implements IPaymentService {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;
​
    public PaymentServiceProxy(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 pay() throws  {
        try {
            super.h.invoke(this, m3, (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 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"));
            m3 = Class.forName("proxy.IPaymentService").getMethod("pay");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

from PaymentServiceProxy We can find it in the code of :

  • PaymentServiceProxy Inherited Proxy class , And it implements all the interfaces that are proxied , as well as equals、hashCode、toString Other methods ;

  • because PaymentServiceProxy Inherited Proxy class , So each proxy class is associated with one InvocationHandler Method call handler ;

  • Class and all methods are public final modification , So the proxy class can only be used , It cannot be inherited ;

  • Each method has one Method Object to describe ,Method The object is static Created in a static code block , With m + Numbers   Format naming ;

  • The method of the proxied object is called through super.h.invoke(this, m1, (Object[])null);  Accomplished , Among them super.h.invoke This is actually passed to when the proxy is created Proxy.newProxyInstance Of PaymentIH object , namely InvocationHandler Implementation class of , Responsible for the actual invocation processing logic ;

  • PaymentIH Of invoke Method received method、args After the parameters such as , The proxy object executes the corresponding method through the reflection mechanism .

Sum up ,JDK The dynamic agent execution process is as follows :

So how is this class generated ? This class and InvocationHandler And how they relate ? With these two questions, let's go deep into Proxy Source code .

 

Two 、JDK Dynamic agent source code interpretation

@CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException{
        // null Check ,h by null Throw out NullPointerException
        Objects.requireNonNull(h);
        //  An array of interface class objects clone One copy .
        final Class<?>[] intfs = interfaces.clone();
​
        // Perform permission check 
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }
​
        /*
         * Look up or generate the designated proxy class.
         */
         //  Find or generate a specific proxy class object 
        Class<?> cl = getProxyClass0(loader, intfs);
​
        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
           
            //  Find the parameter from the proxy class object is InvocationHandler Constructor 
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            //  Determine whether the constructor is Public, If not, set it to accessible .
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            //  By reflection , take h As a parameter , Instantiate proxy class , Return proxy class instance .
            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);
        }
    }

The most important thing in the above code is the second 21 Xing He 44 That's ok , These two lines realize the generation of proxy class and instantiation of proxy object .

First of all, let's take a look getProxyClass0(loader, intfs) The implementation logic of :

private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }
​
        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        //  If the proxy class is defined by the specified class loader , And implement the given interface ,
        //  Then return the cached proxy class object , Otherwise use ProxyClassFactory Create a proxy class .
        return proxyClassCache.get(loader, interfaces);
    }

Analyze according to the notes ,proxyClassCache.get The method is to get the entry of the proxy class , Let's first look at this proxyClassCache What is it? :

private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

proxyClassCache It's a WeakCache The object of , Let's look at his definition :( Due to the large length of the code , Only private variable definitions and constructor definitions are shown here )

final class WeakCache<K, P, V> {
​
    private final ReferenceQueue<K> refQueue
        = new ReferenceQueue<>();
    // the key type is Object for supporting null key
    private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map
        = new ConcurrentHashMap<>();
    private final ConcurrentMap<Supplier<V>, Boolean> reverseMap
        = new ConcurrentHashMap<>();
    private final BiFunction<K, P, ?> subKeyFactory;
    private final BiFunction<K, P, V> valueFactory;
​
    /**
     * 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);
    }

among map Variables are the core variables to implement caching , It's a double Map structure : (key, subKey) -> value. among key It was passed in Classloader The object after packaging ,subKey By WeakCache It's handed down KeyFactory() Generated .value The object that generates the proxy class is WeakCache It's handed down ProxyClassFactory() Generate , This can come from proxyClassCache Initialization of can be seen .

produce subKey Of KeyFactory The code is as follows :

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

There is no need to delve into this part of the code , We just need to know that it was passed in according to interface Generate subKey That's it , Let's go on to see WeakCache.get Method :

public V get(K key, P parameter) {
        //  check parameter Not empty 
        Objects.requireNonNull(parameter);
        //  Clear invalid cache 
        expungeStaleEntries();
        // cacheKey Is the first level key of the cache 
        Object cacheKey = CacheKey.valueOf(key, refQueue);
        //  According to the first key, we get ConcurrentMap<Object, Supplier<V>>, If it doesn't exist, create it 
        // lazily install the 2nd level valuesMap for the particular cacheKey
        ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
        if (valuesMap == null) {
            ConcurrentMap<Object, Supplier<V>> oldValuesMap
                = map.putIfAbsent(cacheKey,
                                  valuesMap = new ConcurrentHashMap<>());
            if (oldValuesMap != null) {
                valuesMap = oldValuesMap;
            }
        }
​
        // create subKey and retrieve the possible Supplier<V> stored by that
        // subKey from valuesMap
        //  according to classloader and interfaces Get secondary key , namely KeyFactory Of apply Method 
        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
        //  According to the following code, you can know this supplier Namely factory, Here is also the first to see if there is... In the cache , If not, recreate ,
        //  This is also the code used here while Reasons for the cycle 
        Supplier<V> supplier = valuesMap.get(subKey);
        Factory factory = null;
​
        while (true) {
            if (supplier != null) {
                // supplier might be a Factory or a CacheValue<V> instance
                //  What I call here is Factory.get, Get the proxy class and return 
                V value = supplier.get();
                if (value != null) {
                    return value;
                }
            }
            // else no supplier in cache
            // or a supplier that returned null (could be a cleared CacheValue
            // or a Factory that wasn't successful in installing the CacheValue)
// lazily construct a Factory
            if (factory == null) {
                factory = new Factory(key, parameter, subKey, valuesMap);
            }
​
            if (supplier == null) {
                supplier = valuesMap.putIfAbsent(subKey, factory);
                if (supplier == null) {
                    // successfully installed Factory
                    supplier = factory;
                }
                // else retry with winning supplier
            } else {
                if (valuesMap.replace(subKey, supplier, factory)) {
                    // successfully replaced
                    // cleared CacheEntry / unsuccessful Factory
                    // with our Factory
                    supplier = factory;
                } else {
                    // retry with current supplier
                    supplier = valuesMap.get(subKey);
                }
            }
        }
    }

The above code is a little more convoluted , In short, try to start from key obtain (subKey -> value) Create... If it doesn't exist (subKey -> value), And then based on subKey obtain value, If the same does not exist, create a new value. Finally through Factory.get Method to get the proxy class , So let's see Factory Code for :

private final class Factory implements Supplier<V> {
​
        private final K key;
        private final P parameter;
        private final Object subKey;
        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() { // serialize access
            // re-check
            Supplier<V> supplier = valuesMap.get(subKey);
            //  What's detected is supplier Is it the current object 
            if (supplier != this) {
                // something changed while we were waiting:
                // might be that we were replaced by a CacheValue
                // or were removed because of failure ->
                // return null to signal WeakCache.get() to retry
                // the loop
                return null;
            }
            // else still us (supplier == this)
// create new value
            V value = null;
            try {
                //  call ProxyClassFactory Create a proxy class 
                value = Objects.requireNonNull(valueFactory.apply(key, parameter));
            } finally {
                if (value == null) { // remove us on failure
                    valuesMap.remove(subKey, this);
                }
            }
            // the only path to reach here is with non-null value
            assert value != null;
​
            // wrap value with CacheValue (WeakReference)
            //  hold value Packaged as weak references 
            CacheValue<V> cacheValue = new CacheValue<>(value);
​
            // try replacing us with CacheValue (this should always succeed)
            if (valuesMap.replace(subKey, this, cacheValue)) {
                // put also in reverseMap
                reverseMap.put(cacheValue, Boolean.TRUE);
            } else {
                throw new AssertionError("Should not reach here");
            }
​
            // successfully replaced us with new CacheValue -> return the value
            // wrapped by it
            return value;
        }
    }

Finally we arrived at ProxyClassFactory The class , This class is the factory class used to create the proxy class :

private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>
    {
        // prefix for all proxy class names
        //  The class name prefix of the proxy class 
        private static final String proxyClassNamePrefix = "$Proxy";
​
        // next number to use for generation of unique proxy class names
        //  Proxy class name number , namely $Proxy0,$Proxy1,$Proxy2......
        private static final AtomicLong nextUniqueNumber = new AtomicLong();
​
        @Override
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
​
            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            //  Verify whether the interface can be used by the current interface classloader Load and whether it is an interface class 
            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());
                }
            }
​
            //  Generate proxy class package name 
            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.
             */
            // Verify that all non-public interfaces are in the same package ; Public ones don't need to be dealt with 
            // Logic for generating package names and class names , The default package name is com.sun.proxy, The default class name is $Proxy  Add a self increasing integer value 
            // If the proxy class is  non-public proxy interface , Then 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.
             */
            //  Proxy class bytecode generation 
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                // Load the proxy class into JVM in , So far, the dynamic proxy process is basically over 
                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());
            }
        }
    }

stay ProxyGenerator.generateProxyClass() Method to assemble the bytecode of the proxy class , Finally, the content of the proxy class saved at the beginning of this article is generated , Interested readers can do their own research , It is worth noting that when creating the constructor of the proxy class , Here you need to pass in InvocationHandler object , So this ensures that our proxy class can be implemented by us InvocationHandler Interface to call the method of the proxy class .

private ProxyGenerator.MethodInfo generateConstructor() throws IOException {
        ProxyGenerator.MethodInfo var1 = new ProxyGenerator.MethodInfo("<init>", "(Ljava/lang/reflect/InvocationHandler;)V", 1);
        DataOutputStream var2 = new DataOutputStream(var1.code);
        this.code_aload(0, var2);
        this.code_aload(1, var2);
        var2.writeByte(183);
        var2.writeShort(this.cp.getMethodRef("java/lang/reflect/Proxy", "<init>", "(Ljava/lang/reflect/InvocationHandler;)V"));
        var2.writeByte(177);
        var1.maxStack = 10;
        var1.maxLocals = 2;
        var1.declaredExceptions = new short[0];
        return var1;
    }

 

版权声明
本文为[Allah lazy magic lamp]所创,转载请带上原文链接,感谢
https://cdmana.com/2021/08/20210804231025367F.html

Scroll to Top