Dependency 추가
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-aop'
}
AOP @Pointcut 내부 문법 정리
Spring AOP는 패키지 + 클래스의 메서드 단위로 컨트롤할 수 있다.
| 표현식 | 의미 |
|---|
execution(public Integer com.edu.aop..*(*) ) | com.edu.aop 패키지 이하, 파라미터 1개, 리턴 타입 Integer인 모든 메서드 |
execution(* com.edu..get*(..)) | com.edu 패키지 이하, 이름이 get으로 시작, 파라미터 0개 이상 |
execution(* com.edu.aop..*Service.*(..)) | com.edu.aop 패키지 이하, 클래스명이 Service로 끝나는 모든 클래스의 메서드 |
execution(* com.edu.aop.BoardService.*(..)) | BoardService 클래스의 모든 메서드 |
execution(* some(*, *)) | 이름이 some이고 파라미터가 2개인 메서드 |
* | 와일드카드: 모든 리턴 타입, 모든 이름 등 |
자바 코드
package com.codeit.aop.advice;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class BasicAdvice {
@Pointcut("execution(* com.codeit.aop.service.UserService.*(..))")
public void servicePointcut() {
}
@Before("servicePointcut()")
public void printBeforeLog(JoinPoint jp) {
System.out.println("Before : " + jp.getSignature().getName() + "() 호출됨");
System.out.println("Before args : " + Arrays.toString(jp.getArgs()));
}
@After("servicePointcut()")
public void printAfterLog(JoinPoint jp) {
System.out.println("@After : " + jp.getSignature().getName() + "() 호출됨");
}
@AfterReturning(pointcut = "servicePointcut()", returning = "result")
public void printAfterReturningLog(JoinPoint jp, Object result) {
System.out.println("@AfterReturning : " + jp.getSignature().getName() + "() 호출됨");
if (result instanceof User u) {
System.out.println("User : " + u);
} else if (result instanceof List<?> list) {
System.out.println("list : " + list);
} else {
System.out.println("result : " + result);
}
}
@AfterThrowing(pointcut = "servicePointcut()", throwing = "ex")
public void printErrorLog(JoinPoint jp, Exception ex) {
System.out.println("@AfterThrowing : " + jp.getSignature().getName() + "() 호출됨");
ex.printStackTrace();
}
@Around("servicePointcut()")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("@Around 실행 전 : " + pjp.getSignature().getName() + "() 호출됨");
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Object result = pjp.proceed();
stopWatch.stop();
System.out.println("@Around 실행 후 : " + pjp.getSignature().getName() + "() 호출됨");
System.out.println("@Around 메소드 실행 시간 : " + stopWatch.getTotalTimeMillis() + "ms");
System.out.println("@Around 메소드 실행 시간 : " + stopWatch.getTotalTimeNanos() + "ns");
return result;
}
}
포인트컷
| 항목 | 내용 |
|---|
| 선언 | @Pointcut("execution(* com.codeit.aop.service.UserService.*(..))") |
| 의미 | com.codeit.aop.service.UserService의 모든 메서드(파라미터 무관) 대상 |
| 메서드 | servicePointcut() – 본문은 비워 둠(표식용) |
어드바이스 요약
| 어노테이션/메서드 | 실행 시점 | 목적/설명 | 리턴/예외 접근 | 비고 |
|---|
@Before → printBeforeLog(JoinPoint jp) | 대상 메서드 호출 전 | 메서드명/인자 로깅 등 사전 처리 | 반환값 접근 불가, 예외 없음 | jp.getSignature().getName(), jp.getArgs() 사용 |
@After → printAfterLog(JoinPoint jp) | 정상/예외 여부와 무관하게 메서드 종료 후 | 공통 정리/로깅 | 반환값 접근 불가, 예외 객체 직접 제공 안 됨 | finally 성격 |
@AfterReturning(pointcut, returning="result") → printAfterReturningLog(JoinPoint jp, Object result) | 정상 반환 직후 | 반환값 기반 로깅/후처리 | 반환값 접근 가능, 예외 시 호출 안 됨 | instanceof로 타입 분기(User, List<?> 등) |
@AfterThrowing(pointcut, throwing="ex") → printErrorLog(JoinPoint jp, Exception ex) | 예외 발생 시 | 에러 로깅/모니터링 | 반환값 없음, 예외 객체 접근 가능 | 스택트레이스 출력 등 |
@Around → aroundAdvice(ProceedingJoinPoint pjp) | 호출 전/후 모두 | 실행 시간 측정, 트랜잭션·권한 등 경계 처리 | pjp.proceed()로 실제 호출 제어, 반환값 가로채기 가능, 예외 재전파 가능 | StopWatch로 ms/ns 측정, 전/후 로깅 |
JoinPoint / ProceedingJoinPoint 사용 포인트
| 타입 | 주요 사용 메서드 | 설명 |
|---|
JoinPoint | getSignature().getName(), getArgs() | 메서드명/인자 조회(비-어라운드에서 사용) |
ProceedingJoinPoint | proceed() | 실제 대상 메서드 호출(오직 @Around에서만 가능) |
Filter
package com.codeit.aop.config;
import com.codeit.aop.filter.LoggingFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<LoggingFilter> loggingFilter() {
FilterRegistrationBean<LoggingFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new LoggingFilter());
registrationBean.addUrlPatterns("/api/*");
registrationBean.setOrder(1); // 필터 실행 순서 (낮을수록 먼저 실행)
return registrationBean;
}
}
package com.codeit.aop.filter;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import java.io.IOException;
public class LoggingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println(">> LoggingFilter 초기화");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("LoggingFilter >> 요청 들어옴: " + request.getRemoteAddr());
chain.doFilter(request, response);
System.out.println("LoggingFilter << 응답 완료");
}
@Override
public void destroy() {
System.out.println("LoggingFilter >> LoggingFilter 종료");
}
}
Interceptor
package com.codeit.aop.config;
import com.codeit.aop.interceptor.LoggingInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoggingInterceptor())
.addPathPatterns("/api/**")
.excludePathPatterns("/api/auth/**");
}
}
package com.codeit.aop.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
public class LoggingInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("LoggingInterceptor >> 요청 URI: " + request.getRequestURI());
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
org.springframework.web.servlet.ModelAndView modelAndView) throws Exception {
System.out.println("LoggingInterceptor >> 컨트롤러 실행 후 처리");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("LoggingInterceptor >> 요청 완료 후 처리");
}
}