JavaGuidによるプロキシモードの詳細な理解

1.エージェンシーモデル

JavaGuidで転載

エージェントモデルは、よりよく理解された設計パターンです。簡単に言うと、実際のオブジェクト(実際のオブジェクト)ではなくプロキシオブジェクトにアクセスするために使用するため、元のターゲットオブジェクトを変更せずにアクセスできるのは、追加の機能操作、つまりターゲットオブジェクトの機能の拡張を提供することを前提としています。

プロキシモードの主な機能は、ターゲットオブジェクトの機能を拡張することです。たとえば、ターゲットオブジェクトの特定のメソッドが実行される前後に、いくつかのカスタム操作を追加できます。

例:あなたはXiaohongに質問を手伝ってくれるように頼み、Xiaohongは私のエージェントと見なし、エージェントの行動(方法)は質問でした。

ここに写真の説明を挿入

プロキシモードには、静的プロキシと動的プロキシの2つの実装方法があります。まず、静的プロキシモードの実装を見てみましょう。

2.静的プロキシ

静的プロキシでは、ターゲットオブジェクトの各メソッドの拡張は手動で行われます(コードは後で詳しく説明します)。これは非常に柔軟性がなく(たとえば、新しいメソッドがインターフェイスに追加されると、ターゲットオブジェクトとプロキシオブジェクトを変更する必要があります)、面倒です。 (ターゲットクラスごとに個別のプロキシクラスを作成する必要があります)。実際のアプリケーションシナリオは非常に少なく、静的エージェントの使用は日常の開発ではほとんど見えません。

上記は、実装とアプリケーションの観点からの静的エージェントです。JVMの観点から、静的エージェントは、编译时インターフェイス、実装クラス、およびプロキシクラスを実際のクラスファイルに変換します。

静的プロキシの実装手順:

  1. インターフェイスとその実装クラスを定義します。
  2. このインターフェイスも実装するプロキシクラスを作成します
  3. ターゲットオブジェクトをプロキシクラスに挿入してから、プロキシクラスの対応するメソッドのターゲットクラスの対応するメソッドを呼び出します。このようにして、プロキシクラスを介してターゲットオブジェクトへのアクセスをブロックし、ターゲットメソッドの実行の前後に必要なことを実行できます。

以下のコードでそれを示してください!

1.SMSを送信するためのインターフェイスを定義します

public interface SmsService {
    
    
    String send(String message);
}

2.SMSを送信するためのインターフェイスを実装します

public class SmsServiceImpl implements SmsService {
    
    
    public String send(String message) {
    
    
        System.out.println("send message:" + message);
        return message;
    }
}

3.プロキシクラスを作成し、SMSを送信するためのインターフェイスも実装します

public class SmsProxy implements SmsService {
    
    

    private final SmsService smsService;

    public SmsProxy(SmsService smsService) {
    
    
        this.smsService = smsService;
    }

    @Override
    public String send(String message) {
    
    
        //调用方法之前,我们可以添加自己的操作
        System.out.println("before method send()");
        smsService.send(message);
        //调用方法之后,我们同样可以添加自己的操作
        System.out.println("after method send()");
        return null;
    }
}

4.実際の使用

public class Main {
    
    
    public static void main(String[] args) {
    
    
        SmsService smsService = new SmsServiceImpl();
        SmsProxy smsProxy = new SmsProxy(smsService);
        smsProxy.send("java");
    }
}

上記のコードを実行すると、コンソールは次のように出力します。

before method send()
send message:java
after method send()

出力を見ることができます、私たちは方法を増やしましSmsServiceImplsend()

3.ダイナミックエージェント

静的エージェントと比較して、動的エージェントはより柔軟性があります。ターゲットクラスごとに個別のプロキシクラスを作成する必要はなく、インターフェイスを実装する必要もありません実装クラスを直接プロキシできます(CGLIB動的プロキシメカニズム)。

JVMの観点からは、動的プロキシは运行时クラスバイトコードを動的に生成してJVMにロードします。

動的プロキシと言えば、Spring AOPとRPCフレームワークは2つ言及する必要があり、それらの実装はすべて動的プロキシに依存しています。

動的エージェントは、私たちの日常の開発では比較的小さいですが、フレームワークではほとんど必要なテクノロジーです。動的エージェントを学習した後、さまざまなフレームワークの原則を理解して学習することも非常に役立ちます。

Javaに関する限り、JDK動的プロキシCGLIB動的プロキシなど、動的プロキシを実装する方法は多数あります

Guide-rpc-frameworkはJDK動的プロキシを使用します。JDK動的プロキシの使用を見てみましょう。

さらに、guide-rpc-frameworkCGLIB動的プロキシを使用しませんが、ここではその使用とJDK動的プロキシとの比較について簡単に紹介します

3.1.JDK動的プロキシメカニズム

3.1.1。はじめに

Java動的プロキシメカニズムInvocationHandlerインターフェイスでは、Proxyクラスがコアです。

