오늘은 좀 늦은감이 있지만 그래도 spring boot 2.2 의 변화에 대해서 알아보도록 하자. 물론 예전에 틈틈이 특정부분은 관련해서도 남기긴 했지만 정리하는 의미에서 다시 한번 살펴보도록 하자. 물론 이것도 필지가 자주 사용할 것들 혹은 자주 사용하는 것들만 정리하니 나머지는 해당 문서를 참고하면 되겠다.

Spring Framework 5.2

알다시피 Spring Framework 5.2로 업그레이드 되었다. 관련해서는 해당 문서를 찾아보면 더 좋을 듯 싶다.
해당 문서는 여기를 살펴보자.

JMX now disabled by default

JMX는 더 이상 기본적으로 비활성화 되어있다. 만약 이 기능을 사용하고 싶다면 spring.jmx.enabled=true를 사용하여 활성화 시킬 수 있다.
사실 이 기능은 많은 사용자가 사용하고 있지 않다고 판단되고 생각보다 많은 리소스를 필요로 하기 때문에 이와 같은 결정을 내린 것이라고 한다.

Jakarta EE dependencies

다들 알다시피 java ee는 몇년전에 이클립스 재단에 넘어갔다. 그러면서 아마도 네임스페이스를 변경작업을 진행했을 것이다. 스프링부트도 그에 맞게 javax. 에서 jakarta.를 사용할 수 있게 디펜더시가 추가 되었다. 아직은 javax의 디펜더시가 존재하지만 추후에는 사라질 예정이니 만약 직접적으로 디펜더시를 받고 있다면 마이그레이션 하는게 좋다.

<jakarta-activation.version>1.2.1</jakarta-activation.version>
<jakarta-annotation.version>1.3.5</jakarta-annotation.version>
<jakarta-jms.version>2.0.3</jakarta-jms.version>
<jakarta-json.version>1.1.6</jakarta-json.version>
<jakarta-json-bind.version>1.0.2</jakarta-json-bind.version>
<jakarta-mail.version>1.6.4</jakarta-mail.version>

...

위는 디펜더시 관리하는 부분의 일부를 가져왔다.
또한 com.sun.mail:javax.mailcom.sun.mail:jakarta.mail 변경이 되었고, org.glassfish:javax.elorg.glassfish:jakarta.el로 artifact ID 가 변경 되었으므로 이것 역시 직접적으로 선언해서 사용한다면 마이그레이션하는게 정신건강에 좋을 듯 싶다.

JUnit 5

기본적으로 spring boot starter test 에는 junit5가 디폴트로 설정되어 있다. 또한 vintage engine 도 포함되어 있으니 junit4를 이용해도 좋다. 만약 spring boot 2.2로 마이그레이션을 한다 한들 문제는 없다. 점진적으로 junit5로 업그레이드도 가능하다. 왜냐하면 동일한 모듈에서 junit5 와 junit4를 혼합하여 테스트를 작성해도 아무 문제가 없다.

허나 주의할 점이 하나 있다. junit4의 listener 기능을 사용한다면 위의 junit5의 모듈들을 사용할 수 없다.

<configuration>
    <properties>
        <property>
            <name>listener</name>
            <value>com.example.CustomRunListener</value>
        </property>
    </properties>
</configuration>

만약 위의 같은 기능을 현재 사용하고 있다면 vintage engine을 사용할 수 없으니 junit4를 직접적으로 선언하여 사용해야 한다.

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.jupiter</groupId>
                <artifactId>junit-jupiter</artifactId>
            </exclusion>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
            <exclusion>
                <groupId>org.mockito</groupId>
                <artifactId>mockito-junit-jupiter</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>

Elasticsearch & Reactive Elasticsearch Auto-configuration

Elasticsearch 관련해 조금 변경 사항이 있다. Elasticsearch 쪽의 TransportClient가 7.0부터 Deprecated 되어 아마도 추후에는 해당 관련 설정은 다 삭제 될 것으로 보인다. 클러스터 노드를 설정할 수 있는 spring.data.elasticsearch.cluster-nodesspring.data.elasticsearch.properties 프로퍼티도 Deprecated 되었다.

jest 또한 Deprecated 되어 이제는 RestHighLevelClient를 권장하는 듯 하다.

Reactive Elasticsearch 자동설정이 추가 되었다. 해당 설정은 spring.data.elasticsearch.client.reactive. 들을 이용하면 된다. 해당 설정후에 ReactiveElasticsearchClient를 직접사용해도 되고, Spring 스럽게(?) 만든 ReactiveElasticsearchOperations 을 이용해도 좋다.

또한 ReactiveElasticsearchRepository을 이용하여 Spring data 스럽게(?) 만든 repository도 이용할 수 있다.

public class FooController {

    private final ReactiveElasticsearchClient reactiveElasticsearchClient;
    private final ReactiveElasticsearchOperations reactiveElasticsearchOperations;

    public FooController(ReactiveElasticsearchClient reactiveElasticsearchClient,
                         ReactiveElasticsearchOperations reactiveElasticsearchOperations) {
        this.reactiveElasticsearchClient = reactiveElasticsearchClient;
        this.reactiveElasticsearchOperations = reactiveElasticsearchOperations;
    }

    // ...
}

public interface FooRepository implements ReactiveElasticsearchRepository<Foo, String> {

    //...
}

Hibernate Dialect

예전에는 아무 설정 하지 않았을 경우 Dialect 를 spring boot 가 결정하곤 했다. 하지만 이제는 spring boot가 결정하지 않고 해당 JPA 컨테이너가 결정하기로 변경하였다. 사실 하이버네이트 말곤 다른 구현체들은 잘 모르겠다. 테스트도 해보지 않아서.. 만약 동작하지 않는다면 명시적으로 선언하면 된다.

Actuator HTTP Trace and Auditing are disabled by default

Actuator HTTP Trace 와 Auditing 기본적으로 활성화가 되지 않는다. 기본적으로는 인 메모리를 사용하기 때문에 불필요한 메모리를 사용하고 클러스터의 친화적이지 않기 때문에 비활성으로 기본값을 변경하였다. 만약 운영에서 사용하려면 Spring Cloud Sleuth 또는 그와 유사한 어떠한 것을 사용해도 좋다.

혹시나 간단한 테스트를 하기 위해 인메모리라도 사용하고 싶다면 다음과 같이 설정하면 된다.

@Bean
public InMemoryHttpTraceRepository traceRepository() {
    return new InMemoryHttpTraceRepository();
}

Logback max history

로그백 기본 max history가 0일에서 7일로 변경되었다. 만약 그 값을 변경하고 싶다면 logging.file.max-history 프로퍼티를 사용해 변경하면 된다.

Sample projects renamed and relocated

Sample projects 들의 이름(smoke)과 소스 재배치가 되었다. 궁금하다면 github을 찾아 보면 된다.

Java 13 support

Spring Boot 2.2는 Java 13에 지원한다. 또한 Java 8 및 11도 여전히 지원하고 있다.

Lazy initialization

모든 빈들을 초기화를 지연 시킬수 있다. spring.main.lazy-initialization 이용해서 모든 빈들의 생성을 지연시킬 수 있는 옵션이다. 여기서 주의할 점은 http 첫 요청이 조금 느리고, startup시 에러가 발생한 코드가 들어있다면 startup시 찾지 못할 수도 있다. 사실 http 첫 요청이 느린건 괜찮지만 후자인 startup시 에러가 발생하지 않는다면 조금 크리티컬한 부분일 수 도 있다. 아마도 운영환경에선 사용하지 않는 것을 추천하고 싶다.

만약 때때로 특정 클래스는 초기화를 지연시킬 필요가 없거나 그러고 싶지 않은 경우도 있을 것이다. 그럴때는 @Lazy(false)어노테이션을 해당 빈에 설정하면 된다.

@Bean
@Lazy(false)
public Filters filters() {
    return new Filters();
}

위의 경우는 초기화 지연을 하지 않는다. 만약 개발자가 직접 컨트롤 할 수 없는 클래스들은 LazyInitializationExcludeFilter를 이용해서 제외 시키면 된다.

@Bean
static LazyInitializationExcludeFilter integrationLazyInitExcludeFilter() {
    return LazyInitializationExcludeFilter.forBeanTypes(Filters.class);
}

Spring Data Moore

Spring Boot 2.2는 Spring Data Moore(spring data 2.2) 를 제공한다. 사실 이부분은 추후에 다룰 예정이니 그때 글을 참고하면 될 것같다.

@ConfigurationProperties scanning

@ConfigurationProperties 어노테이션을 스캐닝 할 수 있는 어노테이션이 추가 되었다. spring boot 2.2 이전엔 @ConfigurationProperties 어노테이션을 직접사용할 경우에 @Component 나 @EnableConfigurationProperties 어노테이션을 사용했지만 이제는 그럴 필요 없다. @ConfigurationPropertiesScan 어노테이션을 사용하면 @ConfigurationProperties 어노테이션이 달린 클래스들은 모두 스캔하므로 추가적인 작업을 할 필요 없다.

@EnableConfigurationProperties(FooProperties.class)
@Configuration
public class Config {
}

or 

@Component
@ConfigurationProperties("foo")
public class FooProperties {
}

예전에는 위와같이 작업을 했다면 이제는 그럴 필요 없이 아래와 같이 작업을 하면 된다.

@SpringBootApplication
@ConfigurationPropertiesScan
public class Application {
   //.. main 
}

참고로 spring 2.2.0 에서는 @SpringBootApplication 어노테이션에 @ConfigurationPropertiesScan 어노테이션이 메타 어노테이션으로 존재했지만 spring boot 2.2.1에서는 버그로 인해 제거 되었다. 그 이유가 궁금하다면 여기를 살펴보면 된다.

Immutable @ConfigurationProperties binding

불변의 @ConfigurationProperties가 추가 되었다. 코틀린을 좀 더 위한거겠지? 어떻게 사용하는지 보자.

@ConfigurationProperties("http")
@ConstructorBinding
public class BarProperties {

    private final String url;
    private final Duration timeout;
    private final LocalDate date;

    public BarProperties(String url, @DefaultValue("10s") Duration timeout,
                         @DateTimeFormat(pattern = "yyyyMMdd") LocalDate date) {
        this.url = url;
        this.timeout = timeout;
        this.date = date;
    }

    public Duration getTimeout() {
        return timeout;
    }

    public String getUrl() {
        return url;
    }

    public LocalDate getDate() {
        return date;
    }
}

위와 같이 @ConstructorBinding 어노테이션을 이용해 생성자 바인딩을 한다고 알려줘야 한다. 그런후엔 생성자로 파라미터들을 받을 수 있다. @DefaultValue 어노테이션을 이용해 기본값을 넣을 수도 있고 @DateTimeFormat 어노테이션을 이용해 date format도 지정할 수 있다.

http.url=http://localhost
http.date=20191111

코틀린 코드도 한번 보자.

더 심플한 코드가 되었다. 이래서 코틀린을 써야.. 아무튼 좀 더 코틀린스럽게(?) 코드가 되었다.

RSocket Support

Rsocket을 지원하기 시작했다. Rsocket이 뭔지 궁금한 분들을 여기를 참고하면 되겠다.
spring-boot-starter-rsocket인 starter 를 디펜더시 받으면 자동설정이 동작한다. spring.rsocket.server. 프로퍼티들을 이용해서 설정할 수 있으니 참고하면 되겠다.
CBOR과 JSON을 사용하여 인코딩 디코딩 설정을 자동으로 구성해주고 있다. 또한 spring-security-rsocket가 클래스패스에 있을 경우 시큐리티도 자동으로 설정 된다.
아주 간단하게 샘플 코드만 보도록 하고 나머지는 해당 문서를 보는 것을 추천한다.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-rsocket</artifactId>
</dependency>

@Controller
public class RSocketController {

    @MessageMapping("foo.{name}")
    public Mono<Foo> foo(@DestinationVariable String name) {
        return Mono.just(new Foo(name));
    }
}

public class Foo {

    private final String name;

    public Foo(@JsonProperty("name") String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

@SpringBootTest(properties = "spring.rsocket.server.port=0")
class RSocketControllerTests {

    @LocalRSocketServerPort
    private int port;

    private final RSocketRequester.Builder builder;

    @Autowired
    RSocketControllerTests(RSocketRequester.Builder builder) {
        this.builder = builder;
    }

    @Test
    void rsocketTest() {
        RSocketRequester requester = this.builder
                .connectTcp("localhost", this.port).block(Duration.ofSeconds(10));
        Mono<Foo> result = requester.route("foo.wonwoo").retrieveMono(Foo.class);
        StepVerifier.create(result)
                .assertNext(it -> Assertions.assertThat(it.getName()).isEqualTo("wonwoo"))
                .verifyComplete();
    }
}

음 딱히 어려운 부분은 없다. 우리가 자주 사용하는 web Controller와 비슷한 느낌이라 많은 거부감은 없는 듯 싶다.

RestTemplateBuilder request customisation

RestTemplateBuilder 에 몇가지 추가 되었다. 기본헤더를 넣을 수 있거나 request를 커스텀할 수 있는 기능이다.

public RestTemplateBuilder defaultHeader(String name, String... values)

public RestTemplateBuilder requestCustomizers(RestTemplateRequestCustomizer<?>... requestCustomizers)

요청 정보 커스터마이저 메서드는 좀 더 있지만 비슷한 맥략이라 생략했다.

private final RestTemplateBuilder builder;

public TestController(RestTemplateBuilder builder) {
    this.builder = builder.defaultHeader("foo", "bar").requestCustomizers(request -> ...).build();
}

Plain text support for Thread dump endpoint

actuator endpoint 중 하나인 Thread dump를 Plain text 내려받을 수 있다. 기존에는 json만 존재했지만 이제는 Plain text도 추가 되어 Thread Dump Analyzer
https://fastthread.io 여기에서 비쥬얼라이징 할 수 있으니 참고하면 되겠다. 사실 테스트는 fastthread 여기에서만 해봤다.

Qualifier for Spring Batch datasource

여러 데이터 소스가 있는 경우 @BatchDataSource어노테이션으로 spring batch에서 사용하는 datasource를 표시할 수 있다.

Health indicator groups

Health indicator를 그룹핑하여 사용할 수 있다. 쿠버네티스의 liveness, readiness 프로브의 대해 다른 상태를 표시할 수 있다는 그런내용?..

management.endpoint.health.group.foo.include=db,redis

위와 같이 설정했다면 엔드포인트 /actuator/health/foo를 실행 할 수 있다. 그러면 db와 redis만 Health indicator에 포함되어 체크한다. 만약 redis를 포함하고 싶지 않다면 redis를 제외 시키면 된다.

management.endpoint.health.group.foo.include=db

이외에도 좀 더 많은 프로퍼티가 존재하니 참고하면 되겠다.

management.endpoint.health.group.foo.show-details=
management.endpoint.health.group.foo.roles=
management.endpoint.health.group.foo.exclude=
management.endpoint.health.group.foo.show-components=

하나만 제외하고 나머지 프로퍼티들은 원래 있던 기능이니 해당 문서를 살펴보길 추천한다. show-components 바로 밑에 설명하겠다.

Health Endpoint component detail

Health Endpoint 의 component detail을 볼 수 있는 기능이 생겼다. 위에서 잠깐 언급했지만 그 프로퍼티는 show-components를 이용하면 된다. 이것 역시 show-details과 동일하게 NEVER, WHEN_AUTHORIZED, ALWAYS 등 3가지 타입이 존재한다.
NEVER는 표시 하지 않겠다는 의미, WHEN_AUTHORIZED 인증 후에 표시 하겠다는 의미, ALWAYS 항상 표시 하겠다는 의미를 갖고 있다.

이 기능은 세부정보와는 다르게 컴포넌트들의 Health 정보의 상태만 보여주는 기능이다.

http :8080/actuator/health

{
    "components": {
        "db": {
            "status": "UP"
        },
        "diskSpace": {
            "status": "UP"
        },
        "ping": {
            "status": "UP"
        }
    },
    "status": "UP"
}

show-details과는 다르게 각 컴포넌트 별 status만 출력 되는 것을 볼 수 있다.

오늘은 위와 같이 spring boot 2.2에 대해서 알아봤다. 사실 더 많은 내용이 있긴 한데 필자가 잘 쓰지 않거나 이해 되지 않은 내용은 작성하지 않았으니 꼭 해당 문서를 통해 확인하길 바란다.