AOP는 개념, Aspect는 그 개념의 구현체
@Before: 메서드 실행 전@After: 메서드 실행 후@AfterReturning: 메서드가 정상 반환된 이후@AfterThrowing: 예외가 발생했을 때@Around: 메서드 호출 전후 전체를 감싸서 가로챔ProceedingJoinPoint 전달받음proceed() 메서드 보유joinPoint.proceed()를 직접 호출해야 실제 비즈니스 메서드 실행 (호출 안하면 원본 메서드가 아예 실행되지 않음)JoinPoint 중 어디에 적용할지 선별@annotation (커스텀 어노테이션 매칭)@Transactional 어노테이션이 이런 방식이다.@Pointcut 어노테이션서비스 메서드마다 시작 시각과 종료 시각을 재는 코드를 넣는다고 가정하자.
public List<PostResponse> findRecent(int limit) {
// 1. 이 두 줄이
long start = System.nanoTime();
try {
return postRepository.findRecent(limit).stream().map(PostResponse::from).toList();
} finally {
// 2. 메서드마다 반복됩니다
log.warn("{}ms", (System.nanoTime() - start) / 1_000_000);
}
}
long start = System.nanoTime();와 log.warn("{}ms", (System.nanoTime() - start) / 1_000_000);은 이 메서드의 본질이 아니다.
AOP 사용을 위해선 build.gradle에 다음 의존성을 추가해야 한다. (Spring Boot 4 버전 이후 기준)
implementation 'org.springframework.boot:spring-boot-starter-aspectj'
Aspect 정의 코드
@Slf4j
@Aspect
@Component
public class ExecutionTimeAspect {
// Pointcut (어디서 실행할지): execution 표현식
// .(점 하나): 정확히 현재 패키지 아래 계층 1개
// ..(점 두개): 현재 패키지 포함 하위 모든 계층
// 마지막의 *.(..)는 메서드, 파라미터. 이 경우 어떤 이름의 메서드든, 몇 개의 어떤 타입의 파라미터를 받든 모두 적용한다는 의미
@Around("execution(public * com.example.board..service..*.*(..))")
public Object measure(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.nanoTime();
try {
return joinPoint.proceed(); // JoinPoint
} finally {
long tookMs = (System.nanoTime() - start) / 1_000_000;
if (tookMs >= 100) {
log.warn("느린 호출 {} — {}ms", joinPoint.getSignature().toShortString(), tookMs);
}
}
}
}
호출부
// 패키지 경로: com.example.board.service
// Aspect의 Pointcut에 포함됨
@Slf4j
@Service
public class PostService {
public String findRecent(
int limit
) {
try {
Thread.sleep(120); // 느린 조회를 흉내 냅니다.
} catch (InterruptedException interrupted) {
Thread.currentThread().interrupt();
}
log.info("본문 실행: findRecent({})", limit);
return "게시글 " + limit + "건";
}
}
해당 API를 보내면 다음과 같이 Aspect에서 정의된 로그가 발생한다.

어노테이션 정의
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExecutionTime { }
포인트컷 지정
@Slf4j
@Aspect
@Component
public class ExecutionTimeAspect {
// @ExecutionTime 어노테이션이 달린 메서드만 가로채서 실행
@Around("@annotation(ExecutionTime)")
public Object measure(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.nanoTime();
try {
return joinPoint.proceed();
} finally {
long tookMs = (System.nanoTime() - start) / 1_000_000;
if (tookMs >= 100) {
log.warn(
"느린 호출 {} — {}ms",
joinPoint.getSignature().toShortString(),
tookMs
);
}
}
}
}
서비스 메서드에 부착
@Service
public class BoardService {
@ExecutionTime
public List<Board> findAll() {
// ...
}
@ExecutionTime
public Board findById(Long id) {
// ...
}
}
스프링 AOP의 동작 흐름
ProceedingJoinPoint.proceed())