스프링부트/Code Tip

[Spring] 커스텀 어노테이션 만들기 - 2.메서드 실행시간 측정 어노테이션

삼록이 2025. 12. 22. 15:22

이전 포스트를 통해 spring-aop-starter의존성을 추가했을 것이다.

이제는 메서드 실행 시간을 측정하는 커스텀 어노테이션을 만들어보자.

 

먼저 아래처럼 쓰일 수 있는 어노테이션을 만들 것이다.

이 어노테이션은 어노테이션이 붙은 메서드가 실행될 때 실행시간을 측정하는 동작을 수행하는 것을 목적으로 한다.

@LogExecutionTime
public void updatePost() {
    ...
}

 

1.커스텀 어노테이션 정의

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
}

 

1-1.@interface 사용

자바에서 어노테이션은 java.lang.annotation.Annotation을 구현한 특수한 인터페이스다.

그래서 우리가 새롭게 커스텀 어노테이션을 정의하려면 @interface로 정의해야한다.(그냥 interface가 아니라 @interface다 그래야 JVM은 이걸 메타데이터 타입으로 인식한다.)

 

1-2.@Target

@Target(ElementType.METHOD) 는 '이 어노테이션은 메서드에만 붙일 수 있다'고 정의하는 것이다.  METHOD 대신 다른 옵션도 사용가능하다.

  • TYPE : 클래스,인터페이스,이넘 타입에도 붙일 수 있다
  • PARAMETER : 매서드 파라미터에도 붙을 수 있다.
  • FIELD :  클래스의 필드레벨에도 붙일 수 있다

그리고 @Target은 아래처럼 여러 개가 지정이 가능하기도 하다.

@Target({ElementType.METHOD, ElementType.PARAMETER}) //이 어노테이션은 메서드와 파라미터에도 붙일 수 있다는 뜻

 

1-3.@Retention

@Retention(RententionPolicy.RUNTIME)은  이 어노테이션이 언제까지 살아남을지를 정한다.

RUNTIME 대신에 올 수 있는 옵션으로는

  • SOURCE : 컴파일 전에 사라짐
  • CLASS : class 파일엔 남음,실행중엔 없음
  • RUNTIME :실행중에도 유지

그러나 Spring AOP 기반 어노테이션은 무조건 RUNTIME시에도 유지되야하기 때문에 사실상 RUNTIME옵션만 쓴다고 보면 된다.

 

2.Aspect정의

 

2-1.AOP 개념 

먼저 지난 포스트에서 본 AOP 개념에 대해서 한 번 더 집고 넘어가자

  • AOP 프록시 : 이 AOP(관점지향프로그래밍)을 구현 하기 위해 만든 프록시 객체.
  • Aspect : 공통적인 기능들을 모듈화 한것을 의미
  • Join point : Aspect가 끼어들 수 있는 지점. 
  • Advice : 실제 이 Aspect의 기능을 정의 한 곳. 실제 실행되는 코드를 의미
  • Point cut : Advice를 적용할 메서드의 범위를 지정하는 것을 의미
  • Target:  Advice의 대상이 되는 객체, 포인트 컷으로 결정 됨

 

2-2. Aspect 정의

위에서 만든 커스텀 어노테이션의 실제 동작을 정의하는 부분이다.

@Aspect
@Component
public class LogExecutionTimeAspect {

    @Around("@annotation(LogExecutionTime)")
    public Object logTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long end = System.currentTimeMillis();

        log.info("{} executed in {} ms",
            joinPoint.getSignature(), end - start);

        return result;
    }
}

 

2-3.@Aspect

커스텀 어노테이션의 실제 동작을 정의하는 부분에 붙이는 어노테이션이다.

AOP개념인 Aspect(공통 관심사)를 의미한다고 보면 된다. 스프링은 @Aspect를 보고 이 클래스 안의 메서드들을 Advide 후보로 인식한다.

 

2-4.@Component

