본문 바로가기

@Aspect 구성

Aspect 메타데이터가 어드바이저로 변환되는 과정을 실행하고 이름 있는 포인트컷·적용 범위·상태 관리 원칙을 설계합니다.

@Aspect는 포인트컷 표현식과 어드바이스 메서드를 한 정책 모듈에 모읍니다.

Spring은 애스펙트 빈을 읽어 어드바이저로 만들고 적용 대상 빈을 프록시로 감쌉니다.

애노테이션을 클래스에 붙였다는 사실만으로 활성화되지 않으며 자동 프록시 생성기와 Spring 빈 등록, 프록시 경로 호출이 모두 필요합니다.

@AspectJ 지원의 공식 구성을 따라 Spring AOP·컨텍스트와 AspectJ 라이브러리를 준비합니다. 순수 AnnotationConfigApplicationContext 예제에서는 원 Config의 @EnableAspectJAutoProxy와 @Bean 등록을 함께 읽으세요. 자동 구성된 Boot 환경과 이 최소 컨텍스트를 무조건 같은 설정으로 가정하지 않습니다.

@Aspect 구성

활성 구성·빈·대상 조건을 만족한 호출만 측정과 proceed를 거치고 불일치는 해당 Advisor 없이 실행된다.

@ASPECT · SETUP AND INVOCATION

활성화·빈 등록과 범위 교집합이 모두 있어야 적용된다

@Aspect는 정책 메타데이터입니다. 자동 프록시 지원과 빈 등록을 갖춘 뒤 application 공개 작업 ∩ @Measured 조건에 맞는 호출을 관찰합니다.

두 포인트컷 조건과 프록시 호출지원·빈 등록과 상속 가능한 대상 등의 전제가 준비된 선택 흐름이다. 패키지와 애노테이션이 모두 맞으면 proxy의 측정과 proceed를 통과한다. 조건 불일치는 직접 업무 실행으로, 이 Advisor는 붙지 않는다. 다른 별도 Advisor가 없고 기록기가 실패하지 않는 예제다. 원 board.aspect 대상은 불일치 반례다.지원 활성화Aspect·Target 빈 등록공개 application∩ @Measured?최종 프록시측정 → proceed 1회업무 대상 실행finally 측정 1회조건 불일치원 대상 직접 실행Advice 0회둘 다아니요
두 포인트컷 조건과 프록시 호출지원·빈 등록과 상속 가능한 대상 등의 전제가 준비된 선택 흐름이다. 패키지와 애노테이션이 모두 맞으면 proxy의 측정과 proceed를 통과한다. 조건 불일치는 직접 업무 실행으로, 이 Advisor는 붙지 않는다. 다른 별도 Advisor가 없고 기록기가 실패하지 않는 예제다. 원 board.aspect 대상은 불일치 반례다.지원 활성화Aspect·Target 빈 등록공개 application∩ @Measured?최종 프록시측정 → proceed 1회업무 대상 실행finally 측정 1회조건 불일치원 대상 직접 실행Advice 0회둘 다아니요

1 · 설정 시점

  1. 활성화 + 빈 등록

    AspectJ 애노테이션 지원과 aspect·target 빈을 준비합니다. @Aspect 하나만으로 적용되지 않습니다.

  2. 이름 있는 조건의 교집합

    applicationOperation()과 measuredOperation(measured)를 독립적으로 검증합니다.

  3. Advisor를 최종 프록시에 연결

    적합한 Advisor를 찾아 caller가 받는 참조에 연결합니다. 지연 생성 빈도 실제 생성 시 이 경로를 확인합니다.

2 · 프록시 호출 시점

  1. 일치 메서드가 체인 진입

    application 공개 작업이며 @Measured가 있는 메서드가 대상입니다.

  2. 한 번 proceed · 호출 로컬 상태

    started와 outcome은 지역 변수에 둡니다. 공유 수집 대상은 스레드 안전해야 합니다.

  3. 결과와 실패 계약 확인

    관찰 완료 뒤 원본 결과·업무 실패가 유지되게 설계합니다. 수집 실패 격리와 프록시 예외 계약은 별도 확인합니다.

