ホームページ Java &#&チュートリアル Javaにおける静的プロキシと動的プロキシの4つの実装方法の紹介

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

Oct 22, 2018 pm 02:53 PM
java jdk proxy_pass 演技

この記事では、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 サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

Javaの完全数 Javaの完全数 Aug 30, 2024 pm 04:28 PM

Java における完全数のガイド。ここでは、定義、Java で完全数を確認する方法、コード実装の例について説明します。

ジャワのウェカ ジャワのウェカ Aug 30, 2024 pm 04:28 PM

Java の Weka へのガイド。ここでは、weka java の概要、使い方、プラットフォームの種類、利点について例を交えて説明します。

Javaのスミス番号 Javaのスミス番号 Aug 30, 2024 pm 04:28 PM

Java のスミス番号のガイド。ここでは定義、Java でスミス番号を確認する方法について説明します。コード実装の例。

Java Springのインタビューの質問 Java Springのインタビューの質問 Aug 30, 2024 pm 04:29 PM

この記事では、Java Spring の面接で最もよく聞かれる質問とその詳細な回答をまとめました。面接を突破できるように。

Java 8 Stream Foreachから休憩または戻ってきますか? Java 8 Stream Foreachから休憩または戻ってきますか? Feb 07, 2025 pm 12:09 PM

Java 8は、Stream APIを導入し、データ収集を処理する強力で表現力のある方法を提供します。ただし、ストリームを使用する際の一般的な質問は次のとおりです。 従来のループにより、早期の中断やリターンが可能になりますが、StreamのForeachメソッドはこの方法を直接サポートしていません。この記事では、理由を説明し、ストリーム処理システムに早期終了を実装するための代替方法を調査します。 さらに読み取り:JavaストリームAPIの改善 ストリームを理解してください Foreachメソッドは、ストリーム内の各要素で1つの操作を実行する端末操作です。その設計意図はです

Java での日付までのタイムスタンプ Java での日付までのタイムスタンプ Aug 30, 2024 pm 04:28 PM

Java での日付までのタイムスタンプに関するガイド。ここでは、Java でタイムスタンプを日付に変換する方法とその概要について、例とともに説明します。

カプセルの量を見つけるためのJavaプログラム カプセルの量を見つけるためのJavaプログラム Feb 07, 2025 am 11:37 AM

カプセルは3次元の幾何学的図形で、両端にシリンダーと半球で構成されています。カプセルの体積は、シリンダーの体積と両端に半球の体積を追加することで計算できます。このチュートリアルでは、さまざまな方法を使用して、Javaの特定のカプセルの体積を計算する方法について説明します。 カプセルボリュームフォーミュラ カプセルボリュームの式は次のとおりです。 カプセル体積=円筒形の体積2つの半球体積 で、 R:半球の半径。 H:シリンダーの高さ(半球を除く)。 例1 入力 RADIUS = 5ユニット 高さ= 10単位 出力 ボリューム= 1570.8立方ユニット 説明する 式を使用してボリュームを計算します。 ボリューム=π×R2×H(4

未来を創る: まったくの初心者のための Java プログラミング 未来を創る: まったくの初心者のための Java プログラミング Oct 13, 2024 pm 01:32 PM

Java は、初心者と経験豊富な開発者の両方が学習できる人気のあるプログラミング言語です。このチュートリアルは基本的な概念から始まり、高度なトピックに進みます。 Java Development Kit をインストールしたら、簡単な「Hello, World!」プログラムを作成してプログラミングを練習できます。コードを理解したら、コマンド プロンプトを使用してプログラムをコンパイルして実行すると、コンソールに「Hello, World!」と出力されます。 Java の学習はプログラミングの旅の始まりであり、習熟が深まるにつれて、より複雑なアプリケーションを作成できるようになります。

See all articles