メソッドの前後にAOPのように一律で処理を入れたい
メソッドの前後にAOP(アスペクト指向プログラミング)のように一律で処理を入れたいが、プロジェクトの極少数の箇所のみ必要で、AOPを持ち出すのは大げさなとき、Javaのメソッド参照やラムダ式を使って解決したい。
環境: Java 16
ユースケース例
一律で処理を入れたい例をあげる。
処理の追加
APIを提供してくれる外部サービスがあり、APIの呼び出し方法をSDKで提供してくれているとする。SDKにはリクエスト・レスポンスのロギング処理がないが、自プロジェクトでは外部サービスとの通信はロギングしたい。
SDKを拡張するのは保守性が悪化するので対応したくないので、SDKのAPI呼び出し実行メソッドの前後にロギングを仕込みたい。
APIが一個だけであればいいが、APIが複数あり、複数箇所でロギングのコードを書くのは微妙なので、AOPのように一律で処理ができるようにしたい。ただ外部APIを呼び出す箇所は一クラスに閉じていて、AOPを持ち出すのは大げさになってしまう。
検査例外のラップ
また、このSDKのAPI呼び出し実行メソッドが検査例外を投げるとする。
自プロジェクトでは、検査例外ではなく独自の非検査例外にした方が、既存コードやフレームワーク等の都合がいいとする。
API呼び出しのメソッドごとにtry catch
を書いたりするのは嫌なので、try catch
する箇所を一つにまとめたい。
メソッド参照・ラムダ式を引数に渡して解決する
まずは実装例を書く。
何かしらの処理を行ってくれるメソッドを定義する。(先ほどのユースケースでいうと、これが外部SDKの提供するクラスとなる)
public class SampleMethodsContainer {
public String joinA(String x) {
return "A" + x;
}
public String joinB(String x) {
return "B" + x;
}
public String joinManyParams(String x, String y, String z) throws Exception {
if (new Random().nextInt() % 2 == 0) {
throw new Exception();
}
return x + y + z;
}
}
この3つのメソッドを呼び出すときに、前後処理を入れたい。ユースケース例の一つ目に対応する前後処理は単純に標準出力にする。ユースケース例の二つ目に対応する前後処理はtry catch
と非検査例外でのラップになる。
これをAOPではなくメソッド参照あるいはラムダ式で解決したい。一文で説明すると以下になる。
呼び出したいメソッドをメソッド参照、もしくは() -> object.method()
の形式でラムダ式にして、前後処理を施した共通メソッドに渡す。
実装は以下になる。
public class Main {
public static void main(String[] args) {
new Test().test();
}
public static class Test {
public void test() {
var c = new SampleMethodsContainer();
String r1 = exec(c::joinA, "aaa");
String r2 = exec(c::joinB, "bbb");
// 直接newしてもOK
String r3 = exec(new SampleMethodsContainer()::joinA, null);
// Function<T, R>以外に自己定義した@FunctionalInterfaceも使えるし、
// さらにメソッド参照ではなくてラムダ式でも問題ない。
// 自己定義した@FunctionalInterfaceが使えるので、検査例外と相性の悪いラムダ式の問題も解決できる。
String r4 = exec(() -> c.joinManyParams("x", "y", "z"));
}
private <T, R> R exec(Function<T, R> f, T t) {
System.out.println("[start] param=" + t);
R r = f.apply(t);
System.out.println("[end] param=" + t + ", return=" + r);
return r;
}
private <R> R exec(MyFunction<R> f) {
try {
System.out.println("[start]");
R r = f.exec();
System.out.println("[end] return=" + r);
return r;
} catch (Exception e) {
System.out.println("error");
throw new RuntimeException(e);
}
}
@FunctionalInterface
public interface MyFunction<R> {
R exec() throws Exception;
}
}
}
実行すると以下の通り出力される。
# 例外発生しない場合
[start] param=aaa
[end] param=aaa, return=Aaaa
[start] param=bbb
[end] param=bbb, return=Bbbb
[start] param=null
[end] param=null, return=Anull
[start]
[end] return=xyz
# 例外発生する場合
[start] param=aaa
[end] param=aaa, return=Aaaa
[start] param=bbb
[end] param=bbb, return=Bbbb
[start] param=null
[end] param=null, return=Anull
[start]
error
# 標準エラー出力
Exception in thread "main" java.lang.RuntimeException: java.lang.Exception