mcpField
S
spring-aop

spring-aop

github.com/claude-dev-suite開発4639 回インストール2 日前に更新
リポジトリ

spring-aop について

Spring 開発者向けの AOP リファレンスです。ポイントカット、アドバイス、カスタムアノテーション、アスペクトの順序と、実践上の注意点を扱います。

主な機能

  1. 01メソッドとアノテーションのポイントカット例
  2. 02各種アドバイスの例
  3. 03カスタムアノテーションの設計
  4. 04アスペクト実行順の制御
  5. 05一般的な落とし穴と実践指針

活用シーン

  1. 01サービス層メソッドへのログ追加
  2. 02アノテーション付きメソッドへのアスペクト適用
  3. 03セキュリティなどのアスペクト順序の制御
  4. 04自己呼び出しによるアスペクト不適用の診断

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/claude-dev-suite/claude-dev-suite --skill spring-aop

インストール後、Claude Code で/spring-aopを入力して読み込まれたか確認します。

同梱ファイルpatterns.md

詳細

Spring AOP

完全なリファレンス:パフォーマンス監視、リトライロジック、監査ログ、セキュリティチェック、レート制限、テストのパターンについては、patterns.md を参照してください。

クイックスタート

@SpringBootApplication
@EnableAspectJAutoProxy
public class Application {}

@Aspect
@Component
@Slf4j
public class LoggingAspect {

    @Around("execution(* com.example.service.*.*(..))")
    public Object logExecution(ProceedingJoinPoint joinPoint) throws Throwable {
        String method = joinPoint.getSignature().toShortString();
        log.info("Executing: {}", method);

        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long duration = System.currentTimeMillis() - start;

        log.info("Completed: {} in {} ms", method, duration);
        return result;
    }
}

ポイントカット式

メソッド実行
@Aspect
@Component
public class PointcutExamples {

    // All methods in a package
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceLayer() {}

    // Public methods
    @Pointcut("execution(public * *(..))")
    public void publicMethods() {}

    // Methods starting with "get"
    @Pointcut("execution(* get*(..))")
    public void getters() {}

    // Package and subpackages
    @Pointcut("execution(* com.example..*.*(..))")
    public void entireApplication() {}
}
アノテーションベース
@Aspect
@Component
public class AnnotationPointcuts {

    // Methods annotated with @Transactional
    @Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
    public void transactionalMethods() {}

    // Classes annotated with @Service
    @Pointcut("@within(org.springframework.stereotype.Service)")
    public void serviceBeans() {}

    // With annotation binding
    @Around("@annotation(loggable)")
    public Object logWithAnnotation(ProceedingJoinPoint pjp, Loggable loggable) throws Throwable {
        String value = loggable.value();
        return pjp.proceed();
    }
}
組み合わせ
@Aspect
@Component
public class CombinedPointcuts {

    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceLayer() {}

    @Pointcut("execution(* com.example.repository.*.*(..))")
    public void repositoryLayer() {}

    // AND
    @Pointcut("serviceLayer() && publicMethods()")
    public void publicServiceMethods() {}

    // OR
    @Pointcut("serviceLayer() || repositoryLayer()")
    public void dataLayer() {}

    // NOT
    @Pointcut("serviceLayer() && !execution(* get*(..))")
    public void nonGetterServiceMethods() {}
}

アドバイスの種類

@Around
@Aspect
@Component
public class AroundAdvice {

    @Around("execution(* com.example.service.*.*(..))")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();

        log.info("Before: {} with args: {}", methodName, Arrays.toString(args));

        try {
            Object result = joinPoint.proceed();
            log.info("After returning: {} returned: {}", methodName, result);
            return result;
        } catch (Exception e) {
            log.error("After throwing: {} threw: {}", methodName, e.getMessage());
            throw e;
        }
    }

    // Modify arguments
    @Around("execution(* com.example.service.UserService.createUser(..))")
    public Object sanitizeInput(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof String str) {
                args[i] = sanitize(str);
            }
        }
        return joinPoint.proceed(args);
    }
}
@Before、@After、@AfterReturning、@AfterThrowing
@Aspect
@Component
public class OtherAdvice {

    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        log.info("Entering: {}", joinPoint.getSignature().toShortString());
    }

    @After("execution(* com.example.service.*.*(..))")
    public void logAfter(JoinPoint joinPoint) {
        log.info("Completed: {}", joinPoint.getSignature().toShortString());
    }

    @AfterReturning(pointcut = "execution(* com.example.service.UserService.createUser(..))", returning = "user")
    public void logCreatedUser(JoinPoint joinPoint, User user) {
        log.info("Created user: {}", user.getId());
    }

    @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")
    public void logException(JoinPoint joinPoint, Exception ex) {
        log.error("Exception in {}: {}", joinPoint.getSignature().toShortString(), ex.getMessage());
    }
}

カスタムアノテーション

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Loggable {
    String value() default "";
    boolean logArgs() default true;
    boolean logResult() default true;
    LogLevel level() default LogLevel.INFO;
}

@Aspect
@Component
@Slf4j
public class LoggableAspect {

    @Around("@annotation(loggable)")
    public Object logMethod(ProceedingJoinPoint joinPoint, Loggable loggable) throws Throwable {
        String methodName = joinPoint.getSignature().toShortString();
        String message = loggable.value().isEmpty() ? methodName : loggable.value();

        if (loggable.logArgs()) {
            log.info("Entering {} with args: {}", message, Arrays.toString(joinPoint.getArgs()));
        }

        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();

        if (loggable.logResult()) {
            log.info("Exiting {} with result: {} ({}ms)", message, result, System.currentTimeMillis() - start);
        }
        return result;
    }
}

// Usage
@Service
public class UserService {
    @Loggable(value = "Creating user", logResult = false)
    public User createUser(CreateUserRequest request) {
        return userRepository.save(new User(request));
    }
}

アスペクトの順序

@Aspect
@Component
@Order(1)  // Executes first
public class SecurityAspect { }

@Aspect
@Component
@Order(2)  // Executes second
public class LoggingAspect { }

@Aspect
@Component
@Order(3)  // Executes third
public class PerformanceAspect { }

ベストプラクティス

  • 具体的なポイントカットを使用してください(範囲を広げすぎないでください)
  • @Order を使ってアスペクトの順序を指定してください
  • 明確さを高めるためにカスタムアノテーションを使用してください
  • アスペクト内で複雑なロジックを避けてください
  • パフォーマンスへの影響を監視してください
  • アスペクトを個別にテストしてください
  • 例外を黙って捕捉しないでください
  • 共有状態を変更しないでください
  • コアビジネスロジックに使用しないでください

よくある落とし穴

エラー原因解決策
アスペクトが適用されないBean が Spring 管理ではない@Component または @Bean を使用する
自己呼び出し内部呼び出しがプロキシを迂回する自己注入を使用する
順序が誤っているアスペクトの順序が指定されていない@Order を使用する
パフォーマンスアスペクトの範囲が広すぎるポイントカットを絞り込む

すばやいトラブルシューティング

問題考えられる原因解決策
アスペクトが実行されないBean が Spring 管理ではないクラスに @Component が付いていることを確認する
自己呼び出しで実行されない内部呼び出しがプロキシを迂回する自己注入を使用する
実行順序が誤っている@Order がない優先度を指定して @Order を追加する
パフォーマンスの低下ポイントカットの範囲が広すぎる範囲を絞り込み、カスタムアノテーションを使用する

リファレンスドキュメント

コメント