As a technology that everyone knows, but few can fully articulate: dynamic proxies.

Today, we’ll figure him out once and for all.

Delve into JDK dynamic proxy implementation

The basic use

Nothing fancy, just the following code:

UserService userService = new UserServiceImpl();
UserService o = (UserService) Proxy.newProxyInstance(UserService.class.getClassLoader(), new Class[]{UserService.class}, new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("--before--");
        Object invoke = method.invoke(userService, args);
        System.out.println("--after--");
        returninvoke; }});Copy the code

As you can see, the key is Proxy creation: proxy.newProxyInstance. Later we also take this as the entrance to further.

Key Step resolution

Generating proxy classes

Generated Proxy class is the most complicated step of dynamic Proxy, the real generated Proxy class bytecode method in: Java. Lang. Reflect. Proxy. ProxyClassFactory# apply.

To simplify the method, there are mainly the following steps:

    // Pre-check
    interfaceClass = Class.forName(intf.getName(), false, loader);
    if(xxx){
        throw newIllegalArgumentException(xxx); }...// Data preparation
    intaccessFlags = Modifier.PUBLIC | Modifier.FINAL; . String proxyName = proxyPkg + proxyClassNamePrefix + num; .Generate the proxy class bytes
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); ./ / load
    return defineClass0(loader, proxyName,
        proxyClassFile, 0, proxyClassFile.length);

Copy the code

Generating and loading proxy classes is the core of dynamic proxies.

Assembly ProxyMethod

Step 1: Assemble ProxyMethod objects for all methods to generate proxy dispatching code for.

Here only RMB will be collected by the method of information encapsulated in the sun. The misc. ProxyGenerator. ProxyMethod kind of storage, and no real generated code.

There are also hashCode,equals, and toString methods saved specifically.

addProxyMethod(hashCodeMethod, Object.class); addProxyMethod(equalsMethod, Object.class); addProxyMethod(toStringMethod, Object.class); . sigmethods.add(new ProxyMethod(name, parameterTypes, returnType,
    exceptionTypes, fromClass));
Copy the code

Assemble FieldInfo/MethodInfo

Step 2: Assemble FieldInfo and MethodInfo structs for all of fields and methods in the class we are generating.

Add constructors and add member properties based on the ProxyMethod assembled in the previous step.

In this step, you will be called sun. Misc. ProxyGenerator. ProxyMethod# generateMethod method, for the generated code.

// Generate the constructor
this.methods.add(this.generateConstructor()); .// Generate member attributes
    fields.add(new FieldInfo(pm.methodFieldName,
        "Ljava/lang/reflect/Method;", ACC_PRIVATE | ACC_STATIC));// Add methodmethods.add(pm.generateMethod()); .// Add a static initialization block
methods.add(generateStaticInitializer());
     
Copy the code

Construct final class

Step 3: Write the final class file.

Generate the final byte array based on the information gathered in the previous steps.

                // u4 magic;
dout.writeInt(0xCAFEBABE);
                            // u2 minor_version;
dout.writeShort(CLASSFILE_MINOR_VERSION);
                            // u2 major_version;
dout.writeShort(CLASSFILE_MAJOR_VERSION);

cp.write(dout); // (write constant pool)

                            // u2 access_flags;
dout.writeShort(accessFlags);
                            // u2 this_class;
dout.writeShort(cp.getClass(dotToSlash(className)));
                            // u2 super_class;dout.writeShort(cp.getClass(superclassName)); .Copy the code

The final result

By configuring sun. Misc. ProxyGenerator. SaveGeneratedFiles = true can be generated proxy class bytecode preserved.

After removing the hashCode,toString, etc methods we don’t care about, the decompilation results in the following:

package com.sun.proxy;

import git.frank.proxy.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;

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

    public final String getUserInfo(a) throws  {
        try {
            return (String)super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("git.frank.proxy.UserService").getMethod("getUserInfo");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw newNoClassDefFoundError(var3.getMessage()); }}}Copy the code

Loading proxy classes

Using the code in the openJdk as a reference, the actual method called here is: JavaLangAccess#defineClass.

Defines a class with the given name to a class loader.

His real code is in java.lang.system, in the form of anonymous inner classes.

    // Allow privileged classes outside of java.lang
    private static void setJavaLangAccess(a) {
     SharedSecrets.setJavaLangAccess(new JavaLangAccess() {
            ...
            publicClass<? > defineClass(ClassLoader loader, String name,byte[] b, ProtectionDomain pd, String source) {
                return ClassLoader.defineClass1(loader, name, b, 0, b.length, pd, source); }... }Copy the code

So, here we use the given class loader to load the class we just generated. When the propped method getUserInfo is called, it is passed to the InvocationHandler#invoke method by the propped class.

So why isn’t it called directly in the codeClassLoader.defineClass1Instead of going all the way around?

Let’s take a closer look, because the defineClass code in the ClassLoader is protected and cannot be called directly.

That’s why the openJdk created a JavaLangAccess class that allows outsiders to call the contents of the java.lang package.

Construct object

Constructing the object is much simpler, simply using the reflection call constructor to create the instance of the proxy class you just generated.

Use a constructor with an InvocationHandler as an input and pass in a user-written InvocationHandler object.

finalConstructor<? > cons = cl.getConstructor(constructorParams); . cons.newInstance(new Object[]{h})
Copy the code

This is actually the com.sun.proxy.$Proxy0 class instance generated above.

Caching mechanisms in dynamic proxies

Dynamic proxies don’t have to go through the same string of code every time. You can use the same proxy class as classLoader + interfaces.

So the JDK also provides a caching mechanism for classes that are dynamically proxy generated, so that the next time you use it, you can create a proxy instance directly through reflection, without having to regenerate clazz.

The cache here mainly through Java. Lang. Reflect. WeakCache implementation.

Use key + sub-key to uniquely determine a value.

Key indicates the incoming classLoader, and sub-key indicates the interface class calculated by key +.

The cache structure is shown as follows:

Lazy loading

Instead of caching the largest proxy class, valuesMap initially caches a ProxyClassFactory.

When valueFactory.apply(key, parameter) is called to generate the real proxy class, the cache is replaced by the factory object with the real proxy.

if(! valuesMap.replace(subKey,this, cacheValue)) {
    throw new AssertionError("Should not reach here");
}
Copy the code

The complete cache acquisition process is as follows: