| MVC 구조로 보는 필터와 인터셉터
- 요청사항에 대해 필터와 인터셉터를 거친다
- 사용되는 메소드의 전반적인 흐름
init -> doFilter -> preHandler -> AOP -> postHandler -> afterCompletion -> doFilter -> destroy |
- 필터와 인터셉터와 AOP
필터 | 인터셉터 | AOP | |
공통점 | 공통처리 기능 | ||
관련 | 웹의 URL주소나, 프로토콜 등과 관련 | 자바코드(Annotation, package) | |
위치 | 스프링 외부 | 스프링 내부 | 스프링 내부 |
특징 | - 대부분의 공통사항 필터에서 처리 - low level 처리 가능 - sql injection, CSRF 등 해킹을 사전에 막는용도로 자주 사용한다. |
- Controller에 가기 전 공통 처리 - 실제 매핑된 handler 정보 확인 가능 - 필터보다 상세한 조건식, 세부적인 스펙(pre,post,after) - 구체적인 시점에 구체적인 동작 가능 |
- 인터셉터보다 구체적인 조건 (ex. Annotation, parameter, 주소), - 인터셉터보다 구체적인 동작위치 (ex. afterThrowing) |
메소드 | init dofilter destroy |
preHandler postHandler(응답성공) afterCompletion(항상) |
@before, @after, @AfterReturning, @AfterThrowing pointcut에 자유롭게 메소드 생성 |
더보기
[ 서블릿의 생명주기 ]
init() | 서블릿을 처음 메모리에 올릴 때에만 한 번 실행 |
service() | 요청 및 응답에 대한 처리 (GET이면 doGet(), POST면 doPost()로 분기) |
destroy() | 서블릿 종료 요청 시 실행 |
| 필터 실습
[1] config 패키지 안에 Filter 클래스를 만든다.
- Filter 클래스는 Filter 인터페이스를 구현하여 만들면 된다.
- Filter 클래스를 만들 때엔 doFilter() 메소드에서 chain.doFilter()를 꼭 해주어야 같은 필터로 인식한다.
// import는 생략
@Slf4j
public class LogFilter implements Filter{
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 외부 -> filter (-> 처리 ->) filter -> 외부
log.info("Hello LogFilter : " + Thread.currentThread());
chain.doFilter(request, response); // chain.doFilter()는 꼭 필요
log.info("Bye LogFilter : " + Thread.currentThread());
}
}
[2] Filter 클래스를 구현할 때 방법 두가지
(1) @Component로 Filter 클래스를 스캐너가 자동 스캔할 수 있도록 하기
// import는 생략
@Slf4j
@Component
public class LogFilter implements Filter{
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 외부 -> filter (-> 처리 ->) filter -> 외부
log.info("Hello LogFilter : " + Thread.currentThread());
chain.doFilter(request, response); // chain.doFilter()는 꼭 필요
log.info("Bye LogFilter : " + Thread.currentThread());
}
}
(2) 별도의 DI 설정 클래스에서 Filter클래스를 Bean으로 등록하기
- 위의 Filter 클래스에서 @Component는 생략한다.
- FilterRegistrationBean<Filter> 클래스를 통해 필터를 등록한다.
- FilterRegistrationBean의 메소드들
setFilter(new 클래스()) | 필터를 세팅하기 (하나만 넣어준다) |
setOrder(순번) | 필터의 순번을 결정 |
addUrlPatterns("url패턴") | 해당 패턴(ex. "/order", "/*") 의 url에서만 필터가 적용된다. |
package com.example.websample.config;
// import 생략
@Configuration
public class WebConfig{
@Bean
public FilterRegistrationBean logginFilter(){
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new LogFilter());
filterRegistrationBean.setOrder(1);
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
}
>> 기존에 컨트롤러는 아래의 것을 사용하고, 실행해보면
@GetMapping("/order/{orderId}")
public String getOrder(
@PathVariable("orderId") String orderId,
@RequestParam("orderAmount") Integer orderAmount)
{
log.info("Get some order");
return "OrderId : "+ orderId +", orderAmount : " + orderAmount;
}
>> 요청
http://localhost:8080/order/252?orderAmount=1000
>> 응답
OrderId : 252, orderAmount : 1000
>> 로그
com.example.websample.config.LogFilter : Hello LogFilter : Thread[http-nio-8080-exec-1,5,main]
com.example.websample.config.LogFilter : Bye LogFilter : Thread[http-nio-8080-exec-1,5,main]
c.e.w.controller.SampleController : Get some order
| 인터셉터 실습
- 일반적으로 Filter로는 간단한 해킹 방지 코드를 작성하고, 인터셉터를 활용해서 세부적인 공통코드를 작성한다.
- preHandle, postHandle(응답 성공), afterCompletion(항상)
[1] 인터셉터는 HandlerInterceptor 인터페이스를 구현하여 만들 수 있다.
packages com.exmaple.websample.config;
// import 생략
@Slf4j
public class LogInterceptor implements HandlerInterceptor{
}
[2] 인터셉터는 세가지의 메소드를 구현해야하는데, 각각의 특징은 아래와 같다.
메소드 | 언제? | 매개변수 |
preHandle() | Handler로 넘어가기 전 | Object handler - handler의 위치를 알 수 있다. |
postHandle() | Handler의 처리가 성공적일 때 | ModelAndView - 처리 후 응답 정보를 알 수 있다. |
afterCompletion() | Handler의 처리의 성공 여부와 상관 없이 항상 | Exception ex - 예외 상황 발생 시 확인 가능 |
packages com.exmaple.websample.config;
// import 생략
@Slf4j
public class LogInterceptor implements HandlerInterceptor{
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
log.info("preHandle LogInterceptor: " + Thread.currentThread());
log.info("preHandle handler: " + handler);
return true; // 이 다음 인터셉터가 진행되길 바라면 true, 아니면 false
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
log.info("postHandle LogInterceptor: " + Thread.currentThread());
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
log.info("afterHandle LogInterceptor: " + Thread.currentThread());
if (ex != null) {
log.error("afterCompletion exception : " + ex.getMessage());
}
}
}
[3] @Configuration 클래스에서 WebMvcConfigurer를 구현하고, addInterceptors()를 통해 인터셉터를 추가한다.
- WebMvcConfigurer의 addInterceptors()는 InterceptorResgistration을 반환한다.
- 내부적으로 아래와 같은 메소드들이 있는데 이들 모두 InterceptorResgistration을 반환하므로 메소드 체이닝을 한다.
- 메소드들
addInterceptor() | 인터셉터 추가 |
order() | 순번 |
addPathPatterns() | 어떤 url패턴(경로)에서 실행할 건지 |
excludePathPatterns() | 일반적으로 css나 images와 같이 정적 페이지들을 대상으로 제외처리 |
package com.example.websample.config;
// import 생략
@Configuration
public class WebConfig implements WebMvcConfigurer{
// Filter 내용 생략
// Interceptor 내용
@Override
public void addInterceptors(InterceptorRegistry registry){
// registry : 등록 내용 관리부
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/**")
.excludePathPatterns("/css/*", "/images/*");
}
}
>> 기존의 컨트롤러에 예외 사항을 임의로 추가
@GetMapping("/order/{orderId}")
public String getOrder(
@PathVariable("orderId") String orderId,
@RequestParam("orderAmount") Integer orderAmount) throws IllegalAccessException {
log.info("Get some order");
if ("500".equals(orderId)) {
throw new IllegalAccessException("500 is not valid orderId");
}
return "OrderId : " + orderId + ", orderAmount : " + orderAmount;
}
>> 요청
http://localhost:8080/order/500?orderAmount=1000
>> 로그
// 필터 진입
Hello LogFilter : Thread[http-nio-8080-exec-1,5,main]
// preInterceptor
preHandle LogInterceptor: Thread[http-nio-8080-exec-1,5,main]
preHandle handler: com.example.websample.controller.SampleController#getOrder(String, Integer)
// handler
Get some order
// ! postInterceptor로 가지 않았다.
// afterInterceptor
afterHandle LogInterceptor: Thread[http-nio-8080-exec-1,5,main]
afterCompletion exception : 500 is not valid orderId
// exception 메세지 - 오류 코드 나머지는 생략
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.IllegalAccessException: 500 is not valid orderId] with root cause
java.lang.IllegalAccessException: 500 is not valid orderId
// 브라우저에 페이지 로딩하며 다시 실행
preHandle LogInterceptor: Thread[http-nio-8080-exec-1,5,main]
preHandle handler: org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#errorHtml(HttpServletRequest, HttpServletResponse)
postHandle LogInterceptor: Thread[http-nio-8080-exec-1,5,main]
afterHandle LogInterceptor: Thread[http-nio-8080-exec-1,5,main]
| 정리
- 필터, 인터셉터, AOP는 모두 공통 기능을 처리하기 위해 사용된다. - 주로 필터, 인터셉터는 웹에 관한 처리(ex.URL주소 및 프로토콜)를 하며, AOP는 자바 코드에 관해 처리한다. - 필터는 Filter 인터페이스를 구현하고, doFilter()안에서 chain.doFilter(request, response)를 작성하여 만들 수 있다. - 인터셉터는 HandlerInterceptor 인터페이스를 구현하고, preHandle()/postHandle()/afterCompletion()을 작성하여 만들 수 있다. - 필터와 인터셉터 모두 @Configuration 클래스에서 @Bean으로 등록해주는 것이 필요한데, - 필터의 경우, FilterRegistrationBean 클래스를 만들고 setFilter()/setOrder()/addUrlPatterns()를 설정한다. - 인터셉터의 경우, WebMvcConfigurer를 구현한 후, addInterceptor()를 통해 이미 등록된 registry의 메소드들을 사용한다. |
[ 참조 및 출처 ]
부트캠프 수업 후 내용 정리
서블릿의 생명주기 https://kadosholy.tistory.com/47
'Framework > Spring' 카테고리의 다른 글
[스프링] 프로젝트 전 꼭 알아두면 좋은 것들 (0) | 2022.09.13 |
---|---|
[스프링] 스프링 MVC - 예외처리 (1) | 2022.09.11 |
[스프링] 스프링MVC - HTTP 요청 및 응답 (1) | 2022.09.11 |
[스프링] 스프링의 주요기술 (0) | 2022.09.05 |
[스프링] OOP의 SOLID 원칙과 스프링 (0) | 2022.08.29 |