두 조건 모두 일치: 포함. 애노테이션만 있는 helper, 애노테이션 없는 application: 제외. JDK/클래스 방식의 메타데이터 바인딩과 제한된 작업 이름도 함께 검증합니다.

이 예의 조건

  • 두 박스는 같은 실행의 진행 정보와 자료·결과를 관찰하는 고정 역할입니다. 박스의 좌우·위아래 위치를 별도 호출 순서로 해석하지 않습니다.
  • 조건별 종료는 대안입니다. 실패 뒤 정상 후속 단계를 실행하거나 독립 비교 사례를 같은 요청으로 연결하지 않습니다.
  • 지원·빈 등록과 상속 가능한 대상 등의 전제가 준비된 선택 흐름이다. 패키지와 애노테이션이 모두 맞으면 proxy의 측정과 proceed를 통과한다. 조건 불일치는 직접 업무 실행으로, 이 Advisor는 붙지 않는다. 다른 별도 Advisor가 없고 기록기가 실패하지 않는 예제다. 원 board.aspect 대상은 불일치 반례다.
관계도의 보충 설명
원래 전체 그림의 표시 문구·조건·주석
순번원문 표시
1@Aspect 구성
단계별 상태·값과 전환 이유
장면관찰 값전환 이유
지원 대상 조건

활성 구성·빈·대상 조건과 패키지·애노테이션을 확인합니다.

일치: advisor 적용 · 불일치: 직접 업무대상 조건이 이 advisor의 진입을 결정합니다.
측정과 proceed

일치한 proxy 호출은 측정을 열고 proceed합니다.

일치: 측정·proceed · 불일치: 이 advice 없음허용된 호출만 측정 경계를 통과합니다.
업무와 종료

대상이 실행되고 기록기 무실패 조건에서 측정이 끝납니다.

업무: 실행 · 전제: 다른 advisor 없음조건별 호출 경계가 완료됩니다.

@Aspect 구성

활성 구성·빈·대상 조건을 만족한 호출만 측정과 proceed를 거치고 불일치는 해당 Advisor 없이 실행된다.

@ASPECT · SETUP AND INVOCATION

활성화·빈 등록과 범위 교집합이 모두 있어야 적용된다

@Aspect는 정책 메타데이터입니다. 자동 프록시 지원과 빈 등록을 갖춘 뒤 application 공개 작업 ∩ @Measured 조건에 맞는 호출을 관찰합니다.

두 포인트컷 조건과 프록시 호출지원·빈 등록과 상속 가능한 대상 등의 전제가 준비된 선택 흐름이다. 패키지와 애노테이션이 모두 맞으면 proxy의 측정과 proceed를 통과한다. 조건 불일치는 직접 업무 실행으로, 이 Advisor는 붙지 않는다. 다른 별도 Advisor가 없고 기록기가 실패하지 않는 예제다. 원 board.aspect 대상은 불일치 반례다.지원 활성화Aspect·Target 빈 등록공개 application∩ @Measured?최종 프록시측정 → proceed 1회업무 대상 실행finally 측정 1회조건 불일치원 대상 직접 실행Advice 0회둘 다아니요
두 포인트컷 조건과 프록시 호출지원·빈 등록과 상속 가능한 대상 등의 전제가 준비된 선택 흐름이다. 패키지와 애노테이션이 모두 맞으면 proxy의 측정과 proceed를 통과한다. 조건 불일치는 직접 업무 실행으로, 이 Advisor는 붙지 않는다. 다른 별도 Advisor가 없고 기록기가 실패하지 않는 예제다. 원 board.aspect 대상은 불일치 반례다.지원 활성화Aspect·Target 빈 등록공개 application∩ @Measured?최종 프록시측정 → proceed 1회업무 대상 실행finally 측정 1회조건 불일치원 대상 직접 실행Advice 0회둘 다아니요

