2020-7-17 스프링 MVC(7) 핸들러메서드와 URI패턴
핸들러메서드
메서드 아규먼트와 리턴타입
- 핸들러 메서드 아규먼트 : 주로 요청 그 자체 또는 요청에 들어있는 정보를 받아오는데 사용
핸들러 메서드 아규먼트 | 설명 |
---|---|
WebRequest NativeWebReqeust ServletRequest(Response) HttpServletRequest(Response) |
요청, 응답 자체에 접근 가능한 API |
InputStream Reader OutputStream Writer |
요청 본문을 읽어오거나 응답 본문을 쓸때 사용하는 API |
PushBuilder | 스프링5, HTTP/2 리소스 푸쉬에 사용 |
HttpMethod | GET, POST… 에 대한 정보 |
Local TimeZone ZoneId |
LocalResolver가 분석한 요청의 Locale정보 |
@PathVariable | URI템플릿 변수 읽을 때 사용 |
@MatrixVariable | URI경로 중에 키/쌍을 읽을때 사용 |
@RequestParam | 서블릿 요청 매개변수 값을 선언한 메서드 아규먼트 타입으로 변호나해준다. 단순 타입인 경우 이 어노테이션을 생략 |
@RequestHandler | 요청 헤더값을 선언한 메서드 아규먼트 타입으로 변환해준다. |
@GetMapping(value = "/events")
@ResponseBody
public String getEvents(PushBuilder builder) {
builder.push();
return "events";
}
@GetMapping(value = "/events")
@ResponseBody
public String getEvents(Locale locale, TimeZone timeZone, ZoneId zoneId) {
locale.getDisplayCountry();
timeZone.getDSTSavings();
zoneId.getId();
return "events";
}
// LocaleResolver인터페이스를 활용해서 분석
- 핸들러 메서드 리턴 : 주로 응답 또는 모델을 렌더링할때 뷰에 대한 정보를 제공
@ResponseBody | 리턴값을 HttpMessageConverter를 사용해 응답 본문으로 사용한다 |
---|---|
HttpEntity ResponseEntity |
응답 본문 뿐 아니라 헤더정보까지 전체 응답을 만들때 사용 |
String | ViewResolver를 활용해 뷰를 찾을 때 사용할 뷰이름 |
View | 암묵적인 모델 정보를 렌더링할 뷰 인스턴스 => ViewResolver를 안탄다. |
Map Model |
(RequestToViewNameTranslator를 통해서) 암묵적으로 판단한 뷰 렌더링할 때 사용할 모델 정보에 추가??. 어노테이션은 생략가능 |
@GetMapping(value = "/events")
@ResponseBody
public ResponseEntity<String> getEvents() {
return ResponseEntity.ok().build();
}
URI패턴
@PathVariable
-
요청 URI패턴의 일부를 핸들러 메서드 아규먼트로 받는 방법
-
타입 변환 지원
-
(기본)값이 반드시 있어야한다.
-
Optional지원 => required=false로 한거랑 비슷
-
pathVariable을 쓰면 원래는 String이다. 하지만 spring이 int로 변환해준다.
-
@Controller public class SampleController { @GetMapping("/events/{id}") @ResponseBody public Event getEvent(@PathVariable int id) { Event event = new Event(); event.setId(id); return event; } } @RunWith(SpringRunner.class) @WebMvcTest public class SampleControllerTest { @Autowired private MockMvc mockMvc; @Test public void deleteEvent() throws Exception { mockMvc.perform(get("/events/1")) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("id").value(1)); } }
-
/events/1;/name/keesun; 이런식으로 하면 불편해서 @MatrixVariable이 등장했다.
- /events/{id}와 @PathVariable int id를 맞출필요는 없지만 맞추는게 좋다.
-
@MatrixVariable
-
요청 URI패턴에서 key/value쌍의 데이터를 메서드 아규먼트로 받는방법
-
타입변환 지원된다. 기본값 있어야한다. Optional지원
-
기본적으로 비활성화가 되어있어서 사용하려면 설정을 해야한다.
-
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void configurePathMatch(PathMatchConfigurer configurer) { UrlPathHelper urlPathHelper = new UrlPathHelper(); urlPathHelper.setRemoveSemicolonContent(false); configurer.setUrlPathHelper(urlPathHelper); } } @Controller public class SampleController { @GetMapping("/events/{id}") @ResponseBody public Event getEvent(@PathVariable int id, @MatrixVariable String name) { Event event = new Event(); event.setId(id); event.setName(name); return event; } } @RunWith(SpringRunner.class) @WebMvcTest public class SampleControllerTest { @Autowired private MockMvc mockMvc; @Test public void getEvent() throws Exception { mockMvc.perform(get("/events/1;name=jimmy")) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("id").value(1)); } }
-
요청 매개변수(단순타입) 및 폼서브밋
@RequestParam
-
요청 매개변수에 들어있는 단순 타입 데이터를 메서드 아규먼트로 받아올 수 있다.
-
값이 반드시 있어야한다. required=false, Optional을 이용할수도 있다.
-
String이 아닌 값들은 타입 컨버전을 지원한다.
-
Map<String, String> 또는 MultiValueMap<String, String>에 사용해서 모든 요청 매개변수를 받아올수도 있다.
-
생략가능하다. 하지만 생략안하는게 좋다.
-
요청매개변수란? 쿼리매개변수, 폼데이터
-
@Controller public class SampleController { @PostMapping("/events") @ResponseBody public Event getEvent(@RequestParam String name, @RequestParam int limit) { Event event = new Event(); event.setName(name); event.setLimit(limit); return event; } } @RunWith(SpringRunner.class) @WebMvcTest public class SampleControllerTest { @Autowired private MockMvc mockMvc; @Test public void getEvent() throws Exception { // Parameter로 보내기 mockMvc.perform(post("/events?name=jimmy&limit=3")) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("name").value("jimmy")) .andExpect(jsonPath("limit").value(3)); // Form으로 보내기 mockMvc.perform(post("/events") .param("name", "jimmy") .param("limit", "3")) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("name").value("jimmy")); } }
-
ModelAttirbute
-
여러 곳에 있는 단순 데이터를 복합 타입 객체로 받아오거나 해당 객체를 새로 만들때 사용.
-
URI패스, 요청 매개변수, 세션
-
값을 바인딩 할 수 없을때는? 400 에러
-
바인딩 에러를 직접다루고 싶으면 BindingResult타입의 아큐먼트를 바로 추가
-
바인딩 이후 검증을 추가로 하고 싶을때 @Valid @Validate를 사용
-
@Controller public class SampleController { // 1 @PostMapping("/events") @ResponseBody public Event getEvent(@ModelAttribute Event event, BindingResult bindingResult) { if (bindingResult.hasErrors()) bindingResult.getAllErrors().forEach(System.out::println); return event; } // 2 @PostMapping("/events") @ResponseBody public Event getEvent(@ModelAttribute Event event) { if (bindingResult.hasErrors()) bindingResult.getAllErrors().forEach(System.out::println); return event; } // 3 @PostMapping("/events") @ResponseBody public Event getEvent(Event event) { return event; } } @RunWith(SpringRunner.class) @WebMvcTest public class SampleControllerTest { @Autowired private MockMvc mockMvc; @Test public void getEvent() throws Exception { // Form으로 보내기 mockMvc.perform(post("/events") .param("name", "jimmy") .param("limit", "3a")) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("name").value("jimmy")); } }
-
테스트에서 3a와 같이 int로 변환이 안되는 경우 400가 발생. 하지만 BindingResult를 매개변수로 추가해주면 에러가 발생하지 않고 에러가 BindingResult에 담긴다.
- @ModelAttribute를 안붙이고 Event만 해도 되지만 협업을 위해 붙이는게 좋다.
-
@Validate
-
@Valid와 달리 @Validate를 사용하면 group을 지정할 수 있다.
-
@Controller public class SampleController { @GetMapping("events/form") public String eventForm(Model model) { Event newEvent = new Event(); newEvent.setLimit(50); model.addAttribute("event", newEvent); return "events/form"; } @PostMapping("/events") @ResponseBody public Event getEvent(@Validated(value = Event.ValidateLimit.class) @ModelAttribute Event event, BindingResult bindingResult) { if (bindingResult.hasErrors()) { System.out.println("==="); bindingResult.getAllErrors().forEach(c -> { System.out.println(c.toString()); }); } return event; } } public class Event { interface ValidateLimit {} interface ValidateName {} private int id; @NotBlank(groups = ValidateName.class) private String name; @Min(value = 0l, groups = ValidateLimit.class) private int limit; ... } @Test public void getEvent() throws Exception { // Form으로 보내기 mockMvc.perform(post("/events") .param("name", "jimmy") .param("limit", "-10")) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("name").value("jimmy")); }
-