피케이
안녕하세요 피케이입니다 :)

chapter16-pk

chapter16-pk

JSON (Javascript )

  • 간단한 형식을 갖는 문자열로 데이터 교환에 주로 사용한다.

Jackson

  • 자바 객체와 JSON 형식 문자열 간 변환을 처리하는 라이브러리

RestController에서는 요청 매핑 어노테이션이 있는 메서드에서 리턴하는 객체를 알맞은 형식으로 변환하여 반환한다. 이 때, 만약 classpath에 Jackson이 있다면 JSON 형식의 문자열로 변환해서 응답한다.

@JsonIgnore과 @JsonFormat

@JsonIgnore

1
2
3
4
5
6
7
8
9
import com.fasterxml.jackson.annotation.JsonIgnore;

public class Person {

    private String name;
    private int age;
    @JsonIgnore
    private String privateInfo;
}
  • 객체를 JSON 형식으로 변환할 때 제외할 부분에 붙인다
  • 비밀번호와 같이 민감한 정보에 사용한다.

@JsonFormat

1
2
3
4
5
6
public class Person {
    private String name;
    private int age;
    private LocalDateTime registeredDateTime; // (1)
    // ...
}
  1. 위의 Person 객체를 Jackson을 이용하여 json 문자열로 변환시킬 때, 시간은 다음과 같이 변환된다:

"registeredDateTime": "2021-06-16T16:37:44.263566"

따라서 원하는 시간 형식으로 출력하길 원한다면 @JsonFormat을 이용하여 설정해준다:

1
2
3
4
5
6
7
public class Person {
    private String name;
    private int age;
    @JsonFormat(shape = Shape.STRING) // (1)
    private LocalDateTime registeredDateTime;
    // ...
}
  1. 시간을 ISO-8601 형식으로 변환한다:

` “registerdDateTime”: “2021-06-16T16:40:57.353016”`

  • 만약 내가 원하는 다른 형식으로 변환하고 싶다면 pattern 속성을 사용하면 된다.
  • pattern 속성은 java.time.format.DatTimeFormatter에 정의된 패턴을 사용한다 (ex: yyyyMMdd, HH:mm:SS)

HttpMessageConverter

스프링 MVC는 자바 객체를 http 응답으로 변환할 때 HttpMessageConverter를 사용한다.

  • Jackson을 사용하여 Java 객체를 json 문자열로 변환할 때 : MappingJackson2HttpMessageConverter
  • Jaxb를 이용해서 XML로 변환할 때 : Jaxb2RootElementHttpMessageConverter

애플리케이션의 모든 날짜 형식 변경 방법

앞서 말한 MappingJackson2HttpMessageConverter를 원하는대로 수정하여 새롭게 등록해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.util.List;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@EnableWebMvc // (1)
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { // (2)
        ObjectMapper objectMapper = Jackson2ObjectMapperBuilder
                .json()
                .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) // (3)
                .build();
        converters.add(
                0, 
                new MappingJackson2HttpMessageConverter(objectMapper)
        ); // (4)
    }
}
  1. Spring MVC의 기본 설정을 활성화하고 DispatcherServlet에 필요한 컴포넌트를 등록한다. SpringBoot에서는 @EnableAutoConfiguration이 WebMvc에 관련된 설정도 모두 자동적으로 처리하기 때문에 꼭 필요하지 않다.
  2. HttpMessageConverter를 추가하기 위한 메서드.
  3. Jackson이 날짜 형식을 출력할 때 유닉스 타임 스탬프로 출력하는 기능을 비활성화하고 ISO-8601 형식으로 출력한다.
  4. 미리 등록된 Jackson 관련 converter보다 먼저 적용시키기 위해 순서를 0으로 둔다.

ResponseEntity

HttpServletResponse

  • HttpServletResponse로 404 응답을 하면 서버가 기본으로 제공하는 HTML을 응답 결과로 제공한다.
  • 요청 처리에 실패했을 때 역시 json 형태로 정보를 응답 body에 담아 반환하는 일관된 방식이 api를 호출하는 client에게 좋다.

위와 같은 이유로 ResponseEntity를 사용한다.

1
2
3
4
5
@PostMapping("/member")
public ResponseEntity<String> createMember(@RequestBody MemberRequest memberRequest) {
    MemberResponse memberResponse = memberService.createMember(memberRequest);
    return ResponseEntity.ok(memberResponse); // (1)
}
  • ResponseEntity.status(HttpStatus.OK).body(memberResponse); 형식으로 statusbody를 명시할 수도 있다.

@Valid 에러 결과를 json으로 응답하기

@Valid 어노테이션을 붙인 객체가 값 검증에 실패하면 400 Bad Request가 응답으로 반환된다. 그러면서 HTML을 응답으로 전송하게 되는데, 이를 json이 담긴 응답으로 바꾸는 두 가지 선택지가 있다:

  1. Errors 객체 사용
  2. MethodArgumentNotValidException 처리

Errors를 사용하여 @Valid 에러 처리하기

이 객체를 사용하는 경우, ExceptionHandler를 따로 사용하지 않고 컨트롤러의 메서드에서 에러처리를 할 수 있다. 예시:

1
2
3
4
5
6
7
8
9
10
11
12
13
@PostMapping("/members")
public ResponseEntity<Object> creatMember(@ResponseBody @Valid memberRequest, Errors errors) {
    if (errors.hasErrors()) {
        String errorCodes = errors.getAllErrors()
            .stream()
            .map(error -> error.getCodes()[0])
            .collect(Collectors.joining(","));
        return ResponseEntity
            .status(HttpStatus.BAD_REQUEST)
            .body(new ErrorResponse("errorCodes = " + errorCodes));
    }
    // valid 관련 에러가 생기지 않았을 경우 정상 작동
}

MethodArgumentNotValidException으로 처리하기

Errors로 valid 관련 에러를 컨트롤러 메서드에서 잡게 되면 중복 코드가 발생할 수 있고, 무엇보다 정상 작동되는 코드의 가독성이 떨어질 수 있다. 따라서 @RestControllerAdvice에서 ExceptionHandler를 이용하여 MethodArgumentNotValidException을 잡는 방식을 고려할 수도 있다. 예시:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.util.stream.Collectors;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalRestControllerAdvice {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(
            MethodArgumentNotValidException e) {
        String errorCodes = e.getBindingResult().getAllErrors()
                .stream()
                .map(error -> error.getCodes()[0])
                .collect(Collectors.joining(","));
        return ResponseEntity
                .badRequest(new ErrorResponse("error codes: " + errorCodes));
    }
}