전체 과정 틀
- 클라이언트가 HTTP 요청
- WAS에서 메시지를 파싱 후 웹 서버에서만 필요한 정보라면 정적인 페이지를 반환하고 동적인 컨텐츠도 필요하다면 Request,Response객체를 만들어 Filter객체로 전달.
- Filter객체 내부에서 요청된 내용을 처리하고 HttpServletRequest, HttpServletResponse 로 변환해 DispatcherServlet으로 전달
- DispatcherServlet의 doDispatch()메소드 실행
- HandlerMapping을 통해 요청을 처리할 Controller를 찾음
- Controller를 찾고 Interceptor의 prehandle이 실행
- DispatcherServlet은 Controller를 실행해줄 HandlerAdapter를 찾는다.
- Adapter를 찾고 controller를 실행하기 위한 파라미터를 생성하기 위해 Resolver 실행
- Controller실행 후 Interceptor의 posthandle 실행
- ViewResolver에게 전달
- ViewResolver는 View에게 전달.
- Client에 View 반환
1. 클라이언트가 HTTP 요청
2. WAS에서 HTTP메시지를 파싱 후 웹 서버에서만 필요한 정보라면 정적인 페이지를 반환하고 동적인 컨텐츠도 필요하다면 Request,Response객체를 만들어 Filter객체로 전달.
Filter란?
- Dispatcher Servlet에 요청이 전달되기 전/후에 url패턴에 맞는 모든 요청에 대해 부가작업을 처리할 수 있는 기능을 제공한다.
- 주로 요청에 대한 인증, 권한 체크 등을 하는데 사용된다. 예를 들어 요청이 DispatcherServlet에 전달되기 전에 헤더를 검사해 인증 토큰이 있는지 없는지, 올바른지 아닌지 등을 검사할 수 있다.
- Spring Context 내부에 도달하기 전인 WAS 인입 시점에 로직을 처리해 준다.
3. Filter객체 내부에서 요청된 내용을 처리하고 HttpServletRequest, HttpServletResponse 로 변환해 DispatcherServlet으로 전달
Filter 메소드
public interface Filter {
public default void init(FilterConfig filterConfig) throws ServletException{}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException;
public default void destroy() {}
}
- init() : 필터가 생성될 때 수행되는 메소드, 필터 객체를 초기화 및 서비스에 추가
- doFilter() : Request, Response가 필터를 거칠 때 수행되는 메소드
- 이 메서드에서 다음에 체이닝 할 필터가 있으면 다음 필터로 넘겨주고, 없으면 서블릿을 호출해 줍니다.
- destroy() : 필터가 소멸될 때 수행되는 메소드, 필터 객체를 제거
Filter Chain
- doFilter()의 매개변수인 FilterChain으로 다음 실행할 Filter가 있는지 알려준다.
- 필터는 다음과 같이 여러 개로 Chaining 설정할 수 있어서 클라이언트의 요청마다 URL 패턴 등을 지정해서 원하는 필터링을 할 수 있다.
Filter Order(’@Order’)
- 값이 작을수록 우선순위가 높게 설정되어있다.
- 값의 범위는 int값으로 지정되어 있다.
- Order 어노테이션을 지정하지 않으면 컴포넌트의 알파벳 순으로 Filter Chaining이 지정된다.
Filter 적용 방식
- @WebServletComponentScan, @Component 방식
- 메인 Application에 ‘@WebServletComponentScan’을 지정한 뒤@WebFilter를 사용하면 URL 패턴을 지정할 수 있다.
- 클래스 호출이 두 번 되기 때문에 ‘@Component’ 어노테이션을 지워야한다.
- FilterRegistrationBean 방식
public abstract class RegistrationBean implements ServletContextInitializer, Ordered {
private static final Log logger = LogFactory.getLog(RegistrationBean.class);
private int order = Ordered.LOWEST_PRECEDENCE; // Integer.MAX_VALUE
// ...
}
→ 직접 필터를 빈으로 등록하는 방식이다.
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean AFilter() {
FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
filterFilterRegistrationBean.setFilter(new ATestFilter());
filterFilterRegistrationBean.setOrder(Integer.MIN_VALUE);
filterFilterRegistrationBean.addUrlPatterns("/*");
return filterFilterRegistrationBean;
}
@Bean
public FilterRegistrationBean BFilter() {
FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
filterFilterRegistrationBean.setFilter(new BTestFilter());
filterFilterRegistrationBean.setOrder(5000);
filterFilterRegistrationBean.addUrlPatterns("/*");
return filterFilterRegistrationBean;
}
@Bean
public FilterRegistrationBean CFilter() {
FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
filterFilterRegistrationBean.setFilter(new CTestFilter());
filterFilterRegistrationBean.setOrder(-100);
filterFilterRegistrationBean.addUrlPatterns("/*");
return filterFilterRegistrationBean;
}
}
→ FilterRegistrationBean 객체를 생성해서 Filter 정보를 만들어서 넣어주면 된다. 이때 반드시 Filter는 구현되어 있어야 한다.
4. DispatcherServlet의 doDispatch()메소드 실행
DispatcherServlet
- HTTP 프로토콜로 들어오는 모든 요청을 가장 먼저 받아 적합한 컨트롤러에 위임해주는 Front Controller이다.
- Front Controller : 웹 어플리케이션에서 주로 사용되는 디자인 패턴으로 컨트롤러에서 중복으로 처리해야 하는 사항을 공통 기능을 한 곳에서 관리하는 입구 역할을 하는 컨트롤러의 개념
- 클라이언트로부터 요청이 오면 Tomcat과 같은 서블릿 컨테이너가 요청을 받는다. 이 모든 요청을 Front Controller인 DispatcherServlet이 가장 먼저 받고, 공통적인 작업을 처리한 후 해당 요청을 처리해야 하는 컨트롤러를 찾아 작업을 위임한다.
정적 자원 처리
- DispatcherServlet이 모든 요청을 처리할 수 없으므로, 정적 자원 요청을 분리해야 한다.
- Dispatcher Servlet이 요청을 처리할 컨트롤러를 먼저 찾고, 요청에 대한 컨트롤러를 찾을 수 없는 경우에, 2차적으로 설정된 자원(Resource)경로를 탐색하여 자원을 탐색하는 것이다.
동작 과정(doDipatch())
1. 클라이언트의 요청을 Dispatcher Servlet이 받는다.
- Web Context에서 Filter들을 지나 Spring Context에서 Dispatcher Servlet이 가장 먼저 요청을 받는다.
2. 요청 정보를 통해 요청을 위임할 컨트롤러를 찾음.
- 요청을 처리할 핸들러(컨트롤러)를 찾고 해당 객체의 메소드를 호출한다.
- 따라서 가장 먼저 어느 컨트롤러가 요청을 처리할 수 있는지를 식별해야 하는데, 해당 역할을 하는 것이 바로 HandlerMapping이다.
- 최근에는 ‘@Controller’에 ‘@RequestMapping’관련 어노테이션을 사용해 컨트롤러를 작성하는 것이 일반적이다.
- 이 ‘@Controller’ 방식은 RequestMappingHandlerMapping가 처리한다.
- 이는 ‘@Controller’로 작성된 모든 컨트롤러를 찾고 파싱하여 HashMap으로 <요청 정보, 처리할 대상> 관리한다.
- 요청이 오면 (Http Method, URI) 등을 사용해 요청 정보를 만들고, HashMap에서 요청을 처리할 대상(HandlerMethod)를 찾은 후에 HandlerExecutionChain으로 감싸서 반환한다
- HandlerExecutionChain으로 감싸는 이유는 컨트롤러로 요청을 넘겨주기 전에 처리해야 하는 인터셉터 등을 포함하기 위해서이다.
3. 요청을 컨트롤러로 위임할 핸들러 어댑터를 찾아서 전달함
- Dispatcher Servlet은 컨트롤러로 직접 위임하는 것이 아닌 HandlerAdapter를 통해 위임한다.
- 그 이유는 컨트롤러의 구현 방식이 다양하기 때문이다.
- Controller 인터페이스로 구현했던 과거와 달리 최근 어노테이션 기반 프로그래밍을 추구하기 때문에 컨트롤러가 다양하게 작성된다.
- 그에 대응하기 위해 HandlerAdapter라는 어댑터 패턴을 적용함으로써 컨트롤러의 구현 방식에 상관없이 요청을 위임할 수 있도록 하였다.
4. 핸들러 어댑터가 컨트롤러로 요청을 위임함.
- 컨트롤러로 위임하기 전/후에 공통적인 전/후처리 과정이 필요하다.
- 대표적으로 인터셉터들을 포함해 요청 시에 ‘@RequestParam’, ‘@RequestBody’ 등을 처리하기 위한 ArgumentResolver 처리
- ResponseEntity의 Body를 Json으로 직렬화하는 등의 처리를 하는 ReturnValueHandler 처리
- ArgumentResolver 등을 통해 파라미터가 준비 되면 리플렉션을 이용해 컨트롤러로 요청을 위임한다.
- 리플렉션 : 구체적인 클래스 타입을 알지 못해도, 그 클래스의 메소드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API
5. 비즈니스 로직을 처리한다.
- 이후에 컨트롤러는 서비스를 호출하고 우리가 작성한 비즈니스 로직들이 진행된다.
6. 컨트롤러가 반환값을 반환한다.
- 주로 ResponseEntity를 반환한다.
- ResponseEntity : HTTP 요청(Request) 또는 응답(Response)에 해당하는 HttpHeader와 HttpBody를 포함하는 클래스인 HttpEntity를 구현한 클래스
7. 핸들러 어댑터가 반환값을 처리한다.
- 컨트롤러로부터 받은 응답을 응답 처리기인 ReturnValueHandler가 후처리한 후에 Dispatcher Servlet으로 돌려준다
- 컨트롤러가 ResponseEntity를 반환하면 HttpEntityMethodProcessor가 MessageConverter를 사용해 응답 객체를 직렬화하고 응답 상태(HttpStatus)를 설정한다.
- 컨트롤러가 View 이름을 반환하면 ViewResolver를 통해 View를 반환한다.
8. 서버의 응답을 클라이언트로 반환한다.
- DispatcherServlet을 통해 반환되는 응답은 다시 Filter들을 거쳐 클라이언트에게 반환된다.
- 응답이 화면이라면 View를 찾아서 반환해주는 ViewResolver가 적절한 화면을 내려준다.
Interceptor
- 인터셉터는 DispatcherServlet과 컨트롤러 사이에서 요청을 가로채는 역할을 수행한다.
- Spring에서 Interceptor의 구현은 HandlerInterceptor(Interface)나 HandlerInterceptorAdapter(Abstract Class)로 구현할 수 있다.
- controller의 핸들러(클라이언트가 요청한 url에 따라 실행되는 메서드)를 호출하기 전과 후에 요청과 응답을 가로채서 원하는 동작을 추가할 수 있도록 해준다
- (ex. 로그인 체크, 응답에서 알림 개수 조회)
- 스프링 컨텍스트에서 동작
- HandlerMapping에서 반환한 HandlerExecutionChain은 1개 이상의 인터셉터가 등록되어 있다면 순차적으로 인터셉터들을 거쳐 컨트롤러가 실행되도록 하고, 인터셉터가 없다면 바로 컨트롤러를 실행한다
Interceptor 장점
- 공통 코드 사용으로 코드 재사용성 증가
- 메모리 낭비, 서버 부하 감소
- 코드 누락에 대한 위험성 감소
Interceptor 메소드
- preHandle 메소드(PreHandle(HttpServletRequest request, HttpServletResponse response, Object handler))
- 컨트롤러가 호출되기 전에 실행된다.
- 컨트롤러 이전에 처리해야하는 작업이나 요청 정보를 가공하거나 추가하는 경우에 사용
- 반환 타입은 boolean이다. true면 다음 작업 실행, false면 중단
- Parameter 중 Object handler는 HandlerMapping이 찾은 Controller Class 객체이다.
- PostHandle 메소드(PostHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView))
- 컨트롤러가 호출된 후 실행
- 컨트롤러 이후 후처리 작업이 필요할 때 사용
- RestAPI기반 컨트롤러(‘@RestController’)를 만들면서 자주 사용되지 않음
- 중간에 예외가 발생하면 호출되지 않는다.
- Parameter 중 ModelAndView modelAndView를 통해 화면 단에 들어가는 Data 등의 조작이 가능하다.
- afterCompletion 메소드(afterComplete(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex))
- 최종 결과를 생성하는 일을 포함해 모든 작업이 완료된 후에 실행된다.
- 중간에 예외가 발생하더라도 반드시 호출된다.
Resolver
1. ArgumentResolver
ArgumentResolver 동작 위치 및 순서
- 컨트롤러를 처리하기 전에 어댑터는 ArgumentResolver에 요청을 해 컨트롤러에서 필요한 파라미터를 요청
- ArgumentResolver는 Controller를 보고 파라미터를 찾음
- 핸들러 어댑터에게 파라미터를 반환
- 핸들러 어댑터는 파라미터를 가지고 컨트롤러를 처리한다.
- 컨트롤러를 처리하고 결과 값을 HandlerMethodReturnValueHandler인터페이스를 걸쳐서 처리한다. 위 인터페이스를 걸쳐 처리하는 이유는 다양한 타입을 처리하기 위함이다.
ArgumentResolver 용도
- 요청에 필요한 parameter에 필요한 정보를 HttpRequestServlet, SecurityContext 또는 그 외에 어떤 곳에서든 가져와서 바인딩하고 싶다면 ArgumentResolver를 활용하면 된다.
2. ViewResolver
- ViewResolver란 view 객체를 생성하는 방법을 결정하는 인터페이스이다. ViewResolver는 컨트롤러가 반환하는 view이름을 view 객체로 변환하는데, 변환된 view 객체는 디스패처 서블릿이 클라이언트에게 반환하는 응답을 생성하기 위해 사용된다.
- 특정 ViewResolver를 Bean으로 등록하지 않으면, DispatcherServlet은 기본 viewResolver인 InternalResourceView를 사용한다.
- InternalResourceViewResolver(default)
[applicationContext.xml]
<bean id ="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/">
<property name="suffix" value=".jsp">
</bean>
→미리 지정된 접두사, 접미사를 사용
HTTP message converter
- Spring내부에서는 HTTP message body의 내용을 문자 또는 객체로 변환하는 방법으로 HTTP message converter를 사용한다.
- Spring은 보통 Jackson을 기본적으로 사용하며, 개발자의 설정에 따라 다른 라이브러리로 교체가 가능하다.
- Request body를 읽거나 Response body를 작성하는 로직에는 관여하지 않는다. 단지 변환할 뿐이다.
HTTP message converter의 발동조건
- ‘@RequestBody’, HttpEntity를 사용하는 경우
- ‘@ResponseBody’, HttpEntity를 사용하는 경우
- 즉, 발동하는 상황
- Request body에 담긴 JSON 또는 String 데이터를 읽는 경우
- Response body에 JSON 또는 String 데이터를 담아 반환하는 경우
→ HTTP message body를 사용한 요청과 응답을 처리할 때 사용된다.
HTTP message converter의 역할
- Request일 경우 JSON을 객체 및 문자열로 변환해주고, Response일 경우 객체 및 문자열을 JSON으로 변환하는 역할을 해준다.
- 객체로 반환하기 위해서는 HTTP message converter의 JSON 컨버터가 동작한다.(StringHttpMessageConverter)
- 문자열을 반환하면, HTTP message converter의 String 컨버터가 동작한다.(MappingJackson2HttpMessageConverter)
Spring MVC구조에서 HTTP message converter 호출 과정
- Request
- RequestMappingHandlerAdapter에서 ArgumentResolver를 호출
- Argument Resolver는 Handler가 필요로 하는 객체를 생성한 후, HTTP message converter를 호출
- HTTP message converter는 Request body의 데이터를 ArgumentResolver가 생성한 객체에 바인딩한다.
- HTTP message converter는 바인딩된 객체를 Argument Resolver에게 반환한다.
- Argument Resolver는 반환된 객체를 최종적으로 Handle에게 전달한다.
- Response
- Handler에서 비즈니스 로직을 완료하고 결과 값을 반환한다.
- 반환된 결과 값은 ReturnValueHandler에게 전달된다.
- ReturnValueHandler는 Handler의 annotation 정보 또는 사용하는 매개변수에 따라 결과 값을 변환한다.
- HTTP message converter를 호출하여 변환된 결과 값을 전달한다.
- HTTP message converter는 Response 메시지를 생성하고 변환된 결과 값을 Response body에 담는다.
[Reference]
'Spring' 카테고리의 다른 글
DTO의 생성 위치(with 장점) (2) | 2025.01.19 |
---|---|
Spring Security의 흐름과 개념 설명 (0) | 2024.08.11 |
Layered Architecture (0) | 2023.10.05 |
IoC 컨테이너 (0) | 2023.09.20 |