ホームページ  >  記事  >  Java  >  Java で JDK 動的プロキシを実装する方法

Java で JDK 動的プロキシを実装する方法

WBOY
WBOY転載
2023-04-30 08:49:06996ブラウズ

コンセプト

エージェント: A オブジェクトを制御するために、新しい B オブジェクトが作成され、代わりに B オブジェクトが A オブジェクトのすべての操作を実行します。これは、オブジェクトと呼ばれます。エージェント。代理店システムの確立には、実オブジェクト (A)、プロキシ オブジェクト (B)、およびクライアントの 3 つの役割が関与します。

プロキシ オブジェクト (B) は、実オブジェクト (A) とクライアントを接続する仲介的な役割を果たし、さらに拡張すると、実オブジェクトへのアクセス制御など、より複雑なロジックを実装できます。

ケース

要件: 従業員ビジネス レイヤー インターフェイスでは、save を呼び出すには管理者権限が必要ですが、リストの呼び出しには権限は必要ありません。権限なしで呼び出すと例外がスローされます。

静的プロキシ

/**
 * 代理接口
 */
public interface IEmployeeService {
    void save();
 
    void list();
}
/**
 * 真实对象
 */
public class EmployeeServiceImpl implements IEmployeeService {
    @Override
    public void save() {
        System.out.println("EmployeeServiceImpl-正常的save....");
    }
    @Override
    public void list() {
        System.out.println("EmployeeServiceImpl-正常的list....");
    }
}
/**
 * 模拟当前登录用户对象
 */
public class SessionHolder {
    private static String currentUser;
    public static String  getCurrentUser(){
        return currentUser;
    }
    public static void   setCurrentUser(String currentUser){
        SessionHolder.currentUser = currentUser;
    }
}
/**
 * 代理对象
 */
public class EmployeeProxy implements IEmployeeService {
    //真实对象
    private EmployeeServiceImpl employeeService;
    public EmployeeProxy(EmployeeServiceImpl employeeService){
        this.employeeService = employeeService;
    }
    @Override
    public void save() {
        //权限判断
        if("admin".equals(SessionHolder.getCurrentUser())){
            employeeService.save();
        }else{
            throw new RuntimeException("当前非admin用户,不能执行save操作");
        }
    }
    @Override
    public void list() {
        employeeService.list();
    }
}
public class App {
    public static void main(String[] args) {
        System.out.println("----------------真实对象--------------------");
        EmployeeServiceImpl employeeService = new EmployeeServiceImpl();
        employeeService.list();
        employeeService.save();
        System.out.println("----------------代理对象--------------------");
        SessionHolder.setCurrentUser("dafei");  //设置权限(当前登录用户)
        EmployeeProxy employeeProxy = new EmployeeProxy(employeeService);
        employeeProxy.list();
        employeeProxy.save();
    }
}
----------------真实对象--------------------
EmployeeServiceImpl-正常的list....
EmployeeServiceImpl-正常的save....
----------------代理对象--------------------
EmployeeServiceImpl-正常的list....
Exception in thread "main" java.lang.RuntimeException: 当前非admin用户,不能执行save操作
	at com.langfeiyes.pattern.proxy.demo.EmployeeProxy.save(EmployeeProxy.java:20)
	at com.langfeiyes.pattern.proxy.demo.App.main(App.java:16)

実際のオブジェクト EmployeeServiceImpl を使用して直接呼び出す場合、リストと保存の両方に直接アクセスできますが、要件に関する管理者権限の制限を満たしていません。プロキシ オブジェクト EmployeeProxy を使用すると、要件を満たすことができます。

プロキシ オブジェクトの新しいクラスを直接作成して、プロキシ ロジックを完成します。この方法は、静的プロキシ モードと呼ばれます。

JDK 動的プロキシ モード

Java で一般的に使用される動的プロキシ モードには、JDK 動的プロキシと cglib 動的プロキシが含まれます。ここでは、JDK 動的プロキシに焦点を当てます

依然として元の需要、以前の IEmployeeService EmployeeServiceImpl SessionHolder は変更されていません。新しい JDK プロキシ コントローラー - EmployeeInvocationHandler

/**
 * jdk动态代理控制类,由它牵头代理类获取,代理方法的执行
 */
public class EmployeeInvocationHandler  implements InvocationHandler {
    //真实对象-EmployeeServiceImpl
    private Object target;
    public EmployeeInvocationHandler(Object target){
        this.target = target;
    }
    //获取jvm在内存中生成代理对象
    public Object getProxy(){
        return  Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                this);
    }
    //代理对象控制执行方法
    //参数1:代理对象
    //参数2:真实对象的方法(使用方式得到方法对象)
    //参数3:真实对象方法参数列表
    //此处是代理对象对外暴露的可编辑的方法处理场所,代理对象每调用一个次方法,就会执行一次invoke
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String name = method.getName();
        if("save".equals(name) && !"admin".equals(SessionHolder.getCurrentUser())){
            throw new RuntimeException("当前非admin用户,不能执行save操作");
        }
        return method.invoke(target, args);
    }
}

テスト アプリ クラスはわずかに変更されています:

public class App {
    public static void main(String[] args) {
        System.out.println("----------------真实对象--------------------");
        EmployeeServiceImpl employeeService = new EmployeeServiceImpl();
        employeeService.list();
        employeeService.save();
 
        System.out.println("----------------代理对象--------------------");
        SessionHolder.setCurrentUser("dafei");
        EmployeeInvocationHandler handler = 
            new EmployeeInvocationHandler(employeeService);
        IEmployeeService proxy = (IEmployeeService) handler.getProxy();
        proxy.list();
        proxy.save();
 
    }
}

