Cet article vous présente les quatre méthodes d'implémentation du proxy statique et du proxy dynamique en Java. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer.
Question d'entretien : Combien de méthodes d'implémentation existe-t-il pour le modèle de conception de proxy en Java ? Cette question est très similaire à la question de Kong Yiji : « Quelles sont les manières d'écrire le mot fenouil pour les haricots de fenouil ?
Le mode dit proxy signifie que le client n'appelle pas directement l'objet réel (le un dans le coin inférieur droit de l'image ci-dessous) RealSubject), mais appelle indirectement l'objet réel en appelant le proxy.
Le mode proxy est généralement utilisé parce que le client ne souhaite pas accéder directement à l'objet réel, ou qu'il existe des obstacles techniques pour accéder à l'objet réel, de sorte que l'objet proxy est utilisé comme un pont pour effectuer un accès indirect.
Première méthode d'implémentation : proxy statique
Développer une interface IDeveloper, qui contient une méthode writeCode , écrire du code.
public interface IDeveloper { public void writeCode(); }
Créez une classe Developer pour implémenter 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"); } }
Test de code : créez une instance de développeur nommée Jerry et écrivez du code !
public class DeveloperTest { public static void main(String[] args) { IDeveloper jerry = new Developer("Jerry"); jerry.writeCode(); } }
Vient maintenant le problème. Le chef de projet de Jerry était très mécontent du fait que Jerry écrivait uniquement du code sans conserver aucune documentation. Supposons que Jerry parte en vacances un jour et que d'autres programmeurs viennent prendre en charge le travail de Jerry, regardant le code inconnu avec des points d'interrogation sur leurs visages. Après discussion au sein du groupe, il a été décidé que lorsque chaque développeur écrit du code, la documentation doit être mise à jour simultanément.
Afin de forcer chaque programmeur à se rappeler d'écrire des documents lors du développement sans affecter l'action d'écriture du code lui-même, nous ne modifions pas la classe Developer d'origine, mais créons une nouvelle classe et implémentons la même interface IDeveloper. Cette nouvelle classe DeveloperProxy maintient une variable membre en interne, 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 document Calling est ajouté. , cela garantit que les programmeurs sont accompagnés de mises à jour de documents lors de l'écriture du code.
Code de test :
Avantages de la méthode proxy statique
1. Compréhension et mise en œuvre
2. La relation entre la classe proxy et la classe réelle est déterminée statiquement lors de la compilation. Par rapport au proxy dynamique présenté immédiatement 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 aux ingénieurs de test, demandant aux ingénieurs de test de mettre à jour les documents de test correspondants en temps opportun chaque fois qu'ils détectent un bug. Ensuite, à l'aide de la méthode 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.
Première méthode d'implémentation du proxy dynamique Java : InvocationHandler
J'ai écrit un article spécifiquement pour présenter le principe d'InvocationHandler : le didacticiel d'introduction le plus simple pour le proxy dynamique Java InvocationHandler
Grâce à InvocationHandler, je peux utiliser une classe proxy EnginnerProxy pour proxy le comportement du développeur et du testeur en même temps.
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.
Résultat du test :
Limitations de l'implémentation d'un proxy dynamique via InvocationHandler
Supposons qu'il existe une classe de gestionnaire de produit (ProductOwner) 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 utilisons toujours la classe proxy EnginnerProxy pour le proxy, et il n'y aura aucune erreur lors de la compilation. Que se passe-t-il au moment de l'exécution ?
ProductOwner po = new ProductOwner("Ross"); ProductOwner poProxy = (ProductOwner) new EnginnerProxy().bind(po); poProxy.defineBackLog();
Une erreur se produit lors de l'exécution. La limitation est donc la suivante : si la classe mandatée n'implémente aucune interface, vous ne pouvez pas proxy son comportement via le proxy dynamique InvocationHandler.
Deuxième méthode d'implémentation du proxy dynamique Java : CGLIB
CGLIB est une bibliothèque de génération de bytecode Java, fournit une API facile à utiliser pour créer et modifier le bytecode Java. Pour plus de détails sur cette bibliothèque open source, veuillez vous rendre sur le référentiel de CGLIB sur github : https://github.com/cglib/cglib
Nous essayons maintenant d'utiliser CGLIB comme proxy avant d'utiliser InvocationHandler sans succès. classe (cette classe n’implémente aucune interface).
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 de test :
ProductOwner ross = new ProductOwner("Ross"); ProductOwner rossProxy = (ProductOwner) new EnginnerCGLibProxy().bind(ross); rossProxy.defineBackLog();
尽管ProductOwner未实现任何代码,但它也成功被代理了:
用CGLIB实现Java动态代理的局限性
如果我们了解了CGLIB创建代理类的原理,那么其局限性也就一目了然。我们现在做个实验,将ProductOwner类加上final修饰符,使其不可被继承:
再次执行测试代码,这次就报错了: Cannot subclass final class XXXX。
所以通过CGLIB成功创建的动态代理,实际是被代理类的一个子类。那么如果被代理类被标记成final,也就无法通过CGLIB去创建动态代理。
Java动态代理实现方式三:通过编译期提供的API动态创建代理类
假设我们确实需要给一个既是final,又未实现任何接口的ProductOwner类创建动态代码。除了InvocationHandler和CGLIB外,我们还有最后一招:
我直接把一个代理类的源代码用字符串拼出来,然后基于这个字符串调用JDK的Compiler(编译期)API,动态的创建一个新的.java文件,然后动态编译这个.java文件,这样也能得到一个新的代理类。
测试成功:
我拼好了代码类的源代码,动态创建了代理类的.java文件,能够在Eclipse里打开这个用代码创建的.java文件,
下图是如何动态创建ProductPwnerSCProxy.java文件:
下图是如何用JavaCompiler API动态编译前一步动态创建出的.java文件,生成.class文件:
下图是如何用类加载器加载编译好的.class文件到内存:
如果您想试试这篇文章介绍的这四种代理模式(Proxy Design Pattern), 请参考我的github仓库,全部代码都在上面。感谢阅读。
https://github.com/i042416/Ja...
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!