Spring MVC 요청 흐름 개요
- 사용자가 요청( URL + parameters )을 서버에게 날린다.
- DispatcherServlet이 처음에 이 요청을 받는다.
- DispatcherServlet은 미리 설정되어 있는 Handlermapping 정보(web.xml or @Controller)를 통해 request를 Handler에게 위임(delegate)한다.
- Handler(Controller)에서 비즈니스 로직을 처리 한다.
- 그 결과에 View와 관련된 정보가 있다면 DispatcherServlet은 ViewResolver를 통해 view에 대한 정보를 받아온다.
- DispatcherServlet은 받아온 View와 Model을 통해 render을 하고 이를 브라우저에 돌려준다.
Filter는 무엇을 하는가?
위의 그림을 보면, Dispatcher Servlet 앞단에 Filter가 존재한다.
spring-web 모듈은 4가지 Filter를 제공한다. 각각에 대한 자세한 기능은 글 맨 아래에 첨부한 Spring 공식 문서를 참고하면 된다.
- Form Data
- Forwarded Headers
- Shallow ETag
- CORS
Servlet, Servlet Container가 도대체 뭔데?
서블릿이란 웹프로그래밍에서 클라이언트의 요청을 처리하고 그 결과를 다시 클라이언트에게 전송하는 javax.servlet.Servlet 클래스의 구현 규칙을 지킨 자바 프로그래밍 기술이다.
- 클라이언트의 요청에 대해 동적으로 작동하는 웹 어플리케이션 컴포넌트
- Java Thread를 이용하여 동작
- MVC 패턴에서 Controller로 동작
- javax.servlet.http.HttpServlet 클래스를 상속받음
서블릿 컨테어는 말그대로 서블릿을 관리해주는 컨테이너이다.
- 웹서버와의 통신을 지원 : 서블릿과 웹서버가 소켓 생성같은 거 신경 안쓰고 손쉽게 통신할 수 있게 해줌.
- 서블릿 생명주기 관리
- 멀티쓰레디 지원 및 관리 : 요청이 올때마다 자바 스레드를 하나 생성하는데, 끝나고나면 또 자동으로 죽여준다.
아래는 Spring project를 키면 기본적으로 사용하는 서블릿컨테이너(톰켓)이 시작된 것을 볼 수 있다. Spring 5.0때부터는 Netty를 사용하는 component도 있다.
Spring MVC - 코드단으로 살펴보기
DispatcherServlet
함수에 대한 설명을 보면
Handler에게 실제로 보내주는 과정을 처리한다. Handler는 순서대로 Servlet의 HandlerMapping들에 의해 적용되어 있는 것을 기반으로 가져온다.
위에서는 다음과 같은 작업을 진행한다.
- Determine handler for the current request
- Determine handler adapter for the current request
- Process last-modified haeder, if supported by the handler
그 작업 후에 실제로 handler를 실행하는 부분이다.
mappedHandler의 handler를 보면 내가 원하던 Controller가 나온다.
Annotation Controllers
Spring MVC는 @Controller와 @RestController annotation 기반으로 프로그래밍할 수 있도록 제공을 해준다.
다양한 Request Mapping 방법이나 URL pattern 등은 Spring MVC 공식문서 1.3.2를 참고하면 된다.
DataBinder
@Controller나 @ControllerAdvice 클래스는 WebDataBinder를 구현한 @InitBinder method를 사용할 수 있다.
이 어노테이션을 사용하면,
- Request 파라미터( form, query data )를 model object에 bind할 수 있다.
- HTML form을 렌더링 할 때, model object 값을 string으로 포맷화 할 수 있다.
Exceptions
@ExceptionHandler
Spring에서 발생한 Exception을 기반으로 오류 처리할 수 있도록 @ExceptionHandler를 제공해준다.
@RestController
@RequestMapping("/boards")
public class BoardController {
@GetMapping("/{id}")
public Board get(@PathVariable Long id) {
if (id < 1L) {
throw new BoardNotFoundException("invalid id: " + id);
}
return new Board("title", "content");
}
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler(BoardNotFoundException.class)
public Map<String, String> handle(BoardNotFoundException e) {
log.error(e.getMessage(), e);
Map<String, String> errorAttributes = new HashMap<>();
errorAttributes.put("code", "BOARD_NOT_FOUND");
errorAttributes.put("message", e.getMessage());
return errorAttribute;
}
}
응답 예제
HTTP/1.1 404
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 17 Feb 2019 04:31:33 GMT
{"code":"BOARD_NOT_FOUND","message":"invalid id: 0"}
- 그렇다면 해당 Exception이 발생했을 때 어떻게 ExceptionHandler를 찾는것일까?
Controller Advice
보통 @Controller에 적용된 @ExceptionHandler, @InitBinder, @ModelAttribute method는 @Controller class에만 해당이 된다. 만약에 global하게 적용을 하고 싶다면 @ControllerAdvice, @RestControllerAdvice에 적용을 하면 된다.
@ControllerAdvice도 @Component를 가지고 있기 때문에, 똑같이 component scaning을 통해 bean으로 등록이 된다.
아래와 같이 범위를 정할 수도 있다.
ErrorController
Filter는 DispatcherServlet 외부에서 발생하기 때문에 ErrorController에서 처리해야 한다.
{
"timestamp": "2019-02-15T21:48:44.447+0000",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/123"
}
404 에러 json은 도대체 누가 기본으로 만들어 주는걸까?
AbstractErrorController와 ErrorAttributes
getErrorAttributes() 함수에서 ErrorAttributes 인터페이스의 getErrorAttributes 호출을 통해 에러 처리에 대해 책임을 위임하고 있다. ( delegate pattern )
public abstract class AbstractErrorController implements ErrorController {
private final ErrorAttributes errorAttributes;
protected Map<String, Object> getErrorAttributes(HttpServletRequest request,
boolean includeStackTrace) {
WebRequest webRequest = new ServletWebRequest(request);
return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
}
}
스프링에서 기본은 ErrorAttributes를 구현한 DefaultErrorAttributes이다. 해당 함수에서 request에 담긴 정보를 토대로 Error 정보를 만든다.
public interface ErrorAttributes {
/**
* Returns a {@link Map} of the error attributes. The map can be used as the model of
* an error page {@link ModelAndView}, or returned as a {@link ResponseBody}.
* @param webRequest the source request
* @param includeStackTrace if stack trace elements should be included
* @return a map of error attributes
*/
Map<String, Object> getErrorAttributes(WebRequest webRequest,
boolean includeStackTrace);
/**
* Return the underlying cause of the error or {@code null} if the error cannot be
* extracted.
* @param webRequest the source request
* @return the {@link Exception} that caused the error or {@code null}
*/
Throwable getError(WebRequest webRequest);
}
public DefaultErrorAttributes {
// 생성자 및 메서드
@Override
public Map<String, Object> getErrorAttributes(WebRequest request, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<>();
errorAttributes.put("timestamp", new Date()); // timestamp 생성
addStatus(errorAttributes, request); // status 생성
addErrorDetails(errorAttributes, request, includeStackTrace); // 오류 상세 내용
addPath(errorAttributes, request); // path 생성
return errorAttributes;
}
}
ErrorAttributes 확장 포인트 - 커스터마이징하기
오류가 발생했을 때 응답을 내려줄 모델을 커스터마이징 하고 싶은 경우가 있다. 스프링 덕분에 ErrorAttributes 인터페이스만 구현해주면 확장할 수 있다.
@Component
public class CustomErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> result = super.getErrorAttributes(webRequest, includeStackTrace);
result.put("greeting", "Hello");
return result;
}
}
응답 예제
{
"timestamp": "2019-02-15T22:24:41.275+0000",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/123",
"greeting": "Hello"
}
Spring Boot yml 설정을 통한 에러 설정
# spring boot의 기본 properties
server.error:
include-exception: false # 오류 응답에 exception의 내용을 포함할지 여부
include-stacktrace: never # 오류 응답에 stacktrace 내용을 포함할지 여부
path: '/error' # 오류 응답을 처리할 Handler의 경로
whitelabel.enabled: true # 서버 오류 발생시 브라우저에 보여줄 기본 페이지 생성 여부
ErrorController는 어떻게 호출되는데??
ErrorController가 어떻게 처리되는지는 이해 되었는데 그전에 어떻게 호출 될까?
- 서블릿 컨테이너에서 등록된 서블릿에서 요청을 처리함
- 오류 발생!
- 해당 서블릿에서 처리하지 못함
- 서블릿 컨테이너까지 오류가 전파됐을 때, 서블릿 컨테이너가 오류를 처리하기 위해 특정 경로 ( server.error.path)로 해당 요청처리를 위임할 때 사용
정리
매일 @Controller를 작성하면, 막상 내부적으로 어떻게 돌아가는지에 대해서는 잘 몰랐다.
그래서 다음 포스팅을 작성하게 되었고,
다음에는 Spring WebFlux에 대해 포스팅도 해보겠다!
참고
https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html
'Back-End > Spring' 카테고리의 다른 글
Jackson 관련 (0) | 2019.10.11 |
---|---|
AOP란 (0) | 2019.06.20 |
Spring layered architecture와 객체지향적으로 개발하기 (0) | 2019.05.31 |
Spring Security 아키텍쳐 (0) | 2019.05.21 |
Spring boot 2.0에 관하여 (0) | 2019.05.20 |