Por casualidad hoy, de repente quise echar un vistazo al proxy dinámico de JDK, porque sabía un poco sobre él antes y solo quería probar su uso. Escribí rápidamente estas interfaces y clases:
Clase de interfaz: UserService.java
Copie el código de código de la siguiente manera:
paquete com.yixi.proxy;
interfaz pública Servicio de usuario {
público int guardar();
actualización pública nula (int id);
}
Clase de implementación: UserServiceImpl.java
Copie el código de código de la siguiente manera:
paquete com.yixi.proxy;
clase pública UserServiceImpl implementa UserService {
@Anular
público int guardar() {
System.out.println("guardar usuario...");
devolver 1;
}
@Anular
actualización de vacío público (int id) {
System.out.println("actualizar un usuario " + id);
}
}
Luego escribí ansiosamente el InvocationHandler que quería: la función de esto es muy simple, es registrar la hora de inicio y finalización de la ejecución del método.
TimeInvocationHandler.java
Copie el código de código de la siguiente manera:
paquete com.yixi.proxy;
importar java.lang.reflect.InvocationHandler;
importar java.lang.reflect.Method;
clase pública TimeInvocationHandler implementa InvocationHandler {
@Anular
Invocación pública de objeto (proxy de objeto, método de método, argumentos de objeto [])
lanza arrojable {
System.out.println("horaInicio: " +System.currentTimeMillis());
Objeto obj = método.invoke(proxy, args);
System.out.println("endTime: " +System.currentTimeMillis());
objeto de retorno;
}
}
Una vez realizados todos los preparativos, ¡por supuesto que es hora de empezar a redactar los exámenes!
prueba.java
Copie el código de código de la siguiente manera:
paquete com.yixi.proxy;
importar java.lang.reflect.Proxy;
Prueba de clase pública {
public static void main(String[] args) { 9 TimeInvocationHandler timeHandler = new TimeInvocationHandler();
UserService u = (UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), timeHandler);
u.actualización(2);
u.save();
}
}
Funcionó felizmente, pero no te dio ninguna cara. El resultado fue una excepción que llenó la pantalla:
Copie el código de código de la siguiente manera:
hora de inicio: 1352877835040
hora de inicio: 1352877835040
hora de inicio: 1352877835040
Excepción en el hilo "principal" java.lang.reflect.UndeclaredThrowableException
en $Proxy0.update(Fuente desconocida)
en com.yixi.proxy.Test.main(Test.java:11)
Causado por: java.lang.reflect.InvocationTargetException
en sun.reflect.NativeMethodAccessorImpl.invoke0(Método nativo)
en sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
en sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
en java.lang.reflect.Method.invoke(Method.java:597)
en com.yixi.proxy.TimeInvocationHandler.invoke(TimeInvocationHandler.java:12)
... 2 más
La excepción com.yixi.proxy.TimeInvocationHandler.invoke(TimeInvocationHandler.java:12) indica claramente el problema en la línea 12 de TimeInvocationHandle: es decir
Copie el código de código de la siguiente manera:
Invocación pública de objeto (proxy de objeto, método de método, argumentos de objeto [])
lanza arrojable {
System.out.println("horaInicio: " +System.currentTimeMillis());
Objeto obj = método.invoke(proxy, args);
System.out.println("endTime: " +System.currentTimeMillis());
objeto de retorno;
}
¡No hay nada malo con el método! Debido a que el método invoke () parece proporcionar todos los parámetros requeridos por método.invoke (Objeto, Objeto []), lo usaremos como algo natural. Si realmente piensas de esa manera, entonces has engañado al JDK. una trampa. Veamos primero la forma correcta de escribirlo. En caso de que algunos estudiantes no estén de humor para leer lo siguiente, al menos dame la solución correcta:
Modificar TimeInvocationHandler.java
Copie el código de código de la siguiente manera:
paquete com.yixi.proxy;
importar java.lang.reflect.InvocationHandler;
importar java.lang.reflect.Method;
clase pública TimeInvocationHandler implementa InvocationHandler {
Objeto privado o;
public TimeInvocationHandler (Objeto o) {
esto.o = o;
}
@Anular
Invocación pública de objeto (proxy de objeto, método de método, argumentos de objeto [])
lanza arrojable {
System.out.println("horaInicio: " +System.currentTimeMillis());
Objeto obj = método.invoke(o, args);
System.out.println("endTime: " +System.currentTimeMillis());
objeto de retorno;
}
}
Modificar prueba.java
Copie el código de código de la siguiente manera:
paquete com.yixi.proxy;
importar java.lang.reflect.Proxy;
Prueba de clase pública {
público estático vacío principal (String [] argumentos) {
TimeInvocationHandler timeHandler = new TimeInvocationHandler(new UserServiceImpl());
UserService u = (UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), timeHandler);
u.actualización(2);
u.save();
}
}
Ahora aquí está la salida correcta:
Copie el código de código de la siguiente manera:
hora de inicio: 1352879531334
actualizar un usuario 2
Hora de finalización: 1352879531334
hora de inicio: 1352879531334
usuario guardar....
Hora de finalización: 1352879531335
Si quieres menos código, puedes escribir una clase anónima directamente:
paquete com.yixi.proxy;
importar java.lang.reflect.InvocationHandler;
importar java.lang.reflect.Method;
importar java.lang.reflect.Proxy;
Prueba de clase pública {
público estático vacío principal (String [] argumentos) {
final UserServiceImpl usi = nuevo UserServiceImpl();
Servicio de usuario u = (Servicio de usuario) Proxy.newProxyInstance(
usi.getClass().getClassLoader(),
usi.getClass().getInterfaces(),
nuevo controlador de invocación() {
@Anular
Invocación pública de objeto (proxy de objeto, método de método, argumentos de objeto [])
lanza arrojable {
System.out.println("horaInicio: " +System.currentTimeMillis());
Objeto obj = método.invoke(usi, args);
System.out.println("endTime: " +System.currentTimeMillis());
objeto de retorno;
}
});
u.actualización(2);
u.save();
}
}
Dado que el primer parámetro en método.invoke(target,args); es el objeto de destino, ¿por qué el método Invoke de invocationHandler necesita un parámetro de proxy de objeto? ¡Miremos hacia abajo!
Para conocer el método de invocación más importante (en mi opinión personal), echemos un vistazo a lo que dice el JDK:
Copie el código de código de la siguiente manera:
invocar
Invocación de objeto(Proxy de objeto,
método método,
Objeto[] argumentos)
throws Throwable maneja las llamadas al método en la instancia de proxy y devuelve el resultado. Este método se llama en el controlador de invocación cuando se llama al método en la instancia de proxy a la que está asociado.
parámetro:
proxy: la instancia de proxy en la que se llama al método
método: una instancia de método correspondiente al método de interfaz llamado en la instancia de proxy. La clase declarante de un objeto Método será la interfaz en la que se declara el método, que puede ser una superinterfaz de la interfaz proxy de la cual la clase proxy hereda el método.
args: una matriz de objetos que contiene los valores de los argumentos pasados a la llamada al método en la instancia del proxy, o nulo si el método de la interfaz no acepta argumentos. Los parámetros de tipos básicos están empaquetados en instancias de la clase contenedora básica adecuada (como java.lang.Integer o java.lang.Boolean).
proxy: ¿la instancia de proxy en la que se llama el método? ¿Qué significa esta frase? ¿interino? ¿Cuál es el método proxy? Entonces, ¿no debería ser mi método para ejecutar el proxy Object obj = método.invoke(proxy, args);? No me di la vuelta en ese momento. Fui al grupo de discusión y fui a Google pero no pude encontrar ninguna inspiración. Pensé que sería mejor mirar el código fuente y tal vez pudiera ver algo.
Abra el código fuente de la clase Proxy y descubra qué es un constructor:
Copie el código de código de la siguiente manera:
protectedInvocationHandler h;
Proxy protegido (InvocationHandler h) {
esto.h = h;
}
Utilice InvocationHandler como parámetro del constructor de Proxy... Entonces, ¿para qué utiliza InvocationHandler? ¿Existe alguna conexión con el método invoke() en InvocationHandler?
Mi primer pensamiento es que Proxy llamará internamente a la siguiente declaración:
Copie el código de código de la siguiente manera:
h.invoke(this,nombredelmétodo,args);
Porque tienes que llamar al método de invocación para ejecutar el método correspondiente.
Echemos un vistazo a esto primero.
Aquí encontrará algo que parece tener sentido: cuando u.update(2), àProxy llamará a handler.invoke(proxyClass,update,2) à, lo que significa proxyClass.update(2);
Cuando u.save(); Proxy llamará a handler.invoke(proxyClass,save,null) es decir, proxyClass.save();
Cuando Test.java se cambia a esto:
Copie el código de código de la siguiente manera:
Prueba de clase pública {
público estático vacío principal (String [] argumentos) {
final UserServiceImpl usi = nuevo UserServiceImpl();
Servicio de usuario u = (Servicio de usuario) Proxy.newProxyInstance(
usi.getClass().getClassLoader(),
usi.getClass().getInterfaces(),
nuevo controlador de invocación() {
@Anular
Invocación pública de objeto (proxy de objeto, método de método, argumentos de objeto [])
lanza arrojable {
devolver nulo;
}
});
u.actualización(2);
u.save();
}
}
Tenga en cuenta que el método de la clase anónima en este momento devuelve nulo. Si lo ejecuta, encontrará:
Copie el código de código de la siguiente manera:
Excepción en el hilo "principal" java.lang.NullPointerException
en $Proxy0.save(Fuente desconocida)
en com.yixi.proxy.Test.main(Test.java:17)
Hay un puntero nulo en la línea 17, es decir, el método u.save () aquí tiene un elemento nulo. ¿Podría ser que u esté vacío? No debería serlo. Si u es nulo, entonces u.update(2) informará una excepción de puntero nulo allí. Cuando comente la línea 17, la excepción desaparece, lo que indica que u.update() puede ejecutarse normalmente. Entonces, ¿por qué es esto?
De hecho, esta es la razón por la que el método de invocación devuelve nulo:
Preste atención a los dos métodos de la clase UserService:
Copie el código de código de la siguiente manera:
interfaz pública Servicio de usuario {
público int guardar();
actualización pública nula (int id);
}
El método Save () devuelve un tipo int y el método de actualización devuelve un tipo void; de acuerdo con la suposición anterior, handler.invoke () implementa proxyClass.update (2); y el método de retorno en el método de invocación corresponde al retorno. valor del método proxy,
Entonces, cuando el método de invocación devuelve nulo, el método de actualización del agente recibe un valor de retorno nulo y originalmente devuelve nulo, por lo que no se informa ninguna excepción y el guardado del agente debe devolver un valor de tipo int. Lo que devolvemos aquí sigue siendo nulo. y la JVM no puede convertir nulo. Se convierte a tipo int, por lo que se informa una excepción. Esta explicación se puede explicar claramente y también puede probar relativamente la suposición anterior.
El primer parámetro proxy en el método de invocación de InvocationHandler parece ser solo para permitir que la clase Proxy pase la referencia del objeto proxy proxyClass cuando llama al método con la referencia de su propio objeto InvocationHandler para completar el negocio que proxyClass necesita completar. .
¡El talento literario no es bueno! ¡La capacidad es limitada! Espero que puedan corregirme...