[이전 포스팅] @RequestMapping 심화(1)
https://kimcoder.tistory.com/513
1. RequestMappingHandlerMapping
1) RequestMappingHandlerMapping 소개
- 요청의 처리 단위로 클래스보다 메소드가 선호되기 시작하면서, Spring 3.1에서 DefaultAnnotationHandlerMapping 전략이 RequestMappingHandlerMapping 전략으로 대체되었다.
- RequestMappingHandlerMapping은 @RequestMapping 메소드의 정보를 추상화한 HandlerMethod 타입의 오브젝트를 핸들러 어댑터에 전달하는 전략을 사용한다.
2) @RequestMapping의 요청 조건
- Spring 3.0에서는 value(URL 패턴), method(HTTP 메소드), params(요청 파라미터), headers(HTTP 헤더)가 @RequestMapping의 요청 조건이었으며, Spring 3.1부터는 consumes(Content-type 헤더), produces(Accept 헤더)가 요청 조건에 추가되었다.
- 확장 포인트를 통해 등록되는 커스텀 조건까지 포함하면 총 7가지의 요청 조건을 사용해서 핸들러 메소드를 선정한다. 각 요청 조건은 RequestCondition 인터페이스의 구현체로 만들어져 있다. 커스텀 조건 역시 RequestCondition의 구현체를 만들어서 지정하면 된다.
- RequestMappingHandlerMapping bean을 이용하면 @RequestMapping의 요청 조건들이 최종적으로 어떻게 조합된건지 확인할 수 있다.
※ RequestMappingHandlerMapping의 getHandlerMethods() 참고
또는, org.springframework.web.servlet.handler 패키지를 기준으로 info 레벨 이상의 로그 조건을 걸어둬도 로그를 통해 매핑 내용을 확인할 수 있다.
3) 요청 조건에 사용되는 추가된 프로퍼티
(1) URL 패턴
- useSuffixPatternMatch : URL 패턴의 뒤에 ".*" 즉, 확장자를 포함시킬지 결정한다. 기본값은 true다.
- useTrailingSlashMatch : URL 패턴의 뒤에 "/"가 붙는 경우를 포함시킬지 결정한다. 기본값은 true다.
(2) Content-Type 헤더 : consumes 속성
- Content-Type 헤더 조건을 지정한다.
@RequestMapping(consumes="application/json")
위의 설정은 다음 설정과 동일하다.
@RequestMapping(headers="Content-Type=application/json")
- 여러 값을 넣은 경우에는 한 가지 컨텐트 타입이라도 일치하면 된다. 또, 클래스와 메소드의 consumes 조건은 결합되지 않고, 메소드 조건이 클래스 조건을 오버라이딩 한다.
(3) Accept 헤더 : produces 속성
- Accept 헤더 조건을 지정한다.
- 조건이 결합되는 방식은 consumes 속성과 동일하다.
2. RequestMappingHandlerAdapter
1) RequestMappingHandlerAdapter 소개
- RequestMappingHandlerMapping이 전달해준 HandlerMethod 오브젝트의 정보를 이용해 컨트롤러 메소드를 실행한다.
- Spring 3.1에서 RequestMappingHandlerAdapter를 사용하게 되면서 메소드 파라미터와 리턴 타입에 몇 가지 타입과 어노테이션이 추가되었다.
2) 파라미터 타입
- Spring 3.1에서 새롭게 추가되었거나 적용 방식에 변화가 있는 파라미터 타입을 살펴보자.
(1) @Validated
- @Validated는 @Valid의 기능을 포함하고 있으며, 모델 오브젝트를 검증할 때 특정 그룹의 제약 조건을 적용하고 싶을 때 사용되는 어노테이션이다. 주로 내용이 없는 마커 인터페이스를 통해 그룹을 나타낸다.
- 먼저, 다음과 같이 JSR-303 빈 검증기 어노테이션의 groups 속성에 마커 인터페이스를 넣어서 그룹을 지정해준다. 여러 그룹을 검증 수행 대상으로 만들 수도 있다.
@NotEmpty(groups={Admin.class, User.class})
String name;
@NotEmpty(groups=Admin.class)
String adminId;
@NotEmpty(groups=User.class)
String userId;
그리고 컨트롤러 메소드에 가서 검증이 필요한 파라미터에 @Validated 어노테이션을 추가하고 디폴트 속성에 제약 조건의 그룹을 지정해주면 된다. 여러 그룹을 지정해야 한다면 {}를 이용하면 된다.
@RequestMapping("/user")
public String method(@ModelAttribute("user") @Validated(User.class) User user) {...}
위의 예시에서는 name, userId 필드에 대해 @NotEmpty 검증이 수행될 것이다.
- @Validated 대신 @Valid를 이용하면 groups로 지정한 그룹과 상관없이 모든 제약 조건이 적용된다.
(2) @Valid와 @RequestBody
- Spring 3.1에서 @Valid와 @RequestBody를 같이 사용할 수 있게 되었다. 그래서 메시지 컨버터 클래스를 확장할 필요 없이 모델 오브젝트와 같은 방법으로 @RequestBody 파라미터를 간단히 검증할 수 있게 되었다.
- 검증에 오류가 있다면 MethodArgumentNotValidException 예외가 발생하고, 이 예외를 처리하지 않으면 400 Bad Request 에러가 발생한다.
(3) UriComponentsBuilder
- URI 정보를 추상화한 UriComponents를 생성해주는 빌더 클래스로, URI를 생성하거나 가공할 때 자주 사용된다.
- 문자열 하나에 여러 요소가 조합된 URI를 안전하고 편리하게 다룰 수 있다.
- UriComponentsBuilder의 사용법은 매우 다양하기 때문에 관련 문서나 예시들을 살펴보며 본인에게 가장 맞는 방식으로 사용하면 된다.
- 컨트롤러 메소드에 UriComponentsBuilder 타입의 파라미터를 넣으면 현재 요청의 URI를 기준으로 생성된 UriComponentsBuilder를 받을 수 있다.
@RequestMapping
public String method(UriComponentsBuilder uriComponentsBuilder) {...}
(4) RedirectAttributes
- Redirect는 컨트롤러에서 새로운 URL로 요청을 다시 보내는 응답 방식을 말한다.
- 컨트롤러에서 POST 요청을 받아서 처리한 뒤에는 리다이렉트를 통해 GET 방식의 새로운 URL로 다시 요청하는 것이 좋다. 브라우저에는 폼의 마지막 전송 결과가 남아있기 때문에 페이지를 갱신하면 폼이 다시 전송될 위험이 있기 때문이다.
- 컨트롤러에서 String 타입으로 뷰를 반환하는 방식이라면, 앞에 redirect: 접두사를 붙여 반환하면 리다이렉트가 이루어진다.
@RequestMapping
public String method() {
...
return "redirect:/result";
}
- 모델 오브젝트는 리다이렉트된 곳에서도 유지된다. 모델에 추가된 오브젝트는 기본적으로 파라미터 이름과 값으로 전환돼서 리다이렉트 URL에 자동으로 추가되는 원리다.
model.addAttribute("status", "on");
return "redirect:/result"; // "/result?status=on"
또 다른 방법으로, 모델을 이용해 URL 경로 파라미터에 값을 넣을 수도 있다.
model.addAttribute("user", 4);
return "redirect:/result/{user}"; // "/result/4"
참고로, 경로 파라미터가 @RequestMapping에 있다면 @PathVariable을 이용하지 않고도 바로 리다이렉트 URL의 경로 파라미터에 들어가도록 할 수 있다.
@RequestMapping("/save/{userId}")
public String method(...) {
...
return "redirect:/result/{userId}";
}
모델을 이용해 URL에 들어갈 애트리뷰트를 설정하는 방식을 사용하게 되면 모든 모델 정보가 URL에 포함되어버린다는 단점이 있다. 이를 해결하기 위해 다음과 같이 컨트롤러 메소드의 Model 파라미터로 모델을 가져와서 기존에 담겨 있는 모델 정보를 제거할 수 있다.
@RequestMapping
public String method(@ModelAttribute User user, Model model) {
...
model.asMap().clear(); // 모델 정보 제거
model.addAttribute("status", "on");
return "redirect:/result";
}
일일이 model.asMap().clear()을 작성하기 번거롭다면, Model 대신 RedirectAttributes를 사용해서 모델 대신에 RedirectAttributes에 담긴 정보를 활용해서 URL를 생성하도록 할 수 있다.
@RequestMapping
public String method(@ModelAttribute User user, RedirectAttributes redirectAttributes) {
...
redirectAttributes.addAttribute("status", "on");
return "redirect:/result";
}
※ RedirectAttributes는 Model의 서브타입이다.
※ RedirectAttribute 참고 : https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/mvc/support/RedirectAttributes.html, https://web-obj.tistory.com/455
- RedirectAttributes.addFlashAttribute() 메소드를 통해 플래시 애트리뷰트를 추가할 수도 있다. 플래시 애트리뷰트는 다음 요청에서 자동으로 모델에 추가된다.
3) 확장 포인트
(1) 컨트롤러 메소드에서 사용할 수 있는 파라미터 타입 추가
- Spring 3.0에서 AnnotationMethodHandlerAdapter을 사용했을 때는 customArgumentResolver 또는 customArgumentResolvers 속성에 WebArgumentResolver의 구현체를 적용했다.
※ https://kimcoder.tistory.com/533의 [4. WebArgumentResolver] 참고
반면에, RequestMappingHandlerAdapter를 사용할 때는 customArgumentResolver 또는 customArgumentResolvers 속성에 HandlerMethodArgumentResolver의 구현체를 적용하면 된다.
※ HandlerMethodArgumentResolver 문서 : https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/method/support/HandlerMethodArgumentResolver.html
(2) 컨트롤러 메소드에서 사용할 수 있는 리턴 타입 추가
- RequestMappingHandlerAdapter의 customReturnValueHandler 또는 customReturnValueHandlers 속성에 HandlerMethodReturnValueHandler의 구현체를 적용하면 된다.
※ HandlerMethodReturnValueHandler 문서 : https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/method/support/HandlerMethodReturnValueHandler.html
● 참고 자료 : 토비의 스프링 3.1 Vol.2
'Spring Series > Spring Framework' 카테고리의 다른 글
[Spring] AOP 포인트컷 표현식 (0) | 2022.10.12 |
---|---|
[Spring] 자바 코드를 이용한 MVC 전략 설정 (0) | 2022.10.06 |
[Spring] AnnotationMethodHandlerAdapter의 확장 (0) | 2022.09.29 |
[Spring] MVC 전용 태그 (0) | 2022.09.29 |
[Spring] 메시지 컨버터를 이용하는 Ajax 컨트롤러 (0) | 2022.09.28 |
댓글