上記のコードは次のようになります。同じです。要件の実装と静的プロキシの違いは、作成されるプロキシ オブジェクトの量が少ないことです。ここで質問がありますが、プロキシ オブジェクトは作成されていませんが、なぜプロキシ クラス呼び出しが実装できるのでしょうか? ?

原理分析

まず、JDK 動的プロキシの基本的な実装原理で結論を述べましょう。実行時にインターフェイス実装を使用して、クラスがメモリ内に動的に構築され、コンパイルされて実行されます。このクラスは 1 回限りの使用であり、JVM を停止するとプロキシ クラスは消えます。

参加するロール JDK 動的プロキシの原理を理解するには、まず JDK 動的プロキシに関連するクラスを理解する必要があります。

Java で JDK 動的プロキシを実装する方法

##InvocationHandler : 実オブジェクト メソッド呼び出しプロセッサ、組み込み呼び出しメソッド、その機能: 実オブジェクトのプロキシ ロジックをカスタマイズ

EmployeeInvocationHandler: 従業員サービスの実オブジェクト メソッド呼び出しプロセッサ、このタイプには 3 つの用途があります: 1>実際のオブジェクトを設定します

     //真实对象-EmployeeServiceImpl
    private Object target;
    public EmployeeInvocationHandler(Object target){
        this.target = target;
    }

2>プロキシ メソッド実装ロジックをカスタマイズします

実際のオブジェクト保存メソッドの権限検証ロジックを追加しました

    //代理对象控制执行方法
    //参数1:代理对象
    //参数2:真实对象的方法(使用方式得到方法对象)
    //参数3:真实对象方法参数列表
    //此处是代理对象对外暴露的可编辑的方法处理场所,代理对象每调用一个次方法,就会执行一次invoke
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String name = method.getName();
        if("save".equals(name) && !"admin".equals(SessionHolder.getCurrentUser())){
            throw new RuntimeException("当前非admin用户,不能执行save操作");
        }
        return method.invoke(target, args);
    }

3>Return プロキシ オブジェクト

メソッドが実行されると、$ProxyX という名前のプロキシ クラスが返されます (X はシリアル番号で、一般にデフォルトは 0)。このプロキシ クラスは JDK によって動的に構築されます。 。

    //获取jvm在内存中生成代理对象
    public Object getProxy(){
        return  Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                this);
    }

Proxy: 動的プロキシ コントロール クラスは、JDK によって動的に生成される $ProxyX クラスの親クラスであり、その機能は次のとおりです: 1> 呼び出してプロキシ オブジェクトを構築しますProxyBuilder クラス ビルダー メソッド Class

private static Constructor<?> getProxyConstructor(Class<?> caller,
                                                      ClassLoader loader,
                                                      Class<?>... interfaces){
            return proxyCache.sub(intf).computeIfAbsent(
                loader,
                (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
            );
}

2> newProxyInstance メソッド

   public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) {
    //...
   }

$Proxy0 を通じて $ProxyX クラスのインスタンスを返します: App クラスが実行されているとき、 JDK によって動的に構築されたプロキシ クラスは、プロキシ クラスに継承されます

public class App {
    public static void main(String[] args) {
        //System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
        System.out.println("----------------真实对象--------------------");
        EmployeeServiceImpl employeeService = new EmployeeServiceImpl();
        employeeService.list();
        employeeService.save();
        System.out.println("----------------代理对象--------------------");
        SessionHolder.setCurrentUser("dafei");
        EmployeeInvocationHandler handler = 
                     new EmployeeInvocationHandler(employeeService);
        IEmployeeService proxy = (IEmployeeService) handler.getProxy();
        proxy.list();
        proxy.save();
 
    }
}

デフォルトでは、JVM は動的に作成されたプロキシ クラスのバイトコード オブジェクトを保存しません。メイン メソッドでプロキシ パラメータを設定して、バイトコードを保持することができます

//JDK8之前
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//JDK8之后
System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");

実行後、プロジェクトのルートディレクトリにプロキシクラスのバイトコードオブジェクトが生成されます。

Java で JDK 動的プロキシを実装する方法#解釈を容易にするために、いくつかの不要なメソッドを削除した後、

$Proxy0 class

public class $Proxy0 extends Proxy implements IEmployeeService {
    private static Method m4;
    private static Method m3;
    static {
        try {
            m4 = Class.forName("com.langfeiyes.proxy.demo.IEmployeeService")
                 .getMethod("save");
            m3 = Class.forName("com.langfeiyes.proxy.demo.IEmployeeService")
                 .getMethod("list");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public $Proxy0(InvocationHandler var1) throws Throwable {
        super(var1);
    }
    public final void save() throws Throwable {
        super.h.invoke(this, m4, (Object[])null);
    }
 
    public final void list() throws  Throwable{
        super.h.invoke(this, m3, (Object[])null);
    }
}

ソース コードから見ると、$Proxy0 の特徴は次のとおりです。

    #1>Proxy クラスを継承し、IEmployeeService インターフェイスを実装します
  • # 2> 静的ブロックを介して IEmployeeService インターフェイスの save および list メソッドを反映して、そのメソッド オブジェクトを取得します Method
  • 3> 親クラスのコンストラクターを呼び出すには、 InvocationHandler パラメータ
  • 4> IEmployeeService インターフェイスの保存リスト メソッドの書き換えは、親クラス Proxy
  • の h 属性.invoke メソッドに依存します。真実が明らかになります。

次の図は、動的プロキシに参加しているすべてのクラスを示しています。

次の図は、操作シーケンスです。上の図の図に従ってくださいJava で JDK 動的プロキシを実装する方法

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

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