Dynamic agent based on Java completion series

Basic use of dynamic proxy

Dynamic proxy is a basic technical means of Java. He can make some modifications to some methods of an object at runtime, and get a new object without the need to pre-define the corresponding object class. Here is a simple way to use the proxy mode:

First define an interface:

public interface IPrint {
    void print();
}

Then define the implementation class of this interface:

public class Person implements IPrint {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public void print() {
        System.out.println(toString());
    }
}

This is a simple entity class, and implements the interface IPrint, we are proxying an instance of this class.

Then define the core classes related to the agent:

public class LogHandler<T> implements InvocationHandler {

    private T targetObject;

    public LogHandler(T targetObject) {
        this.targetObject = targetObject;
    }

    public T newProxyInstance(T targetObject) {
        return (T) Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
                targetObject.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] objects) throws Throwable {
        System.out.println("invoke start");
        Object result = method.invoke(targetObject, objects);
        System.out.println("invoke end");
        return result;
    }
}

In this class, we found a method we did not recognize:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

His source code analysis will be explained in the next chapter. We just need to know that its return value is an instance of each interface class that implements the incoming interfaces interface list, and each method of this proxy class The implementation depends on the specific implementation of the invoke method of h. When a method of the proxy class is called, it is equivalent to calling the invoke method of h, and the invoke method of h generally calls method.invoke (targetObject, objects) , Which is equivalent to calling the method of the proxy object.

Let's look at the specific way of using proxy:

Person tony = new Person("Tony", 1);
LogHandler<IPrint> logHandler = new LogHandler<>(tony);
IPrint iPrint =  logHandler.newProxyInstance(tony);
iPrint.print();
iPrint.toString();

invoke start
Person{name='Tony', age=1}
invoke end
invoke start
invoke end

It can be seen that the object we get through the dynamic proxy implements the IPrintinterface, and the method print in the interface is proxied. In addition to the printing of the original class itself, there is also the printing implemented in the proxy when calling, and the proxy class Methods inherited from Object are also proxied.

We can see what the class looks like by printing the parameter list of the proxy Class:

public final class com.sun.proxy.$Proxy0 extends java.lang.reflect.Proxy
{
 public com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler);

  public final boolean equals(java.lang.Object);
  public final java.lang.String toString();
  public final int hashCode();
  public final void print(){
      h.invoke(this,m0, Object[] objects)
  }

  java.lang.reflect.Method m1;
  java.lang.reflect.Method m3;
  java.lang.reflect.Method m2;
  java.lang.reflect.Method m0;
}

The implementation of print is the result of conjecture.

Source code interpretation of dynamic proxy

After we have learned the basic usage of dynamic agents, let's take a look at the source code of dynamic agents:

Start with

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

Speaking of, his implementation is as follows (implementation on Android):

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
{
    Objects.requireNonNull(h);

    final Class<?>[] intfs = interfaces.clone();
    // Android-removed: SecurityManager calls
    /*
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }
    */

    /*
     * Look up or generate the designated proxy class.
     */
     //获取代理类的Class对象
    Class<?> cl = getProxyClass0(loader, intfs);

    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
        // Android-removed: SecurityManager / permission checks.
        /*
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }
        */

        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {
            // BEGIN Android-changed: Excluded AccessController.doPrivileged call.
            /*
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
            */

            cons.setAccessible(true);
            // END Android-removed: Excluded AccessController.doPrivileged call.
        }
        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);
    }
}

His logic is very simple, is to create a Class object of the proxy class through the interface information and class loader, and then call the constructor to create a specific object, so the most important thing is that the Class object he created is specifically What kind of, let's look at this method in detail:

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

He got it by calling the WeakCache.get method, and the WeakCache class mainly does some buffering work, and he will eventually fall into the apply method of ProxyClassFactory:

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 the default package.
            proxyPkg = "";
        }
    
        {
            // Android-changed: Generate the proxy directly instead of calling
            // through to ProxyGenerator.
            List<Method> methods = getMethods(interfaces);
            Collections.sort(methods, ORDER_BY_SIGNATURE_AND_SUBTYPE);
            validateReturnTypes(methods);
            List<Class<?>[]> exceptions = deduplicateAndGetExceptions(methods);
    
            Method[] methodsArray = methods.toArray(new Method[methods.size()]);
            Class<?>[][] exceptionsArray = exceptions.toArray(new Class<?>[exceptions.size()][]);
    
            /*
             * Choose a name for the proxy class to generate.
             */
            long num = nextUniqueNumber.getAndIncrement();
            String proxyName = proxyPkg + proxyClassNamePrefix + num;
    
            return generateProxy(proxyName, interfaces, loader, methodsArray,
                                 exceptionsArray);
        }
    }
}

