작심십오일러의 스프링 시작하기(15)-1

서은경·2022년 9월 18일
0

Spring

목록 보기
25/43

요즘 회사 다니랴 운동하랴 공부하랴 정신이 하나도 없다 😵‍💫
졸려죽겠지만 1포스팅 시작 !!

JSON

JSON(JavaScript Object Notation) 은 간단한 형식을 갖는 문자열로 데이터 교환에 주로 사용한다.

{
  "name": "김개똥"
  "birthday": "1995-01-01"
  "related": ["김철수", "김영희"]
  "edu":[
    {
      "title": "하버드대학교",
      "year": 2014
    },
    {
      "title": "예일대학교",
      "year": 2014
    }
  ]
}

JSON 규칙은 간단하다. 중괄호를 사용해서 객체를 표현한다. 객체는 (이름, 값) 쌍을 갖는다. 이 때 이름과 값은 클론(:)으로 구분한다. 값으로는

  • 문자열, 숫자, boolean, null
  • 배열
  • 다른 객체
    가 올 수 있다.

💡 문자열은 큰따옴표나 작은따옴표 사이에 위치한 값이다. 문자열은 \"(큰따옴표), \n(뉴라인), \r(캐리지 리턴), \t(탭)과 같이 역슬래시를 이용해 특수 문자를 표시할 수 있다.
💡 숫자는 10진수 표기법이나 지수 표기법을 따른다.
💡 boolean 값은 true, false가 있다.
💡 배열은 대괄호로 표현한다. 대괄호 안에 콤마로 구분한 값 목록을 갖는다. 위 예에서 realted 배열은 문자열 값 목록을 갖고 있고 edu 배열은 객체를 값 목록으로 갖고 있다.

👩‍💻참고 사이트 : https://www.json.org/json-ko.html

Jackson 의존 설정

Jackson은 자바 객체와 JSON 형식 문자열 간 변환을 처리하는 라이브러리이다. 스프링 MVC에서 Jackson 라이브러리를 이용해서 자바 객체를 JSON으로 변환하려면 클래스 패스에 Jackson 라이브러리를 추가하면 된다.

// JACKSON 라이브러리를 이용해 자바 객체를 json으로 변환
implementation "com.fasterxml.jackson.core:jackson-core:2.9.9"
implementation "com.fasterxml.jackson.core:jackson-annotations:2.9.9"
implementation "com.fasterxml.jackson.core:jackson-databind:2.9.9"
implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.9.9"

👩‍💻참고 사이트 : https://github.com/FasterXML/jackson-docs

Jackson은 프로퍼티(get 메서드 또는 설정에 따라 필드)의 이름과 값을 JSON 객체의 (이름, 값) 쌍으로 사용한다. 프로퍼티 타입이 배열이나 List인 경우 JSON 배열로 변환된다.

@RestController로 JSON 형식 응답

스프링 MVC에서 JSON 형식으로 데이터를 응답하는 것은 매우 간단하다. @Controller 어노테이션 대신 @RestController 어노테이션을 사용하면 된다.

package controller;

import com.fasterxml.classmate.MemberResolver;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import spring.*;

import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.util.List;

@RestController
public class RestMemberController {

    private MemberDao memberDao;
    private MemberRegisterService memberRegisterService;

    @GetMapping("/api/members")
    public List<Member> members() {
        return memberDao.selectAll();
    }

    @GetMapping("/api/members/{id}")
    public Member member(@PathVariable Long id, HttpServletResponse response) throws IOException {
        Member member = memberDao.selectById(id);
        if (member == null) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return null;
        }
        return member;
    }

    public void setMemberDao(MemberDao memberDao) {

        this.memberDao = memberDao;
    }

    public void setMemberRegisterService(MemberRegisterService memberRegisterService) {
        this.memberRegisterService = memberRegisterService;
    }
}

🖐 잠깐!
@RestController 어노테이션이 추가되기 전에는 @Controller 어노테이션과 @ResponseBody 어노테이션을 사용했다.

@Controller
public class RestMemberController{
	private MemberDao memberDao;
    private MemberRegisterService registerService;
    
    @RequestMapping(path="/api/members", method=RequestMethod.GET)
    @ResponseBody
    public List<member> members() {
    	return memberDao.selectAll();
    }
}

@JsonIgnore를 이용한 제외 처리

보통 암호와 같이 민감한 데이터는 응답 결과에 포함시키면 안되므로 password 데이터는 응답 결과에서 제외시켜야 한다. Jackson이 제공하는 @JsonIgnore 어노테이션을 사용하면 이를 간단히 처리할 수 있다. JSON 응답에 포함시키지 않을 대상에 @JsonIgnore 어노테이션만 붙여주면 된다.

import.com.fasterxml.jackson.annotation.JsonIgnore;

public class Member {
	private Long id;
    private String email;
    @JsonIgnore
    private String password;
    private String name;
    private LocalDateTime registerDateTime;
}

날짜 형식 변환 처리: @JsonFormat 사용

보통 날짜나 시간은 배열이나 숫자보다는 "2022-09-18 09:29:21" 과 같이 특정 형식을 갖는 문자열로 표현하는 것을 선호한다. Jackson에서 날짜나 시간 값을 특정한 형식으로 표현하는 가장 쉬운 방법은 @JsonFormat 어노테이션을 사용하는 것이다. 예를 들어 ISO-8601 형식으로 변환하고 싶다면 shape 속성을 사용하여 변환대상에 적용하면 된다.

🙋‍♀️ ISO-8601이 뭔가요?
💡 날짜와 시간과 관련된 데이터 교환을 다루는 국제 표준 형식이다.

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonFormat.Shape;

public class Member{
	private Long id;
	private String email;
	private String name;
    @JsonFormat(shape = Shape.STRING)
	private LocalDateTime registerDateTime;
}

이렇게 되면 2022-09-18T09:29:21 형식으로 표시된다.(T 다음 시간이 온다고 생각하면 됨)

ISO-8601이 아닌 원하는 형식으로 변환해서 출력하고 싶다면 pattern 속성을 사용한다.

@JsonFormat(pattern = "yyyyMMddHHmmss")
private LocalDateTime registerDateTime;

이렇게 되면 20220918092921 로 표시됨!

날짜 형식 변환 처리 : 기본 적용 설정

날짜 형식을 변환할 모든 대상에 @JsonFormat 어노테이션을 붙여야한다면 상~~~당히 귀찮다. 이런 귀찮음을 피하려면 날짜 타입에 해당하는 모든 대상에 동일한 변환 규칙을 적용할 수 있어야한다. @JsonFormat 어노테이션을 사용하지 않고 Jackson의 변환 규칙을 모든 날짜 타입에 적용하려면 스프링 MVC 설정을 변경하면 된다.

package config;

// 스프링 부트 사용하긴 하지만 스프링 MVC 구조를 자세히 보기 위해 전부 만들어봄

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import controller.MemberListController;
import controller.RegisterRequestValidator;
import interceptor.AuthCheckInterceptor;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.validation.Validator;
import org.springframework.web.servlet.config.annotation.*;

import java.util.List;

@Configuration
// 스프링 MVC 설정을 활성화한다. 스프링 MVC를 사용하는데 필요한 다양한 설정을 생성한다.
@EnableWebMvc   // OptionalValidatorFactoryBean 을 글로벌 범위 Validator로 등록
public class MvcConfig implements WebMvcConfigurer {
	
    ..생략..
    
    // 모든 날짜 타입을 ISO-8601 형식으로 변환하기 위한 설정
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        ObjectMapper objectMapper = Jackson2ObjectMapperBuilder
                .json()
                .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
                .build();
        converters.add(0, new MappingJackson2HttpMessageConverter(objectMapper));
    }
}

