実際にプロキシパターンを利用することで、そのようなフレームワークやテンプレートを構築することができます。ここでは、動的プロキシ パターンを使用してそれを行う方法について説明します。アイデアは、
- API のレコーダおよびリプレーヤ プロキシをオンデマンドで取得するためのプロキシ マネージャを作成します。
- 収集した情報を格納するラッパー クラスを記述し、同様のデータ構造から効率的に検索するためにそのラッパー クラスのメソッドを実装
hashCode
します。equals
Map
- 最後に、レコーダ プロキシを使用して記録し、再生目的でリプレーヤ プロキシを使用します。
レコーダーの仕組み:
- 実際の API を呼び出す
- 呼び出し情報を収集します
- 期待される永続化コンテキストでデータを永続化します
リプレーヤーの仕組み:
- メソッド情報(メソッド名、パラメータ)を収集する
- 収集された情報が以前に記録された情報と一致する場合は、以前に収集された戻り値を返します。
- 返された値が一致しない場合は、収集した情報を永続化します (必要に応じて)。
では、実装を見てみましょう。API が次MyApi
のような場合:
public interface MyApi {
public String getMySpouse(String myName);
public int getMyAge(String myName);
...
}
次に、 の呼び出しを記録して再生しますpublic String getMySpouse(String myName)
。これを行うには、以下のような呼び出し情報を格納するクラスを使用できます。
public class RecordedInformation {
private String methodName;
private Object[] args;
private Object returnValue;
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public Object[] getArgs() {
return args;
}
public void setArgs(Object[] args) {
this.args = args;
}
public Object getReturnValue() {
return returnType;
}
public void setReturnValue(Object returnValue) {
this.returnValue = returnValue;
}
@Override
public int hashCode() {
return super.hashCode(); //change your implementation as you like!
}
@Override
public boolean equals(Object obj) {
return super.equals(obj); //change your implementation as you like!
}
}
ここからが本編の TheRecordReplyManager
です。これRecordReplyManager
により、記録または再生の必要性に応じて、API のプロキシ オブジェクトが提供されます。
public class RecordReplyManager implements java.lang.reflect.InvocationHandler {
private Object objOfApi;
private boolean isForRecording;
public static Object newInstance(Object obj, boolean isForRecording) {
return java.lang.reflect.Proxy.newProxyInstance(
obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),
new RecordReplyManager(obj, isForRecording));
}
private RecordReplyManager(Object obj, boolean isForRecording) {
this.objOfApi = obj;
this.isForRecording = isForRecording;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result;
if (isForRecording) {
try {
System.out.println("recording...");
System.out.println("method name: " + method.getName());
System.out.print("method arguments:");
for (Object arg : args) {
System.out.print(" " + arg);
}
System.out.println();
result = method.invoke(objOfApi, args);
System.out.println("result: " + result);
RecordedInformation recordedInformation = new RecordedInformation();
recordedInformation.setMethodName(method.getName());
recordedInformation.setArgs(args);
recordedInformation.setReturnValue(result);
//persist your information
} catch (InvocationTargetException e) {
throw e.getTargetException();
} catch (Exception e) {
throw new RuntimeException("unexpected invocation exception: " +
e.getMessage());
} finally {
// do nothing
}
return result;
} else {
try {
System.out.println("replying...");
System.out.println("method name: " + method.getName());
System.out.print("method arguments:");
for (Object arg : args) {
System.out.print(" " + arg);
}
RecordedInformation recordedInformation = new RecordedInformation();
recordedInformation.setMethodName(method.getName());
recordedInformation.setArgs(args);
//if your invocation information (this RecordedInformation) is found in the previously collected map, then return the returnValue from that RecordedInformation.
//if corresponding RecordedInformation does not exists then invoke the real method (like in recording step) and wrap the collected information into RecordedInformation and persist it as you like!
} catch (InvocationTargetException e) {
throw e.getTargetException();
} catch (Exception e) {
throw new RuntimeException("unexpected invocation exception: " +
e.getMessage());
} finally {
// do nothing
}
return result;
}
}
}
メソッド呼び出しを記録したい場合は、次のような API プロキシを取得するだけで済みます。
MyApi realApi = new RealApi(); // using new or whatever way get your service implementation (API implementation)
MyApi myApiWithRecorder = (MyApi) RecordReplyManager.newInstance(realApi, true); // true for recording
myApiWithRecorder.getMySpouse("richard"); // to record getMySpouse
myApiWithRecorder.getMyAge("parker"); // to record getMyAge
...
必要なものをすべて再生するには:
MyApi realApi = new RealApi(); // using new or whatever way get your service implementation (API implementation)
MyApi myApiWithReplayer = (MyApi) RecordReplyManager.newInstance(realApi, false); // false for replaying
myApiWithReplayer.getMySpouse("richard"); // to replay getMySpouse
myApiWithRecorder.getMyAge("parker"); // to replay getMyAge
...
そして、あなたは完了です!
編集:
レコーダーとリプレーヤーの基本的な手順は、上記の方法で実行できます。これらの手順をどのように使用または実行するかは、あなた次第です。レコーダーとリプレーヤーのコード ブロックで好きなことを何でも実行でき、実装を選択するだけです。