2020-7-14 스프링 MVC(5) 어노테이션 기반 MVC 및 URI매핑

스프링 MVC 핵심기술

어노테이션 기반 스프링 MVC

  • 요청 매핑
  • 핸들러메서드
  • 모델과 뷰
  • 데이터바인더
  • 예외처리
  • 글로벌 컨트롤러

요청메서드

  • GET
    • 클라이언트가 서버 리소스를 요청할때 사용, 캐싱할 수 있다, 브라우저에 기록이 남는다, 북마크할 수 있다, 민감한 데이터는 사용하지 마라, idemponent(멱등)
  • POST
    • 클라이언트가 서버의 리소스를 수정하거나 새로만들때 사용, 서버에 보내는 데이터는 POST Body에 담는다, 캐시할 수 없다, 북마크할 수 없다, 브라우저에 기록 안남는다, 데이터길이 제한이 없다, 멱등하지않다.
  • PUT
    • URI에 해당하는 데이터를 새로만들거나 수정할 때 사용,
    • POST와 다른점은 URI에 대한 의미가 다르다 : POST의 URI는 보내는 데이터를 처리할 리소스를 지정, PUT의 URIs는 보내는 데이터에 해당하는 리소스를 지칭
    • 멱등하다
  • PATCH
    • PUT과 비슷하지만 기존 엔티티와 새 데이터의 차이점만 보낸다. 멱등하지 않다.
  • DELETE
    • URI에 해당하는 리소스를 삭제할때 사용.
  • 스프링 웹 MVC에서 HTTP Method매핑하기
    • @RequestMapping(method=RequestMethod.GET)
    • @RequestMapping(method=RequestMethod.POST)
    • @GetMapping, @PostMapping, @DeleteMapping, @PutMapping
    • @RequestMapping을 클래스 레벨에도 할 수 있다.
@Controller
@RequestMapping(method=RequestMethod.GET) // GET요청만 된다.
public class SampleController {
    @RequestMapping(value = "/hello", method = {RequestMethod.GET, RequestMethod.POST})
    @ResponseBody
  	@GetMapping({"/hello", "/hi"}) // 여러개의 URI 매핑도 된다.
    public String hello() {
        return "hello";
    }
}

@RunWith(SpringRunner.class)
@WebMvcTest
public class SampleControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void helloTest() throws Exception {
        mockMvc.perform(get("/hello"))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(content().string("hello"));

        mockMvc.perform(post("/hello"))
                .andDo(print())
                .andExpect(status().isOk());

        mockMvc.perform(put("/hello"))
                .andExpect(status().is4xxClientError());
    }
}

  • method를 배열로 받을 수 있다. GET, POST만 허용하기때문에 이외의 메서드는 테스트에서 에러가 발생한다

URI 매핑

  • @RequestMapping은 여러가지 형태를 지원한다.
    • ? : 한글자 (“/author/?” => /author/1 , “/author/????’ => /author/1234”)
    • *: 여러글자 (“/author/*” => /author/h /author/123…)
    • ** : 여러path (“/author/**” => /author/hello , /author/hello/world)
    • 정규식 : /{name:정규식} => “/{name:[a-z]}”
@Controller
@RequestMapping("/hello")
public class SampleController {

    @GetMapping("")
    public String test() {
        return "hello";
    }

    @RequestMapping("/{name:[a-z]+}")
    @ResponseBody
    public String hello(@PathVariable String name) {
        return "hello " + name;
    }
}

@RunWith(SpringRunner.class)
@WebMvcTest
public class SampleControllerTest {
    @Autowired
    private MockMvc mockMvc;

    @Test
    public void pathTest() throws Exception {
        mockMvc.perform(get("/hello/jimmy"))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(content().string("hello jimmy"));
    }
}

@Controller
@RequestMapping("/hello")
public class SampleController {

    @GetMapping("/jimmy")
    @ResponseBody
    public String test() {
        return "hello jimmy";
    }

    @GetMapping("/**")
    @ResponseBody
    public String test2(@PathVariable String name) {
        return "hello2 " + name;
    }
}

@Test
public void pathTest() throws Exception {
  mockMvc.perform(get("/hello/jimmy"))
    .andDo(print())
    .andExpect(status().isOk())
    .andExpect(content().string("hello jimmy"))
    .andExpect(handler().methodName("test"));
}
  • Class레벨에 @RequestMapping을 하면 해당 uri이하로 만들 수 있다.
  • 정규표현식과 @PathVariable을 이용해서 받을 수 있다.
  • 위의 코드에서 /hello/jimmy를 보내게 되면 hello2 + jimmy가 아닌 hello jimmy가 응답으로 온다. 그 이유는 최대한 명확하게

미디어타입

  • 특정한 타입의 데이터를 담고있는 요청만 처리하는 핸들러

    • @RequestMapping(consumes=”application/json”) => MediaType.**를 쓰자

    • Content-Type헤더로 필터링

    • 매치되지 않으면 415 Not Supported MediaType응답

    • @GetMapping(value = "/jimmy", consumes = MediaType.APPLICATION_JSON_VALUE)
      @ResponseBody
      public String test() {
        return "hello jimmy";
      }
      ...
      
      @Test
      public void pathTest() throws Exception {
        mockMvc.perform(get("/hello/jimmy")
                        .contentType(MediaType.APPLICATION_JSON_VALUE))
          .andDo(print())
          .andExpect(status().isOk())
          .andExpect(content().string("hello jimmy"))
          .andExpect(handler().methodName("test"));
      }
      

    -

  • 특정한 타입의 응답을 만드는 핸들러

    • @RequestMapping(produces=”application/json”) => MediaType.**를 쓰자

    • Accept헤더로 필터링

    • 매치되지 않으면 406에러가 발생.

    • @Controller
      @RequestMapping("/hello")
      public class SampleController {
      
          @GetMapping(value = "/jimmy", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
          @ResponseBody
          public String test() {
              return "hello jimmy";
          }
      }
      
      @Test
      public void pathTest() throws Exception {
        mockMvc.perform(get("/hello/jimmy")
                        .contentType(MediaType.APPLICATION_JSON_VALUE)
                        .accept(MediaType.APPLICATION_JSON))
          .andDo(print())
          .andExpect(status().isOk())
          .andExpect(content().string("hello jimmy"))
          .andExpect(handler().methodName("test"));
      }
      
  • 클래스에 선언한 경우

    • @Controller
      @RequestMapping(value = "/hello", consumes = MediaType.APPLICATION_XML_VALUE)
      public class SampleController {
      
          @GetMapping(value = "/jimmy", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
          @ResponseBody
          public String test() {
              return "hello jimmy";
          }
      }
      
      
    • 위의 경우처럼 class레벨에서 consumes를 정의한 경우와 메서드레벨에서 정의한게 다를경우 메서드 레벨에 있는걸로 오버라이딩한다.
Written on July 14, 2020