1 · 설정 시점

  1. 활성화 + 빈 등록

    AspectJ 애노테이션 지원과 aspect·target 빈을 준비합니다. @Aspect 하나만으로 적용되지 않습니다.

  2. 이름 있는 조건의 교집합

    applicationOperation()과 measuredOperation(measured)를 독립적으로 검증합니다.

  3. Advisor를 최종 프록시에 연결

    적합한 Advisor를 찾아 caller가 받는 참조에 연결합니다. 지연 생성 빈도 실제 생성 시 이 경로를 확인합니다.

2 · 프록시 호출 시점

  1. 일치 메서드가 체인 진입

    application 공개 작업이며 @Measured가 있는 메서드가 대상입니다.

  2. 한 번 proceed · 호출 로컬 상태

    started와 outcome은 지역 변수에 둡니다. 공유 수집 대상은 스레드 안전해야 합니다.

  3. 결과와 실패 계약 확인

    관찰 완료 뒤 원본 결과·업무 실패가 유지되게 설계합니다. 수집 실패 격리와 프록시 예외 계약은 별도 확인합니다.

두 조건 모두 일치: 포함. 애노테이션만 있는 helper, 애노테이션 없는 application: 제외. JDK/클래스 방식의 메타데이터 바인딩과 제한된 작업 이름도 함께 검증합니다.

이 예의 조건

  • 두 박스는 같은 실행의 진행 정보와 자료·결과를 관찰하는 고정 역할입니다. 박스의 좌우·위아래 위치를 별도 호출 순서로 해석하지 않습니다.
  • 조건별 종료는 대안입니다. 실패 뒤 정상 후속 단계를 실행하거나 독립 비교 사례를 같은 요청으로 연결하지 않습니다.
  • 지원·빈 등록과 상속 가능한 대상 등의 전제가 준비된 선택 흐름이다. 패키지와 애노테이션이 모두 맞으면 proxy의 측정과 proceed를 통과한다. 조건 불일치는 직접 업무 실행으로, 이 Advisor는 붙지 않는다. 다른 별도 Advisor가 없고 기록기가 실패하지 않는 예제다. 원 board.aspect 대상은 불일치 반례다.

관계도의 보충 설명

원래 전체 그림의 표시 문구·조건·주석
순번원문 표시
1@Aspect 구성
단계별 상태·값과 전환 이유
장면관찰 값전환 이유
지원 대상 조건

활성 구성·빈·대상 조건과 패키지·애노테이션을 확인합니다.

일치: advisor 적용 · 불일치: 직접 업무대상 조건이 이 advisor의 진입을 결정합니다.
측정과 proceed

일치한 proxy 호출은 측정을 열고 proceed합니다.

일치: 측정·proceed · 불일치: 이 advice 없음허용된 호출만 측정 경계를 통과합니다.
업무와 종료

대상이 실행되고 기록기 무실패 조건에서 측정이 끝납니다.

업무: 실행 · 전제: 다른 advisor 없음조건별 호출 경계가 완료됩니다.

이름 있는 포인트컷

긴 실행 표현식을 어드바이스마다 복사하면 패키지 변경 때 일부만 수정됩니다.

의미 있는 메서드 이름의 빈 포인트컷 메서드로 추출하고 작은 조건을 조합합니다.

applicationOperation()과 measured()를 &&로 결합하면 타입 범위와 명시적 적용 메타데이터를 따로 테스트할 수 있습니다.

src/main/java/board/aspect/Measured.java
package board.aspect;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Measured {
    String value();
}

작업 이름을 애노테이션 값으로 제한하면 런타임 클래스와 메서드 이름 변화에 덜 흔들리는 메트릭을 만들 수 있습니다.

값에는 구성원 ID 같은 고카디널리티 값을 넣지 않습니다.


