Spring Web MVC
- Servlet API 기반으로 빌드하고 Servlet container로 배포하는 웹 애플리케이션 개발 프레임워크
- 리액티브 스트림 기반의 Spring WebFlux가 Spring Framework 5.0에 추가되었다
DispatcherServlet
- DispatcherServlet은 클라이언트 요청을 받아 다른 컴포넌트로 처리를 넘긴다
- 각 DispatcherServlet은 하나의 WebApplicationContext를 갖는다
- 여타 서블릿과 마찬가지로 자바 설정이나 web.xml로 선언해야 한다
자바 설정 Since 3.0 - WebApplicationInitializer 정의 예
정상적으로 완료되면 실행 시 로그에 "INFO: 1 Spring WebApplicationInitializers detected on classpath"처럼 기록된다. 자동으로 스캔되지 않는 경우 /META-INF/services/javax.servlet.ServletContainerInitializer 파일에 해당 클래스 명시
web.xml 기술 예
- 기본적으로 {서블릿_이름}-servlet.xml 설정을 읽어온다
- 다른 파일(들)을 로드하고 싶거나, 아예 로드하고 싶지 않은 경우 contextConfigLocation 조정
- xml에서 ConfigurableWebApplicationContext 구현 클래스를 지정하고 싶은 경우, contextClass 조정
{서블릿_이름}-servlet == default namespace
각 경로들은 ','로 구분하면 된다. 클래스패스에 있는 파일은 classpath:conf.xml처럼 지정하면 된다
↓ 추가 로드 x
기본값은 XmlWebApplicationContext
↓ xml
Context Hierarchy
- 일반적으로 하나의 WebApplicationContext로 충분하지만, 루트 WebApplicationContext를 DispatcherServlet들이 공유하고, 각각은 자식 WebApplicationContext를 갖는 계층 구조로도 구성 가능하다
- 루트 WebApplicationContext는 전형적으로 Repository, Service 등 여러 Servlet 사이에서 공유될 빈들을 갖는다
- 자바 구성 예
- web.xml 예
이들은 자식 WebApplicationContext로 상속되며, 필요에 따라 재정의(override)할 수 있다
Special Bean Types
DispatcherServlet이 요청을 처리하도록 전달하는 빈들을 Special Bean이라고 하며, 정의하지 않은 경우 기본값이 사용된다
타입 | 설명 |
---|---|
HandlerMapping | 요청 URI -> 핸들러 매핑. @RequestMapping, @GetMapping, ... |
HandlerAdapter | 핸들러의 구현에 상관없이 DispatcherServlet이 호출할 수 있도록 래퍼 제공 |
HandlerExceptionResolver | 예외 발생 시 처리 전략 |
ViewResolver | 핸들러가 렌더링할 View 이름을 반환하면 적절한 View를 선택 |
LocaleResolver, LocaleContextResolver | 클라이언트 Locale 선택 |
MultipartResolver | Multi-part 요청 처리 api 제공 |
FlashMapManager | 요청 간 FlashMap 인스턴스 공유 기능 제공 |
Processing; 요청 처리 절차
- 요청에 맞는 WebApplicationContext가 검색되어 바운딩
- LocaleResolver 바운딩
- ThemeResolver 바운딩
- Multi-part 요청이라면 MultipartHttpServletRequest로 래핑
- 요청에 대한 핸들러 호출
Interception
핸들러 요청에 대한 전/후 처리를 추가할 수 있다 Goto - MVC config - Interceptors
↓ interface HandlerInterceptor
Exceptions
HandlerExceptionResolver 종류
- SimpleMappingExceptionResolver
- DefaultHandlerExceptionResolver
- ResponseStatusExceptionResolver
- ExceptionHandlerExceptionResolver
예외 클래스 -> 뷰 이름 매핑
↓ xml
예외 -> HTTP 상태 코드 매핑
예외 발생 메서드 또는 발생한 예외 클래스의 @ResponseStatus -> HTTP 상태 코드 매핑
@Controller 또는 @ControllerAdvice 클래스의 @ExceptionHandler 메서드가 예외 처리 Goto - Annotated Controllers - Exceptions
Chain of Resolvers
여러 HandlerExceptionResolver 빈을 정의하면 order 및 정의된 순서에 따라 호출되는 체인을 구성할 수 있다
Container Error Page
예외가 처리되지 않았고, 응답 상태가 에러(4xx, 5xx)인 경우를 위해 기본 에러 페이지를 지정할 수 있다
↓ web.xml
View Resolution
ViewResolver 종류
- Goto - View Technologies
- AbstractCachingViewResolver
- XmlViewResolver, BeanNameViewResolver
- ResourceBundleViewResolver
- UrlBasedViewResolver
- Url 경로에 대응하는 파일을 뷰로 이용한다
- setContentType()으로 기본 Content-Type 헤더 설정 가능 ─ JVM 기본 인코딩이 잘못된 경우 이걸로 수정 가능
- InternalResourceViewResolver
- ContentNegotiatingViewResolver
뷰 인스턴스를 캐시하여 이용한다. cache 속성을 이용해 전체 캐시를 끄거나, removeFromCache()로 일부 뷰만 캐시에서 제거할 수 있다
현재 컨텍스트에서 뷰 이름과 일치하는 빈을 뷰로 이용한다. XmlViewResolver은 따로 지정하지 않으면 기본으로 /WEB-INF/views.xml를 로드한다
ResourceBundle(기본 리소스 파일 : views.properties)에 저장된 빈 정보로 뷰를 찾는다. 정확히는 [viewname].(class) 속성을 뷰 클래스로, [viewname].url 속성을 뷰 url로 이용한다
↓ java
UrlBasedViewResolver의 서브클래스로, InternalResourceView(Servlet, JSP) 파일을 뷰로 이용한다. 그 외 서브클래스 ─ TilesViewResolver, XsltViewResolver, FreeMarkerViewResolver ─ 들도 존재
↓ xml
컨텐츠 협상이 가능한 ViewResolver. 다른 ViewResolver들이 고른 View 중, contentType이 요청과 일치하는 것을 선택한다
Handling
- 여러 ViewResolver를 이용 가능
- null을 반환하면 뷰를 찾지 못했음을 의미한다
order 속성으로 순서를 제어할 수 있다
InternalResourceViewResolver는 마지막에 동작해야 한다
Redirecting
- UrlBasedViewResolver 및 서브클래스를 이용하는 경우, 뷰 이름 앞에 redirect:를 붙이면 리다이렉션을 수행한다
- redirect:https://myhost.com/some 처럼 외부 절대경로 이용 가능
예. redirect:/myapp/some/resource
Forwarding
- UrlBasedViewResolver 및 서브클래스를 이용하는 경우, 뷰 이름 앞에 forward:를 붙이면 포워딩을 수행한다
- RequestDispatcher.forward()를 호출하는 InternalResourceView 인스턴스를 생성하므로 JSP만 사용하는 경우는 유용하지 않음
다른 종류의 뷰를 이용하지만 서블릿/JSP로 포워딩이 필요한 경우에 적합
Multipart Resolver
Multi-part 요청을 처리하려면 DispatcherServlet에 "multipartResolver" 이름의 MultipartResolver 빈을 정의해야 한다
- commons-fileupload
- JavaConfig 예
- xml 예
- Since Servlet 3.0
- Initializer에서 MultipartConfigElement 추가, 또는 web.xml에 multipart-config 추가
- StandardServletMultipartResolver 빈 정의
- JavaConfig 예
- xml 예
- @RequestParam으로 Map<String, MultipartFile>, MultiValueMap<String, MultipartFile>도 가능
- Since Servlet 3.0 : MultipartFile 대신 javax.servlet.http.Part 이용 가능
Logging - Sensitive Data
기본적으로 요청 인자와 헤더는 로깅하지 않는다. 로깅하려면 DispatcherServlet의 enableLoggingRequestDetails를 true로 설정하면 된다
↓ java
↓ xml
Filters
OncePerRequestFilter 서브클래스
AbstractRequestLoggingFilter, CharacterEncodingFilter, CorsFilter, FormContentFilter, ForwardedHeaderFilter, HiddenHttpMethodFilter, HttpPutFormContentFilter, MultipartFilter, OpenEntityManagerInViewFilter, OpenSessionInViewFilter, RelativeRedirectFilter, RequestContextFilter, ShallowEtagHeaderFilter
Default Encoding
CharacterEncodingFilter는 HttpServletRequest 변수 request에 대하여 아래를 수행한다
↓ java
Form Data
FormContentFilter를 이용하면 HTTP PUT, PATCH, DELETE 요청으로 온 application/x-www-form-urlencoded 내용을 ServletRequest.getParameter*()로 접근할 수 있다
Forwarded Headers
- 프록시 서버를 거치는 경우 실제 클라이언트의 주소는 Forwarded 헤더 ─ X-Forwarded-Host, X-Forwarded-Port, ... ─ 로 옮겨진다
- ForwardedHeaderFilter를 이용하면 요청의 주소를 Forwarded 헤더의 것으로 변경하고 Forwarded 헤더를 지운다
- Forwarded 헤더가 공격자로부터 삽입된 것이라면 이를 사용하지 않고 삭제만 해야한다
- AbstractAnnotationConfigDispatcherServletInitializer를 이용하는 경우, DispatcherType.REQUEST, ASYNC, ERROR 모두에 대해 실행된다
요청을 래핑해야 하므로, 가장 먼저 실행될 필요가 있다
ForwardedHeaderFilter의 removeOnly 속성을 true로 설정하면 된다
그 외의 경우 DispatcherType.REQUEST에 대해서만 동작한다
CORS
Controller 클래스에 애너테이션을 붙여 CORS 설정을 할 수 있지만, Spring Security를 이용하는 경우 CorsFilter를 이용해 Security보다 먼저 실행되도록 설정할 것이 권장된다 Goto - CORS
Annotated Controllers
Declaration
- @Controller : WebApplicationContext에서 웹 요청 처리자로 간주된다
- @RestController : @Controller + 클래스 수준 @ResponseBody
Request Mapping
- 컨트롤러 클래스 및 메서드에 @RequestMapping을 붙여 어떤 요청을 처리하는지 명시
- HTTP 요청에 따라 @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping 존재
↓ java
URI Patterns
Pattern | Description |
---|---|
? | 임의 1글자 |
* | 1개 경로 조각 내의 임의 0개 이상 글자 |
** | 0개 이상의 연속적인 경로 조각 |
{name} | 1개 경로 조각을 name 변수로 획득 |
{name:regex} | 경로가 정규식에 일치하면 해당 부분을 name 변수로 획득 |
↓ java
- 기본 타입과 Date 등 일부 클래스로는 자동으로 변환이 가능하다. 전역적인 변환기를 등록하려면 Goto - MVC config - Type Conversion, @Controller, @ControllerAdvice에만 적용되는 변환기를 등록하려면 Goto - DataBinder 참고
- Goto - property-placeholder와 같은 ${...} 표현식 사용 가능. 앱 시작 시 평가된다
Pattern Comparison
URL 하나가 여러 패턴에 매칭되는 경우, 가장 일반적이지 않은 패턴이 선택된다. 우선순위는 AntPathMatcher.AntPatternComparator.html 참고
Suffix Match
- Before 5.3 : 기본적으로 .* 접미사 매칭이 수행된다. 예를 들어 패턴 "/person"은 "/person.pdf" 같은 URL도 매칭한다.
- 기능을 끄려면 PathMatchConfigurer.useSuffixPatternMatching(false) Goto - MVC config - Path Matching, ContentNegotiationConfigurer.favorPathExtension(false) Goto - MVC config - Content Types를 호출해야 한다
- 브라우저의 Accept 헤더를 일관적으로 해석하지 못하던 때에는 필수적이었지만, 요즘은 그렇지 않다. 5.3 버전부터는 기본적으로 false로 설정된다
- RFD 공격을 예방하기 위해, 아래를 모두 만족하는 경우 응답 헤더에 Content-Disposition:inline;filename=f.txt를 설정한다
- URL에 파일 확장자가 존재
- 확장자가 안전하지 않고 컨텐츠 협상 대상도 아닌 경우
Media Types
- Content-Type:application/json만 처리
- Content-Type:application/json만 처리 안 함
- Accept:application/json만 처리
- Accept:application/json만 처리 안 함
- 클래스 수준의 consumes, produces 가능
- MediaType 클래스에 APPLICATION_JSON_UTF8_VALUE 등 상수 존재
↓ java
↓ java
↓ java
↓ java
클래스와 메서드 모두에 적용하는 경우, 메서드의 것만 적용
Parameters, Headers
- 요청 파라미터에 myParam이 존재하는 경우만 처리
- 요청 파라미터에 myParam이 존재하지 않는 경우만 처리
- 요청 파라미터에 myParam 값이 "myValue"인 경우만 처리
- 헤더의 경우 params 대신 headers 이용
↓ java
↓ java
↓ java
Handler Methods
Method Arguments
- 아래 조건을 모두 만족하는 경우, java.util.Optional을 결합하여 사용할 수 있다
- required 속성이 있는 @RequestParam, @RequestHeader 등의 애너테이션이 적용됨
- required=false임
Argument | Description |
---|---|
WebRequest, NativeWebRequest | 서블릿 API를 직접 사용하지 않고 요청에 대한 일반적인 접근 제공 |
ServletRequest, ServletResponse | HttpServletRequest, MultipartRequest 등 임의 서브클래스 사용 가능 |
HttpSession | 세션이 반드시 존재하도록 한다. 동시성이 필요한 경우 RequestMappingHandlerAdapter 인스턴스의 synchronizeOnSession를 true로 설정 |
PushBuilder | HTTP/2 리소스 푸시를 위한 Servlet 4.0 push builder API 제공. 클라이언트가 HTTP/2를 지원하지 않으면 null |
Principal | 현재 인증 유저 |
HttpMethod | HTTP 요청 타입 |
Locale, TimeZone, ZoneId | 현재 로캐일, 타임존 |
InputStream, Reader | Raw 요청 접근 |
OutputStream, Writer | 응답을 직접 쓰려는 경우 사용 |
@PathVariable | URI 경로 획득 Goto - URI Patterns |
@MatrixVariable | URI 상의 이름-값 쌍 획득 Goto - Matrix Variables |
@RequestParam | 서블릿 요청 파라미터(URL query, Form data 모두 해당) 획득. Multi-part 파일도 가능 |
@RequestHeader | 요청 헤더 획득 |
@CookieValue | 쿠키 획득 |
@RequestBody | HTTP 요청 body 획득. HttpMessageConverter를 통해 지정 타입으로 변환된다 |
HttpEntity<B> | 요청 헤더(HttpHeaders) + body(B) 획득. body는 HttpMessageConverter를 통해 지정 타입으로 변환된다 |
@RequestPart | multipart/form-data 요청 획득. 각 part는 HttpMessageConverter를 통해 지정 타입으로 변환된다 |
Map, Model, ModelMap | 렌더링에 사용할 모델 |
RedirectAttributes | 리다이렉션 쿼리에 덧붙일 속성Goto - Redirect Attributes, 리다이렉션 이후 요청까지 임시로 저장할 속성Goto - Flash Attributes |
@ModelAttribute | 기저 모델에 존재하는 객체 획득(없으면 생성). 기저와 바인딩되며, 존재하는 검증도 수행된다. Goto - @ModelAttribute. 바인딩을 원하지 않는 경우 @ModelAttribute(binding=false) 설정 |
Errors, BindingResult | @ModelAttribute 인자의 검증 및 바인딩 수행 시 발생한 예외 획득. @RequestBody 또는 @RequestPart 검증 시 발생한 예외 획득. 각 Errors, BindingResult 인자는 검증 대상 바로 다음에 위치해야 한다 예 |
SessionStatus + class-level @SessionAttributes | Form 처리가 완료되면 @SessionAttributes로 저장 중인 값을 정리하기 위한 SessionStatus 획득 Goto - @SessionAttribute |
UriComponentsBuilder | 요청 host, port, scheme, context path, servlet mapping으로 구성된 builder 획득 |
@SessionAttribute | 세션에 저장된 임의 인스턴스 접근 Goto - @SessionAttribute |
@RequestAttribute | 임의 요청 속성 접근 Goto - @RequestAttribute |
임의 타입 | 위 타입 중 어느 것에도 일치하지 않는 단순 타입은 @RequestParam, 그 외에는 @ModelAttribute |
Return Values
Return Type | Description |
---|---|
@ResponseBody | HttpMessageConverter 인스턴스가 반환값을 적절히 응답에 쓴다 Goto - @ResponseBody |
HttpEntity<B>, ResponseEntity<B> | HTTP 헤더(HttpHeaders)와 바디(B)를 포함한 전체 응답을 반환하면 HttpMessageConverter가 적절히 응답에 쓴다Goto - ResponseEntity |
HttpHeaders | 응답 헤더 반환. body는 empty |
String | 뷰 이름을 반환하면 ViewResolver가 적절히 뷰를 선택한다 |
View | 렌더링할 뷰 |
Map, Model, @ModelAttribute AnyType | 묵시적 모델에 추가될 속성 |
ModelAndView | 뷰 + 모델 |
void | void 리턴, null 리턴이 아래 상황에서 이루어졌다면 응답이 완료됐다고 간주한다
|
DeferredResult<V>, ListenableFuture<V>, CompletionStage<V>, CompletableFuture<V> | 위 임의 타입을 임의 스레드에서 비동기적으로 반환 Goto - DeferredResult |
Callable<V> | 위 임의 타입을 Spring MVC 관리 스레드에서 비동기적으로 반환 Goto - Callable |
ResponseBodyEmitter, SseEmitter | 비동기적으로 객체를 전달하면 HttpMessageConverter가 변환하여 쓴다. ResponseEntity의 body로 이용 가능. Goto - HTTP Streaming |
StreamingResponseBody | 비동기적으로 OutputStream에 쓴다. ResponseEntity의 body로 이용 가능. Goto - HTTP Streaming |
Reactive types — Reactor, RxJava, or others through ReactiveAdapterRegistry | 한 번에 여러 값을 전송하는 경우 DeferredResult 대안 |
Type Conversion
문자열로 표현되는 요청 인자 ─ @RequestParam, @RequestHeader, @PathVariable, @MatrixVariable, @CookieValue ─ 들을 String 외의 타입으로 받으려면 변환이 필요하다
기본 타입과 Date 등 일부 클래스는 자동으로 변환이 가능하다. 전역적인 변환기를 등록하려면 Goto - MVC config - Type Conversion, @Controller, @ControllerAdvice에만 적용되는 변환기를 등록하려면 Goto - DataBinder 참고
Matrix Variables
- Java Config
- XML Config
- Matrix Variable 예
- 각 path part 모두 matrix variable을 가질 수 있다
- 각 path part 별로 matrix variable을 모아 받을 수 있다
@RequestParam, @RequestBody
- Get 요청 query 예
- 배열 또는 리스트를 이용해 동일 이름의 파라미터들을 모을 수 있다
- Map<String, String>, MultiValueMap<String, String>로 전체 파라미터를 모을 수 있다
- Post 요청 Jackson JSON 예
@RequestHeader
특정 이름의 헤더, 또는 전체 헤더 ─ Map<String, String>, MultiValueMap<String, String>, HttpHeaders ─ 획득 가능
@ModelAttribute
- @ModelAttribute로 선언된 인자는 아래 순서에 따라 획득된다
- 이미 Model에 추가된 객체
- @SessionAttribute로 저장된 객체Goto - @SessionAttribute
- URI path variable로부터 변환(Converter)
- 기본생성자 호출
- Primary 생성자 호출. 생성 인자는 서블릿 요청으로부터 결정
@SessionAttributes, @SessionAttribute
- @SessionAttributes는 자동으로 세션에 객체를 저장하고, 완료 시 제거하는 기능을 제공한다
- @SessionAttribute로 세션 객체를 획득할 수 있다
@RequestAttribute
Filter, HandlerInterceptor 등에 의해 추가된 속성을 획득할 수 있다
Redirect Attributes
- 기본적으로 Model의 속성들은 리다이렉트 URL에 포함된다
- 속성의 자동 추가를 원하지 않는다면, @RequestMapping 메서드에서 RedirectAttributes 매개변수를 명시적으로 이용하면 된다
- 전역적으로 설정을 변경하려면 RequestMappingHandlerAdapter의 ignoreDefaultModelOnRedirect 속성을 true로 변경하면 된다
하위 호환을 위해 false가 기본값이다
Flash Attributes
- Flash attribute는 다음 요청에서 소비하기 위해 일시적으로 세션에 저장하는 데이터다. 주로 리다이렉션 처리에 이용된다
- 임시 속성을 담는 데 FlashMap이 이용되고, FlashMapManager이 FlashMap을 관리한다
- FlashMap은 이전 요청으로부터 전달되는 input, 다음 요청으로 전달할 output으로 구분된다
- @RequestMapping 메서드에서는 input-output FlashMap을 직접 이용하지 않고 RedirectAttributes를 획득하여 사용하면 된다 Goto - Redirect Attributes
- RedirectView는 FlashMap 인스턴스에 대해 리다이렉트 URL과 시각을 기록함으로써, 실제 리다이렉트된 요청에 임시 속성을 전달하게 한다
그럼에도 불구하고 비동기적으로 빈번하게 요청이 들어오는 경우, 실제로 전달되어야 할 곳이 아닌 다른 요청에 임시 속성을 전달될 가능성이 있다
@ResponseBody
- 메서드에 @ResponseBody가 적용된 경우 반환 객체를 HTTP 응답으로(HttpMessageConverter) 전송한다. AJAX에 유용
- Jackson JSON 예
ResponseEntity
예
Model
- @Controller 또는 @ControllerAdvice 클래스의 메서드에 @ModelAttribute를 적용하면 @RequestMapping 전에 호출되어 모델을 구성한다
- @ControllerAdvice 안에서 이용하는 경우 여러 컨트롤러에 공유하는 효과가 있다
- @ModelAttribute 메서드는 @RequestMapping 메서드가 사용하는 매개변수 타입들을 사용할 수 있다 ─ @ModelAttribute, 요청 body 관련 부분 제외
DataBinder
- @Controller, @ControllerAdvice 클래스는 @InitBinder 메서드에서 WebDataBinder 인스턴스를 초기화할 수 있다
- @InitBinder 메서드는 @RequestMapping 메서드가 사용하는 매개변수 타입들을 사용할 수 있다 ─ @ModelAttribute 제외
- 전역적인 변환기 등록은 Goto - MVC config - Type Conversion 참고
이를 통해 PropertyEditor, Converter, Formatter를 등록할 수 있다
Exceptions
- @Controller, @ControllerAdvice 클래스는 예외 처리를 위한 @ExceptionHandler 메서드를 가질 수 있다
- 발생한 최상위 예외 또는 가장 가까운 원인 예외 타입을 기준으로 매칭된다
- 둘 이상의 예외 타입을 처리하려는 경우, 공통 부모 클래스를 인자로 하면 된다
- value 속성으로 특정 예외들을 명시 가능
- @ControllerAdvice 예
↓ java
Method Arguments
Argument | Description |
Exception | 발생 예외. 임의 서브클래스 사용 가능 |
HandlerMethod | 예외 발생 메서드 |
WebRequest, NativeWebRequest | 서블릿 API를 직접 사용하지 않고 요청에 대한 일반적인 접근 제공 |
ServletRequest, ServletResponse | HttpServletRequest, MultipartRequest 등 임의 서브클래스 사용 가능 |
HttpSession | 세션이 반드시 존재하도록 한다. 동시성이 필요한 경우 RequestMappingHandlerAdapter 인스턴스의 synchronizeOnSession를 true로 설정 |
Principal | 현재 인증 유저 |
HttpMethod | HTTP 요청 타입 |
Locale, TimeZone, ZoneId | 현재 로캐일, 타임존 |
OutputStream, Writer | 응답을 직접 쓰려는 경우 사용 |
Map, Model, ModelMap | 예외 응답을 위한 모델. 항상 비어있음 |
RedirectAttributes | 리다이렉션 쿼리에 덧붙일 속성Goto - Redirect Attributes, 리다이렉션 이후 요청까지 임시로 저장할 속성Goto - Flash Attributes |
@SessionAttribute | 세션에 저장된 임의 인스턴스 접근 Goto - @SessionAttribute |
@RequestAttribute | 임의 요청 속성 접근 Goto - @RequestAttribute |
Return Values
Return | Description |
@ResponseBody | HttpMessageConverter 인스턴스가 반환값을 적절히 응답에 쓴다 Goto - @ResponseBody |
HttpEntity<B>, ResponseEntity<B> | HTTP 헤더와 바디를 포함한 전체 응답을 반환하면 HttpMessageConverter가 적절히 응답에 쓴다 Goto - ResponseEntity |
String | 뷰 이름을 반환하면 ViewResolver가 적절히 뷰를 선택한다 |
View | 렌더링할 뷰 |
Map, Model, @ModelAttribute AnyType | 묵시적 모델에 추가될 속성 |
ModelAndView | 뷰 + 모델 |
void | void 리턴, null 리턴이 아래 상황에서 이루어졌다면 응답이 완료됐다고 간주한다
|
REST API 전역 예외 처리
@ControllerAdvice 클래스가 ResponseEntityExceptionHandler를 상속함으로써 스프링 내부 예외를 자동으로 처리하고 ResponseEntity로 반환할 수 있다
Controller Advice
- @ExceptionHandler, @InitBinder, @ModelAttribute 메서드는 @Controller 뿐만 아니라 @ControllerAdvice, @RestControllerAdvice 클래스도 가질 수 있다
- @RestControllerAdvice = @ControllerAdvice + @ResponseBody로, @ExceptionHandler 메서드가 응답을 반환함을 의미한다
- @ExceptionHandler 메서드는 @Controller의 것이 먼저 실행되고, @InitBinder, @ModelAttribute 메서드는 @Controller의 것이 나중에 실행된다
적용 범위를 제한하지 않은 경우(default) 모든 요청에 적용된다
Functional Endpoints
Overview
- WebMvc.fn에서 HTTP 요청은 HandlerFunction을 통해 처리된다
- HTTP 요청에 대한 HandlerFunction의 선택은 RouterFunction을 통해 이루어진다
- 정의된 RouterFunction 빈들은 RouterFunction#andOther()를 통해 합쳐진다
- RouterFunctions.route()가 RouterFunction 빌더를 제공한다
↓ @FunctionalInterface HandlerFunction
↓ @FunctionalInterface RouterFunction
HandlerFunction
- ServerRequest
- ServerResponse
HTTP 요청에 대한 불변 객체. HTTP 요청 방식, URI, 헤더, 쿼리 인자, 요청 본문(body)을 제공한다
HTTP 응답에 대한 불변 객체.
RouterFunction
- RequestPredicates 유틸리티 클래스가 유용한 RequestPredicate들을 제공한다
- 각 라우터는 순서대로 평가되고, 가장 처음으로 매칭되는 핸들러가 요청을 처리한다
- 중첩 경로에 대한 라우팅 예
RequestPredicate#and, or을 이용해 여러 predicate을 조합할 수 있다
↓ java
Filtering Handler Functions
before, after, filter를 이용해 사전/사후 작업을 정의할 수 있다
↓ java
URI Links
UriComponents
↓ java
↓ java
UriBuilder
↓ java
URI Encoding
- UriComponentsBuilder#encode()
- UriComponents#encode()
- DefaultUriBuilderFactory#setEncodingMode()
- EncodingMode.TEMPLATE_AND_VALUES Since 5.0.8 : URI 템플릿을 먼저 인코딩. URI 변수들은 할당되는대로 인코딩
- EncodingMode.URI_COMPONENT : URI 변수들이 할당된 후 전체 인코딩
- EncodingMode.VALUES_ONLY : URI 변수만 UriUtils#encodeUriVariables()으로 인코딩
- EncodingMode.NONE : 인코딩 안 함
URI 템플릿을 먼저 인코딩. URI 변수들은 할당되는대로 인코딩
URI 변수들이 할당된 후 전체 인코딩
Relative Servlet Requests
↓ java
Links in Controllers
↓ java
↓ java
Asynchronous Requests
Configuration
요청 비동기 처리를 위해 서블릿 컨테이너 수준의 옵션을 설정해야 한다
Servlet Container
- Java Config
- web.xml
- DispatcherServlet, Filter 정의에 <async-supported>true</async-supported>
- 필터 매핑에 <dispatcher>ASYNC</dispatcher>
AbstractAnnotationConfigDispatcherServletInitializer를 이용하면 된다
Spring MVC
- Java Config
- XML Config
WebMvcConfigurer의 configureAsyncSupport 재정의
<mvc:annotation-driven> 안에 <async-support> 정의
- ↓ 설정 가능한 옵션들
- timeout : 설정하지 않으면 서블릿 컨테이너의 것이 적용된다
- AsyncTaskExecutor : 설정하지 않으면 SimpleAsyncTaskExecutor가 이용된다
- DeferredResultProcessingInterceptor, CallableProcessingInterceptor
timeout은 DeferredResult, ResponseBodyEmitter, SseEmitter, WebAsyncTask 각각에서도 설정 가능하다
DeferredResult
별개 스레드에서 값을 쓴다
Callable
Callable에서 값을 반환한다
Processing
Interception
- WebMvcConfigurer 예
- DeferredResult#onTimeout 예
- WebAsyncTask 예
Compared to WebFlux
- Servlet 3.0에 추가된 비동기 처리는 Filter-Servlet 체인에서 벗어나 ─ 컨테이너 스레드는 해제된다 ─ 결과를 대기한다
- 반면 WebFlux는 Servlet API를 따르지 않으며, 최초 설계부터 비동기적이다
응답을 쓰기 위해 별도 스레드가 이용되며, IO 자체는 blocking이다
요청 처리에 대한 모든 단계가 비동기적으로 처리될 수 있다. IO는 non-blocking이다
HTTP Streaming
ResponseBodyEmitter
각 객체들은 HttpMessageConverter를 통해 변환된다.
SseEmitter
ResponseBodyEmitter의 서브클래스로, Server-Sent Events를 지원한다.
StreamingResponseBody
Converter를 거치지 않고 OutputStream에 직접 쓴다
Disconnects
Servlet API는 클라이언트 연결 종료를 통지하지 않음에 유의
CORS
@CrossOrigin
- ↓ 기본 설정
- 모든 origin 허용
- 모든 header 허용
- 매핑에 일치하는 모든 HTTP method 허용
- allowedCredentials 비허용
- maxAge 30분
Global Configuration
- Java Configuration
- XML Configuration
↓ java
↓ xml
CorsFilter
↓ java
HTTP Caching
CacheControl
Cache-Control 헤더 정보를 의미한다
↓ java
Controllers
↓ java
View Technologies
Thymeleaf
Spring MVC 연동은 Thymeleaf 프로젝트가 관리한다. 다음 빈들이 필요하다 ─ ServletContextTemplateResolver, SpringTemplateEngine, ThymeleafViewResolver
FreeMarker
예
JSP, JSTL
View Resolvers
ResourceBundleViewResolver 예
InternalResourceViewResolver 예
JSPs versus JSTL
JSTL(JSP Standard Tag Library)을 이용할 때에는 뷰 클래스로 JstlView를 이용해야 한다
Spring’s JSP Tag Library
spring-webmvc.jar 안의 spring.tld에 스프리이 제공하는 태그 정의가 담겨 있다. 또는 package.description 참고
MVC Config
MVC 구성 켜기
- Java Configuration
- XML Configuration
↓ java
↓ xml
Type Conversion
- @Controller, @ControllerAdvice에만 적용되는 변환기를 등록하려면 Goto - DataBinder 참고
- 전역 Java Configuration 예
- XML에서는 FormattingConversionServiceFactoryBean 빈을 정의하면 된다
Validation
- Goto - Bean Validation(예. Hibernate Validator)가 클래스패스에 존재한다면 LocalValidatorFactoryBean이 전역적으로 등록되어 @Valid, Validated를 컨트롤러 메서드에서 사용할 수 있다
- 전역 Validator 설정
- Java Configuration
- XML Configuration
- 컨트롤러 수준 Validator 설정
직접 LocalValidatorFactoryBean를 정의하는 경우, @Primary를 붙여 충돌을 피해야 한다
↓ java
↓ xml
↓ java
Interceptors
- Java Configuration 예
- XML Configuration 예
addInterceptors(InterceptorRegistry)를 재정의하여 추가
mvc:interceptors 태그 안에 작성
Content Types
- ContentNegotiationConfigurer를 이용해 확장자별 응답 Content-Type을 조정할 수 있다
- XML Configuration
ContentNegotiationManagerFactoryBean 빈 정의
Message Converters
- WebMvcConfigurer#configureMessageConverters() 재정의
- WebMvcConfigurer#extendMessageConverters() 재정의
- Spring MVC 기본 컨버터
- StringHttpMessageConverter, FormHttpMessageConverter 등 기본 등록
- 클래스패스에 존재하는 라이브러리에 맞춰 MappingJacksonHttpMessageConverter 등 등록
이 메서드에서 컨버터를 추가하는 경우, Spring MVC 기본 컨버터들은 등록되지 않는다
Spring MVC 기본 컨버터 등록 후 호출되어 사용자 컨버터 등록, 컨버터 수정 등 작업 가능
Static Resources
↓ java
↓ xml
Default Servlet
- DispatcherServlet이 "/" ─ 즉, 모든 요청을 받으면서도 static 리소스 또한 처리할 수 있다
- Java Configuration
- XML Configuration
↓ java
↓ xml
Path Matching
- PathMatchConfigurer.html를 이용해 여러 옵션을 설정할 수 있다
- Java Configuration
- XML Configuration
↓ java
↓ xml
Advanced Java Config
@EnableWebMvc는 DelegatingWebMvcConfiguration를 임포트하며, 이는 아래 작업을 수행한다
- Spring MVC 기본 설정
- WebMvcConfigurer 탐지 후 각 설정 메서드 호출
고급 설정을 원하는 경우 @EnableWebMvc를 제거하고 DelegatingWebMvcConfiguration를 직접 상속하면 된다
REST Clients
RestTemplate
동기적인 HTTP 요청을 수행한다. Spring 5.0부터는 WebClient를 대신 이용할 것이 권장된다
WebClient Since 5.0
- Non-blocking, 리액티브 HTTP 클라이언트
- 함수형 스타일로, Java 8 람다 사용 가능
- 테스트를 위한 WebTestClient도 존재
동기적인 요청도 가능
WebSockets
Introduction to WebSocket
- WebSocket 프로토콜은 1개의 클라이언트-서버 TCP 연결 위에서 전이중, 양방향 통신 채널을 수립하는 표준을 제공한다
- 저수준 전송 프로토콜로, 클라이언트-서버 상호 합의된 임의 형태의 데이터 전송이 가능하다
- HTTP/1.1 업그레이드 기능을 이용해 기존 HTTP/HTTPS를 웹 소켓 연결 WS/WSS로 그대로 전환
- WebSocket 서버 앞에 Nginx와 같은 웹서버가 존재하는 경우, 업그레이드 요청을 전달하도록 설정 필요
- 외부 프록시가 장기간 idle한 연결을 강제로 끊을 수도 있음에 유의
Sec-WebSocket-Protocol 헤더로 미리 전송 형태를 합의할 수 있다
사용하던 80/443포트 그대로 연결하므로 방화벽의 차단에도 안전. 프록시 서버가 HTTP 업그레이드를 처리하지 않는다면 WSS를 이용
마찬가지로 클라우드 환경에서도 별도 설정이 필요할 수 있다
WebSocket API Since JavaEE 7
javax.websocket : 클라이언트/서버 공통 기능
- WebSocketContainer
- ContainerProvider
- @ClientEndpoint
- @OnOpen, @OnClose
- @OnError
- @interface OnMessage
- 텍스트 메시지 처리
- 전체 메시지 String
- 메시지로부터 변환되는 Java primitive or class
- 메시지 청크 String and boolean pair : 마지막 메시지는 true
- 전체 메시지 Reader
- Decoder.Text or Decoder.TextStream
- 이진 메시지 처리
- 전체 메시지 byte[] or ByteBuffer
- 메시지 청크 (byte[] or ByteBuffer) and boolean pair : 마지막 메시지는 true
- 전체 메시지 InputStream
- Decoder.Binary or Decoder.BinaryStream
- 퐁 메시지 처리 : PongMessage
웹소켓 클라이언트 기능. connectToServer()
WebSocketContainer 인스턴스 획득을 위한 getWebSocketContainer()
POJO가 클라이언트측 웹소켓임을 나타낸다. @OnOpen, @OnClose, @OnError, @OnMessage 메서드를 가질 수 있다
선택적 Session 매개변수, 선택적 EndpointConfig 매개변수, 0 ~ n개 @PathParam String 매개변수를 가질 수 있다
선택적 Session 매개변수, Throwable 매개변수, 0 ~ n개 @PathParam String 매개변수를 가질 수 있다
선택적 Session 매개변수, 0 ~ n개 @PathParam String 매개변수, 아래 중 하나의 메시지 매개변수를 가질 수 있다
javax.websocket.server : 서버 전용 기능
- ServerContainer extends WebSocketContainer
ServerEndpointConfig 인스턴스 또는 @ServerEndpoint 클래스 등록
Spring WebSocket API
WebSocketHandler
- 서버측 WebSocket을 생성하려면 WebSocketHandler, TextWebSocketHandler, BinaryWebSocketHandler를 상속하면 된다
- WebSocketHandler 등록 - Java Configuration
- WebSocketHandler 등록 - XML Configuration
- 동시성이 요구되는 경우, ConcurrentWebSocketSessionDecorator를 이용해 한 번에 한 스레드만 WebSocketSession를 이용해 전송하게 할 수 있다
- WebSocketHandlerDecorator를 이용해 WebSocketHandler를 장식할 수 있다
↓ java
↓ xml
ExceptionWebSocketHandlerDecorator는 임의 WebSocketHandler 메서드에서 처리되지 않은 예외 발생 시 1011 상태로 세션을 종료한다
WebSocket Handshake
- HandshakeInterceptor를 이용해 WebSocket 연결 수립 전후 작업을 정의할 수 있다
- HttpSessionHandshakeInterceptor 등록 - Java Configuration
- HttpSessionHandshakeInterceptor 등록 - XML Configuration
- 연결 수립 자체에 참여해야 한다면 DefaultHandshakeHandler를 상속하면 된다
↓ java
↓ xml
Deployment
Spring MVC를 사용하지 않더라도, WebSocketHttpRequestHandler를 이용해 웹소켓 요청을 처리할 수 있다
Server Configuration
- 버퍼 사이즈, idle timeout 등의 설정이 가능하다
- Tomcat, WildFly, GlassFish 설정을 위해 ServletServerContainerFactoryBean를 정의할 수 있다
- Jetty 설정을 위해 WebSocketServerFactory 인스턴스를 DefaultHandshakeHandler에 삽입할 수 있다
- 클라이언트 설정을 위해 WebSocketContainerFactoryBean (XML) 또는 ContainerProvider.getWebSocketContainer() (Java configuration)를 사용할 수 있다
↓ java
Allowed Origins
- Since 4.1.5 : 기본적으로 같은 origin 요청만 허용
- 모든 origin 허용 : *로 설정
- 일부 origin 허용 : http://, https://로 시작하는 origin들을 설정
X-Frame-Options 헤더가 SAMEORIGIN으로 설정된다
↓ Java Configuration
↓ XML Configuration
SockJS Fallback
Overview
SockJS는 웹소켓 연결에 실패하더라도 HTTP 기반의 에뮬레이션을 통해 애플리케이션 API를 지원한다
- SockJS의 구성
- SockJS Protocol
- SockJS JavsScript client
- SockJS server implementation : spring-websocket이 이에 해당
spring-websocket 4.1부터는 SockJS Java client도 포함
SockJS 클라이언트는 GET /info 요청으로 서버 정보를 획득한 후, 전송 수단을 선택한다 가능한 경우 WebSocket을 사용하고, 그 외엔 브라우저 별로 지원되는 HTTP 스트리밍, 최후에는 HTTP (long) polling을 이용한다
각 브라우저별 지원 사항은 #supported-transports-by-browser 참고. 각 전송 방법의 차이는 https://spring.io/blog/2012/05/08/spring-mvc-3-2-preview-techniques-for-real-time-updates/ 참고
모든 요청 URL은 아래의 꼴을 따른다
↓ text
- server-id : 요청을 클러스터로 묶을 경우에만 사용
- session-id : SockJS 세션
- transport : 전송 타입(websocket, xhr-streaming)
서버는 세션 수립 후 'o'(open frame), 25초(default) 동안 idle이면 'h'(heartbeat frame), 종결 시 'c'(close frame)를 전송한다
Enabling SockJS
↓ Java Configuration
↓ XML Configuration
Spring MVC를 사용하지 않더라도, SockJsHttpRequestHandler를 이용해 SockJS를 이용할 수 있다
SockJS and CORS
CORS가 이미 허용된 경우, CORS를 허용하지 않는 경우(AbstractSockJsService#setSuppressCors(true)) 아래 설정을 하지 않는다
- Access-Control-Allow-Origin : 요청 Origin 헤더로 설정
- Access-Control-Allow-Credentials : true
- Access-Control-Request-Headers : 요청 헤더 값으로 설정
- Access-Control-Allow-Methods : 지원 타입(TransportType)으로 설정
- Access-Control-Max-Age : 31536000(1년)
CORS 설정에서 WebSocket endpoint prefix를 제외하여 SockJsService가 처리하도록 하는 걸 고려해볼 것
SockJsClient
브라우저 없이 직접 WebSocket 서버에 연결하는 클라이언트를 제공한다. 지원 타입은 websocket, xhr-streaming, xhr-polling
- websocket : Transport 이용
- xhr : XhrTransport 이용
- RestTemplateXhrTransport : HTTP 요청에 Spring RestTemplate 사용
- JettyXhrTransport : HTTP 요청에 Jetty HttpClient 사용
WebSocketTransport의 생성에는 StandardWebSocketClient(JSR-356), JettyWebSocketClient(Jetty 9+), 임의 WebSocketClient 구현체 사용 가능
↓ SockJsClient 생성 및 연결
STOMP
WebSocket 통신은 text 또는 binary 메시지 전송으로 이루어지며, 원활한 통신을 위해 sub-protocol을 이용할 수 있다
Overview
STOMP(Simple Text Oriented Messaging Protocol)는 양방향 네트워크 상에서 메시지 전송을 위한 최소 규격을 정의한다
↓ STOMP Frame
- Command 종류
- SEND : 클라이언트 -> 서버 전송
- SUBSCRIBE : 클라이언트 -> 서버 구독
- MESSAGE : 서버 -> 클라이언트 브로드캐스트
이하 생략 - 사용하게 되면 정리
Spring WebSocket 예
- javax.websocket-api, spring-websocket 필요
- @EnableWebSocket + WebSocketHandler 예
- @ServerEndpoint 예
- © Donggi Kim. MIT License
- w3css : No license
- highlight.js : BSD-3-Clause License
- MathJax : Apache License 2.0
- qrcodejs : MIT License