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

#### 動作環境
- Java 8
- Spring Framework 4.3 (TERASOLUNA Server Framework for Java 5.3.0)
- JBoss EAP 7.1

#### 例外が発生した経緯

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

- domainパッケージ下に`MethodInterceptor`を実装したクラスを作成します。

```java: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設定に↑で作った`Interceptor`を`TodoServiceImpl`に適用させます。

```xml: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" />
```

- `TodoCotroller`で`Inject`している`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)上でのリクエスト共通処理の実装方法を理解する](https://qiita.com/kazuki43zoo/items/757b557c05f548c6c5db#spring-aop-with-aspectj)によると、インターフェイスを実装しているかどうかのようです。
今回の例では、インターフェイスを実装しているので`JDK DynamicProxy`を使ってProxy化しましたが、`TodoContorller`で実装クラスである`TodoServiceImpl`を`Inject`しようとしたため、例外が発生したようです。
この場合、実装クラスを`Inject`するためには、CGLibの仕組みを使ってProxy化する必要があり、その設定はAOPの`config`要素の`proxy-target-class`属性を`true`にします。
（デフォルトは`false`になっています。）

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

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

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

#### この記事を投稿するに至った経緯
業務で開発しているシステムで実際に発生したのでメモとして:stuck_out_tongue:。

#### その他参考記事・ページ
- [SpringAOPとライブラリによるproxy化対象クラスの違い](https://qiita.com/eri-twin/items/9641f0cdc11122f57e59)

- [SpringのAOPに関するドキュメント](https://docs.spring.io/spring/docs/5.0.2.RELEASE/spring-framework-reference/core.html#aop-pfb-proxy-types)