애스펙트 상태 관리

애스펙트는 기본 싱글톤이라 startedNanos를 필드에 두면 병렬 요청이 덮습니다.

호출별 상태는 주변 어드바이스의 파라미터나 스택 지역 변수에 보관합니다.

스레드 안전한 MeterRegistry나 수집 대상을 의존성으로 주입하되 변경 가능한 버퍼를 직접 공유하지 않습니다.

src/main/java/board/aspect/MeasuredAspect.java
package board.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public final class MeasuredAspect {
    @Pointcut("execution(public * board.application..*(..))")
    public void applicationOperation() { }

    @Pointcut("@annotation(measured)")
    public void measuredOperation(Measured measured) { }

    @Around("applicationOperation() && measuredOperation(measured)")
    public Object time(
            ProceedingJoinPoint joinPoint,
            Measured measured
    ) throws Throwable {
        long started = System.nanoTime();
        String outcome = "success";
        try {
            return joinPoint.proceed();
        } catch (Throwable failure) {
            outcome = failure.getClass().getSimpleName();
            throw failure;
        } finally {
            System.out.printf("metric=%s outcome=%s elapsed=%d%n",
                    measured.value(), outcome,
                    System.nanoTime() - started);
        }
    }
}

Throwable을 받는 이유는 진행 규칙을 보존하기 위해서입니다.

어드바이스가 검사 예외 타입을 새로 선언해 애플리케이션 시그니처를 바꾸지는 않으며 같은 객체를 재전파합니다.


프록시·어드바이스 검증

AspectJ 애노테이션 지원을 활성화하고 애스펙트와 대상을 빈으로 등록합니다.

아래 대상은 board.aspect에 있으므로 board.application..* 범위를 벗어납니다. 빈으로 등록해도 이 어드바이스는 적용되지 않습니다. 애노테이션만으로 충분하지 않음을 확인하는 예시입니다.

src/main/java/board/aspect/AspectContextDemo.java
package board.aspect;

import org.springframework.aop.support.AopUtils;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

public final class AspectContextDemo {
    static class WeeklyReport {
        @Measured("post.weekly")
        public int postCount(long authorId) {
            return authorId == 41L ? 12 : 0;
        }
    }

    @Configuration(proxyBeanMethods = false)
    @EnableAspectJAutoProxy(proxyTargetClass = true)
    static class Config {
        @Bean MeasuredAspect measuredAspect() { return new MeasuredAspect(); }
        @Bean WeeklyReport weeklyReport() { return new WeeklyReport(); }
    }

    public static void main(String[] args) {
        try (var context = new AnnotationConfigApplicationContext(Config.class)) {
            WeeklyReport report = context.getBean(WeeklyReport.class);
            System.out.println("postCount=" + report.postCount(41L));
            System.out.println("proxy=" + AopUtils.isAopProxy(report));
        }
    }
}
AspectContextDemo 소스 기준 예상
postCount=12
proxy=false

테스트에서는 콘솔 텍스트 대신 기록 수집 대상을 주입해 작업, 결과, 개수를 검증하는 편이 안정적입니다.

한 조건씩 바꾸어 양성·음성을 비교하기 원 WeeklyReport는 board.aspect에 있으므로 첫 조건인 application 공개 작업과 맞지 않습니다. Measured 애노테이션·빈 등록·지원 활성화가 있어도 두 조건의 AND가 거짓이어서 postCount=12, proxy=false라는 예상이 됩니다.

양성 변형은 원 파일을 그대로 둔 채 별도 board.application.WeeklyReport 클래스에 같은 공개이며 final이 아닌 postCount 메서드와 @Measured("post.weekly")를 둡니다. 이 클래스도 final이 아니어야 합니다. Config의 반환 타입과 생성식을 그 별도 클래스로 맞추고, 컨텍스트에서도 그 타입으로 꺼내 호출합니다. 이제 패키지 조건과 애노테이션 조건이 모두 참이므로 time의 proceed가 한 번 업무를 실행하고 finally에서 측정 한 번을 남기는 경로를 예상합니다. 이 예상은 컨텍스트를 실제 실행한 결과로 확인해야 합니다.

