L'implémentation spécifique du modèle de conception de proxy Java (Proxy) : proxy statique et proxy dynamique

L'implémentation spécifique du modèle de conception de proxy Java (Proxy) : proxy statique et proxy dynamique

  • Méthode de mise en œuvre 1 : proxy statique
  • Avantages de la méthode proxy statique
  • Inconvénients de la méthode proxy statique
  • Méthode d'implémentation du proxy dynamique Java 1 : InvocationHandler
  • Méthode 2 d'implémentation du proxy dynamique Java : CGLIB
  • Limitations de l'implémentation du proxy dynamique Java avec CGLIB

Question d'entretien : combien de façons existe-t-il d'implémenter le Proxy Design Pattern en Java ? Ce sujet est très similaire à celui de Kong Yiji demandant : "Quelles sont les façons d'écrire le mot fenouil pour le fenouil ?

Le soi-disant mode proxy signifie que le client (Client) n'appelle pas directement l'objet réel (RealSubject dans le coin inférieur droit de la figure ci-dessous), mais appelle indirectement l'objet réel en appelant le proxy (Proxy).

L'utilisation du mode proxy est généralement due au fait que le client ne souhaite pas accéder directement à l'objet réel ou qu'il existe des obstacles techniques à l'accès à l'objet réel, de sorte que l'accès indirect est effectué via l'objet proxy en tant que pont.

image

Méthode de mise en œuvre 1 : proxy statique

Développer une interface IDeveloper, l'interface contient une méthode writeCode, écrire du code.

public interface IDeveloper {
    
    

     public void writeCode();

}

Créez une classe Developer qui implémente cette interface.

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

Testez le code : créez une instance de développeur nommée Jerry et allez écrire le code !

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

Vient maintenant la question. Le chef de projet de Jerry n'était pas satisfait du code d'écriture de Jerry sans conserver aucune documentation. Supposons qu'un jour, Jerry parte en vacances et que d'autres programmeurs viennent reprendre le travail de Jerry, en regardant le code inconnu avec des points d'interrogation. Après toute la discussion de groupe, il a été décidé que chaque développeur doit mettre à jour la documentation de manière synchrone lors de l'écriture du code.

Afin de forcer chaque programmeur à se souvenir d'écrire des documents pendant le développement sans affecter l'action d'écriture de code elle-même, nous n'avons pas modifié la classe Developer d'origine, mais avons créé une nouvelle classe qui implémente également l'interface IDeveloper. Cette nouvelle classe DeveloperProxy gère en interne une variable membre pointant vers l'instance IDeveloper d'origine :

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

Dans la méthode writeCode implémentée par cette classe proxy, avant d'appeler la méthode writeCode du programmeur réel, un appel pour écrire le document est ajouté, ce qui garantit que le programmeur est accompagné de la mise à jour du document lors de l'écriture du code.

Code d'essai :

image

Avantages de la méthode proxy statique

  1. Facile à comprendre et à mettre en œuvre

  2. La relation entre la classe proxy et la classe réelle est déterminée statiquement au moment de la compilation. Par rapport au proxy dynamique décrit ci-dessous, il n'y a pas de surcharge supplémentaire lors de l'exécution.

Inconvénients de la méthode proxy statique

Chaque classe réelle nécessite la création d'une nouvelle classe proxy. En prenant la mise à jour du document ci-dessus comme exemple, supposons que le patron propose également de nouvelles exigences pour l'ingénieur de test, de sorte que chaque fois que l'ingénieur de test détecte un bogue, le document de test correspondant doit également être mis à jour à temps. Ensuite, en utilisant la méthode de proxy statique, la classe d'implémentation ITester de l'ingénieur de test doit également créer une classe ITesterProxy correspondante.

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

C'est précisément à cause de cette lacune de la méthode de code statique que la méthode d'implémentation de proxy dynamique de Java est née.

Méthode d'implémentation du proxy dynamique Java 1 : InvocationHandler

Avec InvocationHandler, je peux utiliser une classe proxy EngineProxy pour proxy à la fois le comportement du développeur et du testeur.

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

Les méthodes writeCode et doTesting de la classe réelle sont exécutées par réflexion dans la classe proxy dynamique.

Sortie d'essai :

image

Limitations du proxy dynamique via InvocationHandler

Supposons qu'il existe une classe de gestionnaire de produit (ProductOwner) qui n'implémente aucune interface.

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

Nous prenons toujours la classe proxy EngineProxy pour le proxy et compilons sans erreur. Que se passe-t-il à l'exécution ?

ProductOwner po = new ProductOwner("Ross");

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

poProxy.defineBackLog();

Erreur lors de l'exécution. La limitation est donc la suivante : si la classe à proxy n'implémente aucune interface, son comportement ne peut pas être proxy par proxy dynamique via InvocationHandler.

image

Méthode 2 d'implémentation du proxy dynamique Java : CGLIB

CGLIB est une bibliothèque de génération de bytecode Java qui fournit une API facile à utiliser pour créer et modifier des bytecodes Java. Pour plus de détails sur cette bibliothèque open source, rendez-vous sur le dépôt CGLIB sur github : https://github.com/cglib/cglib

Nous essayons maintenant d'utiliser CGLIB pour proxy la classe ProductOwner (qui n'implémente aucune interface) qui n'a pas été correctement proxy avec InvocationHandler auparavant.

Maintenant, j'utilise l'API CGLIB à la place pour créer la classe 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();
	}
}

Code d'essai :

ProductOwner ross = new ProductOwner("Ross");

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

rossProxy.defineBackLog();

Bien que ProductOwner n'implémente aucun code, il est également proxy :

image

Limitations de l'implémentation du proxy dynamique Java avec CGLIB

Si nous comprenons le principe de CGLIB pour créer des classes proxy, alors ses limites seront claires en un coup d'œil. Faisons maintenant une expérience et ajoutons le modificateur final à la classe ProductOwner pour la rendre non héritable :

image

Exécutez à nouveau le code de test, et cette fois une erreur est signalée : Impossible de sous-classer la classe finale XXXX.

Par conséquent, le proxy dynamique créé avec succès par CGLIB est en fait une sous-classe de la classe proxy. Ensuite, si la classe proxy est marquée comme finale, il est impossible de créer un proxy dynamique via CGLIB.

Mode d'implémentation de proxy dynamique Java 3 : créez dynamiquement des classes de proxy via l'API fournie au moment de la compilation

Supposons que nous ayons besoin de créer du code dynamique pour une classe ProductOwner qui soit à la fois finale et n'implémente aucune interface. En plus de InvocationHandler et CGLIB, nous avons un dernier recours :

J'épele directement le code source d'une classe proxy avec une chaîne, puis j'appelle l'API du compilateur du JDK (à la compilation) en fonction de cette chaîne, je crée dynamiquement un nouveau fichier .java, puis je compile dynamiquement le fichier .java, qui Peut également obtenir une nouvelle classe proxy.

image

le test a réussi :

image

J'ai assemblé le code source de la classe code, créé dynamiquement le fichier .java de la classe proxy, et peux ouvrir le fichier .java créé avec le code dans Eclipse,

image

image

La figure suivante montre comment créer dynamiquement le fichier ProductPwnerSCProxy.java :

image

La figure suivante montre comment utiliser l'API JavaCompiler pour compiler dynamiquement le fichier .java créé dynamiquement à l'étape précédente pour générer un fichier .class :

image

La figure suivante montre comment utiliser le chargeur de classe pour charger le fichier .class compilé en mémoire :

image

Je suppose que tu aimes

Origine blog.csdn.net/qq_43842093/article/details/123930019
conseillé
Classement