Spring AOPの理解と実装

  1. AOPの概要

1.1 AOPとは AOP(Aspect-Oriented Programming)は、Aspect指向プログラミングを指し、プログラムの機能を統一的に維持するための技術です。これはOOPの延長線上にあり、Springフレームワークの重要な要素であり、関数型プログラミングの一種です。AOPを使用することで、ビジネスロジックの各部分を分離し、結合度を低減し、コードの再利用性を高めることができます。

1.2 SpringにおけるAOPの役割 宣言型トランザクションの提供:ユーザーが独自のアスペクトを定義できる

  • 横断的関心事:アプリケーションの複数のモジュールにまたがるメソッドや機能。つまり、ビジネスロジックとは直接関係ないが、考慮すべき部分(例:ログ、セキュリティ、キャッシュ、トランザクションなど)。
  • アスペクト(Aspect):横断的関心事をモジュール化した特別なオブジェクト。つまり、それはクラスである。
  • アドバイス(Advice):アスペクトが行うべき作業。つまり、それはクラス内のメソッドである。
  • ターゲット(Target):アドバイス対象のオブジェクト。
  • プロキシ(Proxy):ターゲットオブジェクトにアドバイスを適用して生成されたオブジェクト。
  • ポイントカット(Pointcut):アスペクトのアドバイスが実行される「場所」の定義。
  • 結合点(Join Point):ポイントカットと一致する実行点。

Spring AOPでは、アドバイスを使用して横断的なロジックを定義します。Springは5種類のアドバイスをサポートしています。

つまり、AOPは既存のコードを変更せずに新しい機能を追加することができます。

1.3 SpringでのAOPの実装 【重要】AOPを導入するには、以下の依存パッケージを追加する必要があります。

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>

**注意:**runtimeのscopeを持つ依存関係は削除してください。依存関係を追加した後、Ctrl+Shift+Alt+Sで依存関係をCompileに設定してください。

方法1: Spring APIインターフェースを使用

  • インターフェース
package com.example.service;

public interface UserService {
    void add();
    void delete();
    void update();
    void select();
}
  • 実装クラス
package com.example.service;

public class UserServiceImpl implements UserService {
    public void add() {
        System.out.println("ユーザーを追加しました");
    }

    public void delete() {
        System.out.println("ユーザーを削除しました");
    }

    public void update() {
        System.out.println("ユーザーを更新しました");
    }

    public void select() {
        System.out.println("ユーザーを検索しました");
    }
}
  • ログクラス
package com.example.log;

import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;

public class Log implements MethodBeforeAdvice {
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName() + "の" + method.getName() + "が実行されました");
    }
}
package com.example.log;

import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;

public class AfterLog implements AfterReturningAdvice {
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println(method.getName() + "メソッドが実行され、結果は:" + returnValue);
    }
}
  • テストクラス
import com.example.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = context.getBean("userService", UserService.class);
        userService.add();
    }
}
  • bean定義
<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="userService" class="com.example.service.UserServiceImpl"/>
    <bean id="afterLog" class="com.example.log.AfterLog"/>
    <bean id="log" class="com.example.log.Log"/>

    <aop:config>
        <aop:pointcut id="pointcut" expression="execution(* com.example.service.UserServiceImpl.*(..))"/>
        <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    </aop:config>
</beans>

アスペクトなしで、必要なインターフェースを実装するだけで複数のクラスファイルに切り込みが可能です。

方法2: 自己定義によるAOPの実装

  • ログクラス
package com.example.custom;

public class CustomPointCut {
    public void before() {
        System.out.println("===========メソッド実行前================");
    }

    public void after() {
        System.out.println("===========メソッド実行後================");
    }
}
  • bean定義
<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="userService" class="com.example.service.UserServiceImpl"/>
    <bean id="afterLog" class="com.example.log.AfterLog"/>
    <bean id="log" class="com.example.log.Log"/>

    <aop:config>
        <aop:pointcut id="pointcut" expression="execution(* com.example.service.UserServiceImpl.*(..))"/>
        <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    </aop:config>

    <bean id="custom" class="com.example.custom.CustomPointCut"/>
    <aop:config>
        <aop:aspect ref="custom">
            <aop:pointcut id="point" expression="execution(* com.example.service.UserServiceImpl.*(..))"/>
            <aop:before method="before" pointcut-ref="point"/>
            <aop:after method="after" pointcut-ref="point"/>
        </aop:aspect>
    </aop:config>
</beans>

自己定義クラスにより、アスペクト、ポイントカット、通知(いつ実行するか)が明確になります。

方法3: アノテーションを使用したAOPの実装

  • ログクラス
package com.example.custom;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class AnnotationPointCut {

    @Before("execution(* com.example.service.UserServiceImpl.*(..))")
    public void before() {
        System.out.println("=====メソッド実行前========");
    }

    @After("execution(* com.example.service.UserServiceImpl.*(..))")
    public void after() {
        System.out.println("=====メソッド実行後========");
    }

    @Around("execution(* com.example.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("周囲前");

        Object proceed = jp.proceed();

        System.out.println("周囲後");
    }
}
  • bean定義
<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="userService" class="com.example.service.UserServiceImpl"/>
    <bean id="afterLog" class="com.example.log.AfterLog"/>
    <bean id="log" class="com.example.log.Log"/>
    <aop:aspectj-autoproxy proxy-target-class="false"/>
    <bean id="annotationPointCut" class="com.example.custom.AnnotationPointCut"/>
</beans>

タグ: Spring AOP AspectJ Aspect Advice

7月19日 21:27 投稿