コードを書く過程で多くの Proxy クラスを見て、代理パターンであることを知り、コードがどのように進むかも理解しています。
コードを書く過程で多くの Proxy クラスを見て、代理パターンであることを知り、コードがどのように進むかも理解しています。しかし、代理パターンの詳細や原理についてはあまり理解していません。
オンラインでの学習を通じて知ったことは
『代理パターンは Java において一般的なデザインパターンで、代理クラスの作成時期によって静的代理と動的代理に分けられます。』
静的代理#
静的代理とは、コンパイル時にインターフェース、代理されるクラス、代理クラスなどが確定していることを指します。プログラムの実行前に、代理クラスの .class ファイルがすでに生成されています。
静的代理パターンの最も重要な点は、共通のインターフェース、具体的なクラス、代理クラスがあり、代理クラスが具体的なクラスのインスタンスを保持し、そのインスタンスのメソッドを代わりに実行することです。
静的代理の例#
私の考えに基づいて小さな例を作成しました。スターとして活動に出席する必要があり、Jisoo は香奈儿のイベントに出席する予定ですが、体調の理由で Jennie が代わりに出席します。Jisoo が出席するのと同じ効果があると仮定します。
まず、Person インターフェースを作成します。
public interface Person{
//イベントに出席
public void attendance();
}
次に、Idol の実装クラス:
public class Idol implements Person{
String name;
int age;
Idol(String n,int a){
this.name=n;
this.age=a;
}
//インターフェースを実装
@Override
public void attendance() {
System.out.print(this.name+"がChannlイベントに出席しました");
}
}
次に、IdolProxy 代理クラスが Idol のオブジェクトを保持します。Idol オブジェクトと同様に Person インターフェースを実装しています。
public class IdolProxy implements Person{
Idol idol;
public IdolProxy(Person stu){
if(stu.getClass()==Idol.class) {
this.idol=(Idol)stu;
}
}
@Override
public void attendance() {
idol.attendance();
}
}
次はテストクラスです。
public static void main(String[] args) {
Person Jisoo=new Idol("Jisoo",18);
Person Jennie=new IdolProxy(Jisoo);
Jennie.attendance();
}
出力結果は:
JisooがChannlイベントに出席しました
Jisoo を通じてイベントに参加するのではなく、Jennie の代理を通じて参加した、これが代理パターンです;
共通のインターフェース(Person)、実装クラス(Idol)、代理クラス(IdolProxy)がすべてインターフェースを実装しており、
代理クラスは実装クラスのインスタンスオブジェクトを含んでいます。そして、代理オブジェクトが具体的なメソッドを実行します。
代理パターンは、実際のオブジェクトにアクセスする際に一定の間接性を導入するものであり、この間接性によってさまざまな用途を追加できます。
ここでの間接性は、実際のオブジェクトのメソッドを直接呼び出さないことを指し、代理の過程で他の用途を追加できます。例えば AOP の機能のいくつかです。
動的代理#
静的代理の場合、代理クラスはすでに実装されていますが、動的代理はプログラムの実行時に動的に作成される代理クラスです。事前に代理クラスの実装は書かれていません。
主なポイントは Proxy.newProxyInstance (loader, interfaces, h); というメソッドで、このメソッドは Proxy の静的メソッドであり、Proxy は Java.lang.reflect のクラスです。
このメソッドは、上記の静的代理の代理クラスの抽象化に相当し、どのようにオブジェクトを代理するかをコードで指示できます。以下に上記の例を使って簡単に実装します。
動的代理の例#
私たちが実現したいのは、Jennie が体調不良の Jisoo の代理として Channal のイベントに出席することです。私はオンラインのほとんどのチュートリアルが上から下に説明しているのを見ましたが、インターフェースを作成して Invocation インターフェースを実装するクラスまでの流れです。
私自身の理解では、下から上に理解する方がわかりやすいです。それでは、テストクラスから直接始めます:
//代理されるオブジェクトのインスタンスを作成
Person Jisoo = new Idol("Jisoo",18);
//代理オブジェクトに関連付けられた InvocationHandler を作成します。これは静的代理の代理クラス idolProxy に相当しますが、すべてのクラスのオブジェクトを持つ Object です。
InvocationHandler idolHandler = new IdolInvocationHandler<Person>(Jisoo);
//Jisoo を代理する代理オブジェクト Jennie を作成します。代理オブジェクトの各実行メソッドは Invocation の invoke メソッドを置き換えます。
Person Jennie = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, idolHandler);
//代理実行メソッド
Jennie.attendance();
最初のステップ Person Jisoo = new Idol (“Jisoo”,18); は、Person インターフェースを実装するインスタンスオブジェクトを作成することを意味します。
重要なのは、次のステップ InvocationHandler idolHandler = new IdolInvocationHandler (Jisoo); です。
ここでの InvocationHandler は java.lang.reflect.InvocationHandler で、Java が動的代理クラスを実装するためのインターフェースです。このインターフェースを実装する必要があります:
public class IdolInvocationHandler<T> implements InvocationHandler{
//invocationHandler が保持する被代理オブジェクト
T target;
public IdolInvocationHandler(T target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("11111");
method.invoke(target, args);
System.out.println("2222");
return null;
}
}
このインターフェースは invoke メソッドを定義しており、invoke は呼び出すという意味です。つまり、すべての被代理クラスのメソッドはこのメソッドを通じて呼び出されます。
被代理クラスのメソッドをどのように取得するかというと、Java のリフレクションメカニズムを通じてです。invoke メソッドには三つのパラメータがあります。最初は被代理オブジェクト、二つ目はメソッドオブジェクト、三つ目はパラメータです。
次は:
Person Jennie = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, idolHandler);
これにより、代理オブジェクト Jennie が生成されます。Proxy.newProxyInstance (loader, interfaces, h) には三つのパラメータがあります。最初はインターフェースのローダー、二つ目は代理インターフェースのリスト、
三つ目は上記の Invocation インターフェースを実装したクラス IdolInvocationHandler のオブジェクト idolHandler です。このようにして Jennie の attendance メソッドを実行すると、次のように出力されます:
11111
JisooがChannlイベントに出席しました
2222
これで動的代理の機能が実現されました。注意:動的代理はインターフェースに対してのみ代理を行うことができ、クラスに対しては代理を行えません。その理由は、Java が単一継承のみをサポートしており、インターフェースを通じてのみ多重継承の関係を実現できるからです。
原理:
実際には、インターフェースをコピーし、これらのインターフェースとクラスローダーを通じて代理クラス cl を取得します。そして、リフレクション技術を通じて代理クラスのコンストラクタをコピーします(この部分のコードは Class クラスの getConstructor0 メソッドにあります)。最後に、このコンストラクタを使って新しいオブジェクトを作成し、InvocationHandler をバインドします。
PS:
Person idolProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, (proxy,method,agrs)->{
if(method.getName()=="attendance") {
System.out.println("1111");
method.invoke(Jisoo, args);
System.out.println("2222");
return null;
}
return null;
});
動的代理オブジェクトを作成する際に、Java 8 の新機能であるラムダ式を使ってこのように書くこともでき、Invocation の実装クラスを書く必要がなくなります。