
Filter, Interceptor, AOP
다만 이 로직들을 모든 코드에 작성하게 된다면 가독성도 떨어지고 보수하기 굉장히 불편해진다.
| 항목 | Filter | Interceptor | AOP |
|---|---|---|---|
| 처리 위치 | DispatcherServlet 이전 | 컨트롤러 실행 전후 | Spring Bean 메서드 전후 |
| 대상 | 서블릿 요청(Request/Response) | 핸들러(Controller) | 모든 Bean 메서드 |
| 사용 목적 | 인증, 로깅, CORS 처리 등 | 세션 체크, 권한 검사 | 로깅, 트랜잭션, 예외 처리 등 |
| 선언 방법 | Filter 구현체 | HandlerInterceptor 구현체 | @Aspect 클래스 |

[사용자 요청]
↓
[Filter] ← 요청 시작 시 로그 출력
↓
[Interceptor] ← Controller 실행 전/후 로그 출력
↓
[AOP] ← Service 메서드 실행 전/후/예외 시점 로그 출력
Filter – 인증 토큰 처리 (ex. JWT)
Interceptor – 로그인 여부 체크
Filter와 마찬가지로 처음부터 다 구현하는 것이 아닌 이미 구현되어 있는 HandlerInterceptor 를 활용한다.
메서드
@Slf4j
@Component
@RequiredArgsConstructor
public class UserOwnerCheckInterceptor implements HandlerInterceptor {
private final UserService userService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
// 1. 현재 로그인한 사용자 이름 꺼내기
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String currentUsername = auth.getName();
// 2. 요청 URI에서 username 추출
String path = request.getRequestURI(); // /api/user/{username}/email
String decodedPath = URLDecoder.decode(path, StandardCharsets.UTF_8);
String[] parts = decodedPath.split("/");
String username = parts[parts.length - 2];
// 3. 게시글 작성자와 비교
if(!currentUsername.equals(username)) {
log.warn("작성자 아님. 접근 거부");
response.sendError(HttpServletResponse.SC_FORBIDDEN, "작성자만 수정할 수 있습니다.");
return false;
}
return true;
}
}
필요 어노테이션
Slf4j : 로깅을 사용할 시
Component : 빈 등록 필수
RequiredArgsConstructor : 속성의 생성자 생성을 위해 사용
HandlerInterceptor을 해당 클래스를 구현한다.
preHandle 메서드의 매개변수는 HttpServlet 요청/응답, Object handler 이고 예외를던 져준다.
해당 리퀘스트를 통해 내부 사용자의 정보를 꺼내 원하는 조건문을 만들어 메서드를 완성 시킨다.
완성된 인터셉터를 WebConfig에 등록을 시켜준다.
@Configuration
@RequiredArgsConstructor
public class WebMvcConfig implements WebMvcConfigurer {
private final UserOwnerCheckInterceptor userOwnerCheckInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(userOwnerCheckInterceptor)
.addPathPatterns("/api/user/**/email");
}
}
AOP – 실행 시간 측정 / 로깅

package org.example.nbcam_addvanced_1.common.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
@Slf4j
public class TimeCheckAop {
@Around("execution(* org.example.nbcam_addvanced_1.user.service..*(..))")
public Object executionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed(); // 실제 메서드 실행 -> Filter에서 doFilter 와 비슷함.
long end = System.currentTimeMillis();
log.info("[AOP] {} 실행됨 in {}ms" , joinPoint.getSignature() , end - start);
return result;
}
}
@Aspect : AOP를 사용할 클래스다! 지정
@Component : 마찬가지로 빈 등록 필수
Slf4j : 로깅을 사용할 경우 필수
@Around("execution( org.example.nbcam_addvanced_1.user.service..(..))")
* = 와일드카드 모든~ 이라는 뜻JoinPoint를 매개변수로 받아준다.
System.currentTimeMillis(); = 현재 시간을 가져오는 기능
과제를 진행하게 되면서 테스트코드라는 것을 올바르게 작동하도록 수정하는 작업을 하였는데, 틀린 것을 찾고 수정하는 것은 매우 쉬웠지만, 저 코드를 테스트할 코드를 작성하는 것이 굉장히 어려울 것이라고 생각이 들어 좀처럼 손이 가지 않았다.. 튜터님께서 말하신 만큼 중요한 요소라 꼭 도전은 해야겠다만, 지금은 조금 버거워 후에 미뤄볼까 한다.. 절대 귀찮아서가 아니다ㅎㅎ..
또한 배운것을 토대로 인터셉터와 AOP를 구현하는데 손쉽게 따라할 수 있었지만 데이터를 뽑아와 새로운 로직을 생성하는 부분이 재밌으면서도 골이 조금 아팠다. 더 열심히 해야겠당
ㅋㅣㅂㅗㄷㅡㄱㅏ ㅇㅗㅐ ㅇㅣㄹㅓㅎㄱㅔ ㅊㅕㅈㅣㅈㅣ?