저작권 안내: 저작권자표시 Yes 상업적이용 No 컨텐츠변경 No

스프링4 입문

스프링 4

DDD Start

객체 지향과
디자인 패턴

JSP 2.3

JPA 입문

Why Reactive? 책 요약(책은 여기에서 다운로드 가능)


1장. 소개


시스템은 다음과 같아야 함

  • 사용자에게 응답해야 함
  • 실패를 다루고 사용불능 상태에서도 동작해야 함
  • 다양한 부하 상황에서 견뎌야 함
  • 다양한 네트워크 상황에서 메시지를 보내고, 받고, 전달할 수 있어야 함
이를 위해 다음 필요
  • 하드웨어 자원 활용 제어 -> 응답성
  • 구성 요소를 분리 -> 독립적인 확장
  • 시스템 간 비동기 통신 ->  복원력, 탄력성(resilience)
리액티브 시스템의 이점이 여기에 있음. 

왜 지금인가?

다음 몇 가지 움직임이 리액티브 프로그래밍이 뜨는데 일조함
  • IoT와 모바일 : 서버가 동시에 수 백만의 연결된 장치를 처리해야 함 -> 병행과 분산 어플리케이션에 대한 요구가 급격히 증가
  • 클라우드, 컨테이너 : 경량의 가상화와 컨테이너 기술로 세밀한 범위로 더 빠르게 배포 가능 -> 더 편하게 할 수 있는 도구
과거부터 존재한 개념이 요즘 뜨는 이유
  1. 더 나은 자원 활용과 확정성에 대한 필요성 증가. 이를 위한 도구가 사용 가능해짐
  2. 모든 구현체가 상호운영할 수 있는 표준으로 특정 구현체에 얽매이는 위험 감소

2장. 어플리케이션 수준에서의 리액티브


리액티브와 비동기
  • 리액티브 프로그래밍에 있어 핵심 요소 중 하나는 태스크를 비동기로 실행할 수 있는 것
  • 리액티브 라이브러리, 구현의 공통 테마는 어떤 종류의 쓰레드 풀에 기반한 이벤트 루프나 공유 디스패처 인프라를 사용하는 것. 저렴한 구성 요소 간에 비싼 자원(thread)을 공유함으로써 단일 어플리케이션을 멀티 코어로 확장할 수 있음
  • 이런 멀티플렉싱 기술은 단일 장비로 백 만 엔티티를 다룰 수 있게 함
    • 쓰레드를 직접 사용하면, 사용자 당 액터를 만들 수 없고, 너무 무거워서 빨리 할 수 없고, 또한 쓰레드를 직접 다루는 게 간단하지 않으며, 프로그램이 다른 쓰레드 간 데이터 동기화 코드로 빠르게 지배당함
  • 비동기에서 주의할 점은 블로킹임. 파일, 네트워크 IO와 같은 블로킹 연산은 CPU 자원을 사용하지 않는데, 쓰레드가 블로킹되면서 다른 액터(또는 작업)을 실행하지 못하게 됨
    • 블로킹을 별도 쓰레드 풀로 분리해서 실행 -> 메인 이벤트 루프나 디스패처를 블록하면 안 됨
USL(Universal Scalability Law)
  • 통신 비용, 데이터 동기화 비용까지 고려
  • 적정 지점 이상으로 시스템 자원을 사용하게 밀어 붙이면, 더 이상 속도가 빨리지 않을 뿐 아니라, 전체 처리량에 부정적인 영향을 줌(백그라우드에서 일어나는 모든 종류의 조정 때문에 발생, 예 네트워크나 메모리 포화로 이런 조정 발생 가능)
  • 다양한 자원에 대한 경쟁이 발생할 수 있고, 과사용 문제는 CPU부터 네트워크까지 적용
역압(backpressure)
  • 최적 사용 상태 유지를 위한 역압 사용
    • 동기 API는 블록 연산으로 절로 역압이 된다
    • 비동기 API는 최대 성능을 낼 수 있는데, 이는 느린 하류 스트림이나 다른 어플리케이션을 압도할 수 있음
    • 리액티브 스트림 스펙은 스트림 라이브러리 사용자에게 투명하게 역압을 적용할 수 있도록 요구
    • 비동기 + 역압 -> 시스템의 한계까지 push할 수 있으나, 한계를 넘지는 않게 할 수 있음
  • 스트리밍 API와 제한된 메모리
    • 스트리밍의 두 면 : 소비 API, 생성 API
    • 스트리밍 API와 메모리에 제한된 처리 : 필요 이상 데이터를 메모리에 적재하지 않게 해 줌 -> 이는 용량 설계에 유용한 속성
    • 하류가 느릴 때 버퍼를 두어 하류에서 데이터를 처리할 수 있게 함
    • 무한정 버퍼에 담을 수 없으므로 제한된 크기의 버퍼 사용
    • 버퍼에 적재된 메시지의 비율로 지표 측정 가능(클라이언트가 얼마나 느린지, 서비스 레벨, 노드 활용 등)
    • 큐 버퍼를 모니터링해서 이벤트 발생 가능(예, 다 차면 클라이언트를 끊거나 오래된 메시지 삭제)

3장, 시스템 수준에서의 리액티브


분산 시스템과 메시징
  • 요청-응답 API의 문제 : 개발자가 할 수 있는 것을 제한
    • 예, HTTP는 이벤트 스트림을 받으려면 일부 hack이 필요, HTTP로 Job을 제출하고 제출 완료를 polling해야 함
  • 메시지 기반 시스템(akka 등)을 사용하면 어렵지 않게 할 수 있음
    • 예, Job 제출 세미지를 fire-and-forget으로 보내고 진행 상태를 메시지로 받으면 됨
    • 메시지를 받을 수 있는 addressable 엔티티만 있으면 됨
    • 요점: 매우 작음. 다른 시스템과 리액티브하게 상호작용하기 위한 비용 작음 (연결된 커넥션 X, 라운드 트림 X, 폴링 X)
    • HTTP로 메시징 같은 API를 설계 하려면 많은 고민(블로킹, 웹소켓, 폴링, SSE 선택) 필요하고 확장성도 떨어짐
부하에 살아남고 요금 아끼기
  • 최근 추세 : 컨테이너, 클러스터 오케스트레이션 -> 유연함(elastic)이 중요해짐
    • 스케일 up/out 만큼 scale down도 중요
    • 부하에 맞게 확장/축소해서 적정 비용 유지
    • 사전(proactively) 확정 : 언제 확장할지 결정할 수 있는 기술
    • 리액티브 서비스는 서비스 확장을 위한 성공 요인
탄력성(resilience)이 없으면 다른 것은 아무것도 아님
  • 리액티브의 여러 특징(성능, 확장성, 메시징 패턴 등) 중 가장 중요한 것은 탄력성
  • 시스템에 문제가 있을 때 완전히 불능 상태가 되면 제 아무리 빠르고 확장 가능한 시스템도 의미 없음
  • 공통의 비싼 자원에 접근하면, 그 자원이 단일 실패 지점이 될 수 있음
  • 리액티브 시스템은 "own your own data" 패턴을 따른다. 시스템이 주변에 대한 상태를 내부에 보관한다면 외부 정지 상황에서 살아남을 수 있음
4장, 리액티브 시스템의 빌딩 블록

  • 모든 것이 비동기인 경우는 흔치 않으므로, 점진적으로 리액티브 아키텍처로 넘어가기 위한 생산적인 방법 필요(예, Strangler Pattern: 점진적으로 블로킹/동기 앱을 비동기 앱으로 교체하는 패턴)
  • 리액티브로 넘어가기 전에 리액티브에 대해 고민하고 원칙을 이해하는 것이 중요 (단순히 기술만 추종하면 안 됨. 애자일에 대한 이해없이 애자일을 도입하고선 뭔가 잘 안 될 때 애자일을 탓하는 것과 동일한 상황이 발생할 수 있음)


Posted by 최범균 madvirus

댓글을 달아 주세요

"A Journey into Reactive Streams" 글 요약(원문은 여기 참고, 번역글은 여기 참고)


스트림

  • 스트림은 시간이 지남에 따라 생성되는 일련의 요소들
  • 시작과 끝이 없을 수 있음
스트림과 배열의 차이
  • 스트림 처리 시스템에서 모든 스트림 요소를 접근할 수는 없음
  • 요소가 다른 속도로 발생할 뿐만 아니라 모든 요소를 처리할 거라 보장할 수 없음
  • 스트림이 아직 존재하지 않을 수도 있고, 끝 개념이 없음

리액티브 스트림


리액티브 스트림 목표

  • 비즈니스 개발자가 스트림 처리를 위한 저수준 작업에 신경쓰지 않도록 추상화 수준을 높이는 것
스트림 처리 영역의 도전 과제를 해결하기 위해
  1. 무한 버퍼 없이, 소비 비율에 따라 리액티브(push)와 인터랙티브(pull) 모델을 자동 전환
  2. 라이브러리, 시스템, 네트워크, 프로세스 간 상호운영
비동기성
  • 컴퓨팅 자원을 병렬 사용하기 위해 필요 (동기 블로킹 호출 -> 자원 활용의 악!)
  • 비동기 경계 개념이 스펙 핵심에 위치
    • 비동기 경계 -> 시스템 컴포넌트 결합 제거, 시간에 대한 결합 제거
역압(back pressure)
  • 스트림 발생과 구독자 간의 발생/처리 속도 차 발생
    • 스트림 참영자가 흐름 제어에 참여해서 꾸준하게 운영 상태를 유지하고 매끄럽게 저하시킬 수 있게 함으로써 탄력성을 제공한다.
  • 데이터 양방향 흐름 제어
    • 구독자가 퍼블리셔에 demand 신호
    • 퍼블리셔는 안전하게 요청한 개수의 요소 제공
    • demand는 비동기로 요청 -> 자원 낭비 제거
    • demand는 그 다음 상류까지 전파: 전체 흐름에서 역압은 과부하에서 응답할 수 있는 기회를 줌
  • 리액티브 시스템은 사실상 push 기반
    • 구독이 느리고 퍼블리셔가 빠르면 pull처럼 동작
    • demand(1) = pull
  • 주요 가치 : 모든 호환 라이브러리 간 탄력성


Posted by 최범균 madvirus

댓글을 달아 주세요

What is Reactive Programming? 글 요약 (원문은 여기 참고)


원문 제목은 리액티브 프로그래밍이나 실제 내용은 리액티브 시스템에 대한 내용을 담고 있음.


개발자가 직면한 것

  • 1) H/W 발전, 2) 인터넷(엄청난 트래픽!)
  • SW의 중요성, 기대 수준, 규모

문제 상황(grey sky)을 대비하지 않으면, 매우 높은 대가를 치를 수 있다.


리액티브 시스템으로 이런 문제를 해결


리액티브 시스템 4가지 원칙


[출처: https://www.reactivemanifesto.org/]

  • 응답성(Responsive) : 사용자에게 일관된 긍정적인 경험을 제공하기 위해 상황에 상관없이 모든 사용자에게 빠르게 응답
  • 유연성(Elastic) : 다양한 부하 상황에서 응답
  • 탄력성(Resilient) : 실패 상황에서 응답
  • 메시지 구동(Message Driven) : 시간, 공간에 대한 커플링 제거(비동기 경계)

탄력성

  • 오늘날 어플리케이션은 다양한 상황에서 응답성을 유지하기 위해 회복 탄력성을 가져야 한다.
  • 성능, 내구성, 보안 등 모든 측면이 탄력성 필요
메시지 구동에 기반한 탄력성

메시지 구동 아키텍처로 얻을 수 있는 것: 견고한 에러 처리와 내고장성
  • 격리 : 시스템이 자가 회복하기 위해 필요. 격리된 컴포넌트의 실패는 전체 시스템의 응답성에 영향을 주지 않음. 실패한 컴포넌트 또한 회복할 기회를 가짐
  • 위치 투명성 : 같은 VM의 프로세스처럼 다른 노드의 다른 프로세스와 상호작용할 수 있다.
  • 전용 에러 채널 : 호출한 곳에 에러를 던지는 것 외에 다른 어딘가로 에러 신호를 리다이렉트하는 것을 허용
유연성 또는 확장성
  • 다양항 부하 상황에서 응답할 수 있도록 시스템을 쉽게 확대/축소할 수 있어야 함
  • 패러다임 먼저
    • 병행 처리에서 쓰레드 기반은 한계를 가짐
      • 공유 가변 상태, 요청 당 쓰레드, 변이 상태 동시 접근 : 성능, 확장 임계점에 빠르게 도달
      • 쓰레드 안정성을 위한 복잡함,어려움 증가

메시지 구동 2가지 : 이벤트 방식과 액터 기반


이벤트 방식

  • 0개 이상 옵저버가 모니터링하는 이벤트에 기반
  • 호출자가 옵저버 응답을 기다리지 않음
  • 이벤트는 특정 수신자(줏소)를 직접 지정하지 않음
액터 기반
  • 메시지 전달 아키텍처 확장
  • 메시지를 전달할 수취인 지정
  • 메시지를 쓰레드 경계 간에 또는 다른 서버 액터에 전달 가능

액터 장점

  • 네트워크 경계를 넘어 연산 확장 쉬움
  • 액터에 메시지를 직접 보내서 콜백지옥 없어짐(액터 간 메시지 흐름만 생각하면 됨)
  • 컴포넌트 간 결합도 낮춤


Posted by 최범균 madvirus

댓글을 달아 주세요

리액티브 관련 자주 나오는 용어 정리


동기, 비동기


 용어

동기(synchronous)

비동기(asynchronous)

 설명

한 프로세스(쓰레드)가 작업을 순차 실행

다른 프로세스(쓰레드)로 작업을 실행

 비고

작업 완료 여부를 호출하는 곳에서 처리

작업 완료 여부를 호출된 곳에서 처리 

동시에 실행할 수도 있고 지금이 아닌 미래 시점에 실행할 수도 있음


블록, 논블록


 용어

블록(block)

논블록(non-block)

 설명

작업 실행이 끝날 때가지 쓰레드가 다른 작업을 하지 못하고 대기

작업 완료를 기다리지 않고 진행

 비고

호출된 곳에서 작업 완료 후 리턴

호출된 곳에서 바로 리턴


병행, 병렬


 용어

병행(concurrency)

병렬(parallelism)

설명

주어진 시점에 두 개 이상의 작업 진행

주어진 시점에 두 개 이상의 작업이 동시 발생

  • 병렬 처리를 위한 서로 다른 처리 장치(CPU) 필요 (병행처리는 필수 아님)
  • 병렬은 병행을 내포

비고

모듈화, 응답성, 유지보수성 중요

(프로그램 속성)


주요 관심

  • 언제 실행 시작
  • 정보 교환 방식
  • 공유 자원 관리

효율이 주요 관심

(머신 속성)


주요 관심

더 빨리 계산하기 위해

- 큰 문제를 작은 문제로 나누는 방법 고민

- 병렬 HW 사용 최적화

 




Posted by 최범균 madvirus

댓글을 달아 주세요

"5 Things to Know About Reactive Programming" 글 요약(원문은 여기 참고)


1. 리액티브 프로그래밍은 비동기 데이터 스트림을 이용한 프로그래밍

  • 리액티브 프로그래밍 사용시, 데이터 스트림이 어플리케이션의 뼈대가 됨
  • 이벤트, 메시지, 호출, 실패를 데이터 스트림으로 전달
  • 이 스트림을 Observe(Subscrive)하고, 값을 발생(emit)할 때 반응
  • 모든 것이 스트림: 클릭 이벤트, HTTP 요청, 유입 메시지, 변수 변경, 세선 값 등 바뀌거나 발생할 수 있는 모든 것 (이는 본질적으로 비동기와 관련)
2. 콜드(Cold) vs 핫(Hot)
  • 콜드 스트림 : 누군가 Observe를 시작할 때까지 아무것도 안 함. 스트림이 소비될 때 동작하기 시작. 여러 subscriber가 공유하지 않음. 예) 파일 다운로드
  • 핫 스트림 : 소비 전에 활성. 개별 subscriber에 독립적으로 데이터 생성. 구독하기 시작한 이후의 데이터 수신. 예) 실시간 주식 주가, 센서 데이터
3. 비동기 오용 주의
  • 비동기는 리액티브 프로그래밍에서 중요한 단어
  • 중요한 세 가지 : 부수 효과, 쓰레드, 블록
    • 부수 효과 : 불필요한 부수 효과를 피할 것. 부수 효과 없는 함수 -> 쓰레드 안정성
    • 쓰레드 : 너무 많은 쓰레드 사용을 피할 것. 다중 쓰레드는 동기화 문제 위험
    • 블록 : 쓰레드를 소유하지 않으므로 쓰레드를 절대 블록하지 말 것
4. 단순
  • 다른 개발자가 읽을 코드임을 잊지 말 것
5. 리액티브 프로그래밍 != 리액티브 시스템


Posted by 최범균 madvirus

댓글을 달아 주세요

스프링 부트 2.0은 기본 사용하는 커넥션풀을 HikariCP로 교체했다. HikariCP와 관련된 커넥션 풀 속성은 다음과 같다. 


 속성

 설명

기본 값

 connectionTimeout

풀에서 커넥션을 구할 때 대기 시간을 밀리초 단위로 지정한다. 대기 시간 안에 구하지 못하면 익셉션이 발생한다.


허용 가능한 최소 값은 250이다. 

30000

(30초)

 validationTimeout

커넥션이 유효한지 검사할 때 대기 시간을 지정한다. 이 값은 connectionTimeout보다 작아야 한다.


허용 가능한 최소 값은 250이다. 

5000
(5초)

 idleTimeout

커넥션이 풀에서 유휴 상태로 남을 수 있는 최대 시간을 밀리초 단위로 지정한다. 이 값은 minimumIdle이 maximumPoolSize보다 작을 때만 적용된다.


타임아웃이 지난 커넥션을 제거하기까지 최대 30초, 평균 15초 차이 날 수 있다.


이 타임아웃 전에는 유휴 커넥션을 제거하지 않는다. 풀의 커넥션 개수가 minimumIdle이 되면 유휴 여부에 상관없이 커넥션을 풀에서 제거하지 않는다.


이 값이 0이면 유휴 커넥션을 풀에서 제거하지 않는다.


허용 가능한 최소 값은 10000(10초)이다.

600000

(10분)

 maxLifetime

커넥션의 최대 유지 시간을 밀리초 단위로 설정한다. 이 시간이 지난 커넥션 중에서 사용중인 커넥션은 종료된 이후에 풀에서 제거한다.


갑자기 풀에서 많은 커넥션이 제거되는 것을 피하기 위해 negative attenuation(감쇠)를 적용해 점진적으로 제거한다.

 

이 값을 설정할 것을 권장한다. DB나 인프라스트럭처에서 제한한 커넥션 제한 시간 보다 최소한 30초는 짧아야 한다..


이 값이 0이면 풀에서 제거하지 않지만 idleTimeout은 적용된다.

1800000
(30분)
 maximumPoolSize

유휴 상태와 사용중인 커넥션을 포함해서 풀이 허용하는 최대 커넥션 개수를 설정한다. 이 값은 데이터베이스에 대한 실제 커넥션의 최대 개수를 결정한다.


풀이 이 크기에 도달하고 유휴 커넥션이 없을 때 connectionTimeout이 지날 때까지 getConnection() 호출은 블록킹된다.

10

 minimumIdle

풀에 유지할 유휴 커넥션의 최소 개수를 설정한다.

maximumPoolSize와 동일
 connectionTestQuery

커넥션이 유효한지 검사할 때 사용할 쿼리를 지정한다. 드라이버가 JDBC4를 지원하면 이 프로퍼티를 설정하지 말자. 이 프로퍼티를 설정하지 않으면 JDBC4의 Conneciton.isValid()를 사용해서 유효한지 검사를 수행한다.

없음

 leakDetectionThreshold

커넥션이 누수 가능성이 있다는 로그 메시지를 출력하기 전에 커넥션이 풀에서 벗어날 수 있는 시간을 밀리초로 설정한다. 0은 누수 발견을 하지 않는다. 허용하는 최소 값은 2000(2초)이다.


0


스프링 부트가 자동 제공하는 DataSource를 사용한다면 속성 앞에 "spring.datasource.hikari."를 접두어로 붙이면 된다. 다음은 속성의 사용 예이다. 


spring.datasource.hikari.connectionTimeout=5000

spring.datasource.hikari.validationTimeout=1000

spring.datasource.hikari.maxPoolSize=30


Posted by 최범균 madvirus

댓글을 달아 주세요

스프링 부트 날짜 타입을 JSON으로 응답할 때 별도 설정을 하지 않으면 부트 버전에 따라 응답 형식이 다르다. 먼저 간다한 테스트를 위해 다음과 같이 세 개의 날짜 형식을 갖는 Now 클래스를 사용하자.