스프링 MVC는 자바 객체를 HTTP 응답으로 변환할 때 HttpMessageConverter라는 것을 사용한다. 예를 들어 Jackson을 이용해서 자바 객체를 JSON으로 변환할 때에는 MappingJackson2HttpMessageConverter를 사용하고 Jaxb를 이용해서 XML로 변환할 때에는 Jaxb2RootElementHttpMessageConverter를 사용한다.
따라서 JSON으로 변환할 때 사용하는 MappingJackson2HttpMessageConverter를 새롭게 등록해서 날짜 형식을 원하는 형식으로 변환하도록 설정하면 모든 날짜 형식에 동일한 변환 규칙을 적용할 수 있다.

❗️❕ extendMessageConverters() 메서드는 WebMvcConfigurer 인터페이스에 정의된 메서드로서 HttpMessageConverter를 추가로 설정할 때 사용한다. @EnableWebMvc 어노테이션을 사용하면 스프링 MVC는 여러 형식으로 변환할 수 있는 HttpMessageConverter를 미리 등록한다. extendMessageConverters() 는 등록된 HttpMessageConverter 목록을 파라미터로 받는다.

❗️❕ 미리 등록된 HttpMessageConverter에는 Jackson을 이용하는 것도 포함되어 있기 때문에 새로 생성한 HttpMessageConverter는 목록의 제일 앞에 위치시켜야 한다. 그래야 가장 먼저 적용된다. 이를 위해

converters.add(0, new MappingJackson2HttpMessageConverter(objectMapper));

새로운 HttpMessageConverter를 0번 인덱스에 추가했다.

❗️❕ 설정 코드에서 주의 깊에 볼 코드는

ObjectMapper objectMapper = Jackson2ObjectMapperBuilder
                .json()
                .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
                .build();

이 부분이다. 이 코드는 JSON으로 변환할 때 사용할 ObjectMapper를 생성한다. Jackson2ObjectMapperBuilder는 ObjectMapper를 보다 쉽게 생성할 수 있도록 스프링이 제공하는 클래스이다. 위 설정 코드에서

featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)

이 부분은 Jackson이 날짜 형식을 출력할 때 유닉스 타임 스탬프로 출력하는 기능을 비활성화한다. 이 기능을 비활성화하면 ObjectMapper는 날짜 타입의 값을 ISO-8601 형식으로 출력한다.

❗️❕ 새로 생성한 ObjectMapper를 사용하는 MappingJackson2HttpMessageConverter 객체를 converters의 첫 번째 항목으로 등록하면 설정은 끝난다.

🖐 모든 java.util.Date 타입의 값을 원하는 형식으로 출력하고 싶으면

ObjectMapper objectMapper = Jackson2ObjectMapperBuilder
                .json()
                .simpleDateFormat("yyyyMMddHHmmss")
                .build();

이렇게 수정해서 괄호 안에 원하는 형식을 넣어주면 된다! 이건 Date 타입을 변환할 때만 적용되고 LocalDateTime 타입 변환에는 해당하지 않는다.(ISO-8601로 변환됨)

모든 LocalDateTime 타입에 대해 ISO-8601 이 아닌 원하는 패턴을 설정하고 싶을 땐 serializerByType() 메서드를 이용한다.

import java.time.format.DateTimeFormatter;

import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;

@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
	..생략..
    
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?> converters) {
    	DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        ObjectMapper objectMapper = Jackson2ObjectMapperBuilder
        .json()
        .serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(formatter))
        .buile();
        converters.add(0, new MappingJackson2HttpMessageConverter(objectMapper));
    }
}

🖐 MappingJackson2HttpMessageConverter가 사용할 ObjectMapper 자체에 시간 타입을 위한 변환 설정을 추가해도 개별 속성에 적용한 @JsonFormat 어노테이션 설정을 우선한다.

0개의 댓글