動作環境
- Java 8
- Spring Framework 4.3 (TERASOLUNA Server Framework for Java 5.3.0)
- JBoss EAP 7.1
例外が発生した経緯
実際に例外が発生したコードは公開できないので、TERASOLUNAのガイドラインで紹介されているTodoアプリケーションを例に説明させていただきます。
- domainパッケージ下に
MethodInterceptor
を実装したクラスを作成します。
package todo.domain.interceptor;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SampleInterceptor implements MethodInterceptor {
private static final Logger logger = LoggerFactory.getLogger(SampleInterceptor.class);
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 対象のメソッドを実行する前にログ出力するだけ
logger.info("Interceptor called.");
return invocation.proceed();
}
}
- todo-domain.xmlにあるAOP設定に↑で作った
Interceptor
をTodoServiceImpl
に適用させます。
<aop:config>
<aop:pointcut id="sample" expression="execution(* todo.domain.service.todo.TodoServiceImpl.*(..))" />
<aop:advisor advice-ref="resultMessagesLoggingInterceptor" pointcut="@within(org.springframework.stereotype.Service)" /> ←元からあるやつ
<aop:advisor pointcut-ref="sample" advice-ref="sampleAdvice" order="-1" />
</aop:config>
<bean id="sampleAdvice" class="todo.domain.interceptor.SampleInterceptor" />
-
TodoCotroller
でInject
しているService
クラスをTodoServiceImpl
に変えます。
@Controller
@RequestMapping("todo")
public class TodoController {
@Inject
TodoServiceImpl todoService;
で、このTodoアプリケーションを実行すると起動時に以下の例外が発生します。
Caused by: org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'todoServiceImpl' is expected to be of type 'todo.domain.service.todo.TodoServiceImpl' but was actually of type 'com.sun.proxy.$Proxy32'
at org.springframework.beans.factory.support.DefaultListableBeanFactory.checkBeanNotOfRequiredType(DefaultListableBeanFactory.java:1491)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.checkBeanNotOfRequiredType(DefaultListableBeanFactory.java:1498)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1470)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1102):rolling_eyes:
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1064)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585)
... 42 more
TodoServiceImpl
型を想定しているのですが実際はcom.sun.proxy.$Proxy32
型で定義されてしまっているようですね。
理由はProxy化する仕組みの違い
Springでは、AOPを適用するにあたり、2つの仕組みが用意されています。
- JDK DynamicProxy
- CGLib
この違いはSpring MVC(+Spring Boot)上でのリクエスト共通処理の実装方法を理解するによると、インターフェイスを実装しているかどうかのようです。
今回の例では、インターフェイスを実装しているのでJDK DynamicProxy
を使ってProxy化しましたが、TodoContorller
で実装クラスであるTodoServiceImpl
をInject
しようとしたため、例外が発生したようです。
この場合、実装クラスをInject
するためには、CGLibの仕組みを使ってProxy化する必要があり、その設定はAOPのconfig
要素のproxy-target-class
属性をtrue
にします。
(デフォルトはfalse
になっています。)
<aop:config proxy-target-class="true">
上記の設定はProxy化の仕組みを強制的にCGLibにするもので、元々インターフェイスがないクラスの場合は、自動的にCGLibの仕組みでProxy化されます。
ちなみに、Spring AOPはDIコンテナに登録されていないクラスには適用されないので注意しましょう。
この記事を投稿するに至った経緯
業務で開発しているシステムで実際に発生したのでメモとして。