Agente dinámico basado en series de finalización de Java

Uso básico de proxy dinámico

El proxy dinámico es un medio técnico básico de Java, puede hacer algunas modificaciones a algunos métodos de un objeto en tiempo de ejecución y obtener un nuevo objeto, sin la necesidad de predefinir la clase de objeto correspondiente, echemos un vistazo primero Aquí hay una manera simple de usar el modo proxy:

Primero defina una interfaz:

public interface IPrint {
    void print();
}

Luego defina la clase de implementación de esta interfaz:

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

Esta es una clase de entidad simple, e implementa la interfaz IPrint, estamos representando una instancia de esta clase.

Luego defina las clases principales relacionadas con el agente:

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

En esta clase, encontramos un método que no reconocimos:

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

Su análisis del código fuente se explicará en el próximo capítulo. Solo necesitamos saber que su valor de retorno es una instancia de cada clase de interfaz que implementa la lista de interfaces de interfaces entrantes, y cada método de esta clase proxy La implementación depende de la implementación específica del método de invocación de h. Cuando se llama a un método de la clase proxy, es equivalente a llamar al método de invocación de h, y el método de invocación de h generalmente llama a method.invoke (targetObject, objetos) , Que es equivalente a llamar al método del objeto proxy.

Veamos la forma específica de usar 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

Se puede ver que el objeto que obtenemos a través del proxy dinámico implementa la IPrintinterfaz, y el método de impresión en la interfaz es proxy. Además de la impresión de la clase original, también existe la impresión implementada en el proxy cuando se llama, y ​​la clase proxy Los métodos heredados de Object también están representados.

Podemos ver cómo se ve la clase imprimiendo la lista de parámetros de la clase proxy:

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

La implementación de la impresión es el resultado de una conjetura.

Interpretación del código fuente del proxy dinámico

Después de haber aprendido el uso básico de los agentes dinámicos, echemos un vistazo al código fuente de los agentes dinámicos:

Comience con

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

Hablando de eso, su implementación es la siguiente (implementación en 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);
    }
}

Su lógica es muy simple, es crear un objeto Class de la clase proxy a través de la información de la interfaz y el cargador de clases, y luego llamar al constructor para crear un objeto específico, por lo que lo más importante es que el objeto Class que creó es específicamente Qué tipo de, veamos este método en detalle:

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

Lo obtuvo llamando al método WeakCache.get, y la clase WeakCache realiza principalmente un trabajo de almacenamiento en búfer, y eventualmente caerá en el método de aplicación de 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);
        }
    }
}

Está dividido en los siguientes pasos:

  1. Realice un trabajo de inspección, si el cargador de clases puede cargar la interfaz, si la matriz de clase es tanto una interfaz y si la interfaz está duplicada, si el cargador de clase y la matriz de interfaz que pasamos se obtienen utilizando objetos específicos, Estas verificaciones deben ser aprobadas;
  2. Luego obtenga el nombre del paquete de la clase proxy, si todas las interfaces son públicas, el nombre del paquete de la clase proxy está vacío; de lo contrario, es el nombre del paquete de la interfaz no pública (si hay varias interfaces no públicas y el nombre del paquete es diferente, se generará Una excepción, por supuesto, nuestro código no puede realizar la interfaz no pública bajo múltiples paquetes);
  3. Obtenga la lista de métodos de todas las interfaces y verifique si hay el mismo nombre de método y lista de parámetros, pero el método con un valor de retorno diferente arroja una excepción;
  4. Obtenga la lista de excepciones devuelta por el método;
  5. Luego llame al método nativo para crear específicamente el objeto de clase proxy.

Las comprobaciones anteriores, si nuestro cargador de clases y la lista de interfaz se obtienen a través de targetObject.getClass (). GetClassLoader (), targetObject.getClass (). GetInterfaces (), esas comprobaciones definitivamente pasarán.

La lista de métodos es la siguiente: además de los métodos en la interfaz, también agrega tres métodos, equals, hashCode y toString. Cada interfaz también obtiene recursivamente los métodos de su clase padre.

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

El método nativo para crear un objeto de clase es el siguiente:

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

Dado que no hay un archivo de clase correspondiente a la clase proxy, el método convencional de creación de clase no funciona, aquí se logra a través del método nativo.

Hasta ahora hemos terminado el proceso de creación de clases proxy.

Podemos resumir los siguientes puntos del código fuente:

  1. El proxy dinámico solo puede implementar métodos en la interfaz del objeto proxy;
  2. Después de pasar el proxy dinámico, la información del objeto proxy no aparecerá en la clase de la clase proxy, es decir, el proxy dinámico se enfrenta a la lista de interfaces para obtener la clase proxy. Para lograr.

Finalmente, simplemente interpretamos la creación y el uso de Retrofit desde la perspectiva de un agente dinámico.

Creo que las personas que han usado Retrofit están familiarizadas con el siguiente código:

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

En el último paso, llamó al método de creación de Retrofit y pasó un objeto de clase de la interfaz, y finalmente obtuvo una instancia. Podemos adivinar fácilmente que usa un proxy dinámico para lograrlo. Su realización colectiva es la siguiente:

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

Se puede ver que su instancia fue creada a través de proxy dinámico.

En el método invoke, primero determine si el método actualmente definido por la interfaz se llama o pertenece al método Object. El método Object se llama directamente. Si es el método definido por la interfaz, la información de anotación del método se analiza y almacena en serviceMethod, y luego la interfaz de solicitud .

Publicado 19 artículos originales · elogiado 8 · visitas 4041

Supongo que te gusta

Origin blog.csdn.net/u014068277/article/details/102772732
Recomendado
Clasificación