지난 2019-10-16 스프링 부트 2.2가 스프링 5와 발맞춰 출시했다. 스프링 부트 업그레이드와 관련된 내용을 간간히 추가했다. 지속적으로 내용을 보강하겠다.
Release Spring Boot 2.2.0 (2019-10-16)
Spring Boot Reference Documentation
@Deprecated
항목 제거ex) RestTemplateBuilder#setConnectTimeout(int connectTimeout)
@Configuration
애노테이션 속성 proxyBeanMethod
추가MergedAnnotations
: 애노테이션을 처리하기 위한 새로운 API
http://wonwoo.ml/index.php/post/category/web/spring-boot/page/3
MergedAnnotation
MediaType.APPLICATION_JSON_UTF8
제거대상StringHttpMessageConverter
를 제외한 HttpMessageConverter
기본 문자설정은 UTF-8
Content-Type: application/json;charset=utf-8
→ Content-Type: application/json
으로 변환javax
→ jakarta
com.sun.mail:javax.mail
→ com.sun.mail:jakarta.mail
org.glassfish:javax.el
→ org.glassfish:jakarta.el
JpaProperties#determineDatabase
제거대상
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 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()
사용스프링 부트 2.2 부터 JUnit 5 Jupiter 가 적용되었다(JUnit5 == Jupiter, JUnit4 == Vintage).
1
2
3
4
5
6
7
8
test {
useJUnitPlatform {
includeTags "fast", "smoke & feature-a" //@Tag("대응")
// excludeTags "slow", "ci"
includeEngines "junit-jupiter"
// excludeEngines "junit-vintage"
}
}
assert~
)은 뭔가 어색한 방식이다.HttpHiddenMethodFilter
기본 비활성화브라우저에서 지원하는 요청 메서드타입 GET
과 POST
뿐
//
_method
요청 매개변수(parameter)가 포함된 경우 HttpHiddenMethodFilter
에서 요청 본문을 사전에 소비한다.spring.mvc.hiddenmethod.filter.enabled=true
spring.webflux.hiddenmethod.filter.enabled=true
spring.mvc.filter.hiddenmethod.enabled
로 변경될 가능성도~/.config/spring-boot
spring-boot-devtools.properties
spring-boot-devtools.yaml
spring-boot-devtools.yml
spring.jmx.enabled=true
를 통해 활성화 가능@Configuration
속성 proxyBeanMethod
을 이용하여 구동 시간과 메모리 사용량을 줄일 수 있다.
@Configuration(proxyBeanMethod=false)
이라고 해당 클래스의 @Bean
메서드를 호출할 때 메서드로 인식
@Configuration(proxyBeanMethod=false) // 값을 변경해보자~ public class ProxyBeanMethodConfiguration {
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
기능은 소멸되었다.PersistenceUnit
를 준비했으나, 하이버네이트 소유의 엔티티에 대한 탐색은 비활성화하여 속도를 개선spring.main.lazy-initialization
사용@Lazy(false)
가 선언되었거나 LazyInitializationExcludeFilter
를 이용해서 예외대상 선정가능
1
2
3
4
@Bean
static LazyInitializationExcludeFilter integrationLazyInitExcludeFilter() {
return LazyInitializationExcludeFilter.forBeanTypes(IntegrationFlow.class);
}
@ConfigurationpProperties
탐색@EnableConfigurationProperties
에 불러오거나 @Component
를 추가해야했음@SpringBootApplication
에 @ConfigurationPropertiesScan
이 추가되었음
@CofigurationProperties
도 스프링 빈으로 탐색됨(≠ @Component
는 아님@ConfigurationProperties
생성자 바인딩클래스에 @ConfigurationProperties
가 선언되어 있거나 생성자에서 @ConstructorBinding
을 선언
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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
을 생성자 인자에 선언하여 사용가능
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//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
RestTemplateBuilder
요청 재정의다수의 데이터소스가 있는 환경에서 스프링 배치에서 사용하는 DataSource
빈에 @BatchDataSource
를 선언하여 스프링 배치에서 사용하도록 할 수 있다.
time
항목 추가[build.properties](http://build.properties)
내에 build.time
속성을 제공하여 빌드 시간을 제공다음과 같이 Health 인디케이터에 포함할 그룹을 정의할 수 있다. (고 하는데 어따 써먹는지 알수 없다)
1
management.endpoint.health.group.custom.include=db
JavaMigration
빈을 이용해서 자동구성 가능URI
속성 추가configpros
와 env
엔드포인트에 URI 속성 추가됨Gradle 스크립트 작성시 큰따옴표!
1
compile 'org.springframework.cloud:spring-cloud-aws:2.0.1.RELEASE' -> compile("org.springframework.cloud:spring-cloud-aws:2.0.1.RELEASE")
Gradle 작성시 dependencyManagement
BOM(Bill of materials) 을 잘 선택하자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ex: 잘못된 경우: 스프링 부트 2.0.6 으로 고정되어버렸...
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-aws:2.0.1.RELEASE"
}
}
// ex: spring-boot-dependencies 에 영향을 끼치지 않고 spring-cloud-aws 모듈만 영향
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-aws-dependencies:2.1.3.RELEASE"
}
}
//// import a BOM
// implementation platform("org.springframework.boot:spring-boot-dependencies:1.5.8.RELEASE")
wrapper 태스크 오버라이드 안됨
1
2
3
4
5
task wrapper(type: Wrapper) {
gradleVersion = "5.3.1"
}
//> Cannot add task 'wrapper' as a task with that name already exists.
Groovy DSL 변경에 따른 항목 변경
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
선언
testAnnotationProcessor
사용예
1
2
3
4
5
6
7
8
9
10
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 설정추가
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// 관련 구성: 변경전
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초) 단위로 설정값 변경
1
2
3
4
5
6
7
8
9
10
11
12
13
//이전
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
1
2
3
4
5
6
7
8
9
@ConfigurationProperties("honeymon.api")
public class HoneymonApiProperties {
private String rootUri;
private String headerAuthorization;
private Duration readTimeout;
private Duration connectTimeout;
// getter/setter
}
2.2.0 이전
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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 를 구현할 필요없다.)
1
2
3
4
5
6
7
8
9
10
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
이 발생하는 경우
1
spring.main.allow-bean-definition-overriding: true
@SpringBootTest
)를 실행할 때 @TestConfiguration
에서 동일한 유형을 가진 스프링 빈을 선언할 때 발생할 수 있는데 이때는
src/test/resources/application.yml
내에 spring.main.allow-bean-definition-overriding: true
를 설정하거나https://github.com/spring-projects/spring-framework/issues/21697