@Component 어노테이션은 반드시 붙여야한다. 이 어노테이션이 없다면 이 Aspect 클래스가 Spring 컨테이너에 등록되지 않을 것이고 AOP의 대상이 될 수 없기 때문이다. 참고로 Spring AOP는 Spring Bean에만 적용이 가능하다!

 

2-5.@Around

@Around("@annotation(LogExecutionTime)") 이 부분이 핵심이다.

Around대신에 Before, After도 사용가능한다. 

Before같은 경우는 대상이 되는 메서드 실행 직전에 동작되는 것으로 주로 권한체크나 파라미터 검증에 적합하다.

After은 대상이 되는 메서드의 성공 실패 상관없이 메서드 실행 직후 동작되는 것으로 무조건 실행된다

즉 Before와 After는 메서드의 실행 전, 후에 정해진 위치에서만 동작한다.

 

반면 Around는 메서드 호출 자체를 감싸는 구조로 동작한다.

그래서 대상이 되는 메서드를 실행할 지 말지 결정할 수 있고, 실행 시점을 직접 제어할 수 도 있으며 메서드 파라미터를 변경할 수도 있고, 반환값을 가공해서 다시 돌려줄 수도 있다. 이러한 이유로 실행 시간 측정, 캐싱, 파라미터 주입과 같은 복합적인 제어에는 @Around가 사용된다.

 

 

그리고 "@annotation(LogExecutionTime)" 부분은 point cut(advice를 적용할 범위)에 대한 표현식이다. 즉

LogExecutionTime어노테이션이 붙어 있는 메서드라면 아래 advice(여기서 정의한  logTime 메서드)를 실행하라는 것이다.

 

2-6.ProceedingJoinPoint

ProceedingJoinPoint는 지금 가로챈 메서드 호출 자체이다. 위에서 join point개념에 대해 Aspect가 적용될 수 있는 시점이라고 했는데 여기서 JoinPoint는 개념과는 조금 다른 실제 자바 객체로 지금 적용된 시점의 사건을 설명하는 객체다.

(이 객체 안에는 어떤 클래스의 메서드인지 무슨 메서드 이름인지, 파라미터 값은 무엇인지 등)

 

실제 logTime 메서드를 보자

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

        log.info("{} executed in {} ms",
            joinPoint.getSignature(), end - start);

 

joinPoint.proceed()는 지금 호출된 객체를 실행하라는 것이다.

여기서 호출된 객체는 메서드 그 자체다.

즉. 여기서는 메서드가 실행되기 직전에 start 시점을 쟀고 그 다음에 메서드를 실행시키고 종료된 후 end 시점을 재니,

메서드의 실행시간을 파악하는 동작이 되는 것이다.

 


+

아까 아래 부분을 설명할 때, @annotation~부분은 point cut(advice를 적용할 범위)에 대한 표현식이라고 했다.

@Around("@annotation(LogExecutionTime)")

 

그런데 실무에서는 execution 표현식을 쓰는 경우도 많다.

 

execution(반환타입 패키지.클래스.메서드(파라미터))

 

즉 아래처럼 사용할 수 있는데

@Around("execution(* com.example.service.PostService.updatePost(..))")

반환타입은 *로 아무거나고, PostService클래스의 updatePost 메서드로 지정했다. 마지막 (..) 부분은 파라미터 개수/타입이 상관없다는 뜻

즉 이 말은 PostService클래스에 있는 updatePost 메서드가 호출될 때 무조건 정의한 어노테이션 동작(자세히는 @Advice로직)이 자동으로 실행된다는 뜻이다.

따라서, @LogExecution 어노테이션을 붙이지 않더라도 execution표현식에 매칭기되만 하면 AOP advice로직이 실행되는 것이다.

 

그러면 아래처럼 적용하면 PostService클래스에 있는 모든 메서드에 어노테이션을 붙이지 않더라도 자동으로 적용된다.

@Around("execution(* com.example.service.PostService.*(..))")

다시 말해서 이러한 방식은 어노테이션을 트리거로 사용하는 방식이 아니라 메서드의 구조(패키지.클래스) 를 기준으로 AOP를 적용하는 방식이다.