[Spring Boot #13] 스프링 웹 MVC : HttpMessageConverter, ViewResolver

2021. 3. 15. 03:32 Spring Framework/Spring boot #2

| 스프링 부트에서 스프링 웹 MVC 컨트롤러 구현

 

프로젝트 구조

| pom.xml
+---src
| +---main
| | +---java
| | | \---com
| | | \---tutorial
| | | \---sptringbootmvc
| | | | SptringBootMvcApplication.java
| | | |
| | | \---user
| | | UserController.java
| | |
| | \---resources
| | application.properties
| |
| \---test
| \---java
| \---com
| \---tutorial
| \---sptringbootmvc
| \---user
| UserControllerTest.java

 

테스트 코드

@RunWith(SpringRunner.class)
@WebMvcTest(UserController.class)
public class UserControllerTest {

    @Autowired
    MockMvc mockMvc;

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

참조글 :

2021.03.15 - [Spring Framework/Spring boot2] - [Spring Boot #12] 스프링부트에서 테스트 작성하기( Spring Boot Test )

 

소스 코드

@SpringBootApplication
public class SptringBootMvcApplication {

    public static void main(String[] args) {
        SpringApplication.run(SptringBootMvcApplication.class, args);
    }

}
@RestController
public class UserController {

    @GetMapping("/hello")
    // @ResponseBody 가 생략이 되어 있음
    public String hello(){
        // @RestController가 아닌 @Controller가 어노테이션으로 붙어 있을 경우
        // 아래 return되는 문자열은 ViewResolver를 통해 jsp 파일을 찾게 된다.
        // 하지만 @RestController가 붙어있으면 자동적으로 StringMessageConverter가 사용되어
        // HTTP 응답 본문에 들어가게 된다.
        return "hello saelobi";
    }
}

 

  • @RestController : 스프링 4부터 기존 특정한 JSP를 만들어 내는 뷰를 반환하는 방식이 아닌 데이터 자체를 서비스 하는 것을 지원하는 어노테이션. @ResponseBody을 생략해도 HttpMessageConverter 인터페이스를 통해 자동적으로 반환 객체를 HTTP 응답 본문으로 변환한다. 위 예시에서는 StringMessageConverter 구현체가 사용되어 String 객체를 HTTP 응답 본문으로 변환해준다.
  • @GetMapping : @RequestMapping(method=RequestMethod.GET)의 축약형

 

| @RequestBody 어노테이션을 통한 HTTP 메세지와 객체 매핑

 

프로젝트 구조

|   pom.xml
+---src
|   +---main
|   |   +---java
|   |   |   \---com
|   |   |       \---tutorial
|   |   |           \---sptringbootmvc
|   |   |               |   SptringBootMvcApplication.java
|   |   |               |
|   |   |               \---user
|   |   |                       User.java
|   |   |                       UserController.java
|   |   |
|   |   \---resources
|   |           application.properties
|   |
|   \---test
|       \---java
|           \---com
|               \---tutorial
|                   \---sptringbootmvc
|                       \---user
|                               UserControllerTest.java

 

테스트 코드

@RunWith(SpringRunner.class)
@WebMvcTest(UserController.class)
public class UserControllerTest {

    @Autowired
    MockMvc mockMvc;

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

    @Test
    public void createUser_JSON() throws Exception {
        String userJson = "{\"username\":\"saelobi\", \"password\":\"123\"}";
        mockMvc.perform(post("/users/create")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .content(userJson))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.username", is(equalTo("saelobi"))))
                    .andExpect(jsonPath("$.password", is(equalTo("123"))));
    }
}
  • MediaType.[데이터_형식] : 스프링 3부터 지원. HTTP 명세에 정의된 데이터 전달 형식을 나타내는 HTTP Response의 Content-type 리스트
  • jsonPath : https://github.com/json-path/JsonPath 의 경로 규칙을 따라서 response-body의 json 포맷에 접근할 수 있게 지원하는 메서드

 

소스 코드

public class User {

    private Long id;

    private String username;

    private String password;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {                                 
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
@RestController
public class UserController {

    @GetMapping("/hello")
    // @ResponseBody 가 생략이 되어 있음
    public String hello(){
        // @RestController가 아닌 @Controller가 어노테이션으로 붙어 있을 경우
        // 아래 return되는 문자열은 ViewResolver를 통해 jsp 파일을 찾게 된다.
        // 하지만 @RestController가 붙어있으면 자동적으로 StringMessageConverter가 사용되어
        // HTTP 응답 본문에 들어가게 된다.
        return "hello saelobi";
    }

    // HttpMessageConverters는 스프링 프레임워크에서 제공하는 인터페이스며
    // 스프링 MVC의 일부분
    // HTTP 응답 본문을 객체로 변환하거나 그 반대로 변환
    // 만일 HTTP에 content-type으로 JSON이 들어오면 (또한 본문도 JSON) JsonConverter로 바뀜
    // content-type이 문자열이면 StringMessageConverter가 사용됨
    @PostMapping("/users/create")
    public User create(@RequestBody User user){
        return user;
    }
}

 

  • @PostMapping : @GetMapping과 비슷하게 @RequestMapping(method=RequestMethod.POST)의 축약형
  • Controller 단에서 따로 json 타입에 대한 정보를 명시하지 않아도 아래에서 설명할 ContentNegotiatingViewResolver를 통해 자동적으로 json 형식으로 데이터를 반환하도록 스프링 부트에서 제공. 이 ViewResolver는 Converter와 연관되어 있어 Content-type을 기준으로 어떤 Converter를 쓸 지 결정

 

| 스프링부트 ViewResolver : ContentNegotiatingViewResolver

 

  • ViewResolver : 스프링에서 Controller에서 반환한 값(ModelAndView 혹은 Model)을 통해 뷰를 만드는 역할
  • ContentNegotiatingViewResolver : 동일한 URI에서 HTTP Request에 있는 Content-type 및 Accept 헤더를 기준으로 다양한 Content-type으로 응답할 수 있게 하는 ViewResolver

 

다음과 같이 요청(Json) 및 응답(XML) 형식이 다를 때도 잘 동작함.

 

테스트 코드

@RunWith(SpringRunner.class)
@WebMvcTest(UserController.class)
public class UserControllerTest {

    @Autowired
    MockMvc mockMvc;

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

    @Test
    public void createUser_JSON() throws Exception {
        String userJson = "{\"username\":\"saelobi\", \"password\":\"123\"}";
        mockMvc.perform(post("/users/create")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .content(userJson))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.username", is(equalTo("saelobi"))))
                    .andExpect(jsonPath("$.password", is(equalTo("123"))));
    }

    @Test
    public void createUser_XML() throws Exception {
        String userJson = "{\"username\":\"saelobi\", \"password\":\"123\"}";
        mockMvc.perform(post("/users/create")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .accept(MediaType.APPLICATION_XML)
                .content(userJson))
                .andExpect(status().isOk())
                .andExpect(xpath("/User/username").string("saelobi"))
                .andExpect(xpath("/User/password").string("123"));
    }
}

 

참고자료 : https://www.inflearn.com/course/스프링부트



출처: https://engkimbs.tistory.com/770?category=767865 [새로비]