Proxyこのクラスで最も頻繁に使用されるメソッドは次のとおりです。newProxyInstance()このメソッドは、主にプロキシオブジェクトを生成するために使用されます。

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

このメソッドには3つのパラメーターがあります。

  1. ローダー:クラスローダー。プロキシオブジェクトをロードするために使用されます。
  2. インターフェース:プロキシクラスによって実装されるいくつかのインターフェース。
  3. hInvocationHandlerインターフェースを実装するオブジェクト。

動的エージェントを実装する場合は、InvocationHandlerカスタム処理ロジックも実装する必要がありますこのメソッドを呼び出すときにメソッドを呼び出す動的プロキシオブジェクトが、呼び出すInvocationHandlerインターフェイスクラスinvokeメソッドの実現転送される場合

public interface InvocationHandler {
    
    

    /**
     * 当你使用代理对象调用方法的时候实际会调用到这个方法
     */
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

invoke() このメソッドには、次の3つのパラメーターがあります。

  1. プロキシ:動的に生成されたプロキシクラス
  2. メソッド:プロキシクラスオブジェクトによって呼び出されるメソッドに対応します
  3. args:現在のメソッドメソッドのパラメーター

言い換えると、メソッドを呼び出すときにクラスを渡しProxynewProxyInstance()InvocationHandlerinvoke()ます。実際の呼び出しは、クラスインターフェイスメソッドで作成されプロキシオブジェクトを実現しますあなたがすることができinvoke()、処理ロジックのメソッドをカスタマイズする、このような方法は前と実行後に何かをします。

3.1.2.JDK動的プロキシクラスの使用手順

  1. インターフェイスとその実装クラスを定義します。
  2. メソッド、ネイティブメソッド(プロキシクラスのメソッド)を呼び出すメソッドをカスタマイズInvocationHandlerしてオーバーライドし、いくつかの処理ロジックをカスタマイズします。invokeinvoke
  3. Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)プロキシオブジェクトのメソッドを作成します。

3.1.3。コード例

これは少し中空で理解するのが難しいかもしれません。私の最後の例を見てみましょう!

1.SMSを送信するためのインターフェイスを定義します

public interface SmsService {
    
    
    String send(String message);
}

2.SMSを送信するためのインターフェイスを実装します

public class SmsServiceImpl implements SmsService {
    
    
    public String send(String message) {
    
    
        System.out.println("send message:" + message);
        return message;
    }
}

3.JDK動的プロキシクラスを定義します

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author shuang.kou
 * @createTime 2020年05月11日 11:23:00
 */
public class DebugInvocationHandler implements InvocationHandler {
    
    
    /**
     * 代理类中的真实对象
     */
    private final Object target;

    public DebugInvocationHandler(Object target) {
    
    
        this.target = target;
    }


    public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
    
    
        //调用方法之前,我们可以添加自己的操作
        System.out.println("before method " + method.getName());
        Object result = method.invoke(target, args);
        //调用方法之后,我们同样可以添加自己的操作
        System.out.println("after method " + method.getName());
        return result;
    }
}

invoke()メソッド:動的プロキシオブジェクトがネイティブメソッド呼び出しを呼び出して最終invoke()メソッドであるという事実を呼び出す場合、ネイティブメソッドを呼び出すinvoke()代わりのメソッドはプロキシオブジェクトです。

4.プロキシオブジェクトのファクトリクラスを取得します

public class JdkProxyFactory {
    
    
    public static Object getProxy(Object target) {
    
    
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(), // 目标类的类加载
                target.getClass().getInterfaces(),  // 代理需要实现的接口,可指定多个
                new DebugInvocationHandler(target)   // 代理对象对应的自定义 InvocationHandler
        );
    }
}

getProxy():主にProxy.newProxyInstance()メソッドを介して特定のクラスのプロキシオブジェクトを取得ます

5.実際の使用

SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
smsService.send("java");

上記のコードを実行すると、コンソールは次のように出力します。

before method send
send message:java
after method send

3.2.CGLIB動的プロキシメカニズム

3.2.1。はじめに

JDK動的プロキシの最も致命的な問題の1つは、インターフェイスを実装するクラスしかプロキシできないことです。

この問題を解決するために、CGLIB動的プロキシメカニズムを使用して問題を回避できます。

CGLIBコード生成ライブラリ)は、ASMベースのバイトコード生成ライブラリであり、実行時にバイトコードを変更して動的に生成できます。CGLIBは、継承を通じてプロキシを実装します。SpringのAOPモジュールなど、よく知られている多くのオープンソースフレームワークはCGLIBを使用します。ターゲットオブジェクトがインターフェイスを実装する場合、デフォルトでJDK動的プロキシが使用されます。それ以外の場合はCGLIB動的プロキシが使用されます。

CGLIBでは、動的プロキシメカニズムMethodInterceptorインターフェイスとEnhancerクラスがコアです。

拡張メソッドをインターセプトするために使用されるメソッドをカスタマイズMethodInterceptorしてオーバーライドする必要があるのは、プロキシクラスです。interceptintercept

