😸

Re:ゼロから始めるSpring Boot #4 AOP(Aspect-Oriented Programming)アスペクト指向プログラミン

2023/12/04に公開

AOP とは

Spring BootにおけるAOP(Aspect-Oriented Programming)は、特定の横断的な機能や処理をコードに効果的に組み込むための手法です。以下は、AOPの簡潔で分かりやすい説明です。

AOPの概要

AOPは、主にビジネスロジックからは独立した横断的な関心事(cross-cutting concerns)を切り出し、それらの関心事を別のモジュールに分離します。
例えば、ログ記録、トランザクション管理、例外処理などが横断的な関心事の例です。

AOP用語

  • Aspect(切面): 特定の横断的な関心事を実装するクラスやモジュール。例えば、ログを記録するAspectがあります。
  • Joinpoint(結合点): 切面が挿入されるプログラムの特定のポイント。メソッドの呼び出しや例外の発生などが結合点になります。
  • Advice(アドバイス): 切面が結合点で実際に行う横断的な処理。例えば、メソッドの前後にログを挿入するアドバイスがあります。
  • Pointcut(切入点): どの結合点に切り込むかを指定する表現。例えば、特定のパッケージ内のすべてのメソッドなど。

一般的な用途

メソッドの実行前にログを記録する。
特定のメソッドが呼び出される前後にトランザクションを開始または終了する。
特定の例外が発生した場合に通知を送信する。

AOPの実現方法:

Spring Bootでは、AOPは@Aspectアノテーションを使用して実現されます。
アスペクトは@Before、@After、@Aroundなどのアドバイスアノテーションを使用して結合点で横断的な処理を定義します。
@Pointcutアノテーションを使用して切入点を指定します。
簡単に言えば、AOPはコードの異なる部分にまたがる共通の機能や処理を別のモジュールに切り出して、再利用性や保守性を向上させる手法です。 Spring Bootでは、アノテーションを使用してこれを実現し、ビジネスロジックとそれに関連する横断的な関心事を分離できます。

実装してみよう

ファイル構成

├───main
│   ├───java
│   │   └───com
│   │       └───rezerosb
│   │           └───rezerosb04aop
│   │               │   RezeroSb04AopApplication.java
│   │               │
│   │               ├───aspect
│   │               │       LogAspect.java
│   │               │
│   │               └───service
│   │                   │   SomeService.java
│   │                   │
│   │                   └───impl
│   │                           SomeServiceImpl.java
│   │
│   └───resources
│           application.properties
│
└───test
    └───java
        └───com
            └───rezerosb
                └───rezerosb04aop
                        RezeroSb04AopApplicationTests.java

1. Mavenにaop依存の追加

pom.xmlに以下を追加する。追加したらMavenのリロードが必要。

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

Interface:SomeService

public interface SomeService {
    void query(Integer id);
    void save(String name, Integer age);
}

Impl:SomeServiceImpl.java

@Service
public class SomeServiceImpl implements SomeService {
    @Override
    public void query(Integer id) {
        System.out.println("SomeServiceのビジネスロジック:Query");
    }

    @Override
    public void save(String name, Integer age) {
        System.out.println("SomeServiceのビジネスロジック:Save");
    }
}

Advice: LogAspect.java

@Component
@Aspect
public class LogAspect {

    // serviceは以下の全メソッドの実行前に組み込みメソッドを実行
    @Before("execution(* com.rezerosb.rezerosb04aop.service..*.*(..))")
    //組み込むメソッド
    public void sysLog(JoinPoint jp) {
        StringJoiner log = new StringJoiner("|", "{", "}");
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        log.add(formatter.format(LocalDateTime.now()));

        //メソッド名を取得
        String methodName = jp.getSignature().getName();
        log.add(methodName);

        Object[] args = jp.getArgs();
        for (Object arg : args) {
            log.add(arg == null ? "-" : arg.toString());
        }
        System.out.println("ログ:" + log.toString());
    }
}

Test:RezeroSb04AopApplicationTests.java

@SpringBootTest
class RezeroSb04AopApplicationTests {

    @Autowired
    private SomeService someService1;
    @Autowired
    private SomeService someService2;

    @Test
    void testLog() {
        someService1.query(1001);
        someService1.save("Willyang",20);

        someService2.query(999);
        someService2.save("Test2",99);
    }
}

実行結果

SomeService型のオブジェクトが呼び出された度に、組み込みメソッドが実行されることがわかる。

ログ:{2023-12-04 22:43:30|query|1001}
SomeServiceのビジネスロジック:Query
ログ:{2023-12-04 22:43:30|save|Willyang|20}
SomeServiceのビジネスロジック:Save
ログ:{2023-12-04 22:43:30|query|999}
SomeServiceのビジネスロジック:Query
ログ:{2023-12-04 22:43:30|save|Test2|99}
SomeServiceのビジネスロジック:Save

Discussion