4.

【Spring】@Beforeアノテーションとは

編集
この記事の要点
  • @Before は Spring AOP の「対象メソッド実行前」に処理を挿入するアドバイス
  • @Aspect クラス内で定義
  • 切り取り対象 (Pointcut) を指定: メソッド名・アノテーション・パッケージ等
  • ロギング・認証・パフォーマンス計測などに使う
  • 他のアドバイス: @After / @AfterReturning / @AfterThrowing / @Around

 

AOP の基本概念

AOP (Aspect-Oriented Programming / アスペクト指向プログラミング) は、複数のクラスにまたがる横断的関心事(ログ・認証・トランザクション等)を、本来のビジネスロジックから分離する手法です。

@Before の基本

// 1. AOP を有効化
@Configuration
@EnableAspectJAutoProxy
public class AppConfig { }

// または Spring Boot なら依存追加で自動有効

    org.springframework.boot
    spring-boot-starter-aop


// 2. Aspect クラス作成
@Aspect
@Component
public class LoggingAspect {

    private final Logger log = LoggerFactory.getLogger(LoggingAspect.class);

    // すべての Service メソッドの「実行前」にログ出力
    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        String method = joinPoint.getSignature().toShortString();
        Object[] args = joinPoint.getArgs();
        log.info("→ {} args={}", method, Arrays.toString(args));
    }
}

// 3. 対象クラス (Service)
@Service
public class UserService {
    public User findById(Long id) {
        // 実行前に LoggingAspect.logBefore() が呼ばれる
        return userRepository.findById(id).orElseThrow();
    }
}

Pointcut 式

「どのメソッドを対象にするか」を指定する式:

Pointcut意味
execution(* com.example.service.*.*(..))com.example.service の全クラスの全メソッド
execution(* *..service.*.find*(..))名前が find で始まるメソッド
execution(public * *(..))すべての public メソッド
within(com.example.service..*)service パッケージ以下
@annotation(org.springframework.transaction.annotation.Transactional)@Transactional 付きメソッド
@within(org.springframework.stereotype.Service)@Service クラス全体
@args(org.springframework.security.access.annotation.Secured)引数に特定アノテーション
args(java.lang.Long, ..)第 1 引数が Long
this(com.example.MyInterface)プロキシが特定型のインスタンス
target(com.example.MyClass)対象クラス

Pointcut 式の組み合わせ

// AND
@Before("execution(* com.example.service.*.*(..)) && @annotation(myAnnotation)")
public void log(MyAnnotation myAnnotation) { ... }

// OR
@Before("execution(* *..UserService.*(..)) || execution(* *..OrderService.*(..))")

// NOT
@Before("execution(* com.example..*(..)) && !execution(* *..toString(..))")

// 名前付き Pointcut (再利用)
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}

@Before("serviceMethods()")
public void logBefore(JoinPoint jp) { ... }

JoinPoint と引数取得

@Aspect
@Component
public class AuditAspect {

    @Before("@annotation(com.example.AuditLog)")
    public void audit(JoinPoint joinPoint) {
        // メソッド情報
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();

        // 引数
        Object[] args = joinPoint.getArgs();
        String[] paramNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();

        // アノテーション
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        AuditLog annotation = method.getAnnotation(AuditLog.class);

        // ログ出力
        log.info("Audit: {}.{}({})", className, methodName, Arrays.toString(args));

        // メタ情報の活用
        for (int i = 0; i < args.length; i++) {
            log.info("  {} = {}", paramNames[i], args[i]);
        }
    }
}

カスタムアノテーション + AOP

// 1. カスタムアノテーション定義
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuditLog {
    String value() default "";
}

// 2. Aspect で処理
@Aspect
@Component
public class AuditAspect {
    @Before("@annotation(auditLog)")
    public void audit(JoinPoint jp, AuditLog auditLog) {
        log.info("AUDIT [{}]: {} called", auditLog.value(), jp.getSignature());
    }
}

// 3. 利用
@Service
public class OrderService {

    @AuditLog("ORDER_CREATE")
    public Order createOrder(OrderRequest req) {
        // ...
    }
}

5 種類のアドバイス

アノテーションタイミング
@Before対象メソッド実行
@After実行後(正常 / 例外問わず)
@AfterReturning正常終了後(戻り値取得可)
@AfterThrowing例外発生時のみ
@Around前後で完全制御(戻り値・引数を変更可)
@Aspect
@Component
public class FullAspect {

    @Before("serviceMethods()")
    public void before() {
        log.info("Before");
    }

    @After("serviceMethods()")
    public void after() {
        log.info("After (always)");
    }

    @AfterReturning(pointcut = "serviceMethods()", returning = "result")
    public void afterReturning(Object result) {
        log.info("AfterReturning, result={}", result);
    }

    @AfterThrowing(pointcut = "serviceMethods()", throwing = "ex")
    public void afterThrowing(Throwable ex) {
        log.error("AfterThrowing", ex);
    }

    @Around("serviceMethods()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        try {
            Object result = pjp.proceed();  // 元のメソッド実行
            long elapsed = System.currentTimeMillis() - start;
            log.info("{} elapsed={}ms", pjp.getSignature(), elapsed);
            return result;
        } catch (Throwable ex) {
            log.error("Exception in {}", pjp.getSignature(), ex);
            throw ex;
        }
    }
}

典型的な用途

① 実行時間計測

@Around("@annotation(timed)")
public Object measure(ProceedingJoinPoint pjp, Timed timed) throws Throwable {
    long start = System.currentTimeMillis();
    Object result = pjp.proceed();
    log.info("{} took {}ms", pjp.getSignature(), System.currentTimeMillis() - start);
    return result;
}

② 認証チェック

@Before("@annotation(requiredRole)")
public void checkRole(RequiredRole requiredRole) {
    String currentRole = SecurityContextHolder.getContext()
        .getAuthentication().getAuthorities().toString();
    if (!currentRole.contains(requiredRole.value())) {
        throw new AccessDeniedException();
    }
}

③ リトライ

@Around("@annotation(retry)")
public Object retry(ProceedingJoinPoint pjp, Retry retry) throws Throwable {
    int attempts = 0;
    while (attempts < retry.maxAttempts()) {
        try {
            return pjp.proceed();
        } catch (Exception e) {
            attempts++;
            if (attempts >= retry.maxAttempts()) throw e;
            Thread.sleep(retry.delay());
        }
    }
    throw new RuntimeException("リトライ上限");
}

注意点

  • 同一クラス内の自己呼び出しは効かない: AOP は Spring プロキシ経由なので、内部呼び出しはバイパス
  • private メソッドは対象外: プロキシ経由できない
  • final メソッド・クラスは対象外: CGLIB プロキシ生成不可
  • パフォーマンス: 多用するとオーバーヘッド、ホットパスでの使用は注意

関連記事

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. @After
  2. @Autowired
  3. @Bean
  4. @Before
  5. @Column
  6. @Component
  7. @Configuration
  8. @Controller
  9. @Data
  10. @Entity
  11. @GeneratedValue
  12. @Id
  13. @Modifying
  14. @PathVariable
  15. @PropertySource
  16. @Repository
  17. @RequestBody
  18. @RequestMapping
  19. @ResponseBody
  20. @RestController
  21. @Service
  22. @SpringBootApplication
  23. @Table
  24. @Transactional
  25. @Value