He is divided into the following steps:

  1. Do some inspection work, whether the interface can be loaded by the class loader, whether the Class array is both an interface, and whether the interface is duplicated, if the class loader and interface array we pass in are obtained using specific objects, These checks must be passed;
  2. Then get the package name of the proxy class, if all interfaces are public, the package name of the proxy class is empty, otherwise it is the package name of the non-public interface (if there are multiple non-public interfaces, and the package name is different, it will be thrown An exception, of course, our code can not realize the non-public interface under multiple packages);
  3. Obtain the method list of all interfaces, and check whether there is the same method name and parameter list, but the method with a different return value throws an exception;
  4. Get the exception list returned by the method;
  5. Then call the native method to specifically create the proxy class object.

The above checks, if our class loader and interface list is obtained through targetObject.getClass (). GetClassLoader (), targetObject.getClass (). GetInterfaces (), those checks will definitely pass.

The method list is as follows. In addition to the methods in the interface, it also adds three methods, equals, hashCode, and toString. Each interface also recursively obtains the methods of its parent class.

private static List<Method> getMethods(Class<?>[] interfaces) {
    List<Method> result = new ArrayList<Method>();
    try {
        result.add(Object.class.getMethod("equals", Object.class));
        result.add(Object.class.getMethod("hashCode", EmptyArray.CLASS));
        result.add(Object.class.getMethod("toString", EmptyArray.CLASS));
    } catch (NoSuchMethodException e) {
        throw new AssertionError();
    }

    getMethodsRecursive(interfaces, result);
    return result;
}

private static void getMethodsRecursive(Class<?>[] interfaces, List<Method> methods) {
    for (Class<?> i : interfaces) {
        getMethodsRecursive(i.getInterfaces(), methods);
        Collections.addAll(methods, i.getDeclaredMethods());
    }
}

The native method of creating a Class object is as follows:

private static native Class<?> generateProxy(String name, Class<?>[] interfaces,
                                                 ClassLoader loader, Method[] methods,
                                                 Class<?>[][] exceptions);

Since there is no class file corresponding to the proxy class, the conventional method of creating Class does not work, here is achieved through the native method.

So far we have finished the process of creating proxy classes.

We can summarize the following points from the source code:

  1. Dynamic proxy can only implement methods on proxy object interface;
  2. After passing the dynamic proxy, the information of the proxy object will not appear in the class of the proxy class. That is to say, the dynamic proxy is actually facing the interface list to obtain the proxy class. To associate with the proxy object, only through InvocationHandler.invoke to fulfill.

Finally, we simply interpret the creation and use of Retrofit from the perspective of a dynamic agent.

I believe that people who have used Retrofit are familiar with the following code:

api = Retrofit.Builder()
    .baseUrl(BASE_URL)
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
    .addConverterFactory(GsonConverterFactory.create())
    .client(builder.build())
    .build()
    .create(Api::class.java)

In the last step, he called Retrofit's create method and passed in a Class object of the interface, and finally got an instance. We can easily guess that it uses a dynamic proxy to achieve it. His collective realization is as follows:

  public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();

          @Override public Object invoke(Object proxy, Method method, Object... args)
              throws Throwable {
            // If the method is a method from Object then defer to normal invocation.
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            ServiceMethod serviceMethod = loadServiceMethod(method);
            OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  }

It can be seen that his instance was created through dynamic proxy.

In the invoke method, first determine whether the method currently defined by the interface is called or belongs to the method of Object. The method of Object is called directly. If it is the method defined by the interface, the annotation information of the method is parsed, stored in serviceMethod, and then the request interface is called .

Published 19 original articles · praised 8 · visits 4041

Guess you like

Origin blog.csdn.net/u014068277/article/details/102772732