見出し画像

【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パッケージを追加してその中に格納。

スクリーンショット 2019-10-15 20.34.44

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 は別になしでよかった。

=====================================


以上。

この記事が気に入ったらサポートをしてみませんか?