※注意
以下の話はSpringAOP3.1.1で確認しています。
SpringAOPで処理が挟み込まれたbeanは実体がProxyクラスなので、beanを使ってメタプログラミングをしたい場合、ちょっと困ることがありました。
困ること例
例えばCommandパターンを利用した以下のようなCommandのComponentがあったとします。
public interface Data { void readData(DataSource source); } public interface Command<T extends Data> { void execute(T data); } @Component public class SampleCommand implements Command<SampleData> { @Override public void execute(SampleData data) { } } public class SampleData implements Data { @Override public void readData(DataSource source) { } }
この時に以下のようなコマンドを処理する共通処理なんかでCommandクラスの型パラメータを利用したいとします。
(nullチェックとか配列の要素チェック的なエラーハンドリングは除外しております)
public void executeCommand (Command command, DataSource source) throws Exception{ ParameterizedType ptype = (ParameterizedType)command.getClass().getGenericInterfaces()[0]; Data data = (Data)((Class)ptype.getActualTypeArguments()[0]).newInstance(); data.readData(source); command.execute(data); }
で、コマンド毎の共通処理でAOPを使いたいってことで、CommandクラスにSpringAOPを適用して生成されたbeanをexecuteCommandメソッドに渡すと以下の例外が発生します。
(SpringAOPの使い方は略)
java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
executeCommandメソッドの最初の行のParameterizedTypeへのキャストが失敗しております。
変数の中身を見てみると、確かに型パラメータの情報が消えております。
(java.lang.reflect.Proxyの仕様なのか、SpringAOPのProxyの仕様なのかは良く分かってませんが)
解決策
AOPが適用されているクラス用処理を以下のように分けます。
public void executeCommand (Command command) throws Exception{ Class dataClass = null; if(Proxy.isProxyClass(command.getClass())) { // プロキシクラスの場合は元のクラスを取得する final InvocationHandler handler = Proxy.getInvocationHandler(command); for (Field field : handler.getClass().getDeclaredFields()) { if(field.getType().equals(AdvisedSupport.class)) { field.setAccessible(true); AdvisedSupport as = (AdvisedSupport)field.get(handler); dataClass = as.getTargetClass(); } } if(dataClass == null) { throw new IllegalArgumentException(); } } else { dataClass = command.getClass(); } ParameterizedType ptype = (ParameterizedType)dataClass.getGenericInterfaces()[0]; Data data = (Data)((Class)ptype.getActualTypeArguments()[0]).newInstance(); data.readData(source); command.execute(data); }
SpringAOPが使っているInvocationHandlerの実装は
org.springframework.aop.framework.JdkDynamicAopProxy
で、そのメンバである
org.springframework.aop.framework.AdvisedSupport
クラスをリフレクションを使って引っこ抜き、getTargetClassメソッドで実際のプロキシ元のクラスを取得しています。