Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

動作環境

  • 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:

その他参考記事・ページ

neriudon
大学時代からJavaをやっています。 最近はSpring Framework、主にSpring Integrationにお熱。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした