1.エージェンシーモデル
JavaGuidで転載
エージェントモデルは、よりよく理解された設計パターンです。簡単に言うと、実際のオブジェクト(実際のオブジェクト)ではなくプロキシオブジェクトにアクセスするために使用するため、元のターゲットオブジェクトを変更せずにアクセスできるのは、追加の機能操作、つまりターゲットオブジェクトの機能の拡張を提供することを前提としています。
プロキシモードの主な機能は、ターゲットオブジェクトの機能を拡張することです。たとえば、ターゲットオブジェクトの特定のメソッドが実行される前後に、いくつかのカスタム操作を追加できます。
例:あなたはXiaohongに質問を手伝ってくれるように頼み、Xiaohongは私のエージェントと見なし、エージェントの行動(方法)は質問でした。
プロキシモードには、静的プロキシと動的プロキシの2つの実装方法があります。まず、静的プロキシモードの実装を見てみましょう。
2.静的プロキシ
静的プロキシでは、ターゲットオブジェクトの各メソッドの拡張は手動で行われます(コードは後で詳しく説明します)。これは非常に柔軟性がなく(たとえば、新しいメソッドがインターフェイスに追加されると、ターゲットオブジェクトとプロキシオブジェクトを変更する必要があります)、面倒です。 (ターゲットクラスごとに個別のプロキシクラスを作成する必要があります)。実際のアプリケーションシナリオは非常に少なく、静的エージェントの使用は日常の開発ではほとんど見えません。
上記は、実装とアプリケーションの観点からの静的エージェントです。JVMの観点から、静的エージェントは、编译时
インターフェイス、実装クラス、およびプロキシクラスを実際のクラスファイルに変換します。
静的プロキシの実装手順:
- インターフェイスとその実装クラスを定義します。
- このインターフェイスも実装するプロキシクラスを作成します
- ターゲットオブジェクトをプロキシクラスに挿入してから、プロキシクラスの対応するメソッドのターゲットクラスの対応するメソッドを呼び出します。このようにして、プロキシクラスを介してターゲットオブジェクトへのアクセスをブロックし、ターゲットメソッドの実行の前後に必要なことを実行できます。
以下のコードでそれを示してください!
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()
出力を見ることができます、私たちは方法を増やしましSmsServiceImpl
たsend()
。
3.ダイナミックエージェント
静的エージェントと比較して、動的エージェントはより柔軟性があります。ターゲットクラスごとに個別のプロキシクラスを作成する必要はなく、インターフェイスを実装する必要もありません。実装クラスを直接プロキシできます(CGLIB動的プロキシメカニズム)。
JVMの観点からは、動的プロキシは运行时
クラスバイトコードを動的に生成してJVMにロードします。
動的プロキシと言えば、Spring AOPとRPCフレームワークは2つ言及する必要があり、それらの実装はすべて動的プロキシに依存しています。
動的エージェントは、私たちの日常の開発では比較的小さいですが、フレームワークではほとんど必要なテクノロジーです。動的エージェントを学習した後、さまざまなフレームワークの原則を理解して学習することも非常に役立ちます。
Javaに関する限り、JDK動的プロキシ、CGLIB動的プロキシなど、動的プロキシを実装する方法は多数あります。
Guide-rpc-frameworkはJDK動的プロキシを使用します。JDK動的プロキシの使用を見てみましょう。
さらに、guide-rpc-frameworkはCGLIB動的プロキシを使用しませんが、ここではその使用と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つのパラメーターがあります。
- ローダー:クラスローダー。プロキシオブジェクトをロードするために使用されます。
- インターフェース:プロキシクラスによって実装されるいくつかのインターフェース。
- h:
InvocationHandle
rインターフェースを実装するオブジェクト。
動的エージェントを実装する場合は、InvocationHandler
カスタム処理ロジックも実装する必要があります。このメソッドを呼び出すときにメソッドを呼び出す動的プロキシオブジェクトが、呼び出すInvocationHandler
インターフェイスクラスinvoke
メソッドの実現に転送される場合。
public interface InvocationHandler {
/**
* 当你使用代理对象调用方法的时候实际会调用到这个方法
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
invoke()
このメソッドには、次の3つのパラメーターがあります。
- プロキシ:動的に生成されたプロキシクラス
- メソッド:プロキシクラスオブジェクトによって呼び出されるメソッドに対応します
- args:現在のメソッドメソッドのパラメーター
言い換えると、メソッドを呼び出すときにクラスを渡しProxy
newProxyInstance()
InvocationHandler
invoke()
ます。実際の呼び出しは、クラスインターフェイスメソッドで作成されたプロキシオブジェクトを実現します。あなたがすることができinvoke()
、処理ロジックのメソッドをカスタマイズする、このような方法は前と実行後に何かをします。
3.1.2.JDK動的プロキシクラスの使用手順
- インターフェイスとその実装クラスを定義します。
- メソッド、ネイティブメソッド(プロキシクラスのメソッド)を呼び出すメソッドをカスタマイズ
InvocationHandler
してオーバーライドし、いくつかの処理ロジックをカスタマイズします。invoke
invoke
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
してオーバーライドする必要があるのは、プロキシクラスです。intercept
intercept
public interface MethodInterceptor
extends Callback{
// 拦截被代理类中的方法
public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
MethodProxy proxy) throws Throwable;
}
- obj:プロキシされるオブジェクト(拡張する必要があるオブジェクト)
- メソッド:インターセプトされたメソッド(拡張が必要なメソッド)
- args:メソッドパラメータ
- methodProxy:元のメソッドを呼び出すために使用されます
あなたはできるEnhancer
プロキシクラスは、実際の呼び出しがあるメソッドを呼び出すとき、クラスに動的プロキシクラスを取得MethodInterceptor
してintercept
方法を。
3.2.2.CGLIB動的プロキシクラスを使用する手順
- クラスを定義します。
- インターセプトプロキシクラス
MethodInterceptor
を拡張するintercept
メソッドでintercept
あるメソッドのカスタマイズとオーバーライド、およびinvoke
類似のJDK動的プロキシメソッド。 - することで
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動的プロキシの比較
- JDK動的プロキシは、インターフェイスを実装するクラスのみをプロキシできますが、CGLIBは、インターフェイスを実装しないクラスをプロキシできます。さらに、CGLIB動的プロキシは、プロキシクラスのサブクラスを生成することによってプロキシクラスのメソッド呼び出しをインターセプトするため、finalとして宣言されたクラスおよびメソッドをプロキシすることはできません。
- 2つの効率の点では、ほとんどの場合、JDK動的プロキシの方が優れています。JDKバージョンのアップグレードにより、この利点はより明白になります。
4.静的プロキシと動的プロキシの比較
- 柔軟性:動的プロキシはより柔軟性があり、インターフェイスを実装する必要がなく、実装クラスを直接プロキシでき、ターゲットクラスごとにプロキシクラスを作成する必要がありません。さらに、静的プロキシでは、新しいメソッドがインターフェイスに追加されると、ターゲットオブジェクトとプロキシオブジェクトを変更する必要があり、これは非常に面倒です。
- JVMレベル:静的プロキシは、コンパイル時にインターフェイス、実装クラス、およびプロキシクラスを実際のクラスファイルに変換します。動的エージェントは、実行時にクラスバイトコードを動的に生成し、それをJVMにロードします。
5.まとめ
この記事では、主にプロキシモードの2つの実装(静的プロキシと動的プロキシ)を紹介します。静的エージェントと動的エージェントの実際の戦闘、静的エージェントと動的エージェントの違い、およびJDK動的エージェントとCglib動的エージェントの違いについて説明します。
この記事に関連するすべてのソースコードは、https://github.com/Snailclimb/guide-rpc-framework-learning/tree/master/src/main/java/github/javaguide/proxyにあります。