1. プロキシモデルの役割
Java のプロキシ モードは一般的な設計パターンであり、元のコードを変更することなくクラスの機能を強化できます。プロキシ モードには静的プロキシと動的プロキシがあり、AOP の基礎となるメカニズムは動的プロキシです。
プロキシ パターンは、比較的よく理解されている設計パターンです。簡単に言うと、 プロキシ オブジェクトを使用して実際のオブジェクトへのアクセスを置き換え、追加の機能操作を提供し、元のターゲット オブジェクトを変更することなくターゲット オブジェクトの機能を拡張できるようにします。
以下に、2 つのプロキシ モードについて総合的に説明します。
2. 静的プロキシ
静的プロキシは、コンパイル時にプロキシ クラスを決定したコードであり、プロキシ クラスとプロキシ クラスの両方が同じインターフェイスを実装するか、同じ親クラスを継承します。静的プロキシの利点は、記述が簡単で、理解しやすく、保守しやすいことです。ただし、複数のクラスをプロキシする必要がある場合、プロキシ クラスの数が増加し、プロキシ クラスとプロキシ クラスのメソッドが増加すると、プロキシ クラスのコードを手動でメンテナンスする必要があります。
簡単な静的プロキシの例を次に示します。
public interface Subject {
void request();
}
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("RealSubject:request()");
}
}
public class ProxySubject implements Subject {
private RealSubject realSubject;
public ProxySubject(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void request() {
System.out.println("ProxySubject:before request()");
realSubject.request();
System.out.println("ProxySubject:after request()");
}
}
public class Client {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
ProxySubject proxySubject = new ProxySubject(realSubject);
proxySubject.request();
}
}
上記の例では、RealSubject
プロキシ クラスはProxySubject
プロキシ クラスであり、プロキシ クラスはSubject
インターフェイスを実装し、プロキシ クラスのインスタンスを保持しますRealSubject
。プロキシクラスのメソッドではrequest()
、プロキシクラスのメソッドを呼び出しrequest()
、前後に独自の処理ロジックを追加します。
3. 動的エージェント
動的プロキシは、実行時にプロキシ クラスのコードを動的に生成するものであり、事前にプロキシ クラスのコードを知っている必要はありません。Java には動的プロキシの主な形式が 2 つあり、1 つはインターフェイスベースの動的プロキシ、もう 1 つはクラスベースの動的プロキシです。
動的プロキシを実現するには、どのような問題を解決する必要があるでしょうか?
質問 1: メモリにロードされたプロキシ クラスに基づいてプロキシ クラスとそのオブジェクトを動的に作成する方法
質問 2: プロキシ クラスのオブジェクトを通じてメソッドを呼び出す場合、プロキシ クラス内の同名のメソッドを動的に呼び出すにはどうすればよいですか?
インターフェイスベースの動的プロキシ
インターフェイスベースの動的プロキシとは、プロキシ クラスとプロキシ クラスが同じインターフェイスを実装し、プロキシ クラスのコードを手動で記述することなく、実行時にプロキシ クラスが動的に生成されることを意味します。
Java 動的プロキシ メカニズムでは、InvocationHandler
インターフェイスとProxy
クラスが中心となります。
Proxy
このクラスで最も頻繁に使用されるメソッドは次のとおりですnewProxyInstance()
。これは主にプロキシ オブジェクトを生成するために使用されます。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
......
}
このメソッドには合計 3 つのパラメータがあります。
- loader : プロキシ オブジェクトをロードするために使用されるクラス ローダー。
- インターフェイス : プロキシ クラスによって実装されるいくつかのインターフェイス。
- h
InvocationHandler
: インターフェース を実装するオブジェクト 。
動的プロキシを実装するには、InvocationHandler
処理ロジックをカスタマイズするためにも実装する必要があります。動的プロキシ オブジェクトがメソッドを呼び出すと、このメソッドの呼び出しは、呼び出すInvocationHandler
インターフェイス クラスを実装するメソッドに転送されます。invoke
public interface InvocationHandler {
/**
* 当你使用代理对象调用方法的时候实际会调用到这个方法
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
invoke()
このメソッドには次の 3 つのパラメータがあります。
- proxy : 動的に生成されたプロキシ クラス
- Method : プロキシ クラス オブジェクトによって呼び出されるメソッドに対応します
- args : 現在のメソッドメソッドのパラメータ
つまり、クラスを通じて作成したプロキシ オブジェクトがメソッドを呼び出すと、実際にはインターフェイスを実装するクラスのメソッドが呼び出されます。Proxy
newProxyInstance()
InvocationHandler
invoke()
invoke()
メソッドの実行前後に何を行うかなど、メソッド内の処理ロジックをカスタマイズできます。
次に、インターフェイスベースの動的プロキシの例を示します。
public interface Subject {
void request();
}
// 被代理类
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("RealSubject:request()");
}
}
// 解决问题二
public class DynamicProxySubject implements InvocationHandler {
private Object realSubject;
public DynamicProxySubject(Object realSubject) {
this.realSubject = realSubject;
}
//当我们通过代理类的对象,调用方法request时,就会自动的调用如下的方法: invoke()
//将被代理类要执行的方法的功能就言明在invoke()中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//method: 即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
//realSubject: 被代理类的对象
System.out.println("DynamicProxySubject:before " + method.getName() + "()");
Object result = method.invoke(realSubject, args);
System.out.println("DynamicProxySubject:after " + method.getName() + "()");
//上述方法的返回值就作为当前类中的invoke()的返回值。
return result;
}
}
public class Client {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
DynamicProxySubject dynamicProxySubject = new DynamicProxySubject(realSubject);
//调用此方法,返回一个代理类的对象。解决问题一
Subject proxySubject = (Subject)Proxy.newProxyInstance(realSubject.getClass().getClassLoader(),
realSubject.getClass().getInterfaces(), dynamicProxySubject);
proxySubject.request();
}
}
上記の例では、`RealSubject` はプロキシ クラス、`DynamicProxySubject` はプロキシ クラスで、プロキシ クラスは `InvocationHandler` インターフェイスを実装し、プロキシ クラスの対応するメソッドを `invoke()` メソッドで呼び出します。プロキシクラスの前後に独自の処理ロジックを追加します。クライアント コードでは、「Proxy.newProxyInstance()」メソッドを呼び出すことによって、プロキシ クラスのインスタンスが動的に生成されます。
クラスベースの動的プロキシ
クラスベースの動的プロキシとは、プロキシ クラスとプロキシ クラスの両方がクラスであり、プロキシ クラスのコードを手動で記述することなく、プロキシ クラスが実行時に動的に生成されることを意味します。Java のクラスベースの動的プロキシは主にバイトコード生成フレームワークを通じて実現され、より一般的に使用されるバイトコード生成フレームワークには CGLIB や ASM などがあります。次に、クラスベースの動的プロキシ (CGLIB を使用して実装) の例を示します。
public class RealSubject {
public void request() {
System.out.println("RealSubject:request()");
}
}
public class DynamicProxySubject implements MethodInterceptor {
private Object realSubject;
public DynamicProxySubject(Object realSubject) {
this.realSubject = realSubject;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("DynamicProxySubject:before " + method.getName() + "()");
Object result = proxy.invoke(realSubject, args);
System.out.println("DynamicProxySubject:after " + method.getName() + "()");
return result;
}
}
public class Client {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealSubject.class);
enhancer.setCallback(new DynamicProxySubject(new RealSubject()));
RealSubject proxySubject = (RealSubject) enhancer.create();
proxySubject.request();
}
}
上の例ではRealSubject
プロキシクラスとDynamicProxySubject
プロキシクラスですが、プロキシクラスはインターフェースを実装し、MethodInterceptor
プロキシクラスintercept()
のメソッド内で対応するプロキシクラスのメソッドを呼び出し、前後に独自の処理ロジックを追加しています。クライアント コードでは、Enhancer
CGLIB によって提供されるクラスを呼び出すことによって、プロキシ クラスのインスタンスが生成されます。
4. まとめ
静的プロキシと動的プロキシはどちらもプロキシ パターンの特定の実装であり、どちらもクラスの機能を強化できますが、方法は異なります。プロキシ クラスのコードは、静的プロキシのコンパイル中に決定され、少数のクラスをプロキシするのに適しています。プロキシ クラスのコードは、実行時に動的プロキシによって動的に生成され、クラスをプロキシするのに適しています。オブジェクトの数が多いか、どのクラスをプロキシするかが不明です。
Java の静的エージェントは、エージェント クラスのコードを手動で記述する必要があるため、柔軟性は十分ではありませんが、効率は高くなります。動的エージェントは、エージェント クラスのコードを手動で記述する必要がなく、より柔軟性がありますが、効率が低いです。
Java の静的プロキシと動的プロキシはどちらもインターフェイスベースのプロキシであるため、プロキシされたクラスはインターフェイスを実装する必要があります。
動的プロキシはリフレクション メカニズムを通じて Java に実装されており、パフォーマンスに一定のオーバーヘッドが生じます。また、ダイナミックプロキシは実行時に動的に生成されるプロキシクラスであるため、デバッグが容易ではありません。
実際のアプリケーションでは、プロキシする必要があるクラスの数、プロキシ クラスの構造、プロキシ クラスの複雑さ、プロキシのライフ サイクルなど、特定の状況に応じて静的プロキシまたは動的プロキシを選択できます。クラスやその他の要因が選択に影響を与える可能性があります。