spring-aop について
Spring 開発者向けの AOP リファレンスです。ポイントカット、アドバイス、カスタムアノテーション、アスペクトの順序と、実践上の注意点を扱います。
主な機能
- 01メソッドとアノテーションのポイントカット例
- 02各種アドバイスの例
- 03カスタムアノテーションの設計
- 04アスペクト実行順の制御
- 05一般的な落とし穴と実践指針
活用シーン
- 01サービス層メソッドへのログ追加
- 02アノテーション付きメソッドへのアスペクト適用
- 03セキュリティなどのアスペクト順序の制御
- 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 を追加する |
| パフォーマンスの低下 | ポイントカットの範囲が広すぎる | 範囲を絞り込み、カスタムアノテーションを使用する |
リファレンスドキュメント
コメント
Markdown 対応