Spring Boot Web에서 응답 만들기

ys·2024년 5월 21일

Spring공부

목록 보기
5/14
  • spring에서 응답을 만드는 방법은 크게 4가지가 존재한다

  • 1,2번은 앞서 봤었던 내용이다
  • 특히 Object를 이용해 content-type을 application/json으로 받을 때는 항상 http status가 200 OK로 응답을 받았다
  • 이번에는 ResponseEntity를 이용해 커스텀을 해서 응답을 만들어보겠다
  • @RequestBody 는 HTTP Body의 데이터를 객체로 변환할 때 사용한다
  • @ResponseBody는 Api통신보다 Controller을 이용해서 응답할 때 사용한다

1. Object를 return

@RestController
@RequestMapping(path = "/api/v1")
public class ResponseApiController {
    @GetMapping("")
    public UserRequest user(){
        UserRequest user = new UserRequest();
        user.setUserName("홍길동");
        user.setUserAge(10);
        user.setEmail("hong@hmail.com");
        
        return user;
    }
}
  • 자동으로 http status는 200 ok가 설정되는 것을 알 수 있다
  • 스프링 부트가 알아서 Json으로 바꿔서 내려준다
  • 이때 클래스 내용을 만드는 Json@JsonNaming의 camel case, sanke case에 따라 값이 달라진다

2. ResponseEntity

그런데, 우리는 다른 개발자와 협력을 한다...

  • 만약 생성을 할 때는 201 상태코드를 내려야 한다면?
  • 다른 http status를 사용해야 한다면?
  • 400 error든 500 코드 등 기타 코드를 활용하고 싶다면??
  • 그 때 사용할 수 있는게 ResponseEntity<>이다!
@GetMapping("")
    public ResponseEntity<UserRequest> user2(){
        UserRequest user = new UserRequest();

        user.setUserName("홍길동");
        user.setUserAge(10);
        user.setEmail("hong@hmail.com");

        log.info("user : {}", user);

        ResponseEntity<UserRequest> response = ResponseEntity
                .status(HttpStatus.CREATED)
                .header("x-custom","hi")
                .body(user);
        return response;
    }
  • ResponseEntity<UserRequest> 이렇게 제네릭 타입을 사용한다
  • 메서드 체이닝을 이용해서 http 응답 메시지를 custom 할 수 있다
  • stautshttp 상태코드를 넣어줄 수 있다
  • header을 통해 http header도 넣어줄 수 있다
  • body에 객체를 넣어 json타입으로 응답해줄 수 있다

  • 우리가 넣은 header와 http status가 잘 들어간 것을 볼 수 있다
  • 또한 body에도 json 타입으로 잘 들어간 것을 알 수 있다!

🤔 언제 사용할까?

  • 메서드 내에서 응답을 할 때는 주로 객체(Object)를 이용한다
  • responseEntity는 주로 해당 로직을 처리하다가, 예외를 처리할 때 많이 사용한다
    • 커스텀해서 다른 협력하는 개발자와 합의한 내용을 보내줄 수 있다

3. ObjcetMapper

🤔그런데 궁굼한게 생겼다...

  • 우리가 응답 2번째 경우를 보면, 객체를 반환하는데 어떻게 json 타입을 반환해주지?
  • 정답은 ObjectMapper에 있다!
  • 직렬화 : DTO를 JSON형태로
  • 역직렬화 : JSON을 DTO로
  • springboot에서는 ObjectMapper을 이용한다
  • Google에서 제공하는 Gson도 있다
  • objectMapper는 @RequestBody가 들어올 때는, 역직렬화를 통해 json형태에서 dto 형태로 반환하고
  • @RestController@ResponseBody가 있을 때는, 반환시 직렬화를 통해 DTO 형태를 josn으로 만들어 json으로 반환을 해준다
@SpringBootTest
class RestApiApplicationTests {

	@Autowired // 스프링에서 관리하는 빈들 중에서 자동으로 생성되는 오브젝트 메퍼를 가져오겠다는 의미
	private ObjectMapper objectMapper;

	@Test
	void contextLoads() throws JsonProcessingException {
		UserRequest user = new UserRequest();

		user.setUserName("홍길동");
		user.setUserAge(10);
		user.setEmail("hong@hmail.com");
		user.setIsKorean(true);

		String json = objectMapper.writeValueAsString(user);// 직렬화를 쓰기 위해서 write를 한다
		System.out.println(json);  // {"user_name":"홍길동","user_age":10,"email":"hong@hmail.com","is_korean":true}

		UserRequest dto = objectMapper.readValue(json, UserRequest.class);
		System.out.println(dto); // UserRequest(userName=홍길동, userAge=10, email=hong@hmail.com, isKorean=true)

	}

주의!

