この記事では、Java での静的エージェントと動的エージェントの 4 つの実装方法を紹介します。必要な方は参考にしていただければ幸いです。
インタビューの質問: Java のプロキシ デザイン パターンには実装メソッドがいくつありますか?この質問は、Kong Yiji の質問「フェンネル豆をフェンネルと書くにはどうすればよいですか?」とよく似ています。
いわゆるプロキシ モードとは、クライアントが実際のオブジェクト ( 1 つは下の図の右下隅にあります) RealSubject) ですが、プロキシ (Proxy) を呼び出すことで実際のオブジェクトを間接的に呼び出します。
プロキシ モードが使用されるのは、クライアントが実際のオブジェクトに直接アクセスしたくない場合、または実際のオブジェクトにアクセスするのに技術的な障害があるため、プロキシ オブジェクトが間接アクセスを完了するためのブリッジとして使用される場合です。
#実装方法 1: 静的プロキシ
メソッド writeCode を含むインターフェイス IDeveloper を開発します。コードを書きます。public interface IDeveloper { public void writeCode(); }
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"); } }
public class DeveloperTest { public static void main(String[] args) { IDeveloper jerry = new Developer("Jerry"); jerry.writeCode(); } }
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(); } }
静的プロキシ方式の利点
1. 簡単です。理解と実装2. プロキシ クラスと実際のクラスの関係は、コンパイル中に静的に決定されます。すぐ下で紹介する動的プロキシと比較して、実行中に追加のオーバーヘッドはありません。静的プロキシ メソッドの欠点
すべての実際のクラスでは、新しいプロキシ クラスを作成する必要があります。上記のドキュメントの更新を例として、上司がテスト エンジニアに対して新しい要件も提示し、バグを検出するたびに対応するテスト ドキュメントをタイムリーに更新するようテスト エンジニアに要求したとします。次に、静的プロキシ メソッドを使用して、テスト エンジニアの実装クラス ITester も、対応する ITesterProxy クラスを作成する必要があります。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(); } }
Java ダイナミック プロキシの実装方法 1: InvocationHandler
InvocationHandler の原理を紹介する記事を書きました: Java ダイナミック プロキシ InvocationHandler の最も簡単な入門チュートリアルInvocationHandler を通じて、EnginnerProxy プロキシ クラスを使用して、開発者とテスターの動作を同時にプロキシできます。
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; } }
InvocationHandler による動的プロキシ実装の制限
プロダクト マネージャー クラスがあると仮定します。 (ProductOwner) はインターフェイスを実装していません。
public class ProductOwner { private String name; public ProductOwner(String name){ this.name = name; } public void defineBackLog(){ System.out.println("PO: " + name + " defines Backlog."); } }
プロキシには引き続き EnginnerProxy プロキシ クラスを使用します。コンパイル中にエラーは発生しません。実行時に何が起こるのでしょうか?
ProductOwner po = new ProductOwner("Ross"); ProductOwner poProxy = (ProductOwner) new EnginnerProxy().bind(po); poProxy.defineBackLog();
実行時にエラーが発生します。したがって、制限は次のとおりです。プロキシされたクラスがインターフェイスを実装していない場合、InvocationHandler 動的プロキシを介してその動作をプロキシすることはできません。
Java ダイナミック プロキシの実装方法 2: CGLIB
CGLIB は Java バイトコード生成ライブラリであり、以下を提供します。 Java バイトコードを作成および変更するための使いやすい API。このオープン ソース ライブラリの詳細については、github の CGLIB のリポジトリを参照してください: https://github.com/cglib/cglib
現在、InvocationHandler を使用する前に CGLIB を使用してプロキシを試みていますが、成功しません。クラス (このクラスはインターフェイスを実装しません)。
ここでは、代わりに CGLIB API を使用してプロキシ クラスを作成します:
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(); } }
テスト コード:
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...
以上がJavaにおける静的プロキシと動的プロキシの4つの実装方法の紹介の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。