【Java】SpringBootじゃないSpring AOPの導入から利用まで
Maven管理のSpring BootじゃないSpring MVCプロジェクトで、 Spring AOPを使う。(org.springframework-version:5.0.2)
エラーとか出た経緯を記録。最後に結論記載。
pom.xmlの依存関係タブから、spring-aspects、spring-aopを追加。
コードはこちら。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
Bean定義ファイルにaopの利用設定をする。
コンポーネントスキャンは、@Componentなどのアノテーションを使ったSpringプロジェクトであれば設定済みかもしれないが、念のため確認。また。AOP処理を行うファイルをこの範囲内に作成。対象パッケージはプロジェクト毎に違うため要調整。
<!-- AspectJスタイルのSpring AOPを有効化 -->
<aop:aspectj-autoproxy/>
<!-- アノテーションを使用するための宣言 -->
<context:annotation-config />
<!-- コンポーネントスキャンの範囲指定 -->
<context:component-scan base-package="com.example.mvc" />
これだけだとaop:の名前空間がなく、エラーになる。
Bean定義ファイルのBeansタグに太字追加。
-------------
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
---------------
続いてAspectファイルの作成。
今回はプロジェクトのjavaリソースの中にaopパッケージを追加してその中に格納。
LoggingAspect.java
package com.example.mvc.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* *.*(..))")
public void before(JoinPoint joinPoint){
System.out.println("before");
}
@After("execution(* *.*(..))")
public void after(JoinPoint joinPoint){
System.out.println("after");
}
}
ここで問題発生。@Aspectを付与しても認識されない。
なぜかSpringBootの@EnableAspectJAutoProxyなら認識される。
Eclipseのマーケットプレイスで間違ってSpringBoot関連のものをインストール指定ないか確認しに行ったがなさそう。またLoggingAspect.javaファイルに戻ると、今度はエラーが消えている。謎。。
とりあえずこれで設定は完了。
@Aspect ・・・Aspectクラスであることを知らせる
@Component・・・BeanFactoryにこのアスペクトを生成させるために必要
LoggingAspect.javaのメソッドには、「全てのメソッド実行前後にbefore、Afterメソッドを実行させる」ように設定した。
いざプロジェクトを実行するとまたエラーが・・・。
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping': Initialization of bean failed; nested exception is org.springframework.beans.ConversionNotSupportedException: Failed to convert property value of type 'com.sun.proxy.$Proxy28 implementing org.springframework.web.accept.ContentNegotiationStrategy,org.springframework.web.accept.MediaTypeFileExtensionResolver,org.springframework.aop.SpringProxy,org.springframework.aop.framework.Advised,org.springframework.core.DecoratingProxy' to required type 'org.springframework.web.accept.ContentNegotiationManager' for property 'contentNegotiationManager'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'com.sun.proxy.$Proxy28 implementing org.springframework.web.accept.ContentNegotiationStrategy,org.springframework.web.accept.MediaTypeFileExtensionResolver,org.springframework.aop.SpringProxy,org.springframework.aop.framework.Advised,org.springframework.core.DecoratingProxy' to required type 'org.springframework.web.accept.ContentNegotiationManager' for property 'contentNegotiationManager': no matching editors or conversion strategy foun
BeanCreationExceptionとのこと。調べたところこちらの記事に書いてることが原因っぽい。
Bean定義ファイルで定義した下記の記述部分。
<!-- AspectJスタイルのSpring AOPを有効化 -->
<aop:aspectj-autoproxy/>
SpringAOPには
・JDK DynamicProxy
・CGLib
の2種類あってJDK DynamicProxyを使う場合はインターフェースを実装したクラスがProxy化の対象になるので実装クラスを直接DIすることはできないとのこと。うーむ。。
インターフェースを介さないメソッドをProxy化する場合のみCGLIB proxyingを使いましょう!ということらしい。
ということでこちらに書き換え。
Bean定義ファイル
<!-- CGLIBの場合 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
pom.xml
<!-- <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.2.RELEASE</version>
</dependency> -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2</version>
</dependency>
たたCGLibの弊害もあって、Proxyingの対象クラスが多いとエラーになってしまう場合もあるとのこと。
こちらの記事も参考になりました。
そして、またエラー発生。
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'messageSource' defined in ServletContext resource [/WEB-INF/spring/appServlet/servlet-context.xml]: BeanPostProcessor before instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.transaction.config.internalTransactionAdvisor': Cannot resolve reference to bean 'org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#0' while setting bean property 'transactionAttributeSource'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#0': BeanPostProcessor before instantiation of bean failed; nested exception is java.lang.NoClassDefFoundError: org/aspectj/weaver/reflect/ReflectionWorld$ReflectionWorldException
全て丸っとメソッド前後にAspect!という雑な感じがいけないのかもしれない。。
JDK dynamic proxy にまるっと設定を戻して、Aspectファイルでポイントカットで指定するメソッドを、特定クラスに限定して、
@Before("execution(* SampleDaoImpl.*(..))")
public void before(JoinPoint joinPoint){
System.out.println(" ===========before===========");
}
@After("execution(* SampleDaoImpl.*(..))")
public void after(JoinPoint joinPoint){
System.out.println("===========after===========");
}
インターフェース継承クラスに@Componentつけて、Aspectクラスから@Component削除して、いざ実行!
@Aspect
public class LoggingAspect {
....
@Component
public class SampleDaoImpl extends BaseDao implements SampleDao {
...
エラーは消えたが、肝心なメソッド実行前後のメッセージは表示されない。。
一旦アノテーションについて振り返り。こちらにまとめ。
そして無事解決。原因は、Aspect用クラスのポイントカットの記述方法が間違ってました。
=====================================
まずpom.xmlの依存関係からに追加するのは、とりあえず下記の2つでよかった。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
Bean定義ファイルの<bean>タグの設定も下記で問題ない。
Spring AOPに必要な設定を3項目定義。CGLIBじゃなくてaspectj使用。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
//その他の設定内容いろいろ
....
//Spring AOPに必要な設定
<!-- AspectJスタイルのSpring AOPを有効化 -->
<aop:aspectj-autoproxy/>
<!-- アノテーションを使用するための宣言 -->
<context:annotation-config />
<!-- コンポーネントスキャンの範囲指定 -->
<context:component-scan base-package="com.example.mvc" />
//その他の設定内容いろいろ
....
</beans>
LoggingAspect.java(間違ってたとこ!)
package com.example.mvc.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.mvc.dao.SampleDao.*(..))")
public void before(JoinPoint joinPoint){
System.out.println(" ===========▼before===========");
System.out.println("メソッド呼び出し:" + joinPoint.toString());
}
@After("execution(* com.example.mvc.dao.SampleDao.*(..))")
public void after(JoinPoint joinPoint){
System.out.println("===========▲after===========");
}
}
このbeforeメソッドとafterメソッドのポイントカットを、エラーになった時はこのように記述していた。
@Before("execution(* SampleDaoImpl.*(..))")
public void before(JoinPoint joinPoint){
...
@After("execution(* SampleDaoImpl.*(..))")
public void after(JoinPoint joinPoint){
...
この記述だと、LoggingAspect と ポイントカット対象ファイルは別パッケージに入っているのに、Aspectファイルは同じパッケージ内のSampleDaoImplを探してしまい、こんなファイルありませんよ〜となってしまったのが原因だった。
src/main/java
L com.example.mvc.aop
L LoggingAspect
L com.example.mvc.controller
L SampleDao.java
L SampleDaoImpl.java
そしてAOPにおいて、Implクラスの@Component は別になしでよかった。
=====================================
以上。
この記事が気に入ったらサポートをしてみませんか?