Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

release 2.2.0.RELEASE.asciidoc

Kim Ji-Heon edited this page Nov 17, 2019 · 1 revision

Release Spring Boot 2.2.0 (2019年10月16日)

스프링 부트 2.1 과 달라진 점

Gradle 최소 요구버전 변경: 4.10+

	Build file '/home/honeymon/git-repos/boot-spring-boot/build.gradle' line: 6
An exception occurred applying plugin request [id: 'org.springframework.boot', version: '2.2.0.RELEASE']
> Failed to apply plugin [id 'org.springframework.boot']
 > Spring Boot plugin requires Gradle 4.10 or later. The current version is Gradle 4.6

Spring Boot Reference Documentation

스프링 부트 2.1 @Deprecated 항목 제거

ex) RestTemplateBuilder#setConnectTimeout(int connectTimeout)

spring-projects/spring-boot

스프링 프레임워크 5.2 적용

  • @Configuration 애노테이션 속성 proxyBeanMethod 추가
  • MergedAnnotations : 애노테이션을 처리하기 위한 새로운 API
    • http://wonwoo.ml/index.php/post/category/web/spring-boot/page/3

      MergedAnnotation springBootApplication = MergedAnnotations.from(Application.class) .get(SpringBootApplication.class); Class<?>[] scanBasePackageClasses = springBootApplication.getClassArray("scanBasePackageClasses"); String[] scanBasePackages = springBootApplication.getStringArray("scanBasePackages"); boolean proxyBeanMethods = springBootApplication.getBoolean("proxyBeanMethods");

Webflux 및 Reactive 지원 강화

Kotlin 지원 강화(레퍼런스 문서 내 코틀린 예제코드 추가)

  • Kotlin Coroutines 지원

Spring message - RSocket 지원 추가

Web on Reactive Stack

MediaType.APPLICATION_JSON_UTF8 제거대상

Jakarta EE 의존성

  • 스프링 부트 스타터 에서 사용하는 동일한 groupId 의 경우 이동
  • Java EE 의존성 이동: javaxjakarta
    • com.sun.mail:javax.mailcom.sun.mail:jakarta.mail
    • org.glassfish:javax.elorg.glassfish:jakarta.el

Spring HATEOAS 1.0

로깅 파일 사이즈를 좀 더 명확하게 정의할 수 있게 되었다.

Hibernate Dialect

  • 하이버네이트를 사용할 때 데이터베이스를 탐색하여 적절한 방언을 선택하도록 지원한다.

  • JpaProperties#determineDatabase 제거대상

     /**
     * Determine the {@link Database} to use based on this configuration and the primary
     * {@link DataSource}.
     * @param dataSource the auto-configured data source
     * @return {@code Database}
     * @deprecated since 2.2.0 in favor of letting the JPA container detect the database
     * to use.
     */
     @Deprecated
     public Database determineDatabase(DataSource dataSource) {
     	if (this.database != null) {
     		return this.database;
     	}
     	return DatabaseLookup.getDatabase(dataSource);
     }
    
  • JpaProperties#getDatabase()사용

테스트

HttpHiddenMethodFilter 기본 비활성화

  • 브라우저에서 지원하는 요청 메서드타입 GETPOST

    ... //
  • 비활성화 이유: _method 요청 매개변수(parameter)가 포함된 경우 HttpHiddenMethodFilter 에서 요청 본문을 사전에 소비한다.

  • 활성화 방법

    • MVC: spring.mvc.hiddenmethod.filter.enabled=true
    • Webflux: spring.webflux.hiddenmethod.filter.enabled=true
    • 속성명을 보니.. spring.mvc.filter.hiddenmethod.enabled 로 변경될 가능성도

DevTools 설정 디렉터리

액츄에이터(Actuator)

  • Health indicator 구성 클래스가 변경됨
  • JMX 비활성화 기본
    • 액츄에이터 관련한 정보는 보안상 비활성화
    • spring.jmx.enabled=true 를 통해 활성화 가능

스프링 부트 2.2 새로운 점

Java 13 지원

