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"));
      }
      
Written on July 17, 2020