import java.time.LocalDateTime;

import java.time.OffsetDateTime;

import java.util.Date;


public class Now {

    private LocalDateTime localTime;

    private OffsetDateTime offsetTime;

    private Date date;


    public Now() {

        localTime = LocalDateTime.now();

        offsetTime = OffsetDateTime.now();

        date = new Date();

    }


    ...getter 생략

}



Now를 생성해서 JSON으로 응답하는 컨트롤러를 다음과 같이 작성했다.


@RestController

public class SampleController {

    @GetMapping("/now")

    public Now time() {

        return new Now();

    }

}


이 글에서 사용한 코드는 https://github.com/madvirus/boot-jackson 리포지토리에서 참고할 수 있다.


부트 2.0에서 기본 JSON 시간 타입 포맷팅


부트 2.0으로 테스트하면 응답 결과가 다음과 같다. 각 타입을 ISO-8601 형식으로 출력하고 있다.


{

    "localTime": "2018-03-01T17:03:50.445428",

    "offsetTime": "2018-03-01T17:03:50.445428+09:00",

    "date": "2018-03-01T08:03:50.445+0000"

}


시간대 정보가 있는 OffsetDateTime 타입은 "+09:00"이 뒤에 붙어 있다. 반면에 Date 타입은 UTC 기준 시간을 사용했다. 또 다른 차이는 OffsetDateTime과 Date의 시간대 표시가 다르다는 것이다. OffsetDateTime 타입은 "+09:00"와 같이 콜론이 포함되어 있고 Date 타입은 "+0000"과 같이 콜론이 없다. LocalDateTime의 경우 오프셋 정보가 없으므로 시간대 부분이 없다.


부트 1.5에서 기본 JSON 시간 타입 포맷팅


부트 1.5에서 테스하면 응답 결과가 다음과 같다. 아주 난리다!


{

"localTime": {

"month": "MARCH",

"dayOfWeek": "THURSDAY",

"dayOfYear": 67,

"nano": 644321400,

"year": 2018,

"monthValue": 3,

"dayOfMonth": 1,

"hour": 17,

"minute": 28,

"second": 2,

"chronology": {

"id": "ISO",

"calendarType": "iso8601"

}

},

"offsetTime": {

"offset": {

"totalSeconds": 32400,

"id": "+09:00",

"rules": {

"fixedOffset": true,

"transitions": [],

"transitionRules": []

}

},

"month": "MARCH",

"dayOfWeek": "THURSDAY",

"dayOfYear": 67,

"nano": 644321400,

"year": 2018,

"monthValue": 3,

"dayOfMonth": 1,

"hour": 17,

"minute": 28,

"second": 2

},

"date": 1519894518644

}


부트 1.5에서 jackson-datatype-jsr310 모듈 추가


부트 1.5에 아래와 같이 jackson-datatype-jsr310 모듈을 추가해보자. 이 모듈은 LocalDateTime이나 OffsetDateTime과 같이 자바 8의 시간 타입을 지원하는 모듈이다.


<dependency>

    <groupId>com.fasterxml.jackson.datatype</groupId>

    <artifactId>jackson-datatype-jsr310</artifactId>

</dependency>


이 모듈을 추가한 뒤에 JSON 생성 결과를 보면 다음과 같다.


{

"localTime": [

2018,

3,

8,

21,

16,

30,

166225000

],

"offsetTime": 1520511390.166383,

"date": 1520511390166

}



부트 1.5 Date 포맷: WRITE_DATES_AS_TIMESTAMPS 비활성, StdFormat 사용


부트 1.5에서 아래 프로퍼티를 application.properties 파일에 추가하면 Date 타입을 ISO-8601 포맷을 사용해서 변환한다.


spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false


다음 프로퍼티를 설정해도 결과가 같다.


spring.jackson.date-format=com.fasterxml.jackson.databind.util.StdDateFormat


실제 출력 결과는 다음과 같다. UTC 기준으로 출력하고 있다.


"date": "2018-03-08T08:51:53.972+0000"



부트 1.5, 2.0: Jackson2ObjectMapperBuilderCustomizer로 포맷팅 설정


부트는 Jackson2ObjectMapperBuilderCustomizer 인터페이스를 제공하는데 이 인터페이스를 구현한 클래스를 빈으로 등록하면 변환 포맷을 설정할 수 있다. 다음 코드는 사용 예이다.


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

import com.fasterxml.jackson.datatype.jsr310.ser.OffsetDateTimeSerializer;

import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;

import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;


import java.time.LocalDateTime;

import java.time.OffsetDateTime;

import java.time.format.DateTimeFormatter;


@SpringBootApplication

public class Boot15JacksonApplication implements Jackson2ObjectMapperBuilderCustomizer {


    public static void main(String[] args) {

        SpringApplication.run(Boot15JacksonApplication.class, args);

    }


    // Jackson2ObjectMapperBuilderCustomizer 인터페이스 메서드

    @Override

    public void customize(Jackson2ObjectMapperBuilder builder) {

        // LocalDateTime은 오프셋 정보가 없으므로 패턴에 시간대에 해당하는 Z가 없다.

        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");

        LocalDateTimeSerializer localSerializer = new LocalDateTimeSerializer(formatter);


        DateTimeFormatter formatter2 = 

                DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");

        CustomOffsetDateTimeSerializer offsetSerializer = 

                new CustomOffsetDateTimeSerializer(formatter2);


        builder

                .simpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")

                .serializerByType(LocalDateTime.class, localSerializer)

                .serializerByType(OffsetDateTime.class, offsetSerializer)

        ;

    }


    public class CustomOffsetDateTimeSerializer extends OffsetDateTimeSerializer {

        public CustomOffsetDateTimeSerializer(DateTimeFormatter formatter) {

            super(OffsetDateTimeSerializer.INSTANCE, false, formatter);

        }

    }


}


Jackson2ObjectMapperBuilder의 다음 메서드를 이용해서 시간 타입 변환 포맷을 설정했다. 
  • simpleDateFormat: Date 타입을 위한 변환 포맷 설정
  • serializerByType: 타입을 위한 Jackson의 Serializer 설정
serializerByType 메서드를 이용해서 LocalDateTime과 OffsetDateTime을 위한 Serializer를 설정했다. LocalDateTime을 위한 Serializer로는 Jackson이 제공하는 LocalDateTimeSerializer 클래스를 사용했다. 

OffsetDateTime은 약간 수고가 더 필요하다. Jackson이 제공하는 OffsetDateTimeSerializer를 상속해서 구현한 Serializer를 사용했다. 

위 설정을 추가한 뒤 결과는 다음과 같다.

{
"localTime": "2018-03-08T21:26:46",
"offsetTime": "2018-03-08T21:26:46+0900",
"date": "2018-03-08T21:26:46+0900"
}




Posted by 최범균 madvirus

댓글을 달아 주세요

JSTL 1.2의 <fmt:formatDate> 태그는 LocalDateTime과 같이 자바 8부터 제공하는 시간 타입에 대한 포맷팅 출력을 지원하지 않는다. 그래서 JSP에서 LocalDateTime 값을 지정한 형식에 맞춰 출력해주는 간단한 태그 파일을 만들었다.


아래의 태그 파일을 /WEB-INF/tags 폴더에 formatDateTime.tag 이름으로 만들었다.


<%@ tag body-content="empty" pageEncoding="utf-8" %>

<%@ tag import="java.time.format.DateTimeFormatter" %>

<%@ tag trimDirectiveWhitespaces="true" %>

<%@ attribute name="value" required="true" 

              type="java.time.temporal.TemporalAccessor" %>

<%@ attribute name="pattern" type="java.lang.String" %>

<%

if (pattern == null) pattern = "yyyy-MM-dd";

%>

<%= DateTimeFormatter.ofPattern(pattern).format(value) %>


JSP 코드에서는 다음과 같이 태그 파일을 사용해서 원하는 형식으로 값을 출력한다.


<%@ page contentType="text/html; charset=utf-8" %>

<%@ taglib prefix="tf" tagdir="/WEB-INF/tags" %>

<!DOCTYPE html>

<html>

<head>

    <title>회원 조회</title>

</head>

<body>

    ...

    <tf:formatDateTime

            value="${mem.registerDateTime}" 

            pattern="yyyy-MM-dd" />

    ...

</body>

</html>


Posted by 최범균 madvirus

댓글을 달아 주세요