부트 개념과 활용-15.HttpMessageConverters/ViewResolve(웹 MVC)


스프링 웹 MVC 2부: HttpMessageConverters

https://docs.spring.io/spring/docs/5.0.7.RELEASE/spring-framework-reference/web.html#mvc-config-message-converters

  • HTTP 요청 본문을 객체로 변경하거나, 객체를 HTTP 응답 본문으로 변경할 때 사용
    {“username”:”keesun”, “password”:”123”} <-> User
    • @ReuqestBody
    • @ResponseBody
@RestController
public class UserController {

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

    @PostMapping("/user")
    // public @ResponseBody User create(@RequestBody User user) {
    // @RestController 사용시 @ResponseBody 생략가능
    public @ResponseBody User create(@RequestBody User user) {
        return null;
    }
}

일반적으로 보낼 때 컨텐츠 타입 : json, 본문 : json으로 보낸다면(json 요청, json 본문이 들어온다면)

json 메세지 컨버터가 사용되어서 유저 객체로 리턴이 된다. (유저 객체로 파라미터 받는다.)

이후 유저를 리턴할 때, 변환해서 보내야 하니까

기본적으로 컴포지션 타입(안에 여러 개의 프로퍼티)인 json 메세지 컨버터가 사용되어 json으로 변환된다.

만약 그냥 문자열 반환이라면(return string) 스트링 메세지 컨버터가 사용된다.

인트도 마찬가지.(스트링 메세지 컨버터 사용)

@RestController가 붙어있으면 @ResponseBody 생략 가능

    @PostMapping("/users/create")
    public User create(@RequestBody User user) {
        return user;
    }
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@ExtendWith(SpringExtension.class)
@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    MockMvc mockMvc; // @WebMvcTest 사용시 자동으로 만들어짐

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

    @Test
    public void createUser_JSON() throws Exception {
        String userJson = "{\"username\":\"jaeuk\", \"password\":\"123\"}";
        System.out.println(userJson);
        mockMvc.perform(post("/users/create")
                .contentType(MediaType.APPLICATION_JSON) // APPLICATION_JSON_UTF8 미사용
                .accept(MediaType.APPLICATION_JSON)
                .content(userJson))
                .andExpect(jsonPath("$.username", is(equalTo("jaeuk"))))
                .andExpect(jsonPath("$.password", is(equalTo("123"))));
    }
}

APPLICATION_JSON_UTF8 는 이제 사용하지 않는다.
APPLICATION_JSON Chrome과 같은 주요 브라우저는 이제 사양을 준수 하고 charset=UTF-8 매개변수를 요구하지 않고
(UTF-8 특수 문자를 올바르게 해석 하므로. 5.2 기준)

스프링 웹 MVC 3부: ViewResolver

스프링 부트

  • 뷰 리졸버(View Resolver) 설정 제공
  • HttpMessageConvertersAutoConfiguration
  • 뷰 리졸버(View Resolver) 중 하나.

View Resolver란 간단하게만 정리하면.

DispatcherServlet에게 뷰 정보를 전달하는 방법은 두 가지가 있는데,

  1. View 타입의 오브젝트를 주는 방법
  2. String 타입의 뷰 이름을 주는 방법

String 뷰 이름을 주는 경우 이름으로부터 실제로 사용할 뷰 객체를 결정해주는 뷰 리졸버가 필요하다.

뷰 오브젝트를 넘겨주는 것 보다, 뷰 이름을 넘겨주어서 뷰 리졸버를 사용하는 것이 성능 면에서 유리
뷰 리졸버는 보통 뷰 오브젝트를 캐싱하기 때문

즉, View Resolver는 이름으로부터 실제로 사용할 뷰 객체를 결정하는 것.

다시 돌아가서 View Resolver는
들어오는 요청의 Accept 헤더에 따라 응답이 달라진다.(Accept 헤더 또한 사용되는 뷰 객체를 선택하는 데 영향을 끼친다.)

Accept 헤더는 브라우저가 어떠한 타입의 본문을 응답을 원한다. 라고 서버한테 알려주는거
Accept 헤더에 따라 응답이 달라질 수 있다.

View Resolver 설정이 복잡하고 로직이 복잡해서 핵심만 정리하면.

  1. 요청이 들어오면 그 요청에 응답을 만들수 있는 모든 뷰를 찾아낸다.

  2. 최종적으로 뷰의 타입과 Accept 헤더랑 비교를 해서 선택을 한다.
    난 클라이언트가 이 뷰를 원했다고 생각을 해
    이 뷰를 리턴한다

사용자가 어떤 뷰를 원하느냐 판단하는데 가장 좋은 정보는 Accept 헤더이다.

경우에 따라서는 Accept 헤더를 제공하지 않는 요청들도 많기 때문에, 포멧이라는 파마메터도 쓰는 경우도 있다

“/path?format=pdf” // pdf를 원하는구나.. 등..

요청은 제이슨으로 보내고
응답은 xml으로 받는다면

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

이렇게 실행하면 지금은 오류가 난다.

MappingJackson2XmlHttpMessageConverterConfiguration는
@ConditionalOnClass(XmlMapper.class)
XmlMapper 클래스가 있을 때만 등록이 되도록 설정.
(xml 메세지를 컨버팅할 수 있는 컨버터가 없는 상태. 해당 클래스가 클래스페스에 없어서.)

그 클래스를 추가하는 작업

XML 메시지 컨버터 추가하기

<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.6</version>
</dependency>

의존성을 추가하면 해당 클래스가 생기고, 오류 없이 정상적으로 작동한다.

최종 예제 코드

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

// xml 의존성 추가해야 사용가능
@Test
public  void createUser_XML() throws Exception {
    String userJson = "{\"username\":\"jaeuk\", \"password\":\"123\"}";
    mockMvc.perform(post("/users/create")
            .contentType(MediaType.APPLICATION_JSON)
            .accept(MediaType.APPLICATION_XML)
            .content(userJson))
            .andExpect(status().isOk())
            .andExpect(xpath("/User/username").string("jaeuk"))
            .andExpect(xpath("/User/password").string("123"));
}





© 2019. by jaeuk