La implementación específica del patrón de diseño de proxy de Java (Proxy): proxy estático y proxy dinámico

La implementación específica del patrón de diseño de proxy de Java (Proxy): proxy estático y proxy dinámico

  • Método de implementación 1: proxy estático
  • Ventajas del método de proxy estático
  • Desventajas del método de proxy estático
  • Método de implementación de proxy dinámico de Java 1: InvocationHandler
  • Método de implementación de proxy dinámico de Java 2: CGLIB
  • Limitaciones de la implementación de Java Dynamic Proxy con CGLIB

Pregunta de la entrevista: ¿Cuántas formas hay de implementar el patrón de diseño de proxy en Java? Este tema es muy similar a la pregunta de Kong Yiji: "¿Cuáles son las formas de escribir la palabra hinojo para hinojo?

El llamado modo proxy significa que el cliente (Cliente) no llama directamente al objeto real (RealSubject en la esquina inferior derecha de la figura a continuación), sino que llama indirectamente al objeto real llamando al proxy (Proxy).

El uso del modo proxy generalmente se debe a que el cliente no desea acceder directamente al objeto real, o existen obstáculos técnicos para acceder al objeto real, por lo que el acceso indirecto se completa a través del objeto proxy como un puente.

imagen

Método de implementación 1: proxy estático

Desarrollar una interfaz IDeveloper, la interfaz contiene un método writeCode, código de escritura.

public interface IDeveloper {
    
    

     public void writeCode();

}

Cree una clase de desarrollador que implemente esta interfaz.

public class Developer implements IDeveloper{
    
    
	private String name;
	public Developer(String name){
    
    
		this.name = name;
	}
	@Override
	public void writeCode() {
    
    
		System.out.println("Developer " + name + " writes code");
	}
}

Pruebe el código: cree una instancia de desarrollador llamada Jerry y vaya a escribir el código.

public class DeveloperTest {
    
    
	public static void main(String[] args) {
    
    
		IDeveloper jerry = new Developer("Jerry");
		jerry.writeCode();
	}
}

Ahora viene la pregunta. El administrador de proyectos de Jerry no estaba satisfecho con el código de escritura de Jerry sin mantener ninguna documentación. Supongamos que un día Jerry se fue de vacaciones y otros programadores vinieron a hacerse cargo del trabajo de Jerry, mirando el código desconocido con signos de interrogación. Después de la discusión de todo el grupo, se decidió que cada desarrollador debe actualizar la documentación de forma sincrónica al escribir el código.

Para obligar a todos los programadores a recordar escribir documentos durante el desarrollo sin afectar la acción de escribir el código en sí, no modificamos la clase Developer original, sino que creamos una nueva clase que también implementa la interfaz IDeveloper. Esta nueva clase DeveloperProxy mantiene internamente una variable miembro que apunta a la instancia original de IDeveloper:

public class DeveloperProxy implements IDeveloper{
    
    
	private IDeveloper developer;
	public DeveloperProxy(IDeveloper developer){
    
    
		this.developer = developer;
	}
	@Override
	public void writeCode() {
    
    
		System.out.println("Write documentation...");
		this.developer.writeCode();
	}
}

En el método writeCode implementado por esta clase de proxy, antes de llamar al método writeCode del programador real, se agrega una llamada para escribir el documento, lo que garantiza que el programador esté acompañado por la actualización del documento al escribir código.

Código de prueba:

imagen

Ventajas del método de proxy estático

  1. Fácil de entender e implementar

  2. La relación entre la clase proxy y la clase real se determina estáticamente en el momento de la compilación. En comparación con el proxy dinámico que se describe a continuación, no hay sobrecarga adicional en la ejecución.

Desventajas del método de proxy estático

Cada clase real requiere que se cree una nueva clase proxy. Tomando la actualización del documento anterior como ejemplo, suponga que el jefe también presenta nuevos requisitos para el ingeniero de pruebas, de modo que cada vez que el ingeniero de pruebas detecte un error, el documento de prueba correspondiente también debe actualizarse a tiempo. Luego, utilizando el método de proxy estático, la clase de implementación ITester del ingeniero de pruebas también tiene que crear una clase ITesterProxy correspondiente.

public interface ITester {
    
    
	public void doTesting();
}
Original tester implementation class:
public class Tester implements ITester {
    
    
	private String name;
	public Tester(String name){
    
    
		this.name = name;
	}
	@Override
	public void doTesting() {
    
    
		System.out.println("Tester " + name + " is testing code");
	}
}
public class TesterProxy implements ITester{
    
    
	private ITester tester;
	public TesterProxy(ITester tester){
    
    
		this.tester = tester;
	}
	@Override
	public void doTesting() {
    
    
		System.out.println("Tester is preparing test documentation...");
		tester.doTesting();
	}
}

Es precisamente debido a esta deficiencia del método de código estático que nació el método de implementación de proxy dinámico de Java.

Método de implementación de proxy dinámico de Java 1: InvocationHandler