독립 확인: 자동 프록시 지원·빈 등록·호출 가능한 공개이며 final이 아닌 메서드는 모두 갖추었다고 가정합니다. A는 board.application 안이고 @Measured가 있으며, B는 같은 패키지지만 애노테이션이 없습니다. C는 board.helper 안이며 애노테이션은 있습니다. 어느 메서드가 time의 대상인가요? 패키지 조건을 없애면 어느 부정 예제가 달라지나요?

풀이 확인

두 조건을 동시에 만족하는 A만 대상입니다. B는 애노테이션 조건, C는 패키지 조건에서 제외됩니다. 패키지 조건을 빼면 C까지 선택되므로 helper를 제외해야 한다는 원 연습의 계약이 깨집니다. 원 AspectContextDemo는 C와 비슷한 범위 밖 반례이며, 양성 fixture는 별도 패키지 파일을 준비해야 합니다.


애노테이션·프록시 유형

인터페이스 메서드에 애노테이션을 둘지 구현에 둘지 섞으면 포인트컷 바인딩이 프록시 방식과 가장 구체적인 메서드 해결에 영향을 받을 수 있습니다.

애플리케이션 규칙 애노테이션이면 인터페이스, 구현 세부 관찰이면 구체 메서드처럼 원칙을 정하고 JDK·클래스 프록시 양쪽 테스트를 둡니다.

메타 애노테이션을 만들어 여러 정책을 한 번에 붙일 수 있지만 트랜잭션과 재시도처럼 순서가 중요한 관심사를 숨기지 않습니다.

합성 애노테이션의 속성 별칭과 기본값을 명확히 합니다.

패키지 전체를 대상으로 하는 실행 포인트컷은 신규 빈도 자동 포함합니다.

보안 애스펙트라면 누락 방지 장점이 있지만 원하지 않은 관리 또는 상태 메서드까지 적용될 수 있어 부정 테스트가 필요합니다.


단일 정책 애스펙트

로깅, 재시도, 인가 어드바이스를 한 클래스에 모으면 이름 기반 포인트컷을 공유한다는 이유로 변경 원인이 섞입니다.

각각 별도 애스펙트와 순서를 두고 통합 테스트에서 체인을 검증합니다.

재사용 가능한 포인트컷 라이브러리는 의존성 방향을 애플리케이션 패키지 밖으로 새지 않게 관리합니다.

애스펙트 내부에서 어드바이스가 적용된 다른 빈을 호출하면 추가 프록시 체인과 트랜잭션이 생길 수 있습니다.

메트릭 수집 대상은 가능하면 어드바이스가 없는 단순한 인프라로 두고 재귀를 방지합니다.


연습 문제

@Measured("post.create")와 애플리케이션 패키지를 동시에 요구하는 애스펙트를 구성하세요.

같은 애노테이션을 테스트 도우미에 붙여도 적용되지 않고, 대상 실패에서 결과와 원본 예외가 모두 보존되는 컨텍스트 테스트를 작성하세요.

해설 보기

포인트컷을 애노테이션 하나로만 만들지 않고 패키지 스코프와 교집합하면 도우미가 제외됩니다.

테스트 픽스처의 패키지가 실제 구조를 반영해야 합니다.

package board.aspect;

public final class Outcome {
    public static String from(Throwable failure) {
        if (failure == null) return "success";
        if (failure instanceof IllegalArgumentException) return "invalid";
        return "failure";
    }

    private Outcome() { }
}

메트릭 결과를 예외의 단순 이름 그대로 쓰면 하위 타입 증가로 카디널리티가 커질 수 있어 제한된 분류로 바꿉니다.