public interface MethodInterceptor
extends Callback{
    
    
    // 拦截被代理类中的方法
    public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
                               MethodProxy proxy) throws Throwable;
}

  1. obj:プロキシされるオブジェクト(拡張する必要があるオブジェクト)
  2. メソッド:インターセプトされたメソッド(拡張が必要な​​メソッド)
  3. args:メソッドパラメータ
  4. methodProxy:元のメソッドを呼び出すために使用されます

あなたはできるEnhancerプロキシクラスは、実際の呼び出しがあるメソッドを呼び出すとき、クラスに動的プロキシクラスを取得MethodInterceptorしてintercept方法を。

3.2.2.CGLIB動的プロキシクラスを使用する手順

  1. クラスを定義します。
  2. インターセプトプロキシクラスMethodInterceptorを拡張するinterceptメソッドでinterceptあるメソッドのカスタマイズとオーバーライド、およびinvoke類似のJDK動的プロキシメソッド。
  3. することでEnhancer、クラスcreate()のプロキシクラスを作成します。

3.2.3。コード例

JDK動的プロキシとは異なり、追加の依存関係は必要ありません。CGLIBコード生成ライブラリ)は実際にはオープンソースプロジェクトです。これを使用する場合は、関連する依存関係を手動で追加する必要があります。

<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.3.0</version>
</dependency>

1. AlibabaCloudを使用してSMSを送信するクラスを実装します

package github.javaguide.dynamicProxy.cglibDynamicProxy;

public class AliSmsService {
    
    
    public String send(String message) {
    
    
        System.out.println("send message:" + message);
        return message;
    }
}

2.カスタムMethodInterceptor(メソッドインターセプター)

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * 自定义MethodInterceptor
 */
public class DebugMethodInterceptor implements MethodInterceptor {
    
    


    /**
     * @param o           被代理的对象(需要增强的对象)
     * @param method      被拦截的方法(需要增强的方法)
     * @param args        方法入参
     * @param methodProxy 用于调用原始方法
     */
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    
    
        //调用方法之前,我们可以添加自己的操作
        System.out.println("before method " + method.getName());
        Object object = methodProxy.invokeSuper(o, args);
        //调用方法之后,我们同样可以添加自己的操作
        System.out.println("after method " + method.getName());
        return object;
    }

}

3.プロキシクラスを取得します

import net.sf.cglib.proxy.Enhancer;

public class CglibProxyFactory {
    
    

    public static Object getProxy(Class<?> clazz) {
    
    
        // 创建动态代理增强类
        Enhancer enhancer = new Enhancer();
        // 设置类加载器
        enhancer.setClassLoader(clazz.getClassLoader());
        // 设置被代理类
        enhancer.setSuperclass(clazz);
        // 设置方法拦截器
        enhancer.setCallback(new DebugMethodInterceptor());
        // 创建代理类
        return enhancer.create();
    }
}

4.実際の使用

AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
aliSmsService.send("java");

上記のコードを実行すると、コンソールは次のように出力します。

before method send
send message:java
after method send

3.3.JDK動的プロキシとCGLIB動的プロキシの比較

  1. JDK動的プロキシは、インターフェイスを実装するクラスのみをプロキシできますが、CGLIBは、インターフェイスを実装しないクラスをプロキシできます。さらに、CGLIB動的プロキシは、プロキシクラスのサブクラスを生成することによってプロキシクラスのメソッド呼び出しをインターセプトするため、finalとして宣言されたクラスおよびメソッドをプロキシすることはできません。
  2. 2つの効率の点では、ほとんどの場合、JDK動的プロキシの方が優れています。JDKバージョンのアップグレードにより、この利点はより明白になります。

4.静的プロキシと動的プロキシの比較

  1. 柔軟性:動的プロキシはより柔軟性があり、インターフェイスを実装する必要がなく、実装クラスを直接プロキシでき、ターゲットクラスごとにプロキシクラスを作成する必要がありません。さらに、静的プロキシでは、新しいメソッドがインターフェイスに追加されると、ターゲットオブジェクトとプロキシオブジェクトを変更する必要があり、これは非常に面倒です。
  2. JVMレベル:静的プロキシは、コンパイル時にインターフェイス、実装クラス、およびプロキシクラスを実際のクラスファイルに変換します。動的エージェントは、実行時にクラスバイトコードを動的に生成し、それをJVMにロードします。

5.まとめ

この記事では、主にプロキシモードの2つの実装(静的プロキシと動的プロキシ)を紹介します。静的エージェントと動的エージェントの実際の戦闘、静的エージェントと動的エージェントの違い、およびJDK動的エージェントとCglib動的エージェントの違いについて説明します。

この記事に関連するすべてのソースコードは、https//github.com/Snailclimb/guide-rpc-framework-learning/tree/master/src/main/java/github/javaguide/proxyにあります

おすすめ

転載: blog.csdn.net/qianzhitu/article/details/108472805