성능향상

  • 스프링 프레임워크 5.2.M1 에서 소개된 @Configuration 속성 proxyBeanMethod 을 이용하여 구동 시간과 메모리 사용량을 줄일 수 있다.
    • @Configuration(proxyBeanMethod=false)이라고 해당 클래스의 @Bean 메서드를 호출할 때 메서드로 인식

      @Configuration(proxyBeanMethod=false) // 값을 변경해보자~ public class ProxyBeanMethodConfiguration {

       @Bean
       InnerClass innerClass() {
       return new InnerClass();
       }
       public static class InnerClass {
       public InnerClass() {
       System.out.println("InnerClass init!");
       }
       public void call() {
       System.out.println("InnerClass call!");
       }
       }
      

      }

      @Configuration public class AppConfiguration { private final ProxyBeanMethodConfiguration proxyBeanMethodConfiguration;

       public AppConfiguration(ProxyBeanMethodConfiguration proxyBeanMethodConfiguration) {
       this.proxyBeanMethodConfiguration = proxyBeanMethodConfiguration;
       }
       @Bean
       OtherInnerClass otherInnerClass() {
       return new OtherInnerClass(proxyBeanMethodConfiguration.innerClass());
       }
       public static class OtherInnerClass {
       public OtherInnerClass(ProxyBeanMethodConfiguration.InnerClass innerClass) {
       innerClass.call();
       }
       }
      

      }

      // proxyBeanMethods=false 선언 후 실행시 InnerClass init! InnerClass call! InnerClass init!

      // proxyBeanMethod=true(기본값) 실행시 InnerClass init! InnerClass call!

    • @SpringBootApplication@SpringBootConfiguration 에서도 사용가능하다.

  • 개발단계에서 빌드도구를 통해 부트를 실행할 때 플래그(-Xverify:none 혹은 -XX:TieredStopAtLevel=1)로 JVM을 설정하여 실행시간을 단축할 수 있다. JDK 13 에서는 -Xverify:none 기능은 소멸되었다.
  • 시간을 지체하는 부분들에 대한 성능개선이 있었다:
    • 많은 수의 구성속성을 바인딩하는 과정에서 시간이 소요되었다.
    • 스프링 부트에서는 JPA 엔티티를 탐색하여 PersistenceUnit 를 준비했으나, 하이버네이트 소유의 엔티티에 대한 탐색은 비활성화하여 속도를 개선
    • 빈이 생성되어 있을 때만 자동구성 내에서 주입하도록 재정의
    • 스프링 빈과 관련된 액츄에이터는 JMX 혹은 HTTP 종점이 활성화 되어 노출될 때 생성
    • 더이상 사용하지 않는 코덱 제거
    • 톰캣 MBean Registry 를 기본 비활성화하여 메모리 절약

Lazy initialization

  • https://spring.io/blog/2019/03/14/lazy-initialization-in-spring-boot-2-2

  • spring.main.lazy-initialization 사용

  • 얻게되는 이득: 애플리케이션 구동속도를 높일 수 있다.

    • 스프링 애플리케이션이 구동시 스프링 빈을 탐색하고 적재하는 과정을 생략하여 속도 상승!
  • @Lazy(false) 가 선언되었거나 LazyInitializationExcludeFilter 를 이용해서 예외대상 선정가능

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

@ConfigurationpProperties 탐색

  • 2.2.1: @SpringBootConfiguration 에서 @ConfigurationPropertiesScan 빠짐
  • 2.2.0 이전: @EnableConfigurationProperties 에 불러오거나 @Component를 추가해야했음
  • @SpringBootApplication@ConfigurationPropertiesScan 이 추가되었음
    • @CofigurationProperties 도 스프링 빈으로 탐색됨(≠ @Component 는 아님

@ConfigurationProperties 생성자 바인딩

  • 생성자를 기반한 속성값 연동이 가능하다.

  • 클래스에 @ConfigurationProperties 가 선언되어 있거나 생성자에서 @ConstructorBinding을 선언

     @ConstructorBinding
     @ConfigurationProperties("honeymon.api")
     public class HoneymonApiProperties {
     private String rootUri;
     private String headerAuthorization;
     private Duration connectTimeout;
     private Duration readTimeout;
     
     public HoneymonApiProperties(String rootUri, String headerAuthorization, Duration connectTimeout, Duration readTimeout) {
     this.rootUri = rootUri;
     this.headerAuthorization = headerAuthorization;
     this.connectTimeout = connectTimeout;
     this.readTimeout = readTimeout;
     }
     	// getter 생략
     }
    
  • @DefaultValue@DateTimeFormat 을 생성자 인자에 선언하여 사용가능

     //application.yml
     honeymon.api:
     root-uri: http://honeymon.io/api
     connect-timeout: 10s
     read-timeout: 5s
     header-authorization: Berear 2019年01月01日
     today: 2019年11月06日
     
     //HoneymonApiProperties
     public HoneymonApiProperties(String rootUri, String headerAuthorization, Duration connectTimeout, Duration readTimeout, 
     @DefaultValue("Sample") String value, 
     @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate today) { // yyyy-MM-dd
     this.rootUri = rootUri;
     this.headerAuthorization = headerAuthorization;
     this.connectTimeout = connectTimeout;
     this.readTimeout = readTimeout;
     this.value = value;
     this.today = today;
     }
     
     // 오류 발생
     Caused by: java.lang.IllegalArgumentException: Parse attempt failed for value [2019年11月06日]
     	at org.springframework.format.support.FormattingConversionService$ParserConverter.convert(FormattingConversionService.java:223)
     	at org.springframework.format.support.FormattingConversionService$AnnotationParserConverter.convert(FormattingConversionService.java:338)
     	at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:41)
     	... 101 more
     Caused by: java.time.format.DateTimeParseException: Text '2019/11/06' could not be parsed at index 4
     	at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1949)
     	at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851)
     	at java.time.LocalDate.parse(LocalDate.java:400)
     	at org.springframework.format.datetime.standard.TemporalAccessorParser.parse(TemporalAccessorParser.java:69)
     	at org.springframework.format.datetime.standard.TemporalAccessorParser.parse(TemporalAccessorParser.java:46)
     	at org.springframework.format.support.FormattingConversionService$ParserConverter.convert(FormattingConversionService.java:217)
     	... 103 more
    
  • https://docs.spring.io/spring-boot/docs/2.2.0.RELEASE/reference/html/spring-boot-features.html#boot-features-external-config-constructor-binding

RSocket 지원

RestTemplateBuilder 요청 재정의

  • 이건 밑에서 자세히 설명

스프링 배치 데이터소스 정의

다수의 데이터소스가 있는 환경에서 스프링 배치에서 사용하는 DataSource 빈에 @BatchDataSource를 선언하여 스프링 배치에서 사용하도록 할 수 있다.

빌드 정보 time 항목 추가

  • 빌드 정보를 제공하는 [build.properties](http://build.properties) 내에 build.time 속성을 제공하여 빌드 시간을 제공

Health indicator groups 설정

다음과 같이 Health 인디케이터에 포함할 그룹을 정의할 수 있다. (고 하는데 어따 써먹는지 알수 없다)

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

Flyway JavaMigration 으로 자동구성

  • JavaMigration 빈을 이용해서 자동구성 가능

URI 속성 추가

  • configprosenv 엔드포인트에 URI 속성 추가됨

Spring Boot 유용팁!

Gradle 4.6+ 업그레이드시

  • wrapper 태스크 오버라이드 안됨

     task wrapper(type: Wrapper) {
     gradleVersion = "5.3.1"
     }
     
     //> Cannot add task 'wrapper' as a task with that name already exists.
    
  • Groovy DSL 변경에 따른 항목 변경

     jacocoTestReport {
     reports {
     xml.enabled false
     csv.enabled false
     html.destination "${buildDir}/reports/jacoco/html"
     }
     executionData = files("${buildDir}/jacoco/jacoco.exec")
     }
     //> Could not find method destination() for arguments [/Users/생략/build/reports/jacoco/html] on Report html of type org.gradle.api.reporting.internal.TaskGeneratedSingleDirectoryReport.
     
     jacocoTestReport {
     reports {
     xml.enabled false
     csv.enabled false
     html.setDestination(file("${buildDir}/reports/jacoco/html")) // setDestination 사용
     }
     executionData = files("${buildDir}/jacoco/jacoco.exec")
     }
    
  • 애노테이션을 사용하는 라이브러리에 annotationProcessor 선언

    • test 태스크: testAnnotationProcessor 사용

    • https://docs.gradle.org/4.6/release-notes.html#convenient-declaration-of-annotation-processor-dependencies

    •  dependencies {
       		compile("org.projectlombok:lombok")
       annotationProcessor("org.projectlombok:lombok")
       		testAnnotationProcessor("org.projectlombok:lombok") // test 계층에서 사용시 다음과 같이 선언한다.
       
       		compile("com.querydsl:querydsl-jpa")
       annotationProcessor("com.querydsl:querydsl-jpa")
       compile("com.querydsl:querydsl-apt")
       annotationProcessor("com.querydsl:querydsl-apt")
       }
      
  • Querydsl plugin 설정추가

     // 관련 구성: 변경전
     dependencies {
     		compile("com.querydsl:querydsl-jpa")
     compile("com.querydsl:querydsl-apt")
     }
     
     configure(querydslProjects) {
     apply plugin: "com.ewerk.gradle.plugins.querydsl"
     
     def querydslSrcDir = "src/main/generated"
     querydsl {
     library = "com.querydsl:querydsl-apt"
     jpa = true
     querydslSourcesDir = querydslSrcDir
     }
     
     sourceSets {
     main {
     java {
     srcDirs = ["src/main/java", querydslSrcDir]
     }
     }
     }
     }
     
     // 오류발생
     > Task :honeymon-core:compileQuerydsl FAILED
     
     FAILURE: Build failed with an exception.
     
     * What went wrong:
     Execution failed for task ':honeymon-core:compileQuerydsl'.
     > Annotation processor 'com.querydsl.apt.jpa.JPAAnnotationProcessor' not found
     
     // 관련 구성: 변경후
     dependencies {
     		compile("com.querydsl:querydsl-jpa")
     annotationProcessor("com.querydsl:querydsl-jpa")
     compile("com.querydsl:querydsl-apt")
     annotationProcessor("com.querydsl:querydsl-apt")
     }
     
     configure(querydslProjects) {
     apply plugin: "com.ewerk.gradle.plugins.querydsl"
     
     def querydslSrcDir = "src/main/generated"
     querydsl {
     library = "com.querydsl:querydsl-apt"
     jpa = true
     querydslSourcesDir = querydslSrcDir
     }
     
     sourceSets {
     main {
     java {
     srcDirs = ["src/main/java", querydslSrcDir]
     }
     }
     }
     
     		compileQuerydsl { // querydsl 컴파일시 사용하는 애노테이션프로세서('com.querydsl.apt.jpa.JPAAnnotationProcessor')의 경로를 querydsl 이 지정한 경로를 이용한다는 선언
     options.annotationProcessorPath = configurations.querydsl
     }
     }
    

RestTemplateBuilder 활용법

  • setReadTimeout(long readTimeout), setConnectTimeout(long connectTimeout) 메서드 제거됨

  • setReadTimeout(Duration readTimeout), setConnectTimeout(Duration connectTimeout) 으로 대체됨

  • 밀리세컨드(1/1000초) 단위로 설정값 변경

     //이전
     honeymon.api:
     root-uri: http://honeymon.io/api
     header-authorization: Berear 2019年11月01日
     read-timeout: 5_000
     connect-timeout: 10_000
     
     // 이후
     honeymon.api:
     root-uri: http://honeymon.io/api
     header-authorization: Berear 2019年11月01日
     read-timeout: 5s
     connect-timeout: 10s // java.time.Duration 으로 변환됨
    
  • HoneymonApiProperties

     @ConfigurationProperties("honeymon.api")
     public class HoneymonApiProperties {
     private String rootUri;
     private String headerAuthorization;
     	private Duration readTimeout;
     private Duration connectTimeout;
     
     	// getter/setter
     }
    
  • RestTemplateBuilder 선언방식 변화

    • 2.2.0 이전

       public HoneymonApiClient(HoneymonApiProperties properties) {
       this.properties = properties;
       this.restTemplate = new RestTemplateBuilder()
       .rootUri(properties.getRootUri())
       .additionalInterceptors(new HoneymonApiHttpInterceptor(properties.getHeaderAuthorization()))
       .setReadTimeout(properties.getReadTimeout())
       .setConnectTimeout(properties.getConnectTimeout())
       .build();
       }
       
       // RestTemplate 요청시 Authorization 값을 추가하기 위해 ClientHttpRequestInterceptor 구현체를 추가
       public static class HoneymonApiHttpInterceptor implements ClientHttpRequestInterceptor {
       private final String authorizationToken;
       
       public HoneymonApiHttpInterceptor(String authorizationToken) {
       this.authorizationToken = authorizationToken;
       }
       
       @Override
       public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
       HttpHeaders headers = request.getHeaders();
       headers.set(HttpHeaders.AUTHORIZATION, authorizationToken);
       
       return execution.execute(request, body);
       }
       }
      
    • 2.2.0 이후(Header 를 변경하기 위해 ClientHttpRequestInterceptor 를 구현할 필요없다.)

       public HoneymonApiClient(HoneymonApiProperties properties) {
       this.properties = properties;
       this.restTemplate = new RestTemplateBuilder()
       .rootUri(properties.getRootUri())
       .defaultHeader(HttpHeaders.AUTHORIZATION, properties.getHeaderAuthorization())
       .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
       .setReadTimeout(properties.getReadTimeout())
       .setConnectTimeout(properties.getConnectTimeout())
       .build();
       }
      
  • org.springframework.beans.factory.support.BeanDefinitionOverrideException 이 발생하는 경우

     spring.main.allow-bean-definition-overriding: true
    
    • 스프링 부트 2.1(https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.1-Release-Notes) 에 추가된 기능으로 동일한 유형(타입)의 스프링 빈이 중복생성되는 것을 강제차단한다. 위의 설정읠 통해서 이 기능을 비활성화할 수 있다.
    • 스프링 부트 테스트(@SpringBootTest)를 실행할 때 @TestConfiguration 에서 동일한 유형을 가진 스프링 빈을 선언할 때 발생할 수 있는데 이때는
      • src/test/resources/application.yml 내에 spring.main.allow-bean-definition-overriding: true 를 설정하거나

참고

2.2.x - jdbc-url && driver-class-name 차이

Clone this wiki locally

AltStyle によって変換されたページ (->オリジナル) /