LoginSignup
7
3

More than 5 years have passed since last update.

SpringでAOPを適用したらBeanNotOfRequiredTypeExceptionが発生したお話

Posted at

動作環境

  • Java 8
  • Spring Framework 4.3 (TERASOLUNA Server Framework for Java 5.3.0)
  • JBoss EAP 7.1

例外が発生した経緯

実際に例外が発生したコードは公開できないので、TERASOLUNAのガイドラインで紹介されているTodoアプリケーションを例に説明させていただきます:sweat:

  • domainパッケージ下にMethodInterceptorを実装したクラスを作成します。
SampleInterceptor.java
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設定に↑で作ったInterceptorTodoServiceImplに適用させます。
todo-domain.xml
  <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" />
  • TodoCotrollerInjectしているServiceクラスをTodoServiceImplに変えます。
TodoController.java
@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型で定義されてしまっているようですね:scream:

理由はProxy化する仕組みの違い

Springでは、AOPを適用するにあたり、2つの仕組みが用意されています。

  1. JDK DynamicProxy
  2. CGLib

この違いはSpring MVC(+Spring Boot)上でのリクエスト共通処理の実装方法を理解するによると、インターフェイスを実装しているかどうかのようです。
今回の例では、インターフェイスを実装しているのでJDK DynamicProxyを使ってProxy化しましたが、TodoContorllerで実装クラスであるTodoServiceImplInjectしようとしたため、例外が発生したようです。
この場合、実装クラスをInjectするためには、CGLibの仕組みを使ってProxy化する必要があり、その設定はAOPのconfig要素のproxy-target-class属性をtrueにします。
(デフォルトはfalseになっています。)

todo-domain.xml
 <aop:config proxy-target-class="true">

上記の設定はProxy化の仕組みを強制的にCGLibにするもので、元々インターフェイスがないクラスの場合は、自動的にCGLibの仕組みでProxy化されます。

ちなみに、Spring AOPはDIコンテナに登録されていないクラスには適用されないので注意しましょう。

この記事を投稿するに至った経緯

業務で開発しているシステムで実際に発生したのでメモとして:stuck_out_tongue:

その他参考記事・ページ

7
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3