ホームページ  >  記事  >  Java  >  Javaにおける静的プロキシと動的プロキシの4つの実装方法の紹介

Javaにおける静的プロキシと動的プロキシの4つの実装方法の紹介

不言
不言転載
2018-10-22 14:53:532752ブラウズ

この記事では、Java での静的エージェントと動的エージェントの 4 つの実装方法を紹介します。必要な方は参考にしていただければ幸いです。

インタビューの質問: Java のプロキシ デザイン パターンには実装メソッドがいくつありますか?この質問は、Kong Yiji の質問「フェンネル豆をフェンネルと書くにはどうすればよいですか?」とよく似ています。

いわゆるプロキシ モードとは、クライアントが実際のオブジェクト ( 1 つは下の図の右下隅にあります) RealSubject) ですが、プロキシ (Proxy) を呼び出すことで実際のオブジェクトを間接的に呼び出します。

プロキシ モードが使用されるのは、クライアントが実際のオブジェクトに直接アクセスしたくない場合、または実際のオブジェクトにアクセスするのに技術的な障害があるため、プロキシ オブジェクトが間接アクセスを完了するためのブリッジとして使用される場合です。

Javaにおける静的プロキシと動的プロキシの4つの実装方法の紹介

#実装方法 1: 静的プロキシ

メソッド writeCode を含むインターフェイス IDeveloper を開発します。コードを書きます。

public interface IDeveloper {

     public void writeCode();

}
このインターフェイスを実装するための Developer クラスを作成します。

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");
    }
}
テスト コード: Jerry という名前の開発者インスタンスを作成し、コードを作成します。

public class DeveloperTest {
    public static void main(String[] args) {
        IDeveloper jerry = new Developer("Jerry");
        jerry.writeCode();
    }
}
ここで問題が起こります。ジェリーのプロジェクト マネージャーは、ジェリーがドキュメントを維持せずにコードだけを書いたことに非常に不満を感じていました。ある日、ジェリーが休暇に出かけ、他のプログラマがジェリーの仕事を引き継ぎにやって来て、顔に疑問符を浮かべた見慣れないコードを眺めているとします。グループ全体での議論の結果、各開発者がコードを作成する際には、ドキュメントも同時に更新する必要があることが決定されました。

コードを書く行為自体に影響を与えることなく、すべてのプログラマが開発時にドキュメントを書くことを忘れないようにするために、元の Developer クラスを変更せず、新しいクラスを作成して同じ IDeveloper インターフェイスを実装します。この新しいクラス DeveloperProxy は、元の 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();
    }
}
このプロキシ クラスによって実装された writeCode メソッドでは、実際のプログラマの writeCode メソッドを呼び出す前に、ドキュメントを書き込むための呼び出しが追加されます。これにより、プログラマはコードを作成するときに最新のドキュメントを使用できるようになります。

テストコード:

Javaにおける静的プロキシと動的プロキシの4つの実装方法の紹介

静的プロキシ方式の利点

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 の動的プロキシ実装方法が生まれたのは、まさに静的コード方法のこの欠点のためです。

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;
    }
}
実際のクラスの writeCode メソッドと doTesting メソッドは、動的プロキシ クラスのリフレクションを通じて実行されます。

テスト出力:

Javaにおける静的プロキシと動的プロキシの4つの実装方法の紹介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における静的プロキシと動的プロキシの4つの実装方法の紹介

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未实现任何代码,但它也成功被代理了:

Javaにおける静的プロキシと動的プロキシの4つの実装方法の紹介

用CGLIB实现Java动态代理的局限性

如果我们了解了CGLIB创建代理类的原理,那么其局限性也就一目了然。我们现在做个实验,将ProductOwner类加上final修饰符,使其不可被继承:

Javaにおける静的プロキシと動的プロキシの4つの実装方法の紹介

再次执行测试代码,这次就报错了: Cannot subclass final class XXXX。

所以通过CGLIB成功创建的动态代理,实际是被代理类的一个子类。那么如果被代理类被标记成final,也就无法通过CGLIB去创建动态代理。

Java动态代理实现方式三:通过编译期提供的API动态创建代理类

假设我们确实需要给一个既是final,又未实现任何接口的ProductOwner类创建动态代码。除了InvocationHandler和CGLIB外,我们还有最后一招:

我直接把一个代理类的源代码用字符串拼出来,然后基于这个字符串调用JDK的Compiler(编译期)API,动态的创建一个新的.java文件,然后动态编译这个.java文件,这样也能得到一个新的代理类。

Javaにおける静的プロキシと動的プロキシの4つの実装方法の紹介

测试成功:

Javaにおける静的プロキシと動的プロキシの4つの実装方法の紹介

我拼好了代码类的源代码,动态创建了代理类的.java文件,能够在Eclipse里打开这个用代码创建的.java文件,

Javaにおける静的プロキシと動的プロキシの4つの実装方法の紹介

Javaにおける静的プロキシと動的プロキシの4つの実装方法の紹介

下图是如何动态创建ProductPwnerSCProxy.java文件:

Javaにおける静的プロキシと動的プロキシの4つの実装方法の紹介

下图是如何用JavaCompiler API动态编译前一步动态创建出的.java文件,生成.class文件:

Javaにおける静的プロキシと動的プロキシの4つの実装方法の紹介

下图是如何用类加载器加载编译好的.class文件到内存:

Javaにおける静的プロキシと動的プロキシの4つの実装方法の紹介

如果您想试试这篇文章介绍的这四种代理模式(Proxy Design Pattern), 请参考我的github仓库,全部代码都在上面。感谢阅读。

https://github.com/i042416/Ja...

以上がJavaにおける静的プロキシと動的プロキシの4つの実装方法の紹介の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はsegmentfault.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。