  • ObjectMapper는 writeValueAsString을 통해서 직렬화를 해주는데
  • 이때 변수 기준이 아닌 get이라는 이름이 들어가는 메서드를 참고한다
  • 🤔 즉 프로퍼티 규약중 getter가 없으면, 잘 작동하지 않는다
  • 또한 ✅ @JsonProperty를 통해 Json으로 직렬화시 이름을 바꿔 줄 수 있다

만약 다른 사림이 get메서드를 만든다면?

  • 만약 누가 get메서드를 하나더 만들었다고 가정하자
@Data
public class UserRequest {
    private String userName;
    private Integer userAge;
    private String email;
    private Boolean isKorean; 
    public String getUser(){
        return userName;
    }
  • user가 2개가 되었다 (직렬화시 get을 참고하기 때문에)
  • 만약 내가 json으로 사용하지 않는 변수가 있다면 꼭 ✅ @JsonIgnore을 붙여주자!
@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class) // 헤당 클래스의 변수들을 snake_case로 매핑하겠다!
public class UserRequest {

    private String userName;
    
    private Integer userAge;
    
    @JsonProperty("user_email")
    private String email;
    
    private Boolean isKorean; 
    
    @JsonIgnore
    public String getUser(){
        return userName;
    }
}
  • 결과가, user_email 로 바뀌고, user도 한개만 나왔다
  • 원하는데로 잘 나온 것을 알 수 있다

그렇다면 역 질렬화는?

  • private으로 기본생성자를 막아놨지만...
  • ObjectMapper는 리플렉터를 이용해서 , 객체를 생성했다(이부분은 지금은 넘어가자)
  • 역직렬화는 프로퍼티 setter을 통해 역직렬화를 한다
  • 🤔즉 프로퍼티 규약중 setter가 없으면, 잘 작동하지 않는다
  • 그런데 setter 메서드가 없으면 getter메서드를 이용해 역직렬화를 하기도 한다!

만약 getter와 setter가 없다면???

  • 모두 @JsonProperty를 이용해 모두 매칭을 할 수 있다!
  • 그런데 변수가 많다면??? -> 활용하기 어렵다

3-2. Grobal Object Mapper 설정

  • 이름, 이메일, 현재 시간을 받는 간단한 entity와 controller을 통해 api를 연결해보았다
    -
  • 다음과같은 결과가 나왔다.. -> 해당 시간 형식은 ISO 8601로 국제 표준이다
  • 지금은 @JsonNaming으로 snake_case로 바꿔주지 않았기 때문에, default값인 camel_case로 출력이 되었다

그런데.. 🤔하나하나 @JsonNaming을 붙인다면? -> 우리가 사용하는 프로젝트에서는 정말 많은 Contoller가 있을 것이다! -> 공통화 처리를 해주자

  • 커스텀된 Object Mapper를 @Bean으로 등록을 해주면
  • Spring Boot는 커스텀 된 @Bean을 가져다가 사용한다

ObjectMapperConfig

@Configuration
public class ObjectMapperConfig {

    @Bean
    public ObjectMapper objectMapper(){
        ObjectMapper objectMapper = new ObjectMapper();

        objectMapper.registerModule(new Jdk8Module()); // jdk 8 버전 이후 클래스

        objectMapper.registerModule(new JavaTimeModule()); // << local date

        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // 모르는 json field에 대해서는 무시 한다

        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS,false);

        //날짜 관련 직렬화
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        
        // 스테이크 케이스
        objectMapper.setPropertyNamingStrategy(new PropertyNamingStrategies.SnakeCaseStrategy());

        return objectMapper;
    }
}
  • 다음과 같이 수정되서 화면에 데이터가 전달되었다

4. 🤔내 생각 & Best Practice

  • 우리는 DTO를 통해, 서버로부터 데이터를 받는다
  • 그리고 우리는 Entity와 분리된 DTO를 사용한다
  • 그리고 DTO에는 대부분 @Data 어노테이션을 이용한다
  • 그렇기에, ObjectMapper을 통해 직렬화, 역직렬화하는데 문제가 생기지 않을 것이다

물론 is붙은 변수들은 조심해야 한다!!!

결론

  • 그렇기에 ObjectMapper을 이용을 원칙으로 한다
  • 그리고 특이한 이름을 변경해야 되는 경우 @JsonProperty를 이용한다
  • DTO중 직렬화를 하지 않을 변수에는 @JsonIgnore을 붙여준다
profile
개발 공부,정리

0개의 댓글