📙 목차
1. 공통 관심사(cross-cutting concerns)
공통 관심사(cross-cutting concerns, 횡단 관심사 )
- 비즈니스 로직과는 별개로 여러 기능에서 공통적으로 필요한 처리 로직
- 여러 클래스나 모듈에서 반복적으로 사용되는 부가적인 기능
- 비즈니스 로직에 직접 넣으면 중복 코드, 관심사 분리 실패, 유지보수 어려움 등의 문제가 발생한다.
- ex. 인증, 인가, 로깅, 인코딩 처리 등
@PostMapping("/post")
public PostResponseDto create(PostCreateRequestDto request) {
// 로그인 여부 확인 -> 공통 관심사
// 글 생성 로직 -> 비즈니스 로직
}
공통 관심사 분리 방법
방법 | 적용 시점 | 주요 용도 | 특징 |
Filter | 서블릿 요청 전/후 | 인코딩, 인증, 로깅 등 | DispatcherServlet 이전 처리 |
Interceptor | 컨트롤러 호출 전/후 | 로그인 인증, 권한 체크 | HandlerMapping 이후 처리 |
AOP | 메서드 실행 전/후/예외 | 트랜잭션, 로깅, 예외처리 등 | 비즈니스 로직 전후 세밀한 제어 |
Proxy 패턴 | 객체 접근 시 | 서비스 계층에서 주로 사용 | 클래스 구조로 해결, 확장성 용이 |
더보기
Filter - 서블릿 단계에서 처리
- 서블릿 컨테이너 레벨에서 작동
- 모든 요청에 대해 전처리/후처리 가능
- Spring의 DispatcherServlet 이전 단계
@WebFilter(urlPatterns = "/*")
public class LogFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
System.out.println("Request Logging...");
chain.doFilter(request, response);
}
}
Interceptor - Spring MVC 요청 처리 중간
- 컨트롤러 호출 전/후에 개입 가능
- 인증/인가, 사용자 세션 확인 등에 유용
- View 렌더링 직전까지 개입 가능
public class AuthInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 로그인 여부 확인
return true;
}
}
AOP (Aspect-Oriented Programming) - 메서드 단위 세밀 제어
- @Before, @After, @Around 등을 이용해 메서드 단위로 공통 로직 삽입
- 트랜잭션 처리, 로깅, 성능 측정 등에 활용
- 비즈니스 로직과 완전히 분리 가능
@Aspect
@Component
public class LogAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("메서드 호출 전 로깅: " + joinPoint.getSignature());
}
}
프록시 패턴 (Proxy Pattern)
- 객체 앞단에 대리 객체(Proxy)를 세워 공통 로직 수행
- 객체 구조 기반이라 AOP 미적용 환경에서도 활용 가능
public class LoggingServiceProxy implements UserService {
private final UserService target;
public LoggingServiceProxy(UserService target) {
this.target = target;
}
public void join(User user) {
System.out.println("회원 가입 시작");
target.join(user);
System.out.println("회원 가입 완료");
}
}
언제 어떤 방법을 써야 할까?
상황 | 추천 방법 |
요청/응답 전처리 필요 (예: 인코딩, 인증) | Filter |
컨트롤러 실행 전후에 제어 필요 | Interceptor |
서비스/DAO에서 로깅, 트랜잭션 등 적용 | AOP |
프레임워크 없이 공통 로직 분리 필요 | Proxy 패턴 |
2. Servlet Filter
Servlet Filter
- HTTP 요청/응답을 가로채 전처리 또는 후처리할 수 있는 컴포넌트
- 요청 전처리 (Request Logging, 인증, 인코딩 설정 등)
- 응답 후처리 (Response 압축, 응답 로그 등)
Servlet Filter 특징
- 공통 관심사 로직 처리
- 공통된 로직을 중앙 집중적으로 구현하여 재사용성이 높고 유지보수가 쉽다.
- 모든 요청이 하나의 입구를 통해 처리되어 일관성을 유지한다.
- HTTP 요청 및 응답 필터링
- Filter Chain
- 여러 개의 필터가 순차적으로 적용될 수 있다.
- filterChain.doFilter(request, response); 다음 필터로 제어를 전달한다.
- doFilter()
- 실제 필터링 작업을 수행하는 주요 메소드로 필터가 처리할 작업을 정의한다.
- 다음 필터로 제어를 넘길지 여부를 결정한다.
Filter 동작 순서
- Filter를 적용하면 Servlet이 호출되기 이전에 Filter를 항상 거치게된다.
- 공통 관심사를 필터에만 적용하면 모든 요청 or 응답에 적용된다 .
- Filter는 특정 URL Pattern에 적용할 수 있다.
- Spring을 사용하는 경우 Servlet은 Dispatcher Servlet이다.
3. Servlet Filter 구현 방법
Filter Interface
- Filter Interface를 Implements하여 구현하고 Bean으로 등록하여 사용한다.
- Servlet Container가 Filter를 Singleton 객체로 생성 및 관리한다.
Filter Interface 주요 메서드
- init()
- Filter를 초기화하는 메서드이다.
- Servlet Container가 생성될 때 호출된다.
- default method이기 때문에 implements 후 구현하지 않아도 된다.
- doFilter()
- Client에서 요청이 올 때 마다 doFilter() 메서드가 호출된다.
- doFilter() 내부에 필터 로직(공통 관심사 로직)을 구현하면 된다.
- WAS에서 doFilter() 를 호출해주고 하나의 필터의 doFilter()가 통과된다면
- Filter Chain에 따라서 순서대로 doFilter() 를 호출한다.
- 더이상 doFilter() 를 호출할 Filter가 없으면 Servlet이 호출된다.
- Client에서 요청이 올 때 마다 doFilter() 메서드가 호출된다.
- destroy()
- 필터를 종료하는 메서드이다.
- Servlet Container가 종료될 때 호출된다.
- default method이기 때문에 implements 후 구현하지 않아도 된다.
주요 메서드 정리
메서드 | 시점 | 목적 | 구현 필수 여부 |
init() | 필터 생성 시 1회 | 필터 초기화 작업 | 선택 구현 |
doFilter() | 요청마다 반복 호출 | 요청/응답 전후 처리 (핵심) | 필수 구현 |
destroy() | 필터 종료 시 1회 | 자원 정리 | 선택 구현 |
4. Servlet Filter 구현 및 등록 예제
Filter 구현체: 요청 URL 로그 출력
- doFilter() 메서드는 요청마다 실행되며, 로그 출력 후 다음 필터나 서블릿으로 제어를 넘긴다.
- ServletRequest는 기능이 제한적이므로, 일반적으로 HttpServletRequest로 다운캐스팅하여 사용한다.
- 필터 체인에 더 이상 필터가 없으면 최종적으로 DispatcherServlet 또는 서블릿이 호출된다.
@Slf4j
public class CustomFilter implements Filter {
@Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain
) throws IOException, ServletException {
// HttpServletRequest로 다운캐스팅
HttpServletRequest httpRequest = (HttpServletRequest) request;
String requestURI = httpRequest.getRequestURI();
log.info("request URI={}", requestURI);
// 다음 Filter 또는 서블릿 호출
chain.doFilter(request, response);
}
}
Filter 등록: FilterRegistrationBean 사용
- setFilter() : 등록할 필터를 지정한다.
- setOrder() : 필터 체인 순서를 지정한다. 숫자가 낮을수록 먼저 실행된다.
- addUrlPatterns("/*") : 모든 요청에 대해 해당 필터를 적용한다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Bean
public FilterRegistrationBean<Filter> customFilter() {
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
// 1. 필터 등록
filterRegistrationBean.setFilter(new CustomFilter());
// 2. 필터 실행 순서 설정 (낮을수록 우선순위 ↑)
filterRegistrationBean.setOrder(1);
// 3. URL 패턴 지정 - 전체 요청에 대해 필터 적용
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
}
정리
항목 | 설명 |
Filter 인터페이스 | 요청 전/후에 로직을 삽입할 수 있는 Servlet 컴포넌트 |
doFilter() | 요청 처리의 핵심 메서드. 필수 구현 |
FilterRegistrationBean | 필터를 Spring Boot에 등록하기 위한 클래스 |
URL 패턴 지정 | addUrlPatterns()로 지정. "/*"는 전체 요청에 필터 적용 |
필터 우선순위 | setOrder()로 지정. 숫자가 낮을수록 먼저 실행됨 |
Controller보다 먼저 실행됨 | 존재하지 않는 URL이어도 필터는 동작 → 로깅, 보안 등에 적합함 |
5. [실습]
요구사항1: Cookie/Session 방식으로 로그인 기능을 구현한다.(가정)
요구사항2: 로그인 인증은 Servlet Filter로 구현한다.
요구사항3: /, /user/signup, /login, /logout URL로 들어오는 요청은 인증 Filter 로직을 수행하지 않도록 해야한다.
* spring-filter 실습 파일에서 진행
GitHub - UK-spring/spring-filter: Spring 숙련주차 Filter
Spring 숙련주차 Filter. Contribute to UK-spring/spring-filter development by creating an account on GitHub.
github.com
Login Filter 만들기
- /filter/LoginFilter.java
@Slf4j
public class LoginFilter implements Filter {
private static final String[] WHITE_LIST = {"/", "/user/signup", "/login", "/logout"};
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
String requestURI = httpRequest.getRequestURI();
HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
log.info("로그인 필터 로직 실행");
if(!isWhiteList(requestURI)) {
HttpSession session = httpRequest.getSession(false);
if(session == null || session.getAttribute("sessionKey") == null) {
throw new RuntimeException("로그인 해주세요.");
}
log.info("로그인에 성공했습니다.");
}
// 다음 필터가 없으면 Servlet -> Controller, 다음 필터가 있으면 다음 Filter 호출
filterChain.doFilter(servletRequest, servletResponse);
}
private boolean isWhiteList(String requestURI) {
return PatternMatchUtils.simpleMatch(WHITE_LIST, requestURI);
}
}
Filter 등록하기
- /config/WebConfig.java
@Configuration
public class WebConfig implements WebMvcConfigurer {
...
@Bean
public FilterRegistrationBean loginFilter() {
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new LoginFilter());
filterRegistrationBean.setOrder(2);
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
}
결과
동작 순서
'Spring > 강의' 카테고리의 다른 글
[📙 숙련 Spring] 3-2. JPA Entity 만들기 (2) | 2025.05.21 |
---|---|
[📙 숙련 Spring] 3-1. JPA (0) | 2025.05.20 |
[📙 숙련 Spring] 2-3. Token, JWT(JSON Web Token) (0) | 2025.05.16 |
[📙 숙련 Spring] 2-2. Cookie, Session (2) | 2025.05.16 |
[📙 숙련 Spring] 2-1. 인증과 인가 (0) | 2025.05.16 |