Con InvocationHandler, puedo usar una clase de proxy EngineProxy para representar el comportamiento del desarrollador y el probador.

public class EnginnerProxy implements InvocationHandler {
    
    
	Object obj;
	public Object bind(Object obj)
	{
    
    
		this.obj = obj;
		return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
		.getClass().getInterfaces(), this);
	}
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
	throws Throwable
	{
    
    
		System.out.println("Enginner writes document");
		Object res = method.invoke(obj, args);
		return res;
	}
}

Los métodos writeCode y doTesting de la clase real se ejecutan por reflexión en la clase proxy dinámica.

Salida de prueba:

imagen

Limitaciones del proxy dinámico a través de InvocationHandler

Supongamos que hay una clase de administrador de productos (ProductOwner) que no implementa ninguna interfaz.

public class ProductOwner {
    
    
	private String name;
	public ProductOwner(String name){
    
    
		this.name = name;
	}
	public void defineBackLog(){
    
    
		System.out.println("PO: " + name + " defines Backlog.");
	}
}

Todavía tomamos la clase de proxy EngineProxy para representarla y compilar sin errores. ¿Qué sucede en tiempo de ejecución?

ProductOwner po = new ProductOwner("Ross");

ProductOwner poProxy = (ProductOwner) new EnginnerProxy().bind(po);

poProxy.defineBackLog();

Error al ejecutar. Entonces, la limitación es: si la clase que se va a utilizar como proxy no implementa ninguna interfaz, su comportamiento no se puede controlar mediante el proxy dinámico a través de InvocationHandler.

imagen

Método de implementación de proxy dinámico de Java 2: CGLIB

CGLIB es una biblioteca de generación de código de bytes de Java que proporciona una API fácil de usar para crear y modificar códigos de bytes de Java. Para obtener más detalles sobre esta biblioteca de código abierto, vaya al repositorio de CGLIB en github: https://github.com/cglib/cglib

Ahora tratamos de usar CGLIB para representar la clase ProductOwner (que no implementa ninguna interfaz) que anteriormente se envió sin éxito con InvocationHandler.

Ahora uso la API de CGLIB para crear la clase de proxy:

public class EnginnerCGLibProxy {
    
    
	Object obj;
	public Object bind(final Object target)
	{
    
    
		this.obj = target;
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(obj.getClass());
		enhancer.setCallback(new MethodInterceptor() {
    
    
			@Override
			public Object intercept(Object obj, Method method, Object[] args,
			MethodProxy proxy) throws Throwable
			{
    
    
				System.out.println("Enginner 2 writes document");
				Object res = method.invoke(target, args);
				return res;
			}
		}
		);
		return enhancer.create();
	}
}

Código de prueba:

ProductOwner ross = new ProductOwner("Ross");

ProductOwner rossProxy = (ProductOwner) new EnginnerCGLibProxy().bind(ross);

rossProxy.defineBackLog();

Aunque ProductOwner no implementa ningún código, también se procesa con éxito:

imagen

Limitaciones de la implementación de Java Dynamic Proxy con CGLIB

Si entendemos el principio de CGLIB para crear clases de proxy, entonces sus limitaciones serán claras de un vistazo. Hagamos un experimento ahora y agreguemos el modificador final a la clase ProductOwner para que no sea heredable:

imagen

Ejecute el código de prueba nuevamente y esta vez se informa un error: No se puede subclasificar la clase final XXXX.

Por lo tanto, el proxy dinámico creado con éxito por CGLIB es en realidad una subclase de la clase de proxy. Entonces, si la clase de proxy se marca como final, es imposible crear un proxy dinámico a través de CGLIB.

Modo de implementación de proxy dinámico de Java 3: cree dinámicamente clases de proxy a través de la API proporcionada en tiempo de compilación

Supongamos que necesitamos crear un código dinámico para una clase ProductOwner que sea final y no implemente ninguna interfaz. Además de InvocationHandler y CGLIB, tenemos un último recurso:

Deletreo directamente el código fuente de una clase de proxy con una cadena y luego llamo a la API del compilador (tiempo de compilación) de JDK en función de esta cadena, creo dinámicamente un nuevo archivo .java y luego compilo dinámicamente el archivo .java, que También puede obtener una nueva clase de proxy.

imagen

la prueba fue exitosa:

imagen

Reuní el código fuente de la clase de código, creé dinámicamente el archivo .java de la clase de proxy y puedo abrir el archivo .java creado con el código en Eclipse,

imagen

imagen

La siguiente figura muestra cómo crear dinámicamente el archivo ProductPwnerSCProxy.java:

imagen

La siguiente figura muestra cómo usar la API JavaCompiler para compilar dinámicamente el archivo .java creado dinámicamente en el paso anterior para generar un archivo .class:

imagen

La siguiente figura muestra cómo usar el cargador de clases para cargar el archivo .class compilado en la memoria:

imagen

Supongo que te gusta

Origin blog.csdn.net/qq_43842093/article/details/123930019
Recomendado
Clasificación