- 코어 기능(Core Features)
- 7.1. 스프링애플리케이션(SpringApplication)
- 7.1.1. Startup Failure
- 7.1.2. Lazy Initialization
- 7.1.3. Customizing the Banner
- 7.1.4. Customizing SpringApplication
- 7.1.5. Fluent Builder API
- 7.1.6. Application Availability
- Liveness State
- Readiness State
- Managing the Application Availability State
- 7.1.7. Application Events and Listeners
- 7.1.8. Web Environment.
- 7.1.9. Accessing Application Arguments
- 7.1.10. Using the ApplicationRunner or CommandLineRunner
- 7.1.11. Application Exit
- 7.1.12. Admin Features
- 7.1.13. Application Startup tracking
- 7.2. Externalized Configuration
- 7.2.1. Accessing Command Line Properties
- 7.2.2. JSON Application Properties
- 7.2.3. External Application Properties
- Optional Locations
- Wildcard Locations
- Profile Specific Files
- Importing Additional Data
- Importing Extensionless Files
- Using Configuration Trees
- Property Placeholders
- Working With Multi-Document Files
- Activation Properties
- 7.2.4. Encrypting Properties
- 7.2.5. Working With YAML
- Mapping YAML to Properties
- Directly Loading YAML
- 7.2.6. Configuring Random Values
- 7.2.7. Configuring System Environment Properties
- 7.2.8. Type-safe Configuration Properties
- JavaBean Properties Binding
- Constructor Binding
- Enabling @ConfigurationProperties-annotated Types
- Using @ConfigurationProperties-annotated Types
- Third-party Configuration
- Relaxed Binding
- Merging Complex Types
- Properties Conversion
- @ConfigurationProperties Validation
- @ConfigurationProperties vs. @Value
- 7.3. Profiles
- 7.3.1. Adding Active Profiles
- 7.3.2. Profile Groups
- 7.3.3. Programmatically Setting Profiles
- 7.3.4. Profile-specific Configuration Files
- 7.4. Logging
- 7.4.1. Log Format
- 7.4.2. Console Output
- Color-coded Output
- 7.4.3. File Output
- 7.4.4. File Rotation
- 7.4.5. Log Levels
- 7.4.6. Log Groups
- 7.4.7. Using a Log Shutdown Hook
- 7.4.8. Custom Log Configuration
- 7.4.9. Logback Extensions
- Profile-specific Configuration
- Environment Properties
- 7.4.10. Log4j2 Extensions
- Profile-specific Configuration
- Environment Properties Lookup
- Log4j2 System Properties
- 7.5. Internationalization
- 7.6. JSON
- 7.6.1. Jackson
- Custom Serializers and Deserializers
- Mixins
- 7.6.2. Gson
- 7.6.3. JSON-B
- 7.6.1. Jackson
- 7.7. Task Execution and Scheduling
- 7.8. Testing
- 7.8.1. Test Scope Dependencies
- 7.8.2. Testing Spring Applications
- 7.8.3. Testing Spring Boot Applications
- Detecting Web Application Type
- Detecting Test Configuration
- Using the Test Configuration Main Method
- Excluding Test Configuration
- Using Application Arguments
- Testing With a Mock Environment
- Testing With a Running Server
- Customizing WebTestClient
- Using JMX
- Using Metrics
- Using Tracing
- Mocking and Spying Beans
- Auto-configured Tests
- Auto-configured JSON Tests
- Auto-configured Spring MVC Tests
- Auto-configured Spring WebFlux Tests
- Auto-configured Spring GraphQL Tests
- Auto-configured Data Cassandra Tests
- Auto-configured Data Couchbase Tests
- Auto-configured Data Elasticsearch Tests
- Auto-configured Data JPA Tests
- Auto-configured JDBC Tests
- Auto-configured Data JDBC Tests
- Auto-configured jOOQ Tests
- Auto-configured Data MongoDB Tests
- Auto-configured Data Neo4j Tests
- Auto-configured Data Redis Tests
- Auto-configured Data LDAP Tests
- Auto-configured REST Clients
- Auto-configured Spring REST Docs Tests
- Auto-configured Spring Web Services Tests
- Additional Auto-configuration and Slicing
- User Configuration and Slicing
- Using Spock to Test Spring Boot Applications
- 7.8.4. Testcontainers
- Service Connections
- Dynamic Properties
- Using Testcontainers at Development Time
- 7.8.5. Test Utilities
- ConfigDataApplicationContextInitializer
- TestPropertyValues
- OutputCapture
- TestRestTemplate
- 7.9. Docker Compose Support
- 7.9.1. Service Connections
- 7.9.2. Custom Images
- 7.9.3. Skipping Specific Containers
- 7.9.4. Using a Specific Compose File
- 7.9.5. Waiting for Container Readiness
- 7.9.6. Controlling the Docker Compose Lifecycle
- 7.9.7. Activating Docker Compose Profiles
- 7.10. Creating Your Own Auto-configuration
- 7.10.1. Understanding Auto-configured Beans
- 7.10.2. Locating Auto-configuration Candidates
- 7.10.3. Condition Annotations
- Class Conditions
- Bean Conditions
- Property Conditions
- Resource Conditions
- Web Application Conditions
- SpEL Expression Conditions
- 7.10.4. 자동 구성 테스트(Testing your Auto-configuration)
- 웹 컨텍스트 시뮬레이션(Simulating a Web Context)
- 클래스패스 오버라이딩(Overriding the Classpath)
- 7.10.5. 나만의 스타터 만들기(Creating Your Own Starter)
- 네이밍(Naming)
- 구성 키(Configuration keys)
- “자동구성” 모듈(The “autoconfigure” Module)
- 스타터 모듈(Starter Module)
- 7.11. 코틀린 지원(Kotlin Support)
- 7.11.1. 요구사항(Requirements)
- 7.11.2. 널 안전성(Null-safety)
- 7.11.3. 코틀린(Kotlin) API
- runApplication
- 확장(Extensions)
- 7.11.4. 의존성 관리(Dependency management)
- 7.11.5. @ConfigurationProperties
- 7.11.6. 테스팅(Testing)
- 7.11.7. 리소스(Resources)
- Further reading
- Examples
- 7.12. SSL
- 7.12.1. 자바 키스토어 파일로 SSL 구성(Configuring SSL With Java KeyStore Files)
- 7.12.2. PEM 인코딩 인증서로 SSL 구성(Configuring SSL With PEM-encoded Certificates)
- 7.12.3. SSL 번들 적용(Applying SSL Bundles)
- 7.12.4. SSL 번들 사용(Using SSL Bundles)
- 7.13. 다음에 읽을 내용(What to Read Next) ***
- 7.1. 스프링애플리케이션(SpringApplication)
7. 코어 기능(Core Features)
이 장에서는 스프링 부트 세부 사항을 자세히 살펴본다. 여기서는 사용하고 커스텀할 수 있는 주요 기능에 대해 알아볼 수 있다. “Getting Started” 및 “Developing with Spring Boot“을 아직 읽지 않았다면 먼저 읽고 기본 사항에 대한 기초를 다지는 것이 좋다.
7.1. 스프링애플리케이션(SpringApplication)
스프링애플리케이션(SpringApplication)
클래스는 main()
메서드에서 시작되는 스프링 애플리케이션을 부트스트랩하는 편리한 방법을 제공한다. 다양한 상황에서 다음 예제와 같이 static SpringApplication.run
메서드에 위임할 수 있다:
자바
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
코틀린
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class MyApplication
fun main(args: Array<String>) {
runApplication<MyApplication>(*args)
}
애플리케이션이 시작되면 다음 출력과 비슷한 내용이 표시된다:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.1.1)
2023-06-22T12:08:14.396Z INFO 22973 --- [ main] o.s.b.d.f.s.MyApplication
: Starting MyApplication using Java 17.0.7 with PID 22973 (/opt/apps/myapp.jar started by myuser in /opt/apps/)
2023-06-22T12:08:14.413Z INFO 22973 --- [ main] o.s.b.d.f.s.MyApplication
: No active profile set, falling back to 1 default profile: "default" 2023-06-22T12:08:16.739Z INFO 22973 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2023-06-22T12:08:16.765Z INFO 22973 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2023-06-22T12:08:16.766Z INFO 22973 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.10]
2023-06-22T12:08:17.261Z INFO 22973 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2023-06-22T12:08:17.280Z INFO 22973 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2705 ms
2023-06-22T12:08:18.801Z INFO 22973 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2023-06-22T12:08:18.840Z INFO 22973 --- [ main] o.s.b.d.f.s.MyApplication
: Started MyApplication in 5.871 seconds (process running for 7.263)
22T12:08:18.916Z INFO 22973 --- [ionShutdownHook] o.apache.catalina.core.StandardService : Stopping service [Tomcat]
기본적으로, 애플리케이션을 시작한 사용자와 같이, 일부 관련 시작 세부 정보를 포함한 INFO
로깅 메시지가 표시된다. INFO
이외의 로그 레벨이 필요한 경우 로그 레벨에 설명된 대로 설정할 수 있다. 애플리케이션 버전은 메인 애플리케이션 클래스 패키지의 구현 버전을 사용하여 결정된다. spring.main.log-startup-info
를 false
로 설정하면 시작 정보 로깅을 끌 수 있다. 그러면 애플리케이션의 활성 프로필에 대한 로깅도 꺼진다.
시작하는 동안 로깅을 추가하려면 스프링애플리케이션(SpringApplication)
의 하위 클래스에서 logStartupInfo(boolean)
을 오버라이드할 수 있다.
7.1.1. Startup Failure
애플리케이션이 시작되지 않으면 등록된 페일러애널라이저(FailureAnalyzer)
가 전용 오류 메시지와 문제 해결을 위한 구체적인 조치사항를 제공한다. 예를 들어, 포트 8080
에서 웹 애플리케이션을 시작했는데 해당 포트가 이미 사용 중이라면 다음 메시지와 비슷한 내용이 표시된다:
***************************
APPLICATION FAILED TO START
***************************
Description:
Embedded servlet container failed to start. Port 8080 was already in use.
Action:
Identify and stop the process that is listening on port 8080 or configure this application to listen on another port.
스프링 부트는 다양한 페일러애널라이저(FailureAnalyzer)
구현체를 제공하며 사용자가 직접 추가할 수 있다.
페일러 애널라이저(failure analyzer)가 예외를 처리할 수 없는 경우에도 전체 상태를 표시하여 무엇이 잘못되었는지 잘 이해할 수 있다. 이렇게 하려면 디버그(debug) 프로퍼티를 활성화
하거나 org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
에 대한 DEBUG
로깅을 활성화해야 한다.
예를 들어, java -jar
을 사용하여 애플리케이션을 실행하는 경우 다음과 같이 디버그(debug) 프로퍼티을 활성화할 수 있다:
$ java -jar myproject-0.0.1-SNAPSHOT.jar --debug
7.1.2. Lazy Initialization
스프링애플리케이션(SpringApplication)
을 사용하면 애플리케이션을 느리게(lazily) 초기화할 수 있다. 지연 초기화(lazy initialization)가 활성화되면 애플리케이션 시작 중이 아닌 필요할 때 빈이 생성된다. 결과적으로 지연 초기화를 활성화하면 애플리케이션을 시작하는 데 걸리는 시간을 줄일 수 있다. 웹 애플리케이션에서 지연 초기화를 활성화하면 HTTP 요청이 수신될 때까지 많은 웹 관련 빈이 초기화되지 않는다.
지연 초기화의 단점은 애플리케이션의 문제 발견이 지연될 수 있다는 것이다. 잘못 구성된 빈이 느리게 초기화되면, 시작 중에는 실패가 발생하지 않으며 빈이 초기화될 때만 문제가 발생한다. 또한 JVM에 시작 중에 초기화되는 빈뿐만 아니라 모든 애플리케이션 빈을 수용할 수 있는 충분한 메모리가 있는지 확인해야 한다. 이러한 이유로 지연 초기화는 기본적으로 활성화되지 않으며 지연 초기화를 활성화하기 전에 JVM의 힙 크기를 조정하는 것이 좋다.
지연 초기화는 스프링애플리케이션빌더(SpringApplicationBuilder)
의 lazyInitialization
메소드 또는 스프링애플리케이션(SpringApplication)
의 setLazyInitialization
메소드를 사용하여 프로그래밍 방식으로 활성화할 수 있다. 또는 다음 예제와 같이 spring.main.lazy-initialization
프로퍼티을 사용하여 활성화할 수 있다:
프로퍼티스(Properties)
spring.main.lazy-initialization=true
Yaml
spring:
main:
lazy-initialization: true
애플리케이션의 나머지 부분에 대한 지연 초기화를 사용하는 동안 특정 빈에 대한 지연 초기화를 비활성화하려는 경우 @Lazy(false)
어노테이션을 사용하여 해당 빈의 lazy
애트리뷰트를 false
로 설정할 수 있다.
7.1.3. Customizing the Banner
시작할 때 인쇄되는 배너는 클래스패스에 banner.txt
파일을 추가하거나 spring.banner.location
프로퍼티를 해당 파일의 위치로 설정하여 변경할 수 있다. 파일에 UTF-8 이외의 인코딩이 있는 경우 spring.banner.charset
을 설정할 수 있다.
banner.txt
파일 내에서, 환경변수에서 사용 가능한 모든 키와 다음의 자리표시자(placeholders)를 사용할 수 있다:
테이블 4. 배너 변수
변수 | 설명 |
---|---|
${application.version} | MANIFEST.MF 에 선언된 애플리케이션의 버전이다. 예를 들어, Implement-Version: 1.0 은 1.0으로 표시된다. |
${application.formatted-version} | MANIFEST.MF 에 선언되고 표시용 포맷으로 지정된 애플리케이션의 버전 번호이다(대괄호로 묶이고 접두사 v가 붙음). 예를 들어 (v1.0) . |
${spring-boot.version} | 사용 중인 스프링 부트 버전이다. 예를 들어 3.1.1. |
${spring-boot.formatted-version} | 사용 중인 스프링 부트 버전으로, 표시용으로 포맷이 지정됐다(대괄호로 묶이고 접두사 v가 붙음). 예를 들면 (v3.1.1) 이다. |
${Ansi.NAME}(or ${AnsiColor.NAME}, ${AnsiBackground.NAME}, ${AnsiStyle.NAME}) | 여기서 NAME 은 ANSI 이스케이프 코드의 이름입니다. 자세한 내용은 AnsiPropertySource 를 참고하자. |
${application.title} | MANIFEST.MF 에 선언된 애플리케이션의 타이틀이다. 예를 들어 Implement-Title: MyApp 은 MyApp 로 표시된다. |
프로그래밍 방식으로 배너를 생성하려는 경우 SpringApplication.setBanner(...)
메서드를 사용할 수 있다. org.springframework.boot.Banner
인터페이스를 사용하고 자신만의 printBanner()
메소드를 구현해보자.
spring.main.banner-mode
프로퍼티을 사용하여 배너를 표시해야 하는지 결정할 수도 있다. System.out
(console
)에서 구성된 로거로 전송되거나(log
) 전혀 생성되지 않는다(off
).
표시된 배너는 springBootBanner
이라는 명칭으로 싱글톤 빈이 등록된다.
${application.version} 및 ${application.formatted-version} 프로퍼티는 스프링 부트 런처(launchers)를 사용하는 경우에만 사용할 수 있다. 압축이 풀린 jar를 실행하고 java -cp <classpath> <mainclass>
로 시작하는 경우 값이 확인되지 않는다.
7.1.4. Customizing SpringApplication
스프링애플리케이션(SpringApplication)
기본값이 마음에 들지 않으면 대신 로컬 인스턴스를 생성하고 커스텀할 수 있다. 예를 들어 배너를 끄려면 다음과 같이 작성할 수 있다:
자바
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(MyApplication.class);
application.setBannerMode(Banner.Mode.OFF);
application.run(args);
}
}
코틀린
import org.springframework.boot.Banner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class MyApplication
fun main(args: Array<String>) {
runApplication<MyApplication>(*args) {
setBannerMode(Banner.Mode.OFF)
}
}
스프링애플리케이션(SpringApplication)
에 전달된 생성자 아규먼트는 스프링 빈의 구성 소스이다. 대부분의 경우 @Configuration
클래스에 대한 참조이지만 @Component
클래스를 직접 참조할 수도 있다.
application.properties
파일을 사용하여 스프링애플리케이션(SpringApplication)
을 구성하는 것도 가능하다. 자세한 내용은 Externalized Configuration을 참고하자.
구성 옵션의 전체 목록은 스프링애플리케이션(SpringApplication)
Javadoc을 참고하자.
7.1.5. Fluent Builder API
애플리케이션컨텍스트(ApplicationContext)
계층 구조(상위/하위 관계가 있는 다중 컨텍스트)를 구축해야 하거나 “플루언트(fluent)
” 빌더 API 사용을 선호하는 경우 스프링애플리케이션빌더(SpringApplicationBuilder)
를 사용할 수 있다.
스프링애플리케이션빌더(SpringApplicationBuilder)
를 사용하면 여러 메서드 호출을 연결할 수 있으며 다음 예제와 같이 계층 구조를 만들 수 있는 상위 및 하위 메서드가 포함되어 있다:
자바
new SpringApplicationBuilder().sources(Parent.class)
.child(Application.class)
.bannerMode(Banner.Mode.OFF)
.run(args);
코틀린
SpringApplicationBuilder()
.sources(Parent::class.java)
.child(Application::class.java)
.bannerMode(Banner.Mode.OFF)
.run(*args)
애플리케이션컨텍스트(ApplicationContext)
계층 구조를 생성할 때 몇 가지 제한 사항이 있다. 예를 들어 웹 컴포넌트는 하위 컨텍스트 내에 포함되어야 하며 상위 및 하위 컨텍스트 모두에 동일한 환경이 사용된다. 자세한 내용은 스프링애플리케이션빌더(SpringApplicationBuilder)
Javadoc을 참고하자.
7.1.6. Application Availability
플랫폼에 배포되면, 애플리케이션은 쿠버네티스 프로브와 같은 인프라를 사용하여 플랫폼에 가용성에 대한 정보를 제공할 수 있다. 스프링 부트에는 일반적으로 사용되는 “활성” 및 “준비성” 가용성 상태에 대한 지원이 있다. 스프링 부트의 “액추에이터”를 사용하는 경우 이러한 상태는 헬스(health) 엔드포인트 그룹으로 노출된다.
또한, 애플리케이션어베일러빌리티(ApplicationAvailability)
인터페이스를 자신의 빈에 삽입하여 가용성 상태를 얻을 수도 있다.
Liveness State
애플리케이션의 “활성(Liveness)” 상태는 애플리케이션이 올바르게 작동할 수 있는지, 아니면 현재 오류가 발생한 경우 자체적으로 복구할 수 있는지를 알려준다. 손상된 “활성” 상태는 애플리케이션이 복구할 수 없는 상태에 있으며 인프라가 애플리케이션을 재시작해야 함을 의미한다.
일반적으로, 헬스 체크가 확인하는 “활성” 상태는 외부 서버의 확인을 기반으로 해서는 안 된다. 만약 그렇다면, 외부 시스템(데이터베이스, 웹 API, 외부 캐시)의 실패로 인해 대규모 재시작이 발생하고 플랫폼 전체에 연속적인 오류가 발생하게 된다.
스프링 부트 애플리케이션의 내부 상태는 대부분 스프링 애플리케이션컨텍스트(ApplicationContext)
로 표현된다. 애플리케이션 컨텍스트가 성공적으로 시작된 경우 스프링 부트는 애플리케이션이 유효한 상태에 있다고 가정한다. 컨텍스트가 새로 고쳐지는 즉시 애플리케이션은 라이브로 간주한다. 스프링 부트 애플리케이션 생명주기 및 관련 애플리케이션 이벤트를 참고하자.
Readiness State
애플리케이션의 “준비성” 상태는 애플리케이션이 트래픽을 처리할 준비가 되었는지 여부를 알려준다. 실패한 “준비성” 상태는 지금은 트래픽을 애플리케이션으로 라우팅해서는 안 된다는 것을 플랫폼에 알린다. 이는 일반적으로 커맨드라인러너(CommandLineRunner)
및 애플리케이션러너(ApplicationRunner)
컴포넌트가 시작하고 처리하는 동안 또는 애플리케이션이 추가 트래픽을 처리하기에는 너무 바쁘다고 판단하는 경우 언제든지 발생한다.
애플리케이션 및 커맨드라인 러너가 호출되자마자 애플리케이션은 준비된 것으로 간주된다. 스프링 부트 애플리케이션 생명주기 및 관련 애플리케이션 이벤트를 참고하자.
시작 중에 실행될 것으로 예상되는 작업은 @PostConstruct
와 같은 스프링 컴포넌트 생명주기 콜백을 사용하는 대신 커맨드라인러너(CommandLineRunner)
및 애플리케이션러너(ApplicationRunner)
컴포넌트에 의해 실행되어야 한다.
Managing the Application Availability State
애플리케이션 컴포넌트는 애플리케이션어베일러빌리티(ApplicationAvailability)
인터페이스를 주입하고, 이에 대한 메소드를 호출하여 언제든지 현재 가용성 상태를 검색할 수 있다. 애플리케이션은 상태 업데이트를 수신하거나 애플리케이션 상태 업데이트를 하려는 경우가 많다.
예를 들어, 쿠버네티스 “exec Probe”가 이 파일을 볼 수 있도록 애플리케이션의 “준비성” 상태를 파일로 내보낼 수 있다:
자바
import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.ReadinessState;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class MyReadinessStateExporter {
@EventListener
public void onStateChange(AvailabilityChangeEvent<ReadinessState> event) {
switch (event.getState()) {
case ACCEPTING_TRAFFIC:
// /tmp/healthy 파일 생성
break;
case REFUSING_TRAFFIC:
// /tmp/healthy 파일 제거
break;
}
}
}
코틀린
import org.springframework.boot.availability.AvailabilityChangeEvent
import org.springframework.boot.availability.ReadinessState
import org.springframework.context.event.EventListener
import org.springframework.stereotype.Component
@Component
class MyReadinessStateExporter {
@EventListener
fun onStateChange(event: AvailabilityChangeEvent<ReadinessState?>) {
when (event.state) {
ReadinessState.ACCEPTING_TRAFFIC -> {
// /tmp/healthy 파일 생성
}
ReadinessState.REFUSING_TRAFFIC -> {
// /tmp/healthy 파일 제거
}
else -> {
// ...
}
}
}
}
애플리케이션이 중단되어 복구할 수 없는 경우 애플리케이션 상태를 업데이트할 수도 있다:
자바
import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.LivenessState;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
@Component
public class MyLocalCacheVerifier {
private final ApplicationEventPublisher eventPublisher;
public MyLocalCacheVerifier(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void checkLocalCache() {
try {
// ...
} catch (CacheCompletelyBrokenException ex) {
AvailabilityChangeEvent.publish(this.eventPublisher, ex, LivenessState.BROKEN);
}
}
}
코틀린
import org.springframework.boot.availability.AvailabilityChangeEvent
import org.springframework.boot.availability.LivenessState
import org.springframework.context.ApplicationEventPublisher
import org.springframework.stereotype.Component
@Component
class MyLocalCacheVerifier(private val eventPublisher: ApplicationEventPublisher) {
fun checkLocalCache() {
try {
// ...
} catch (ex: CacheCompletelyBrokenException) {
AvailabilityChangeEvent.publish(eventPublisher, ex, LivenessState.BROKEN)
}
}
}
스프링 부트는 액추에이터 헬스 엔드포인트를 통해 “활성” 및 “준비성”에 대한 쿠버네티스 HTTP 프로브를 제공한다. 다음 절에서 쿠버네티스에 스프링 부트 애플리케이션을 배포하는 방법에 대한 자세한 지침을 얻을 수 있다.
7.1.7. Application Events and Listeners
컨텍스트리프레시이벤트(ContextRefreshedEvent)
와 같은 일반적인 스프링 프레임워크 이벤트 외에도 스프링애플리케이션(SpringApplication)
은 몇 가지 추가 애플리케이션 이벤트를 보낸다.
노트
일부 이벤트는 실제로 스프링애플리케이션(SpringApplication)
이 생성되기 전에 트리거되므로, 해당 이벤트에 대한 리스너를 @Bean
으로 등록할 수 없다. SpringApplication.addListeners(...)
메소드 또는 SpringApplicationBuilder.listeners(...)
메소드를 사용하여 등록할 수 있다.
애플리케이션 생성 방식에 관계없이 해당 리스너를 자동으로 등록하려면, 다음 예제처럼, 프로젝트에 META-INF/spring.factories
파일을 추가하고 org.springframework.context.ApplicationListener
키(key)를 사용하여 리스너를 참조할 수 있다:
org.springframework.context.ApplicationListener=com.example.project.MyListener
애플리케이션이 실행될 때, 애플리케이션 이벤트는 다음 순서로 전송된다:
애플리케이션스타팅이벤트(ApplicationStartingEvent)
는 실행 시작 시, 리스너 및 이니셜라이저(initializer) 등록을 제외한 모든 처리가 발생하기 전에 전송된다.애플리케이션인바이런먼트프리페어드이벤트(ApplicationEnvironmentPreparedEvent)
는 컨텍스트에서 사용할 환경이 알려졌지만 컨텍스트가 생성되기 전에 전송된다.애플리케이션컨텍스트이니셜라이즈이벤트(ApplicationContextInitializedEvent)
는애플리케이션컨텍스트(ApplicationContext)
가 준비되고애플리케이션컨텍스트이니셜라이저(ApplicationContextInitializers)
가 호출되었지만 빈 정의가 로드되기 전에 전송된다.애플리케이션프리페어드이벤트(ApplicationPreparedEvent)
는 새로고침(refresh)이 시작되기 직전 빈 정의가 로드된 후에 전송된다.애플리케이션스타트이벤트(ApplicationStartedEvent)
는 컨텍스트 새로고침 후 애플리케이션 및 커맨드라인 러너가 호출되기 전에 전송된다.LivenessState.CORRECT
직후에어베일러빌리티체인지이벤트(AvailabilityChangeEvent)
가 전송되어 애플리케이션이 라이브로 간주됨을 나타낸다.애플리케이션레디이벤트(ApplicationReadyEvent)
는 애플리케이션 및 커맨드라인 러너가 호출된 후에 전송된다.어베일러빌리티체인지이벤트(AvailabilityChangeEvent)
는ReadinessState.ACCEPTING_TRAFFIC
과 함께 바로 전송되어 애플리케이션이 요청을 처리할 준비가 되었음을 나타낸다.- 시작 시 예외가 발생하면,
애플리케이션페일이벤트(ApplicationFailedEvent)
가 전송된다.
위 목록에는 스프링애플리케이션(SpringApplication)
에 연결된 스프링애플리케이션이벤트(SpringApplicationEvents)
만 포함되어 있다. 이 외에도 애플리케이션프리페어드이벤트(ApplicationPreparedEvent)
및 애플리케이션스타트이벤트(ApplicationStartedEvent)
다음 이벤트도 나타난다:
웹서버이니셜라이즈이벤트(WebServerInitializedEvent)
는웹서버(WebServer)
가 준비된 후 전송된다.서블릿웹서버이니셜라이즈이벤트(ServletWebServerInitializedEvent)
및리액티브웹서버이니셜라이즈이벤트(ReactiveWebServerInitializedEvent)
는 각각 서블릿 및 리액티브 변수다.컨텍스트리프레시이벤트(ContextRefreshedEvent)
는애플리케이션컨텍스트(ApplicationContext)
가 새로고침(refreshed) 될 때 전송된다.
애플리케이션 이벤트를 사용할 필요가 없는 경우가 많지만 이벤트가 존재한다는 사실을 알아두면 편리할 수 있다. 내부적으로 스프링 부트는 이벤트를 사용하여 다양한 작업을 처리한다.
이벤트 리스너는 기본적으로 동일한 스레드에서 실행되므로 시간이 오래 걸릴 수 있는 작업을 실행해서는 안 된다. 대신 애플리케이션 및 커맨드라인 러너를 사용하자.
애플리케이션 이벤트는 스프링 프레임워크의 이벤트 퍼블리싱(publishing) 메커니즘을 사용하여 전송된다. 이 메커니즘의 일부는 하위 컨텍스트의 리스너에 퍼블리시된 이벤트가 모든 상위 컨텍스트의 리스너에도 퍼블리시되도록 보장한다. 결과적으로 애플리케이션이 스프링애플리케이션 인스턴스의 계층 구조를 사용하는 경우 리스너는 동일한 타입의 애플리케이션 이벤트에 대한 여러 인스턴스를 수신할 수 있다.
리스너가 컨텍스트에 대한 이벤트와 하위 컨텍스트에 대한 이벤트를 구별할 수 있도록 하려면, 애플리케이션 컨텍스트가 주입되도록 요청한 다음 주입된 컨텍스트를 이벤트 컨텍스트와 비교해야 한다. 애플리케이션컨텍스트어웨어(ApplicationContextAware)
를 구현하거나 리스너가 빈인 경우 @Autowired
를 사용하여 컨텍스트를 주입할 수 있다.
7.1.8. Web Environment
스프링애플리케이션(SpringApplication)
은 사용자를 대신하여 올바른 타입의 애플리케이션컨텍스트(ApplicationContext)
를 생성하려고 시도한다. 웹어플리케이션타입(WebApplicationType)
을 결정하는 데 사용되는 알고리즘은 다음과 같다:
- 스프링 MVC가 존재하는 경우
어노테이션컨피그서블릿웹서버애플리케이션컨텍스트(AnnotationConfigServletWebServerApplicationContext)
가 사용된다. - 스프링 MVC가 없고 스프링 웹플럭스가 있는 경우
어노테이션컨피그리액티브웹서버애플리케이션컨텍스트(AnnotationConfigReactiveWebServerApplicationContext)
가 사용된다. - 그렇지 않으면,
어노테이션컨피그애플리케이션컨텍스트(AnnotationConfigApplicationContext)
가 사용된다.
이는 동일한 애플리케이션에서 스프링 MVC와 스프링 웹플럭스의 새로운 웹클라이언트(WebClient)
를 사용하는 경우 기본적으로 스프링 MVC가 사용된다는 것을 의미한다. setWebApplicationType(WebApplicationType)
을 호출하여 이를 쉽게 오버라이드할 수 있다.
setApplicationContextFactory(...)
를 호출하여 사용되는 애플리케이션컨텍스트(ApplicationContext)
타입을 완전히 제어하는 것도 가능하다.
JUnit 테스트에서 스프링애플리케이션(SpringApplication)
을 사용할 때 setWebApplicationType(WebApplicationType.NONE)
을 호출하는 것이 바람직한 경우가 있다.
7.1.9. Accessing Application Arguments
SpringApplication.run(...)
에 전달된 애플리케이션 아규먼트에 접근해야 하는 경우 org.springframework.boot.ApplicationArguments
빈을 주입할 수 있다. 애플리케이션아규먼트(ApplicationArguments) 인터페이스는 다음 예제와 같이 String[]
아규먼트와 파싱된 옵션
및 비옵션(non-option)
아규먼트 모두에 대한 접근할 수 있다.
자바
import java.util.List;
import org.springframework.boot.ApplicationArguments;
import org.springframework.stereotype.Component;
@Component
public class MyBean {
public MyBean(ApplicationArguments args) {
boolean debug = args.containsOption("debug");
List<String> files = args.getNonOptionArgs();
if (debug) {
System.out.println(files);
}
// "--debug logfile.txt"로 실행하면 ["logfile.txt"]가 표시된다.
}
}
코틀린
import org.springframework.boot.ApplicationArguments
import org.springframework.stereotype.Component
@Component
class MyBean(args: ApplicationArguments) {
init {
val debug = args.containsOption("debug")
val files = args.nonOptionArgs
if (debug) {
println(files)
}
// "--debug logfile.txt"로 실행하면 ["logfile.txt"]가 표시된다.
}
}
스프링 부트는 또한 스프링 환경에 커맨드라인프로퍼티소스(CommandLinePropertySource)
를 등록한다. 이를 통해 @Value
어노테이션을 사용하여 싱글 애플리케이션 아규먼트를 주입할 수도 있다.
7.1.10. Using the ApplicationRunner or CommandLineRunner
스프링애플리케이션(SpringApplication)
이 시작된 후 특정 코드를 실행해야 하는 경우 애플리케이션러너(ApplicationRunner)
또는 커맨드라인러너(CommandLineRunner)
인터페이스를 구현할 수 있다. 두 인터페이스 모두 동일한 방식으로 작동하며 SpringApplication.run(...)
이 완료되기 직전에 호출되는 run
메서드를 제공한다.
이 기능은 애플리케이션 시작 후 트래픽 수신을 시작하기 전에 실행해야 하는 작업에 매우 적합하다.
커맨드라인러너(CommandLineRunner)
인터페이스는 문자열 배열로 애플리케이션 아규먼트에 대한 접근를 제공하는 반면, 애플리케이션러너(ApplicationRunner)
는 앞에서 설명한 애플리케이션아규먼트(ApplicationArguments)
인터페이스를 사용한다. 다음 예에서는 run
메서드가 있는 커맨드라인러너(CommandLineRunner)
를 보여준다:
자바
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class MyCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) {
// 무엇인가 작동하는..
}
}
코틀린
import org.springframework.boot.CommandLineRunner
import org.springframework.stereotype.Component
@Component
class MyCommandLineRunner : CommandLineRunner {
override fun run(vararg args: String) {
// 무엇인가 작동하는..
}
}
특정 순서로 호출되어야 하는 여러 커맨드라인러너(CommandLineRunner)
또는 애플리케이션러너(ApplicationRunner)
빈이 정의된 경우 org.springframework.core.Ordered
인터페이스를 추가로 구현하거나 org.springframework.core.annotation.Order
어노테이션을 사용할 수 있다.
7.1.11. Application Exit
각 스프링애플리케이션(SpringApplication)
은 종료 시 애플리케이션컨텍스트(ApplicationContext)
가 정상적(gracefully)으로 닫히도록 JVM에 종료 후크(hook)를 등록한다. 모든 표준 스프링 생명주기 콜백(예: DisposableBean 인터페이스 또는 @PreDestroy 어노테이션)을 사용할 수 있다.
게다가, SpringApplication.exit()
가 호출될 때 특정 종료 코드를 반환하려는 경우, org.springframework.boot.ExitCodeGenerator
인터페이스를 구현할 수 있다. 그런 다음 이 종료 코드를 System.exit()
에 전달하여 다음 예제와 같이 상태 코드로 반환할 수 있다:
import org.springframework.boot.ExitCodeGenerator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class MyApplication {
@Bean
public ExitCodeGenerator exitCodeGenerator() {
return () -> 42;
}
public static void main(String[] args) {
System.exit(SpringApplication.exit(SpringApplication.run(MyApplication.class, args)));
}
}
import org.springframework.boot.ExitCodeGenerator
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
import kotlin.system.exitProcess
@SpringBootApplication
class MyApplication {
@Bean
fun exitCodeGenerator() = ExitCodeGenerator { 42 }
}
fun main(args: Array<String>) {
exitProcess(SpringApplication.exit(runApplication<MyApplication>(*args)))
}
또한 엑시트코드제너레이터(ExitCodeGenerator)
인터페이스는 예외로 구현될 수 있다. 이러한 예외가 발생하면 스프링 부트는 구현된 getExitCode()
메서드에서 제공하는 종료 코드를 반환한다.
엑시트코드제너레이터(ExitCodeGenerator)
가 두 개 이상인 경우 생성된 첫 번째의 0이 아닌 종료 코드가 사용된다. 제너레이터가 호출되는 순서를 제어하려면 org.springframework.core.Ordered
인터페이스를 추가로 구현하거나 org.springframework.core.annotation.Order
어노테이션을 사용하자.
7.1.12. Admin Features
spring.application.admin.enabled
프로퍼티를 지정하여 애플리케이션에 대한 관리 기능을 활성화할 수 있다. 이는 플랫폼 MBeanServer
에 SpringApplicationAdminMXBean
을 노출한다. 이 기능을 사용하여 스프링 부트 애플리케이션을 원격으로 관리할 수 있다. 이 기능은 모든 서비스 래퍼 구현에도 유용할 수 있다.
애플리케이션이 실행 중인 HTTP 포트를 알고 싶다면, local.server.port
키를 사용하여 프로퍼티를 가져오자.
7.1.13. Application Startup tracking
애플리케이션 시작 중에 스프링애플리케이션(SpringApplication)
과 애플리케이션컨텍스트(ApplicationContext)
는 애플리케이션 생명주기, 빈 생명주기 또는 심지어 애플리케이션 이벤트 처리와 관련된 많은 작업을 수행한다. 애플리케이션스타트업(ApplicationStartup)
을 사용하면 스프링 프레임워크를 사용하여 스타트업스텝(StartupStep)
객체를 사용하여 애플리케이션 시작 순서를 추적할 수 있다. 이 데이터는 프로필링 목적으로 수집되거나 애플리케이션 시작 프로세스를 더 잘 이해하기 위해 수집할 수 있다.
스프링애플리케이션(SpringApplication)
인스턴스를 설정할 때 애플리케이션스타트업(ApplicationStartup)
구현을 선택할 수 있다. 예를 들어 버퍼링애플리케이션스타트업(BufferingApplicationStartup)
을 사용하려면 다음과 같이 작성할 수 있다:
자바
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(MyApplication.class);
application.setApplicationStartup(new BufferingApplicationStartup(2048));
application.run(args);
}
}
코틀린
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup
import org.springframework.boot.runApplication
@SpringBootApplication
class MyApplication
fun main(args: Array<String>) {
runApplication<MyApplication>(*args) {
applicationStartup = BufferingApplicationStartup(2048)
}
}
사용 가능한 첫 번째 구현체인 플라이트레코더애플리케이션스타트업(FlightRecorderApplicationStartup)
은 스프링 프레임워크에서 제공된다. 이는 자바 플라이트 레코더 절에 스프링 관련 시작 이벤트를 추가하며 애플리케이션을 프로파일링하고 스프링 컨텍스트 생명주기을 JVM 이벤트(할당, GC, 클래스 로딩 등)와 연관시키는 데 사용된다. 일단 구성되면 플라이트 레코더가 활성화된 상태에서 애플리케이션을 실행하여 데이터를 기록할 수 있다.
$ java -XX:StartFlightRecording:filename=recording.jfr,duration=10s -jar demo.jar
스프링 부트는 버퍼링애플리케이션스타트업(BufferingApplicationStartup)
변수과 함께 제공된다. 이 구현체는 시작 단계를 버퍼링하고 이를 외부 측정 시스템으로 배출하기 위한 것이다. 애플리케이션은 모든 컴포넌트에서 버퍼링애플리케이션스타트업(BufferingApplicationStartup)
타입의 빈을 요청할 수 있다.
스프링 부트는 이 정보를 JSON 문서로 제공하는 스타트업 엔드포인트를 노출하도록 구성할 수도 있다.
7.2. Externalized Configuration
스프링 부트를 사용하면 구성을 외부화하여 다양한 환경에서 동일한 애플리케이션 코드로 작업할 수 있다. 자바 프로퍼티스 파일, YAML 파일, 환경 변수, 커맨드라인 아규먼트를 비롯한 다양한 외부 구성 소스를 사용할 수 있다.
프로퍼티 값은 @Value
어노테이션을 사용하여 빈에 직접 주입하거나 스프링의 환경 추상화를 통해 접근하거나 @ConfigurationProperties
를 통해 구조화된 객체에 바인딩할 수 있다.
스프링 부트는 값 오버라이드를 허용하도록 설계된 특별한 PropertySource
순서를 사용한다. 이후 프로퍼티 소스는 이전 프로퍼티 소스에서 정의된 값을 오버라이드할 수 있다. 소스는 다음 순서를 고려한다:
- 기본 프로퍼티(
SpringApplication.setDefaultProperties
설정을 통해 지정). @Configuration
클래스의@PropertySource
어노테이션. 이러한 프로퍼티 소스는 애플리케이션 컨텍스트가 새로고침될 때까지 환경에 추가되지 않는다. 새로고침이 시작되기 전에 읽혀지는logging.*
및spring.main.*
과 같은 특정 프로퍼티를을 구성하기에는 너무 늦다.- 구성 데이터(예:
application.properties
파일) random.*
프로퍼티을 갖는랜덤벨류프로퍼티소스(RandomValuePropertySource)
이다.- OS 환경 변수
- 자바 시스템 프로퍼티스(
System.getProperties()
). java:comp/env
의 JNDI 애트리뷰트.서블릿컨텍스트(ServletContext)
의 초기화 파라미터.서블릿컨피그(ServletConfig)
의 초기화 파라미터.SPRING_APPLICATION_JSON
프로퍼티의 (환경 변수 또는 시스템 프로퍼티에 포함된 인라인 JSON)- 커맨드라인 아규먼트
- 테스트의 프로퍼티스 애트리뷰트.
@SpringBootTest
및 애플리케이션의 특정 부분을 테스트하기 위한 테스트 어노테이션에서 사용할 수 있다. - 테스트의
@DynamicPropertySource
어노테이션. - 테스트의
@TestPropertySource
어노테이션. 데브툴즈(devtools)
가 활성화된 경우$HOME/.config/spring-boot
디렉토리에 있는Devtools
글로벌 설정 프로퍼티.
구성 데이터 파일은 다음 순서를 고려한다:
- jar 내부에 패키지된 애플리케이션 프로퍼티(
application.properties
및YAML
변수) - jar 내부에 패키지된 프로필(Profile)별 애플리케이션 프로퍼티스(
application-{profile}.properties
및YAML
변수) - 패키지된 jar 외부의 애플리케이션 프로퍼티스(
application.properties
및YAML
변수) - 패키지된 jar 외부의 프로필(Profile)별 애플리케이션 프로퍼티스(
application-{profile}.properties
및YAML
변수)
전체 애플리케이션에 대해 하나의 포맷을 사용하는 것이 좋다. .properties
및 YAML
포맷 모두 포함된 구성 파일이 동일한 위치에 있는 경우 .properties
가 우선 적용된다.
시스템 프로퍼티 대신 환경 변수를 사용하는 경우, 대부분의 운영 체제에서는 마침표로 구분된 키 이름을 허용하지 않지만 대신 밑줄을 사용할 수 있다(예: spring.config.name
대신 SPRING_CONFIG_NAME
). 자세한 내용은 환경 변수 바인딩을 참고하자.
애플리케이션이 서블릿 컨테이너 또는 애플리케이션 서버에서 실행되는 경우 JNDI 프로퍼티스(java:comp/env
) 또는 서블릿 컨텍스트 초기화 파라미터를 환경 변수 또는 시스템 프로퍼티 대신 사용할 수 있다.
구체적인 예를 제공하기 위해 다음 예와 같이 name
프로퍼티을 사용하는 @Component
를 개발한다고 가정한다:
자바
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class MyBean {
@Value("${name}")
private String name;
// ...
}
코틀린
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
@Component
class MyBean {
@Value("\${name}")
private val name: String? = null
// ...
}
애플리케이션 클래스 패스(예: jar 내부)에는 name
에 대한 적절한 프로퍼티 값을 제공하는 application.properties
파일이 있다. 새 환경에서 실행할 때 이름을 오버라이드하는 jar 외부에 application.properties
파일을 제공할 수 있다. 일회성 테스트의 경우 특정 커맨드라인 스위치(예: java -jar app.jar --name="Spring"
)를 사용하여 시작할 수 있다.
env
및 configprops
엔드포인트는 프로퍼티에 특정 값이 있는 이유를 확인하는 데 유용할 수 있다. 이러한 두 엔드포인트를 사용하여 예기치 않은 프로퍼티 값을 진단할 수 있다. 자세한 내용은 “Production ready features” 절을 참고하자.
7.2.1. Accessing Command Line Properties
기본적으로 스프링애플리케이션(SpringApplication)
은 모든 커맨드라인 옵션 아규먼드(즉, --server.port=9000
과 같이 --
로 시작하는 아규먼트)를 프로퍼티로 변환하고 이를 스프링 환경에 추가한다. 앞에서 언급한 것처럼 커맨드라인 프로퍼티는 항상 파일 기반 프로퍼티 소스보다 우선한다.
환경에 커맨드라인 프로퍼티를 추가하지 않으려면 SpringApplication.setAddCommandLineProperties(false)
를 사용하여 비활성화할 수 있다.
7.2.2. JSON Application Properties
환경 변수 및 시스템 프로퍼티에는 일부 프로퍼티명을 사용할 수 없는 제한이 있다. 이를 돕기 위해 스프링 부트를 사용하면 프로퍼티 블록을 싱글 JSON 구조로 인코딩할 수 있다.
애플리케이션이 시작되면 spring.application.json
또는 SPRING_APPLICATION_JSON
프로퍼티가 파싱되어 환경에 추가된다.
예를 들어, SPRING_APPLICATION_JSON
프로퍼티는 UN*X 셸의 커맨드라인에 환경 변수로 제공될 수 있다:
$ SPRING_APPLICATION_JSON='{"my":{"name":"test"}}' java -jar myapp.jar
앞의 예에서는, 스프링 환경에서 my.name=test
로 끝난다.
동일하게 JSON을 시스템 프로퍼티로 제공할 수도 있다:
$ java -Dspring.application.json='{"my":{"name":"test"}}' -jar myapp.jar
또는 커맨드라인 아규먼트를 사용하여 JSON을 제공할 수 있다:
$ java -jar myapp.jar --spring.application.json='{"my":{"name":"test"}}'
클래식 애플리케이션 서버(Application Server)에 배포하는 경우 java:comp/env/spring.application.json
이라는 JNDI 변수를 사용할 수도 있다.
JSON의 null
값이 프로퍼티 소스에 추가되지만, 프로퍼티소스프로퍼티리졸버(PropertySourcesPropertyResolver)
는 null
프로퍼티를 누락된 값으로 처리한다. 이는 JSON이 null
값을 사용하여 하위 프로퍼티 소스의 프로퍼티를 오버라이드할 수 없음을 의미한다.
7.2.3. External Application Properties
스프링 부트는 애플리케이션이 시작될 때 아래 위치에서 application.properties
및 application.yaml
파일을 자동으로 찾아 로드한다:
- 클래스패스
- 루트 클래스패스
/config
패키지 클래스패스
- 디렉토리
- 디렉토리
- 디렉토리의
config/
하위디렉토리 config/
하위 디렉토리의 바로 하위 디렉토리
목록은 우선 순위에 따라 정렬된다(낮은 항목의 값이 이전 항목보다 우선됨). 로드된 파일 문서는 스프링 환경에 프로퍼티소스(PropertySource)
로 추가된다.
애플리케이션의 구성 파일명이 마음에 들지 않으면 spring.config.name
환경 프로퍼티를 지정하여 다른 파일명으로 전환할 수 있다. 예를 들어 myproject.properties
및 myproject.yaml
파일을 찾으려면 다음과 같이 애플리케이션을 실행할 수 있다:
$ java -jar myproject.jar --spring.config.name=myproject
spring.config.location
환경 프로퍼티를 사용하여 위치를 참조할 수도 있다. 이 프로퍼티는 확인할 위치가 하나 이상일 경우 쉼표로 구분된 목록을 받을 수 있다.
다음 예에서는 두 개의 개별 파일을 지정하는 방법을 보여준다:
$ java -jar myproject.jar --spring.config.location=\
optional:classpath:/default.properties,\
optional:classpath:/override.properties
optional:
접두사를 사용하기: 위치는 옵셔널이고 위치가 없어도 괜찮을 경우.
spring.config.name
, spring.config.location
및 spring.config.additional-location
은 로드해야 할 파일을 결정하기 위해 초기에 사용된다. 이는 환경 프로퍼티(일반적으로 OS 환경 변수, 시스템 프로퍼티 또는 커맨드라인 아규먼트)으로 정의되어야 한다.
spring.config.location
에 파일이 아닌 디렉토리가 포함되어 있으면 /
로 끝나야 한다. 런타임 시 로드되기 전에 spring.config.name
에서 생성된 이름이 추가된다. spring.config.location
에 지정된 파일을 직접 가져온다.
프로필별(profile) 파일을 확인하기 위해 디렉토리 및 파일 위치 값도 모두 확장된다. 예를 들어 classpath:myconfig.properties
의 spring.config.location
이 있는 경우 적절한 classpath:myconfig- <profile>.properties
파일이 로드되는 것을 볼 수 있다.
대부분의 상황에서, 추가하는 각 spring.config.location
항목은 싱글 파일이나 디렉토리를 참조한다. 위치는 정의된 순서대로 처리되며, 이후 위치는 이전 위치의 값을 오버라이드할 수 있다.
위치 설정이 복잡하면서 프로필별 구성 파일을 사용하는 경우 스프링 부트가 그룹화 방법을 알 수 있도록 추가 힌트를 제공해야 할 수도 있다. 위치 그룹은 모두 동일한 레벨로 간주되는 위치 모음이다. 예를 들어 모든 클래스패스의 위치를 그룹화할 수 있다. 위치 그룹 내의 항목은 ;로 구분되어야 한다. 자세한 내용은 “프로필별 파일” 절의 예를 참고하자.
spring.config.location
을 사용하여 구성된 위치는 기본 위치를 대체한다. 예를 들어, spring.config.location
이 optional:classpath:/custom-config/,optional:file:./custom-config/
값으로 구성된 경우 고려되는 전체 위치 세트는 다음과 같다:
optional:classpath:custom-config/
optional:file:./custom-config/
위치를 바꾸는 대신 위치를 추가하려는 경우 spring.config.additional-location
을 사용할 수 있다. 추가 위치에서 로드된 프로퍼티는 기본 위치의 프로퍼티를 오버라이드할 수 있다. 예를 들어, spring.config.additional-location
이 optional:classpath:/custom-config/,optional:file:./custom-config/
값으로 구성된 경우 고려되는 전체 위치 세트는 다음과 같다:
optional:classpath:/;optional:classpath:/config/
optional:file:./;optional:file:./config/;optional:file:./config/*/
optional:classpath:custom-config/
optional:file:./custom-config/
This search ordering lets you specify default values in one configuration file and then selectively override those values in another. You can provide default values for your application in application.properties (or whatever other basename you choose with spring.config.name) in one of the default locations. These default values can then be overridden at runtime with a different file located in one of the custom locations.
이 검색 순서를 사용하면 한 구성 파일에서 기본값을 지정한 다음 다른 구성 파일에서 해당 값을 선택적으로 오버라이드할 수 있다. 기본 위치 중 하나에 있는 application.properties
(또는 spring.config.name
으로 선택한 다른 이름)에 애플리케이션의 기본값을 제공할 수 있다. 그러면 이러한 기본값은 런타임 시 커스텀 위치 중 하나에 있는 다른 파일로 오버라이드될 수 있다.
Optional Locations
기본적으로, 지정된 구성 데이터 위치가 없으면 스프링 부트는 컨피그데이터로케이션낫파운드익셉션(ConfigDataLocationNotFoundException)
을 발생시키고 애플리케이션이 시작되지 않는다.
위치를 지정하고 싶지만 위치가 항상 존재하지 않더라도 상관없는 경우 optional: 접두사를 사용할 수 있다. spring.config.location
및 spring.config.additional-location
프로퍼티는 물론 spring.config.import
선언과 함께 이 접두사를 사용할 수 있다.
예를 들어, spring.config.import
에 option:file:./myconfig.properties
를 할당하면 myconfig.properties
파일이 누락된 경우에도 애플리케이션을 시작할 수 있다.
모든 컨피그데이터로케이션낫파운드익셉션(ConfigDataLocationNotFoundException)
을 무시하고 항상 애플리케이션을 계속 시작하려면 spring.config.on-not-found
프로퍼티를 사용할 수 있다. SpringApplication.setDefaultProperties(...)
를 사용하거나 시스템/환경 변수를 사용하여 무시하도록 값을 설정한다.
Wildcard Locations
구성 파일 위치의 마지막 패스 세그먼트에 *
문자가 포함되어 있으면, 와일드카드 위치로 간주한다. 구성이 로드되면 와일드카드가 확장되어 하위 디렉토리도 검사한다. 와일드카드 위치는 구성 프로퍼티의 소스가 여러 개 있는 쿠버네이티스와 같은 환경에서 특히 유용하다.
예를 들어, 일부 레디스 구성과 일부 MySQL 구성이 있는 경우 두 구성 부분을 별도로 유지하면서 둘 다 application.properties
파일에 존재하도록 할 수 있다. 이로 인해 /config/redis/application.properties
및 /config/mysql/application.properties
와 같은 서로 다른 위치에 두 개의 별도 application.properties
파일이 마운트될 수 있습니다. 이러한 경우 와일드카드 위치가 config/*/이면 두 파일이 모두 처리된다.
기본적으로, 스프링 부트는 기본 검색 위치에 config/*/
를 포함한다. 이는 jar 외부의 /config
디렉토리의 모든 하위 디렉토리 검색을 의미한다.
spring.config.location
및 spring.config.additional-location
프로퍼티를 사용하여 와일드카드 위치를 직접 사용할 수 있다.
와일드카드 위치에는 *
하나만 포함되어야 하며, 디렉토리 검색 위치의 경우 */
로 끝나야 하고, 파일 검색 위치의 경우 */<filename>
으로 끝나야 한다. 와일드카드가 있는 위치는 절대 경로에서 알파벳순으로 정렬된다.
와일드카드 위치는 외부 디렉토리에서만 작동한다. classpath:
위치에는 와일드카드를 사용할 수 없다.
Profile Specific Files
애플리케이션 프로퍼티 파일뿐만 아니라, 스프링 부트는 명명 규칙 application-{profile}
을 사용하여 프로필별 파일 로드를 시도한다. 예를 들어, 애플리케이션이 prod
라는 프로필을 활성화하고, YAML 파일을 사용하는 경우 application.yaml
과 application-prod.yaml
이 모두 고려된다.
프로필별 프로퍼티는 표준 application.properties
와 동일한 위치에서 로드되며, 프로필별 파일은 항상 특정되지 않은 파일보다 우선시 된다. 여러 프로필이 지정된 경우 최후 승리 전략(last-wins strategy)이 적용된다. 예를 들어 prod
,live
프로필이 spring.profiles.active
프로퍼티에 의해 지정되면 application-prod.properties
의 값은 application-live.properties
의 값으로 오버라이드될 수 있다.
노트
최후 승리 전략은 위치(location) 그룹 레벨에서 적용된다. classpath:/cfg/,classpath:/ext/
로 설정된 spring.config.location
에는 classpath:/cfg/;classpath:/ext/
과 같은 오버라이드 규칙이 없다.
예를 들어, 위의 prod
,live
예제를 계속하면 다음 파일이 있을 수 있다:
/cfg
application-live.properties
/ext
application-live.properties
application-prod.properties
spring.config.location
이 classpath:/cfg/,classpath:/ext/
인 경우 모든 /ext
파일보다 먼저 모든 /cfg
파일을 처리한다:
/cfg/application-live.properties
/ext/application-prod.properties
/ext/application-live.properties
대신 classpath:/cfg/;classpath:/ext/
(;
구분 기호 사용)가 있는 경우 /cfg
및 /ext
를 동일한 레벨에서 처리한다:
/ext/application-prod.properties
/cfg/application-live.properties
/ext/application-live.properties
***
환경에는 활성 프로필이 설정되지 않은 경우 사용되는 기본 프로필 세트(기본적으로 [default])가 있다. 즉, 명시적으로 활성화된 프로필이 없으면 application-default
의 프로퍼티가 고려된다.
프로퍼티 파일은 한 번만 로드된다. 이미 프로필별 프로퍼티 파일을 직접 가져온 경우에는 두 번째로 가져오지 않는다.
Importing Additional Data
애플리케이션 프로퍼티는 spring.config.import
프로퍼티을 사용하여 다른 위치에서 추가 구성 데이터를 가져올 수 있다. 임포트(Imports)는 발견된 대로 처리되며, 임포트를 요청한 문서 바로 아래에 삽입될 추가 문서로 처리된다.
예를 들어, 클래스패스 application.properties
파일이 다음과 같다:
프로퍼티스(Properties)
spring.application.name=myapp
spring.config.import=optional:file:./dev.properties
Yaml
spring:
application:
name: "myapp"
config:
import: "optional:file:./dev.properties"
그러면 현재 디렉토리에 dev.properties
파일이 있는 경우 임포트가 트리거된다. 가져온 dev.properties
의 값은 임포트를 요청하고 트리거한 파일보다 우선한다. 위의 예에서 dev.properties
는 spring.application.name
을 다른 값으로 오버라이드할 수 있다.
임포트는 선언 횟수에 상관없이 한 번만 가져와 진다. 임포트가 properties/yaml 파일 내에서 정의되는 순서는 중요하지 않다. 예를 들어, 아래 두 예는 동일한 결과를 보여준다:
프로퍼티스(Properties)
spring.config.import=my.properties
my.property=value
Yaml
spring:
config:
import: "my.properties"
my:
property: "value"
프로퍼티스(Properties)
my.property=value
spring.config.import=my.properties
Yaml
my:
property: "value"
spring:
config:
import: "my.properties"
위의 두 예제에서 my.properties
파일의 값은 해당 임포트를 트리거한 파일보다 우선시 된다.
단일 spring.config.import
키 아래에 여러 위치를 지정할 수 있다. 위치는 정의된 순서대로 처리되며 이후 임포트가 우선 적용됩니다.
해당하는 경우 프로필별 변수도 임포트 대상으로 고려된다. 위의 예에서는 my.properties
와 my-<profile>.properties
변수를 모두 가져온다.
TIP
스프링 부트에는 다양한 위치를 지원할 수 있는 플러그형 API가 있다. 기본적으로 자바 프로퍼티스, YAML 및 “구성 트리”를 가져올 수 있다.
서드파티 jar는 추가 기술에 대한 지원을 한다. 예를 들어 Consul
, 아파치 주키퍼(ZooKeepe)r 또는 넷플릭스 Archaius와 같은 외부 저장소의 구성 데이터를 상상할 수 있다.
커스텀 위치를 지원하려면, org.springframework.boot.context.config
패키지의 컨피그데이터로케이션리졸버(ConfigDataLocationResolver)
및 컨피스데이터로더(ConfigDataLoader)
클래스를 참고하자. ***
Importing Extensionless Files
일부 클라우드 플랫폼은 볼륨 마운트 파일에 파일 확장자를 추가할 수 없다. 이러한 확장자가 없는 파일을 가져오려면 스프링 부트에 힌트를 제공하여 로드 방법을 알 수 있도록 해야 한다. 대괄호 안에 확장 힌트를 넣으면 된다.
예를 들어, yaml로 임포트하려는 /etc/config/myconfig
파일이 있다고 가정하자. 다음 예제의 application.properties
에서 가져올 수 있다:
프로퍼티스(Properties)
spring.config.import=file:/etc/config/myconfig[.yaml]
Yaml
spring:
config:
import: "file:/etc/config/myconfig[.yaml]"
Using Configuration Trees
클라우드 플랫폼(예: 쿠버네티스)에서 애플리케이션을 실행할 때 플랫폼이 제공하는 구성 값을 읽어야 하는 경우가 많다. 이러한 목적으로 환경 변수를 사용하는 것은 드문 일이 아니지만, 특히 값을 비밀로 유지해야 하는 경우에는 단점이 있을 수 있다.
환경 변수의 대안으로, 이제 많은 클라우드 플랫폼에서 구성을 탑재된 데이터 볼륨에 매핑할 수 있다. 예를 들어 쿠버네티스는 컨피그맵(ConfigMap)
과 시크릿(Secret)
을 모두 볼륨 마운트할 수 있다.
사용할 수 있는 두 가지 일반적인 볼륨 마운트 패턴이 있다:
- 싱글 파일에는 전체 프로퍼티 세트(일반적으로 YAML로 작성됨)가 포함되어 있다.
- 여러 파일이 디렉토리 트리에 기록되며, 파일 이름은 ‘키’가 되고 내용은 ‘값’이 된다.
첫 번째 경우, 위에서 설명한 대로 spring.config.import
를 사용하여 YAML 또는 프로퍼티 파일을 직접 가져올 수 있다. 두 번째 경우에는 스프링 부트가 모든 파일을 프로퍼티로 노출해야 한다는 것을 알 수 있도록 configtree:
접두사를 사용해야 한다.
예를 들어 쿠버네티스가 다음 볼륨을 마운트했다고 가정해 보자.
etc/
config/
myapp/
username
password
사용자명 파일의 내용은 컨피그 값(config value)이고 비밀번호의 내용은 시크릿(secret)이다.
이러한 프로퍼티를 가져오려면, application.properties
또는 application.yaml
파일에 다음 내용을 추가하면 된다.
프로퍼티스(Properties)
spring.config.import=optional:configtree:/etc/config/
Yaml
spring:
config:
import: "optional:configtree:/etc/config/"
그런 다음 일반적인 방법으로 환경에서 myapp.username
및 myapp.password
프로퍼티스에 접하거나 주입할 수 있다.
구성 트리(Configuration tree) 아래의 폴더가 프로퍼티명을 형성한다. 위의 예에서 사용자 이름과 비밀번호로 프로퍼티에 접근하려면 spring.config.import
를 optional:configtree:/etc/config/myapp
으로 설정할 수 있다.
점 표기법이 있는 파일명도 올바르게 매핑된다. 예를 들어 위의 예제에서 /etc/config
에 myapp.username
이라는 파일이 있으면 환경에 myapp.username
프로퍼티가 생성된다.
구성 트리 값은 예상되는 내용에 따라 String
및 byte[]
타입 모두에 바인딩될 수 있다.
동일한 상위 폴더에서 가져올 구성 트리가 여러 개 있는 경우 와일드카드 단축키를 사용할 수 있다. /*/
로 끝나는 모든 configtree:
위치는 모든 하위 항목을 구성 트리로 가져온다.
예를 들어 다음과 같은 볼륨이 있다고 가정하자:
etc/
config/
dbconfig/
db/
username
password
mqconfig/
mq/
username
password
configtree:/etc/config/*/
를 임포트 위치로 사용할 수 있다.
프로퍼티스(Properties)
spring.config.import=optional:configtree:/etc/config/*/
Yaml
spring:
config:
import: "optional:configtree:/etc/config/*/"
그러면 db.username
, db.password
, mq.username
및 mq.password
프로퍼티가 추가된다.
와일드카드를 사용하여 로드된 디렉토리는 알파벳순으로 정렬된다. 다른 순서가 필요한 경우 각 위치를 별도의 임포트로 나열해야 한다.
구성 트리는 도커 시크릿에도 사용할 수 있다. 도커 스웜(Swarm) 서비스에 시크릿에 대한 접근 권한이 부여되면 해당 시크릿이 컨테이너에 탑재된다. 예를 들어, db.password
라는 시크릿이 /run/secrets/
위치에 마운트된 경우 다음 내용을 사용하여 스프링 환경에서 db.password
를 사용할 수 있다:
프로퍼티스(Properties)
spring.config.import=optional:configtree:/run/secrets/
Yaml
spring:
config:
import: "optional:configtree:/run/secrets/"
Property Placeholders
application.properties
및 application.yaml
의 값은 사용 시 기존 환경을 통해 필터링되므로 이미 존재하는 값(예: 시스템 프로퍼티 또는 환경 변수)을 다시 참조할 수 있다. 표준 ${name} 프로퍼티-자리 표시자(placeholder) 문법은 값 내 어디에서나 사용할 수 있다. 프로퍼티 자리 표시자는 프로퍼티명에서 기본값을 구분하기 위해 :을 사용하여 기본값을 지정할 수도 있다(예: ${name:default}).
기본값이 있거나 없는 자리 표시자의 사용은 다음 예재에 나와 있다.
프로퍼티스(Properties)
app.name=MyApp
app.description=${app.name} is a Spring Boot application written by ${username:Unknown}
Yaml
app:
name: "MyApp"
description: "${app.name} is a Spring Boot application written by ${username:Unknown}"
username
프로퍼티가 다른 곳에서 설정되지 않았다고 가정하면 app.description
의 값은 MyApp is a Spring Boot application written by Unknown이 된다.
노트
항상 표준 포맷(소문자만 사용하는 케밥 케이스)을 사용하여 자리 표시자에서 프로퍼티명을 참조해야 한다. 이렇게 하면 스프링 부트가 @ConfigurationProperties
바인딩할 때와 동일한 로직를 사용할 수 있다.
예를 들어 ${demo.item-price}
는 application.properties
파일에서 demo.item-price
및 demo.itemPrice
양식을 선택하고 시스템 환경에서 DEMO_ITEMPRICE
를 선택한다. 대신 ${demo.itemPrice}
를 사용한 경우, deco.item-price
및 DEMO_ITEMPRICE
는 고려되지 않는다. ***
또한 이 기술을 사용하여 기존 스프링 부트 프로퍼티의 “짧은” 변수을 만들 수도 있다. 자세한 내용은 ‘짧은’ 커맨드라인 아규먼트 사용 방법을 참고하자.
Working With Multi-Document Files
스프링 부트를 사용하면 파일을 각각 독립적으로 추가되는 여러 논리적 문서로 분할할 수 있다. 문서는 위에서 아래로 순서대로 처리된다. 최신 문서는 이전 문서에서 정의된 프로퍼티를 오버라이드할 수 있다.
application.yaml
파일의 경우 표준 YAML
다중 문서 문법이 사용된다. 세 개의 연속된 하이픈은 한 문서의 끝과 다음 문서의 시작을 나타낸다.
예를 들어 다음 파일에는 두 개의 논리적 문서가 있다:
spring:
application:
name: "MyApp"
---
spring:
application:
name: "MyCloudApp"
config:
activate:
on-cloud-platform: "kubernetes"
application.properties
파일의 경우 특수 #— 또는 !— 주석을 사용하여 문서 분할을 표시한다.
spring.application.name=MyApp
#---
spring.application.name=MyCloudApp
spring.config.activate.on-cloud-platform=kubernetes
프로퍼티 파일 구분기호(separator)에는 선행 공백이 없어야 하며 정확히 3개의 하이픈 문자가 있어야 한다. 구분 기호 바로 앞과 뒤의 줄은 동일한 주석 접두사가 아니어야 한다.
다중 문서 프로퍼티 파일은 spring.config.activate.on-profile
과 같은 activation
프로퍼티과 함께 사용되는 경우가 많다. 자세한 내용은 다음 절을 참고하자.
@PropertySource
또는 @TestPropertySource
어노테이션을 사용하여 다중 문서 프로퍼티 파일을 로드할 수 없다.
Activation Properties
특정 조건이 충족될 때 특정 프로퍼티스 세트만 활성화하는 것이 유용한 경우가 있다. 예를 들어 특정 프로필이 활성화된 경우에만 관련된 프로퍼티가 있을 수 있다.
spring.config.activate.*
를 사용하여 프로퍼티 문서를 조건부로 활성화할 수 있다.
다음 활성화 프로퍼티를 사용할 수 있다:
테이블 5. 활성화 프로퍼티스
프로퍼티 | 설명 |
---|---|
on-profile | 문서가 활성화되려면 일치해야 하는 프로필 표현식이다. |
on-cloud-platform | 문서를 활성화하기 위해 클라우드플랫폼(CloudPlatform)을 감지해야 한다. |
예를 들어 다음은 쿠버네티스에서 실행될 때 두 번째 문서가 활성화되고, “prod” 또는 “staging” 프로필이 활성화될 때 활성화되도록 지정한다:
프로퍼티스(Properties)
myprop=always-set
#---
spring.config.activate.on-cloud-platform=kubernetes
spring.config.activate.on-profile=prod | staging
myotherprop=sometimes-set
Yaml
myprop: "always-set"
---
spring:
config:
activate:
on-cloud-platform: "kubernetes"
on-profile: "prod | staging"
myotherprop: "sometimes-set"
7.2.4. Encrypting Properties
스프링 부트는 프로퍼티 값 암호화에 대한 지원을 제공하지 않지만 스프링 환경(Environment)
에 포함된 값을 수정하는 데 필요한 후크 포인트를 제공한다. 인바이런먼트포스트프로세서(EnvironmentPostProcessor)
인터페이스를 사용하면 애플리케이션이 시작되기 전 환경(Environment)
를 조작할 수 있다. 자세한 내용은 Customize the Environment or ApplicationContext Before It Starts을 참고하자.
크리덴셜(credentials)과 비밀번호를 안전하게 저장하는 방법이 필요한 경우, 스프링 클라우드 볼트(Vault) 프로젝트는 하시코프 볼트(HashiCorp Vault)에 외부화된 구성을 저장하기 위한 지원을 제공한다.
7.2.5. Working With YAML
YAML은 JSON의 상위 집합이므로 계층적 구성 데이터를 지정하는 데 편리한 포맷이다. 스프링애플리케이션(SpringApplication) 클래스는 클래스패스에 SnakeYAML
라이브러리가 있을 때 프로퍼티 대신 YAML을 자동으로 지원한다.
“Starters”를 사용하는 경우 SnakeYAML
은 spring-boot-starter
에 의해 자동 제공된다.
Mapping YAML to Properties
YAML 문서는 계층 포맷에서 스프링 환경과 함께 사용할 수 있는 플랫 구조(flat structure)로 변환되어야 한다. 예를 들어 다음 YAML 문서를 생각해보자:
environments:
dev:
url: "https://dev.example.com"
name: "Developer Setup"
prod:
url: "https://another.example.com"
name: "My Cool App"
환경에서 이러한 프로퍼티에 접근하려면 다음과 같이 플랫화 되어아한다:
environments.dev.url=https://dev.example.com
environments.dev.name=Developer Setup
environments.prod.url=https://another.example.com
environments.prod.name=My Cool App
마찬가지로, YAML 리스트도 플랫화해야 한다. [index] 역참조자를 사용하여 속성 키로 표시된다. 예를 들어 다음 YAML을 생각해보자:
my:
servers:
- "dev.example.com"
- "another.example.com"
앞의 예는 다음 프로퍼티로 변환된다.
my.servers[0]=dev.example.com
my.servers[1]=another.example.com
[index] 표기법을 사용하는 프로퍼티는 스프링 부트의 바인더(Binder)
클래스를 사용하여 자바 리스트 또는 세트(Set) 객체에 바인딩될 수 있다. 자세한 내용은 아래의 “Type-safe Configuration Properties” 절을 참고하자.
@PropertySource
또는 @TestPropertySource
어노테이션을 사용하여 YAML 파일을 로드할 수 없다. 따라서 이러한 방식으로 값을 로드해야 하는 경우 프로퍼티스 파일을 사용해야 합니다.
Directly Loading YAML
스프링 프레임워크는 YAML 문서를 로드하는 데 사용할 수 있는 두 가지 편리한 클래스를 제공한다. Yaml프로퍼티스팩토리빈(YamlPropertiesFactoryBean)
은 YAML을 프로퍼티스로 로드하고 Yaml맵팩토리빈(YamlMapFactoryBean)
은 YAML을 맵으로 로드한다.
YAML을 스프링 프로퍼티소스(PropertySource)
로 로드하려는 경우 Yaml프로퍼티소스로더(YamlPropertySourceLoader)
클래스를 사용할 수도 있다.
7.2.6. Configuring Random Values
랜덤벨류프로퍼티소스(RandomValuePropertySource)
는 랜덤 값을 주입하는 데 유용하다(예: 시크릿 또는 테스트). 다음 예제와 같이 정수, long, uuid 또는 string을 생성할 수 있다:
프로퍼티스(Properties)
my.secret=${random.value}
my.number=${random.int}
my.bignumber=${random.long}
my.uuid=${random.uuid}
my.number-less-than-ten=${random.int(10)}
my.number-in-range=${random.int[1024,65536]}
Yaml
my:
secret: "${random.value}"
number: "${random.int}"
bignumber: "${random.long}"
uuid: "${random.uuid}"
number-less-than-ten: "${random.int(10)}"
number-in-range: "${random.int[1024,65536]}"
random.int*
구문은 OPEN value (,max) CLOSE
이다. 여기서 OPEN,CLOSE
는 랜덤 문자이고 value,max
는 정수이다. max
가 제공되면 value
는 최소값이고 max
는 최대값(제외, 미만)입니다.
7.2.7. Configuring System Environment Properties
스프링 부트는 환경 프로퍼티에 대한 접두사 설정을 지원한다. 이는 구성 요구 사항이 서로 다른 여러 스프링 부트 애플리케이션에서 시스템 환경을 공유하는 경우 유용하다. 시스템 환경 프로퍼티의 접두사는 스프링애플리케이션(SpringApplication)
에서 직접 설정할 수 있다.
예를 들어, 접두어를 input
으로 설정하면 시스템 환경에서는 remote.timeout
과 같은 프로퍼티도 input.remote.timeout
으로 해석된다.
7.2.8. Type-safe Configuration Properties
@Value("${property}")
어노테이션을 사용하여 구성 프로퍼티를 주입하는 것은 때로 번거로울 수 있다. 특히 여러 프로퍼티로 작업하거나 데이터가 본질적으로 계층적인 경우에 더욱 그렇다. 스프링 부트는 강력한 타입의 빈이 애플리케이션 구성을 관리하고 유효성을 검사할 수 있도록 하는 프로퍼티를 사용하여 작업하는 대안 메소드를 제공한다.
@Value
와 type-safe configuration
프로퍼티의 차이점도 참고하자.
JavaBean Properties Binding
다음 예제와 같이 표준 자바빈(JavaBean)
프로퍼티를 선언한 빈을 바인딩하는 것이 가능하다:
자바
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("my.service")
public class MyProperties {
private boolean enabled;
private InetAddress remoteAddress;
private final Security security = new Security();
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public InetAddress getRemoteAddress() {
return this.remoteAddress;
}
public void setRemoteAddress(InetAddress remoteAddress) {
this.remoteAddress = remoteAddress;
}
public Security getSecurity() {
return this.security;
}
public static class Security {
private String username;
private String password;
private List<String> roles = new ArrayList<>(Collections.singleton("USER"));
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
public List<String> getRoles() {
return this.roles;
}
public void setRoles(List<String> roles) {
this.roles = roles;
}
}
}
코틀린
import org.springframework.boot.context.properties.ConfigurationProperties
import java.net.InetAddress
@ConfigurationProperties("my.service")
class MyProperties {
var isEnabled = false
var remoteAddress: InetAddress? = null
val security = Security()
class Security {
var username: String? = null
var password: String? = null
var roles: List<String> = ArrayList(setOf("USER"))
}
}
앞의 POJO는 다음 프로퍼티를 정의한다:
my.service.enabled
, 기본값은false
my.service.remote-address
, 문자열에서 강제 변환할 수 있는 타입.my.service.security.username
, 프로퍼티명에 따라 이름이 결정되는 중첩된 “security” 객체가 있다. 이 타입은 전혀 사용되지 않으며시큐리티프로퍼티스(SecurityProperties)
일 수 있다.my.service.security.password.
my.service.security.roles
, 기본값이 USER인 문자열 컬렉션이다.
NOTE
프로퍼티스 파일, YAML 파일, 환경 변수 및 기타 메커니즘을 통해 구성되는 스프링 부트에서 @ConfigurationProperties
클래스에 매핑되는 프로퍼티는 공개 API이지만 클래스 자체의 접근자(getter/setter)는 공개 API가 아니다.
이러한 배열은 기본 빈 생성자에 의존하며 getter 및 setter는 일반적으로 필수다. 바인딩은 스프링 MVC에서와 마찬가지로 표준 자바 빈즈 프로퍼티 디스크립터(Java Beans property descriptors)를 통해 이루어지기 때문이다. 다음과 같은 경우에는 setter가 생략될 수 있다:
- 맵은, 초기화되면 getter가 필요하지만 바인더에 의해 변경될 수 있으므로 반드시 setter는 필요하지 않다.
- 컬렉션과 배열은 인덱스(일반적으로 YAML 사용)를 하거나, 쉼표로 구분된 단일 값(프로퍼티스)을 사용하여 접근할 수 있다. 후자의 경우 setter가 필수다. 이러한 타입에 대해서는 항상 setter를 추가하는 것이 좋다. 컬렉션을 초기화하는 경우 이전 예제와 같이 컬렉션이 변경 불가능하지 않은지 확인하자.
- 중첩된 POJO 프로퍼티스가 초기화되면(이전 예의 Security 필드와 같이) setter가 필요하지 않다. 바인더가 기본 생성자를 사용하여 즉시 인스턴스를 생성하도록 하려면 setter가 필요하다.
어떤 사람들은 롬복(Lombok)
을 사용하여 getter와 setter를 자동으로 추가합니다. 객체를 인스턴스화하기 위해 컨테이너에서 자동으로 사용되므로 롬복이 이러한 타입에 대해 특정 생성자를 생성하지 않는지 확인해야 한다.
마지막으로, 표준 자바 빈 프로퍼티스만 고려되며, 스태틱 프로퍼티에 대한 바인딩은 지원되지 않는다. ***
Constructor Binding
이전 절의 예제는 다음 예제와 같이 불변(immutable) 방식으로 다시 작성할 수 있다:
자바
import java.net.InetAddress;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;
@ConfigurationProperties("my.service")
public class MyProperties {
private final boolean enabled;
private final InetAddress remoteAddress;
private final Security security;
public MyProperties(boolean enabled, InetAddress remoteAddress, Security security) {
this.enabled = enabled;
this.remoteAddress = remoteAddress;
this.security = security;
}
public boolean isEnabled() {
return this.enabled;
}
public InetAddress getRemoteAddress() {
return this.remoteAddress;
}
public Security getSecurity() {
return this.security;
}
public static class Security {
private final String username;
private final String password;
private final List<String> roles;
public Security(String username, String password, @DefaultValue("USER") List<String> roles) {
this.username = username;
this.password = password;
this.roles = roles;
}
public String getUsername() {
return this.username;
}
public String getPassword() {
return this.password;
}
public List<String> getRoles() {
return this.roles;
}
}
}
코틀린
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.bind.DefaultValue
import java.net.InetAddress
@ConfigurationProperties("my.service")
class MyProperties(val enabled: Boolean, val remoteAddress: InetAddress, val security: Security) {
class Security(val username: String, val password: String, @param:DefaultValue("USER") val roles: List<String>)
}
이 설정에서 파라미터화된 생성자가 있다는 것은 생성자 바인딩을 사용해야 함을 의미한다. 이는 바인더가 바인딩하려는 파라미터가 있는 생성자를 찾는것을 의미한다. 클래스에 여러 생성자가 있는 경우 @ConstructorBinding
어노테이션을 사용하여 생성자 바인딩에 사용할 생성자를 지정할 수 있다. 단일 파라미터화된 생성자가 있는 클래스에 대한 생성자 바인딩을 선택 해제하려면 생성자에 @Autowired
어노테이션을 달아야 한다. 생성자 바인딩은 레코드와 함께 사용할 수 있다. 레코드에 생성자가 여러 개 있지 않으면 @ConstructorBinding
을 사용할 필요가 없다.
생성자 바인딩 클래스(예: 위 예의 Security)의 중첩 멤버도 해당 생성자를 통해 바인딩된다.
기본값은 생성자 파라미터 및 레코드 컴포넌트에 @DefaultValue
를 사용하여 지정할 수 있다. 어노테이션의 문자열 값을 누락된 프로퍼티의 대상 타입으로 강제 변환하기 위해 변환 서비스(conversion service)가 적용된다.
이전 예제를 참고하면 Security
에 바인딩된 프로퍼티스가 없으면, MyProperties
인스턴스에는 Security
을 위해 null
값이 포함된다. 바인딩된 프로퍼티스가 없는 경우에도 Null
이 아닌 Security
인스턴스를 포함하려면(코틀린을 사용하는 경우 기본값이 없으므로 Security
의 사용자 이름 및 비밀번호 파라미터를 null
허용으로 선언해야 함) 빈(empty) @DefaultValue
어노테이션 사용한다:
자바
public MyProperties(boolean enabled, InetAddress remoteAddress, @DefaultValue Security security) {
this.enabled = enabled;
this.remoteAddress = remoteAddress;
this.security = security;
}
코틀린
class MyProperties(val enabled: Boolean, val remoteAddress: InetAddress, @DefaultValue val security: Security) {
class Security(val username: String?, val password: String?, @param:DefaultValue("USER") val roles: List<String>)
}
생성자 바인딩을 사용하려면 @EnableConfigurationProperties
또는 컨피규레이션 프로퍼티 스캐닝(configuration property scanning)을 사용하여 클래스를 활성화해야 한다. 일반 스프링 메커니즘으로 생성된 빈(예: @Component Bean
, @Bean
메서드를 사용하여 생성된 Bean
또는 @Import
를 사용하여 로드된 Bean
)에는 생성자 바인딩을 사용할 수 없다.
네이티브 이미지에서 생성자 바인딩을 사용하려면 클래스를 -parameters
로 컴파일해야 한다. 이는 스프링 부트의 그레이들 플러그인을 사용하거나 메이븐 및 spring-boot-starter-parent
를 사용하는 경우 자동으로 동작한다.
@ConfigurationProperties
와 함께 java.util.Optional
을 사용하는 것은 주로 리턴 타입으로 사용하기 위한 것이므로 권장되지 않는다. 따라서 구성 프로퍼티 주입에는 적합하지 않다. 다른 타입의 프로퍼티와의 일관성을 위해 옵셔널(Optional)
프로퍼티를 선언했는데 값이 없으면 빈 옵셔널(Optional)
이 아닌 null
이 바인딩된다.
Enabling @ConfigurationProperties-annotated Types
스프링 부트는 @ConfigurationProperties
타입을 바인딩하고 이를 빈으로 등록하는 인프라스트럭처를 제공한다. 클래스별로 구성 프로퍼티를 활성화하거나 컴포넌트 스캐닝(component scanning)과 유사한 방식으로 작동하는 구성 프로퍼티 스캐닝(configuration property scanning)을 활성화할 수 있다.
때로는, @ConfigurationProperties
로 어노테이션이 달린 클래스가 스캐닝에 적합하지 않을 수 있다. 예를 들어, 자체 자동 구성(auto-configuration)을 개발 중이거나 조건부로 활성화하려는 경우가 그렇다. 이러한 경우 @EnableConfigurationProperties
어노테이션을 사용하여 처리할 타입 목록을 지정한다. 다음 예제와 같이 모든 @Configuration
클래스에서 이 작업을 수행할 수 있다:
자바
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(SomeProperties.class)
public class MyConfiguration { }
코틀린
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Configuration
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(SomeProperties::class)
class MyConfiguration
자바
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("some.properties")
public class SomeProperties { }
코틀린
import org.springframework.boot.context.properties.ConfigurationProperties
@ConfigurationProperties("some.properties")
class SomeProperties
컨피규레이션 프로퍼티 스캐닝(configuration property scanning)을 사용하려면, 애플리케이션에 @ConfigurationPropertiesScan
어노테이션을 추가하자. 일반적으로 @SpringBootApplication
이라는 어노테이션이 달린 메인 애플리케이션 클래스에 추가되지만, 모든 @Configuration
클래스에 추가할 수 있다. 기본적으로, 스캐닝은 어노테이션을 선언하는 클래스의 패키지에서 동작한다. 스캔할 특정 패키지를 정의하려면, 다음 예제에 표시된 대로 수행할 수 있다:
자바
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
@SpringBootApplication
@ConfigurationPropertiesScan({ "com.example.app", "com.example.another" })
public class MyApplication { }
코틀린
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
@SpringBootApplication
@ConfigurationPropertiesScan("com.example.app", "com.example.another")
class MyApplication
Note
@ConfigurationProperties
빈이 컨피규레이션 프로퍼티 스캐닝을 사용하거나 @EnableConfigurationProperties
를 통해 등록되면 빈은 <prefix>-<fqn>
이라는 일반적인 이름을 갖는다. 여기서fix>는 `@ConfigurationProperties` 어노테이션에 지정된 환경 키 접두사이고
com.example.app
패키지에 있다고 가정하면, 위 SomeProperties
예제의 빈 이름은 some.properties-com.example.app.SomeProperties
이다. ***
@ConfigurationProperties
는 환경에서만 처리하고 특히 컨텍스트에서 다른 빈을 주입하지 않는 것이 좋다. 특수한 경우에는 setter 주입을 사용하거나 프레임워크에서 제공하는 *Aware
인터페이스(예: 환경에 접근해야 하는 경우 인바이런먼트어웨어(EnvironmentAware)
)를 사용할 수 있다. 생성자를 사용하여 다른 빈을 계속 주입하려면 구성 프로퍼티 빈에 @Component
로 어노테이션을 달고 자바빈즈 기반 프로퍼티 바인딩을 사용해야 한다.
Using @ConfigurationProperties-annotated Types
이 구성 스타일은 다음 예제와 같이 스프링어노테이션(SpringApplication)
외부 YAML 구성과 특히 잘 작동한다:
my:
service:
remote-address: 192.168.1.1
security:
username: "admin"
roles:
- "USER"
- "ADMIN"
@ConfigurationProperties
빈을 사용하려면 다음 예제와 같이 다른 빈과 동일한 방식으로 이를 주입할 수 있다:
자바
import org.springframework.stereotype.Service;
@Service
public class MyService {
private final MyProperties properties;
public MyService(MyProperties properties) {
this.properties = properties;
}
public void openConnection() {
Server server = new Server(this.properties.getRemoteAddress());
server.start();
// ...
}
// ...
}
코틀린
import org.springframework.stereotype.Service
@Service
class MyService(val properties: MyProperties) {
fun openConnection() {
val server = Server(properties.remoteAddress)
server.start()
// ...
}
// ...
}
@ConfigurationProperties
를 사용하면 IDE에서 자체 키에 대한 자동 완성 기능을 제공하는 데 사용할 수 있는 메타데이터 파일을 생성할 수도 있다. 자세한 내용은 부록을 참고하자.
Third-party Configuration
@ConfigurationProperties
를 사용하여 클래스에 어노테이션을 달 수 있을 뿐만 아니라 퍼블릭(public) @Bean
메서드에서도 사용할 수 있다. 이렇게 하면 제어할 수 없는 서드 파티 컴포넌트에 프로퍼티스를 바인딩하려는 경우 특히 유용할 수 있다.
환경 프로퍼티스에서, 빈을 구성하려면 다음 예제와 같이 해당 빈 등록에 @ConfigurationProperties
를 추가하자:
자바
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class ThirdPartyConfiguration {
@Bean
@ConfigurationProperties(prefix = "another")
public AnotherComponent anotherComponent() {
return new AnotherComponent();
}
}
코틀린
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration(proxyBeanMethods = false)
class ThirdPartyConfiguration {
@Bean
@ConfigurationProperties(prefix = "another")
fun anotherComponent(): AnotherComponent = AnotherComponent()
}
다른 접두사로 정의된 자바빈 프로퍼티는 이전 SomeProperties
예제와 유사한 방식으로 해당 AnotherComponent
빈에 매핑된다.
Relaxed Binding
스프링 부트는 환경 프로퍼티스를 @ConfigurationProperties
빈에 바인딩하기 위해 몇 가지 완화된(relaxed) 규칙을 사용하므로 환경(Environment) 프로퍼티스명과 빈 프로퍼티스명이 정확히 일치할 필요는 없다. 이것이 유용한 예시는 대시(-)로 구분된 환경 프로퍼티스(예: context-path가 contextPath에 바인딩됨) 및 대문자로 표시된 환경 프로퍼티스(예: PORT가 port에 바인딩됨)이 포함된다.
예를 들어 다음 @ConfigurationProperties
클래스를 생각해보자:
자바
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "my.main-project.person")
public class MyPersonProperties {
private String firstName;
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
}
코틀린
import org.springframework.boot.context.properties.ConfigurationProperties
@ConfigurationProperties(prefix = "my.main-project.person")
class MyPersonProperties {
var firstName: String? = null
}
앞의 코드에서는 다음 프로퍼티스명을 모두 사용할 수 있다:
테이블 6. 완화된 바인딩 정책
프로퍼티 | 설명 |
---|---|
my.main-project.person.first-name | .properties 및 YAML 파일에 사용하도록 권장되는 케밥(Kebab) 케이스다 |
my.main-project.person.firstName | 표준 카멜 케이스 문법. |
my.main-project.person.first_name | .properties 및 YAML 파일에 사용하기 위한 대체 형식인 밑줄 표기법이다. |
MY_MAINPROJECT_PERSON_FIRSTNAME | 시스템 환경 변수를 사용할 때 권장되는 대문자 형식이다. |
어노테이션의 접두사 값은 케밥 대소문자(my.main-project.person과 같이 소문자로 -로 구분됨)여야 한다.
프로퍼티 소스당 완화된 바인딩 규칙 테이블 7. relaxed binding rules per property source
프로퍼티 소스 | 심플 | 리스트 |
---|---|---|
프로퍼티스 파일(Properties Files) | 카멜 케이스, 케밥 케이스 또는 밑줄 표기 | [ ] 또는 쉼표로 구분된 값을 사용하는 표준 목록 구문 |
YAML 파일 | 카멜 케이스, 케밥 케이스 또는 밑줄 표기 | 표준 YAML 목록 구문 또는 쉼표(,)로 구분된 값 |
환경 변수 | 밑줄을 구분 기호로 사용하는 대문자 형식이다(환경 변수에서 바인딩 참조). | 밑줄로 묶인 숫자 값(환경 변수에서 바인딩 참조) |
시스템 프로퍼티스 | 카멜 케이스, 케밥 케이스 또는 밑줄 표기 | [ ] 또는 쉼표로 구분된 값을 사용하는 표준 목록 구문 |
가능하면 프로퍼티를 my.person.first-name=Rod
와 같은 소문자 케밥 형식으로 저장하는 것이 좋다. .
Binding Maps
맵
프로퍼티스에 바인딩할 때 원래 키
값이 유지되도록 특수 대괄호 표기법을 사용해야 할 수도 있다. 키가 []
로 묶이지 않은 경우 영숫자가 아닌 문자
또는 -
또는 .
가 제거된다.
예를 들어, 다음 프로퍼티스를 Map<String,String>
에 바인딩하는 것을 생각해보자:
프로퍼티스(Properties)
my.map.[/key1]=value1
my.map.[/key2]=value2
my.map./key3=value3
Yaml
my:
map:
"[/key1]": "value1"
"[/key2]": "value2"
"/key3": "value3"
YAML 파일의 경우, 키를 올바르게 파싱하려면 대괄호를 따옴표로 묶어야 한다.
위의 속성은 맵의 키로 /key1
, /key2
및 key3
을 사용하여 맵에 바인딩된다. 슬래시는 대괄호로 묶이지 않았기 때문에 key3에서 제거됐다.
스칼라 값에 바인딩할 때 .
가 포함된 키는 []
로 묶을 필요가 없다. 스칼라 값에는 오브젝트(Object)
를 제외한 java.lang
패키지의 모든 타입과 이넘(enum)이 포함된다. a.b=c
를 Map<String, String>
에 바인딩하면 키의 .
가 유지되고 {"a.b"="c"}
항목이 포함된 맵(Map)이 반환된다. 다른 타입의 경우 키에 .
가 포함되어 있으면 대괄호 표기법을 사용해야 한다. 예를 들어, a.b=c
를 Map<String, Object>
에 바인딩하면 {"a"={"b"="c"}}
항목이 포함된 Map이 반환된다. 반면 [a.b]=c
는 {"a.b"="c"}
항목이 포함된 맵를 반환한다.
Binding From Environment Variables
대부분의 운영 체제는 환경 변수에 사용할 수 있는 이름에 대해 엄격한 규칙을 적용한다. 예를 들어 리눅스 셸 변수에는 문자(a
~z
또는 A
~Z
), 숫자(0
~9
) 또는 밑줄 문자(_
)만 포함될 수 있다. 관례적으로 유닉스 쉘 변수명도 대문자로 표시된다.
스프링 부트의 완화된 바인딩 규칙은 가능한 한 이러한 명명 제한 사항과 호환되도록 설계됐다.
표준 형식의 프로퍼티명을 환경 변수명으로 변환하려면 다음 규칙을 따르면 된다:
- 점(.)을 밑줄(_)로 바꾼다.
- 대시(-)를 제거하자.
- 소문자로 변경하자.
예를 들어, 구성 프로퍼티 spring.main.log-startup-info
는 SPRING_MAIN_LOGSTARTUPINFO
라는 환경 변수가 된다.
객체 목록에 바인딩할 때, 환경 변수를 사용할 수도 있다. 목록에 바인딩하려면 변수명에서 엘리먼트 번호를 밑줄로 묶어야 한다.
예를 들어 my.service[0].other
구성 프로퍼티는 MY_SERVICE_0_OTHER
라는 환경 변수를 사용한다.
Merging Complex Types
목록이 여러 위치에 구성된 경우 오버라이드는 전체 목록을 바꾸는 방식으로 작동한다.
예를 들어, 기본적으로 null인 name
및 description
애트리뷰트을 가진 MyPojo
객체를 가정해보자. 다음 예에서는 MyProperties
의 MyPojo
객체 리스트를 노출한다:
자바
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("my")
public class MyProperties {
private final List<MyPojo> list = new ArrayList<>();
public List<MyPojo> getList() {
return this.list;
}
}
코틀린
import org.springframework.boot.context.properties.ConfigurationProperties
@ConfigurationProperties("my")
class MyProperties {
val list: List<MyPojo> = ArrayList()
}
다음 구성을 생각해보자:
프로퍼티스(Properties)
my.list[0].name=my name
my.list[0].description=my description
#---
spring.config.activate.on-profile=dev
my.list[0].name=my another name
Yaml
my:
list:
- name: "my name"
description: "my description"
---
spring:
config:
activate:
on-profile: "dev"
my:
list:
- name: "my another name"
dev
프로필이 활성화되지 않은 경우 MyProperties.list
에는 이전에 정의된 대로 하나의 MyPojo
항목이 포함된다. 그러나 dev
프로필이 활성화된 경우에도 리스트에는 여전히 하나의 항목만 포함된다(my another name
의 name
과 null
의description
포함). 이 구성은 두 번째 MyPojo
인스턴스를 목록에 추가하지 않으며 항목을 병합하지 않는다.
여러 프로필에 리스트가 지정되면 우선 순위가 가장 높은 프로필(해당 프로필만)이 사용된다. 다음 예를 생각해보자:
프로퍼티스(Properties)
my.list[0].name=my name
my.list[0].description=my description
my.list[1].name=another name
my.list[1].description=another description
#---
spring.config.activate.on-profile=dev
my.list[0].name=my another name
Yaml
my:
list:
- name: "my name"
description: "my description"
- name: "another name"
description: "another description"
---
spring:
config:
activate:
on-profile: "dev"
my:
list:
- name: "my another name"
앞의 예에서 dev
프로필이 활성화된 경우 MyProperties.list
에는 하나의 MyPojo
항목(my another name
의 name
과 null
의 description
포함)이 포함된다. YAML의 경우 쉼표로 구분된 목록과 YAML 목록을 모두 사용하여 목록 내용을 완전히 오버라이드할 수 있다.
맵 프로퍼티스의 경우, 여러 소스에서 가져온 프로퍼티 값과 바인딩할 수 있다. 그러나 여러 소스의 동일한 프로퍼티에 대해서는 우선순위가 가장 높은 프로퍼티가 사용된다. 다음 예에서는 MyProperties
에서 Map<String, MyPojo>
를 노출한다:
자바
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("my")
public class MyProperties {
private final Map<String, MyPojo> map = new LinkedHashMap<>();
public Map<String, MyPojo> getMap() {
return this.map;
}
}
코틀린
import org.springframework.boot.context.properties.ConfigurationProperties
@ConfigurationProperties("my")
class MyProperties {
val map: Map<String, MyPojo> = LinkedHashMap()
}
다음 구성을 생각해보자:
프로퍼티스(Properties)
my.map.key1.name=my name 1
my.map.key1.description=my description 1
#---
spring.config.activate.on-profile=dev
my.map.key1.name=dev name 1
my.map.key2.name=dev name 2
my.map.key2.description=dev description 2
Yaml
my:
map:
key1:
name: "my name 1"
description: "my description 1"
---
spring:
config:
activate:
on-profile: "dev"
my:
map:
key1:
name: "dev name 1"
key2:
name: "dev name 2"
description: "dev description 2"
dev
프로필이 활성화되지 않은 경우 MyProperties.map
에는 키가 key1
(name
에 my name 1
과 description
에 my description 1
)이 있는 하나의 항목이 포함된다. 그러나 dev
프로필이 활성화된 경우 맵에는 key1
(name
에 dev name 1
과 description
에 my description 1
) 및 key2
(name
에 dev name 2
과 description
에 dev description 2
)가 있는 두 개의 항목이 포함된다.
앞의 병합 규칙은 파일뿐만 아니라 모든 프로퍼티 소스의 프로프티스에 적용된다.
Properties Conversion
스프링 부트는 @ConfigurationProperties
빈에 바인딩될 때 외부 애플리케이션 프로퍼티스을 올바른 타입으로 강제 변환하려고 시도한다. 커스텀 타입 변환이 필요한 경우 컨버전서비스(ConversionService)
빈(conversionService
라는 빈 포함) 또는 커스텀 프로퍼티 에디터(커스텀에디터컨피규어러(CustomEditorConfigurer)
빈을 통해) 또는 커스텀 컨버터(@ConfigurationPropertiesBinding
어노테이션이 달린 빈 정의 포함)를 제공할 수 있다.
이 빈은 애플리케이션 생명주기 중 매우 초기에 요청되므로 컨버전서비스(ConversionService)
가 사용하는 의존성을 제한해야 한다. 일반적으로 필요한 의존성은 생성 시 완전히 초기화되지 않을 수 있다. 구성 키 강제 변환에 필요하지 않고 @ConfigurationPropertiesBinding
으로 자격을 갖춘 커스텀 컨버터에만 의존하는 경우 커스텀 컨버전서비스(ConversionService)
의 이름을 바꿀 수 있다.
Converting Durations
스프링 부트에는 기간(durations) 표현을 위한 전용 지원이 있다. java.time.Duration
프로퍼티를 노출하는 경우, 애플리케이션 프로퍼티스에서 다음 포맷을 사용할 수 있다:
- 일반적인 긴 표현(
@DurationUnit
이 지정되지 않은 한 기본 단위로 밀리초 사용) java.time.Duration
에서 사용되는 표준 ISO-8601 형식- 값(value)과 단위(unit)가 결합되어 더 읽기 쉬운 형식(10s는 10초를 의미함).
다음 예제를 생각해보자:
자바
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DurationUnit;
@ConfigurationProperties("my")
public class MyProperties {
@DurationUnit(ChronoUnit.SECONDS)
private Duration sessionTimeout = Duration.ofSeconds(30);
private Duration readTimeout = Duration.ofMillis(1000);
public Duration getSessionTimeout() {
return this.sessionTimeout;
}
public void setSessionTimeout(Duration sessionTimeout) {
this.sessionTimeout = sessionTimeout;
}
public Duration getReadTimeout() {
return this.readTimeout;
}
public void setReadTimeout(Duration readTimeout) {
this.readTimeout = readTimeout;
}
}
코틀린
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.convert.DurationUnit
import java.time.Duration
import java.time.temporal.ChronoUnit
@ConfigurationProperties("my")
class MyProperties {
@DurationUnit(ChronoUnit.SECONDS)
var sessionTimeout = Duration.ofSeconds(30)
var readTimeout = Duration.ofMillis(1000)
}
세션 시간 초과를 30초로 지정하려면 30, PT30S 및 30s가 모두 동일하다. 500ms의 읽기 시간 제한은 500, PT0.5S 및 500ms 포맷으로 지정할 수 있다.
지원되는 단위를 사용할 수도 있다:
ns
for nanosecondsus
for microsecondsms
for millisecondss
for secondsm
for minutesh
for hoursd
for days
기본 단위는 밀리초(ms)이며, 위 샘플에 표시된 대로 @DurationUnit
을 사용하여 오버라이드할 수 있다.
생성자 바인딩을 사용하려는 경우 다음 예제와 같이 동일한 프로퍼티스를 노출할 수 있다:
자바
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.boot.convert.DurationUnit;
@ConfigurationProperties("my")
public class MyProperties {
private final Duration sessionTimeout;
private final Duration readTimeout;
public MyProperties(@DurationUnit(ChronoUnit.SECONDS) @DefaultValue("30s") Duration sessionTimeout, @DefaultValue("1000ms") Duration readTimeout) {
this.sessionTimeout = sessionTimeout;
this.readTimeout = readTimeout;
}
public Duration getSessionTimeout() {
return this.sessionTimeout;
}
public Duration getReadTimeout() {
return this.readTimeout;
}
}
코틀린
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.bind.DefaultValue
import org.springframework.boot.convert.DurationUnit
import java.time.Duration
import java.time.temporal.ChronoUnit
@ConfigurationProperties("my")
class MyProperties(@param:DurationUnit(ChronoUnit.SECONDS) @param:DefaultValue("30s") val sessionTimeout: Duration, @param:DefaultValue("1000ms") val readTimeout: Duration)
Long
프로퍼티로 업그레이드하는 경우 밀리초가 아닌 경우 단위를 정의해야 한다(@DurationUnit
사용). 이렇게 하면 훨씬 더 풍부한 포맷을 지원하면서 투명한 업그레이드가 제공된다.
Converting Periods
기간(Durations) 외에도, 스프링 부트는 java.time.Period
타입에서도 작동할 수 있다. 애플리케이션 프로퍼티스에서는 다음 타입을 사용할 수 있다:
- 일반 int 표현(
@PeriodUnit
이 지정되지 않은 한 일(days)을 기본 단위로 사용) java.time.Period
에서 사용되는 표준 ISO-8601 형식- 값과 단위 쌍이 결합되는 더 간단한 형식(1y3d는 1년 3일을 의미함)
단순 형식에서는 다음 단위가 지원된다:
y
for yearsm
for monthsw
for weeksd
for days
java.time.Period
타입은 실제로 주(weeks) 수를 저장하지 않으며, “7일”을 의미하는 단축어다.
Converting Data Sizes
스프링 프레임워크에는 크기를 바이트 단위로 표현하는 데이터사이즈(DataSize)
값 타입이 있다. 데이터사이즈(DataSize) 프로퍼티를 노출하는 경우 애플리케이션 프로퍼티에서 다음 타입을 사용할 수 있다:
- 일반적인 긴 표현(
@DataSizeUnit
이 지정되지 않은 한 바이트를 기본 단위로 사용) - 값과 단위가 결합되어 더 읽기 쉬운 형식(10MB는 10MB를 의미함)
다음 예제를 생각해보자:
자바
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DataSizeUnit;
import org.springframework.util.unit.DataSize;
import org.springframework.util.unit.DataUnit;
@ConfigurationProperties("my")
public class MyProperties {
@DataSizeUnit(DataUnit.MEGABYTES)
private DataSize bufferSize = DataSize.ofMegabytes(2);
private DataSize sizeThreshold = DataSize.ofBytes(512);
public DataSize getBufferSize() {
return this.bufferSize;
}
public void setBufferSize(DataSize bufferSize) {
this.bufferSize = bufferSize;
}
public DataSize getSizeThreshold() {
return this.sizeThreshold;
}
public void setSizeThreshold(DataSize sizeThreshold) {
this.sizeThreshold = sizeThreshold;
}
}
코틀린
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.convert.DataSizeUnit
import org.springframework.util.unit.DataSize
import org.springframework.util.unit.DataUnit
@ConfigurationProperties("my")
class MyProperties {
@DataSizeUnit(DataUnit.MEGABYTES)
var bufferSize = DataSize.ofMegabytes(2)
var sizeThreshold = DataSize.ofBytes(512)
}
10MB의 버퍼 크기를 지정하려면, 10과 10MB가 동일하다. 256바이트의 크기 임계값(threshold)은 256 또는 256B로 지정할 수 있다.
지원되는 단위를 사용할 수도 있다:
B
for bytesKB
for kilobytesMB
for megabytesGB
for gigabytesTB
for terabytes
기본 단위는 바이트이며 위 샘플에 표시된 대로 @DataSizeUnit
을 사용하여 오버라이드할 수 있다.
생성자 바인딩을 사용하려는 경우 다음 예제와 같이 동일한 프로퍼티를 노출할 수 있다.
자바
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.boot.convert.DataSizeUnit;
import org.springframework.util.unit.DataSize;
import org.springframework.util.unit.DataUnit;
@ConfigurationProperties("my")
public class MyProperties {
private final DataSize bufferSize;
private final DataSize sizeThreshold;
public MyProperties(@DataSizeUnit(DataUnit.MEGABYTES) @DefaultValue("2MB") DataSize bufferSize, @DefaultValue("512B") DataSize sizeThreshold) {
this.bufferSize = bufferSize;
this.sizeThreshold = sizeThreshold;
}
public DataSize getBufferSize() {
return this.bufferSize;
}
public DataSize getSizeThreshold() {
return this.sizeThreshold;
}
}
코틀린
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.bind.DefaultValue
import org.springframework.boot.convert.DataSizeUnit
import org.springframework.util.unit.DataSize
import org.springframework.util.unit.DataUnit
@ConfigurationProperties("my")
class MyProperties(@param:DataSizeUnit(DataUnit.MEGABYTES) @param:DefaultValue("2MB") val bufferSize: DataSize, @param:DefaultValue("512B") val sizeThreshold: DataSize)
Long
프로퍼티를 업그레이드하는 경우, 바이트가 아닌 경우 단위를 정의해야 한다(@DataSizeUnit
사용). 이렇게 하면 훨씬 더 풍부한 포맷을 지원하면서 투명한 업그레이드가 제공된다.
@ConfigurationProperties Validation
스프링 부트는 스프링의 @Validated
어트리뷰트가 추가될 때마다, @ConfigurationProperties
클래스의 유효성을 검사하려고 시도한다. 구성 클래스에서 직접 JSR-303 jakarta.validation
제약 조건(constraint) 어노테이션을 사용할 수 있다. 이렇게 하려면, 다음 예제에 표시된 대로 호환 JSR-303
구현이 클래스패스에 있는지 확인한 후 필드에 제약 조건 어노테이션을 추가하자:
자바
import java.net.InetAddress;
import jakarta.validation.constraints.NotNull;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
@ConfigurationProperties("my.service")
@Validated
public class MyProperties {
@NotNull
private InetAddress remoteAddress;
public InetAddress getRemoteAddress() {
return this.remoteAddress;
}
public void setRemoteAddress(InetAddress remoteAddress) {
this.remoteAddress = remoteAddress;
}
}
코틀린
import jakarta.validation.constraints.NotNull
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.validation.annotation.Validated
import java.net.InetAddress
@ConfigurationProperties("my.service")
@Validated
class MyProperties {
var remoteAddress: @NotNull InetAddress? = null
}
@Validated
로 구성 프로퍼티스를 생성하는 @Bean
메서드에 어노테이션을 달아 유효성 검사를 트리거할 수도 있다.
프로퍼티가 발견되지 않은 경우에도, 중첩된 프로퍼티스에 대해 유효성 검사가 항상 트리거되도록 하려면, 연결된 필드에 @Valid
어노테이션을 달아야 한다. 다음 예제는 이전 MyProperties
예제를 기반으로 한다:
자바
import java.net.InetAddress;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
@ConfigurationProperties("my.service")
@Validated
public class MyProperties {
@NotNull
private InetAddress remoteAddress;
@Valid
private final Security security = new Security();
public InetAddress getRemoteAddress() {
return this.remoteAddress;
}
public void setRemoteAddress(InetAddress remoteAddress) {
this.remoteAddress = remoteAddress;
}
public Security getSecurity() {
return this.security;
}
public static class Security {
@NotEmpty
private String username;
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
}
}
코틀린
import jakarta.validation.Valid
import jakarta.validation.constraints.NotEmpty
import jakarta.validation.constraints.NotNull
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.validation.annotation.Validated
import java.net.InetAddress
@ConfigurationProperties("my.service")
@Validated
class MyProperties {
var remoteAddress: @NotNull InetAddress? = null
@Valid
val security = Security()
class Security {
@NotEmpty
var username: String? = null
}
}
컨피규레이션프로퍼티스밸리데이터(ConfigurationPropertiesValidator)
라는 빈을 생성하여 커스텀 스프링 밸리데이터(Validator)
를 추가할 수도 있다. @Bean
메소드는 스태틱으로 선언되어야 한다. 구성 프로퍼티스 밸리데이터는 애플리케이션 생명주기 초기에 생성되며 @Bean
메서드를 스태틱으로 선언하면 @Configuration
클래스를 인스턴스화하지 않고도 빈을 생성할 수 있다. 이렇게 하면 초기 인스턴스화로 인해 발생할 수 있는 문제를 피할 수 있다.
spring-boot-actuator
모듈에는 모든 @ConfigurationProperties
빈을 노출하는 엔드포인트가 포함되어 있다. 웹 브라우저에서 /actuator/configprops
를 가리키거나 동등한 JMX 엔드포인트를 사용해보자. 자세한 내용은 “Production ready features” 절을 참고하자.
@ConfigurationProperties vs. @Value
@Value
어노테이션은 코어 컨테이너 기능이며 타입 세이프한 구성 프로퍼티
와 동일한 기능을 제공하지 않는다. 다음 표에는 @ConfigurationProperties
및 @Value
에서 지원되는 기능이 요약되어 있다:
기능 | @ConfigurationProperties | @Value |
---|---|---|
완화된 바인딩(Relaxed binding) | Yes | 제한됨(아래 참고 참조) |
메타데이터(Meta-data) 지원 | Yes | No |
SpEL evaluation | No | Yes |
@Value
를 사용하려면 표준 포맷(소문자만 사용하는 케밥 케이스)을 사용하여 프로퍼티명을 참조하는 것이 좋다. 이렇게 하면 스프링 부트가@ConfigurationProperties
바인딩을 완화할 때(Relaxed)와 동일한 논리를 사용할 수 있다.
예를 들어
@Value("\${demo.item-price}")
는application.properties
파일에서demo.item-price
및demo.itemPrice
포맷을 선택하고 시스템 환경에서DEMO_ITEMPRICE
를 선택한다. 대신@Value("\${demo.itemPrice}")
를 사용한 경우,deco.item-price
및DEMO_ITEMPRICE
는 고려하지 않는다.
자체 컴포넌트에 대한 구성 키 세트을 정의하는 경우 @ConfigurationProperties
어노테이션이 달린 POJO로 그룹화하는 것이 좋다. 그렇게 하면 자신의 빈에 주입할 수 있는 구조화되고 타입이 안전한 객체가 제공된다.
애플리케이션 특성 파일의 SpEL 표현식은 해당 파일을 파싱하고 환경을 채울 때 처리되지 않는다. 그러나 @Value
에 SpEL 표현식
을 작성할 수 있다. 애플리케이션 프로퍼티스 파일의 프로퍼티 값이 SpEL
표현식인 경우 @Value
를 통해 사용될 때 평가된다.
7.3. Profiles
스프링 프로필(Profile)은 애플리케이션 구성의 일부를 분리하고 특정 환경에서만 사용할 수 있도록 한다. 다음 예제와 같이 @Component
, @Configuration
또는 @ConfigurationProperties
를 @Profile
로 표시하여 로드 시 제한할 수 있다:
자바
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@Configuration(proxyBeanMethods = false)
@Profile("production")
public class ProductionConfiguration {
// ...
}
코틀린
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Profile
@Configuration(proxyBeanMethods = false)
@Profile("production")
class ProductionConfiguration {
// ...
}
{. :note} @ConfigurationProperties
빈이 자동 스캐닝(automatic scannin) 대신 @EnableConfigurationProperties
를 통해 등록된 경우, @EnableConfigurationProperties
어노테이션이 있는 @Configuration
클래스에 @Profile
어노테이션을 지정해야 한다. @ConfigurationProperties
를 스캔하는 경우 @ConfigurationProperties
클래스 자체에 @Profile
을 지정할 수 있다.
spring.profiles.active
환경 프로퍼티를 사용하여 활성할 프로필을 지정할 수 있다. 이 장의 앞부분에서 설명한 방법으로 프로퍼티를 지정할 수 있다. 예를 들어 다음 예제와 같이 이를 application.properties
에 포함할 수 있다.
프로퍼티스(Properties)
spring.profiles.active=dev,hsqldb
Yaml
spring:
profiles:
active: "dev,hsqldb"
다음 스위치를 사용하여 커맨드라인에서 이를 지정할 수도 있다: --spring.profiles.active=dev,hsqldb
활성화된 프로필이 없으면 기본 프로필이 활성화된다. 기본 프로필명은 default
이며 다음 예제와 같이 spring.profiles.default
환경 프로퍼티를 사용하여 조정할 수 있다:
프로퍼티스(Properties)
spring.profiles.default=none
Yaml
# 유효한 문서
spring:
profiles:
active: "prod"
---
# 유효하지 않은 문서
spring:
config:
activate:
on-profile: "prod"
profiles:
active: "metrics"
7.3.1. Adding Active Profiles
spring.profiles.active
프로퍼티는 다른 프로퍼티와 동일한 순서 규칙을 따른다. 가장 높은 프로퍼티소스(PropertySource)
가 우선이다. 즉, application.properties
에서 활성(active) 프로필을 지정한 다음 커맨드라인 스위치를 사용하여 해당 프로필을 바꿀 수 있다.
경우에 따라 활성 프로필을 대체하는 대신 추가하는 프로퍼티를 갖는 것이 유용할 수 있다. spring.profiles.include
프로퍼티는 spring.profiles.active
프로퍼티에 의해 활성화된 프로필에 추가 프로필을 붙이는데 사용될 수 있다. 스프링애플리케이션(SpringApplication)
엔드포인트에는 추가 프로필을 설정하기 위한 자바 API도 있다. 스프링애플리케이션의 setAdditionalProfiles()
메서드를 참고하자.
예를 들어, 다음 프로퍼티스를 가진 애플리케이션이 실행되면 --spring.profiles.active
스위치를 사용하여 실행 중에도 공통 및 로컬 프로필이 활성화된다:
프로퍼티스(Properties)
spring.profiles.include[0]=common
spring.profiles.include[1]=local
Yaml
spring:
profiles:
include:
- "common"
- "local"
spring.profiles.active
와 유사하게 spring.profiles.include
는 프로필이 아닌 특정 문서에서만 사용할 수 있다. 이는 spring.config.activate.on-profile
에 의해 활성화된 프로필 특정 파일이나 문서에 포함될 수 없음을 의미한다.
다음 절에 설명된 프로필 그룹은 특정 프로필이 활성화된 경우 활성 프로필을 추가하는 데에도 사용할 수 있다.
7.3.2. Profile Groups
때때로 애플리케이션에서 정의하고 사용하는 프로필이 너무 세밀하여 사용하기 불편해지는 경우가 있다. 예를 들어, 데이터베이스 및 메시징 기능을 독립적으로 활성화하는 데 사용하는 proddb
및 prodmq
프로필이 있을 수 있다.
이를 돕기 위해 스프링 부트에서는 프로필 그룹을 정의할 수 있다. 프로필 그룹을 사용하면 관련 프로필 그룹에 대한 논리적 이름을 정의할 수 있다.
예를 들어, proddb
및 prodmq
프로필로 구성된 프로덕션 그룹을 만들 수 있다.
프로퍼티스(Properties)
spring.profiles.group.production[0]=proddb
spring.profiles.group.production[1]=prodmq
Yaml
spring:
profiles:
group:
production:
- "proddb"
- "prodmq"
이제 --spring.profiles.active=production
을 사용하여 애플리케이션을 시작하여 프로덕션, proddb
및 prodmq
프로필을 한 번에 활성화할 수 있다.
7.3.3. Programmatically Setting Profiles
애플리케이션이 실행되기 전에 SpringApplication.setAdditionalProfiles(...)
를 호출하여 프로그래밍 방식으로 활성 프로필을 설정할 수 있다. 스프링의 컨피규러블인바이런먼트(ConfigurableEnvironment)
인터페이스를 사용하여 프로필을 활성화하는 것도 가능하다.
7.3.4. Profile-specific Configuration Files
application.properties
(또는 application.yaml
)과 @ConfigurationProperties
를 통해 참조되는 파일의 프로필별 변수는 파일로 간주되어 로드된다. 자세한 내용은 “Profile Specific Files”을 참고하자.
7.4. Logging
스프링 부트는 모든 내부 로깅에 커먼즈 로깅(Commons Logging)을 사용하지만 기본 로그 구현은 열어 둔다. 자바 유틸 로깅(Java Util Logging)
, 로그4j2(Log4j2)
및 로그백(Logback)
에 대한 기본 구성이 제공된다. 각 상황에 로거는 옵셔널하게 파일 출력도 사용할 수 있는 콘솔 출력을 사용하도록 사전 구성되어 있다.
기본적으로 “Starters”를 사용하면 로그백(Logback)이 로깅에 사용된다. 자바 유틸 로깅(Java Util Logging), 커먼즈 로깅(Commons Logging), 로그4J(Log4J) 또는 SLF4J를 사용하는 의존 라이브러리가 모두 올바르게 작동하도록 적절한 로그백 라우팅도 포함되어 있다.
자바에 사용할 수 있는 로깅 프레임워크는 많다. 위 내용이 혼란스러워 보이더라도 걱정하지 말자. 일반적으로 로깅 의존성을 변경할 필요가 없으며 스프링 부트는 기본값으로도 잘 작동한다.
애플리케이션을 서블릿 컨테이너 또는 애플리케이션 서버에 배포할 때 자바 유틸 로깅 API(Java Util Logging API)로 수행된 로깅은 애플리케이션의 로그로 라우팅되지 않는다. 이렇게 하면 컨테이너나 컨테이너에 배포된 다른 애플리케이션이 수행한 로깅이 애플리케이션 로그에 표시되지 않는다.
7.4.1. Log Format
스프링 부트의 기본 로그 출력은 다음 예제같다:
2023-06-22T12:08:05.861Z INFO 22768 --- [ main] o.s.b.d.f.s.MyApplication
: Starting MyApplication using Java 17.0.7 with PID 22768 (/opt/apps/myapp.jar started by myuser in /opt/apps/)
2023-06-22T12:08:05.872Z INFO 22768 --- [ main] o.s.b.d.f.s.MyApplication
: No active profile set, falling back to 1 default profile: "default"
2023-06-22T12:08:09.854Z INFO 22768 --- [ main]
o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080(http)
2023-06-22T12:08:09.892Z INFO 22768 --- [ main]
o.apache.catalina.core.StandardService : Starting service [Tomcat]
2023-06-22T12:08:09.892Z INFO 22768 --- [ main]
o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.10]
2023-06-22T12:08:10.160Z INFO 22768 --- [ main]
o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2023-06-22T12:08:10.162Z INFO 22768 --- [ main]
w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 4038 ms
2023-06-22T12:08:11.512Z INFO 22768 --- [ main]
o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2023-06-22T12:08:11.534Z INFO 22768 --- [ main] o.s.b.d.f.s.MyApplication : Started MyApplication in 7.251 seconds (process running for 8.584)
다음 항목이 출력된다:
- 날짜 및 시간: 밀리초 단위의 정밀도로 쉽게 정렬할 수 있다.
- 로그 레벨:
ERROR
,WARN
,INFO
,DEBUG
, orTRACE
. - 프로세스 ID.
- — 실제 로그 메시지의 시작을 구분하는 구분 기호
- 스레드명(Thread name) : 대괄호로 묶는다(콘솔 출력의 경우 잘릴 수 있음).
- 로거명(Logger name): 이는 일반적으로 소스 클래스 이름이다(종종 축약됨).
- 로그 메세지.
로그백(Logback)에는 FATAL
레벨이 없다. ERROR
에 매핑된다.
7.4.2. Console Output
기본 로그 구성은 메시지가 기록될 때 콘솔에 메시지를 표시한다. 기본적으로 ERROR
레벨, WARN
레벨 및 INFO
레벨 메시지가 기록된다. --debug
플래그로 애플리케이션을 시작하여 “debug” 모드를 활성화할 수도 있다.
$ java -jar myapp.jar --debug
application.properties
에 debug=true
를 지정할 수도 있다.
디버그(debug) 모드가 활성화되면 코어 로거 선택(임베디드 컨테이너, 하이버네이트 및 스프링 부트)이 더 많은 정보를 출력하도록 구성한다. 디버그 모드를 활성화해도 DEBUG 레벨의 모든 메시지를 기록하도록 애플리케이션이 구성되지는 않는다.
또는, --trace
플래그(또는 application.properties
의 trace=true
)를 사용하여 애플리케이션을 시작하여 “trace” 모드를 활성화할 수 있다. 이렇게 하면 핵심 로거 선택(임베디드 컨테이너, 하이버네이트 스키마 생성 및 전체 스프링 포트폴리오(Spring portfolio))에 대한 추적 로깅이 가능해진다.
Color-coded Output
터미널이 ANSI를 지원하는 경우 가독성을 돕기 위해 컬러 출력이 사용된다. spring.output.ansi.enabled
를 지원되는 값으로 설정하여 자동 감지를 오버라이드 할 수 있다.
컬러 코딩은 %clr
변환 단어를 사용하여 구성된다. 가장 간단한 형태의 컨버터는 다음 예와 같이 로그 레벨에 따라 출력 색상을 지정한다:
%clr(%5p)
다음 표에서는 로그 레벨과 색상의 매핑을 설명한다:
Level | Color |
---|---|
FATAL | Red |
ERROR | Red |
WARN | Yellow |
INFO | Green |
DEBUG | Green |
TRACE | Green |
또는 변환 옵션으로 제공하여 사용해야 하는 색상이나 스타일을 지정할 수 있다. 예를 들어 텍스트를 노란색으로 만들려면 다음 설정을 사용해보자:
%clr(%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX}){yellow}
다음 색상과 스타일이 지원된다:
blue
cyan
faint
green
magenta
red
yellow
7.4.3. File Output
기본적으로 스프링 부트는 콘솔에만 기록하고 로그 파일을 기록하지 않는다. 콘솔 출력 외에 로그 파일을 작성하려면 login.file.name
또는 login.file.path
프로퍼티(예: application.properties
)을 설정해야 한다.
다음 표에서는 logging.* properties
을 함께 사용할 수 있는 방법을 보여준다.
테이블 8. 로깅 프로퍼티즈
logging.file.name | logging.file.path | 예제 | 설명 |
---|---|---|---|
(none) | (none) | 오직 콘솔 로깅. | |
지정된 파일 | (none) | my.log | 지정된 로그 파일에 쓴다. 이름은 정확한 위치일 수도 있고 현재 디렉토리 기준일 수 있다. |
(none) | 지정된 디렉토리 | /var/log | 지정된 디렉토리에 spring.log 를 쓴다. 이름은 정확한 위치일 수도 있고 현재 디렉토리 기준일 수 있다. |
로그 파일은 10MB에 도달하면 로테이션 처리되며, 콘솔 출력과 마찬가지로 ERROR
레벨, WARN
레벨 및 INFO
레벨 메시지가 기본적으로 기록된다.
로깅 프로퍼티는 실제 로깅 인프라와 독립적이다. 결과적으로 특정 구성 키(예: 로그백(Logback)의 logback.configurationFile
)는 스프링 부트에서 관리되지 않는다.
7.4.4. File Rotation
로그백을 사용하는 경우 application.properties
또는 application.yaml
파일을 사용하여 로그 로테이션 설정을 미세 조정할 수 있다. 다른 모든 로깅 시스템의 경우 로테이션 설정을 직접 구성해야 한다(예를 들어 로그4j2(Log4j2)를 사용하는 경우 log4j2.xml
또는 log4j2-spring.xml
파일을 추가할 수 있음).
다음 로테이션 정책 프로퍼티스가 지원된다:
명칭 | 설명 |
---|---|
logging.logback.rollingpolicy.file-name-pattern | 로그 압축파일를 생성하는 데 사용되는 파일 이름 패턴이다. |
logging.logback.rollingpolicy.clean-history-on-start | 애플리케이션이 시작될 때 로그 압축파일 정리가 발생해야 하는지 여부다. |
logging.logback.rollingpolicy.max-file-size | 압축되기 전 로그 파일의 최대 크기다. |
logging.logback.rollingpolicy.total-size-cap | 로그 압축파일이 삭제되기 전까지 도달할 수 있는 최대 크기다. |
logging.logback.rollingpolicy.max-history | 보관할 최대 보관 로그 파일 수(기본값은 7)다. |
7.4.5. Log Levels
지원되는 모든 로깅 시스템은 log.level.<logger-name>=<level>
을 사용하여 스프링 환경(예: application.properties
)에서 로거 레벨을 설정할 수 있다. 여기서 레벨은 TRACE, DEBUG, INFO, WARN, ERROR, FATAL 또는 OFF. 루트(root)
로거는 login.level.root
를 사용하여 구성할 수 있다.
다음 예에서는 application.properties
의 잠재적인 로깅 설정을 보여준다:
프로퍼티스(Properties)
logging.level.root=warn
logging.level.org.springframework.web=debug
logging.level.org.hibernate=error
Yaml
logging:
level:
root: "warn"
org.springframework.web: "debug"
org.hibernate: "error"
환경 변수를 사용하여 로깅 레벨을 설정할 수도 있습니다. 예를 들어 LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_WEB=DEBUG
는 org.springframework.web
을 DEBUG
로 설정한다.
위의 접근 방식은 패키지 레벨 로깅에만 작동한다. 완화된 바인딩은 항상 환경 변수를 소문자로 변환하므로 이러한 방식으로 개별 클래스에 대한 로깅을 구성하는 것은 불가능하다. 클래스에 대한 로깅을 구성해야 하는 경우 SPRING_APPLICATION_JSON
변수를 사용할 수 있다.
7.4.6. Log Groups
관련 로거를 그룹화하여 동시에 구성할 수 있으면 유용한 경우가 많이 있다. 예를 들어 모든 톰캣 관련 로거에 대한 로깅 레벨을 공통적으로 변경할 수 있지만 최상위 레벨 패키지를 쉽게 기억할 수 없다.
이를 돕기 위해 스프링 부트를 사용하면 스프링 환경에서 로깅 그룹을 정의할 수 있다. 예를 들어, application.properties
에 “tomcat” 그룹을 추가하여 정의하는 방법은 다음과 같다.
프로퍼티스(Properties)
logging.group.tomcat=org.apache.catalina,org.apache.coyote,org.apache.tomcat
Yaml
logging:
group:
tomcat: "org.apache.catalina,org.apache.coyote,org.apache.tomcat"
정의한 후에, 한 줄로 그룹 내 모든 로거의 레벨을 변경할 수 있다:
프로퍼티스(Properties)
logging.level.tomcat=trace
Yaml
logging:
level:
tomcat: "trace"
스프링 부트에는 즉시 사용할 수 있는 다음과 같은 사전 정의된 로깅 그룹이 포함되어 있다:
명칭 | 로거(Loggers) |
---|---|
web | org.springframework.core.codec ,org.springframework.http,org.springframework.web ,org.springframework.boot.actuate.endpoint.web ,org.springframework.boot.web.servlet.ServletContextInitializerBeans |
sql | org.springframework.jdbc.core ,org.hibernate.SQL, org.jooq.tools.LoggerListener |
7.4.7. Using a Log Shutdown Hook
애플리케이션이 종료될 때 로깅 리소스를 해제하기 위해 JVM이 종료될 때 로그 시스템 정리를 트리거하는 종료 후크가 제공된다. 이 종료 후크는 애플리케이션이 war 파일로 배포되지 않는 한 자동으로 등록된다. 애플리케이션에 복잡한 컨텍스트 계층 구조가 있는 경우 종료 후크가 요구 사항을 충족하지 못할 수 있다. 그렇지 않은 경우 종료 후크를 비활성화하고 기본 로깅 시스템에서 직접 제공하는 옵션을 조사해보자. 예를 들어 로그백은 각 로거(Logger)가 자체 컨텍스트에서 생성될 수 있도록 하는 컨텍스트 셀렉터를 제공한다. login.register-shutdown-hook
프로퍼티을 사용하여 종료 후크를 비활성화할 수 있다. false
로 설정하면 등록이 비활성화된다. application.properties
또는 application.yaml
파일에서 프로퍼티를 설정할 수 있다:
프로퍼티스(Properties)
logging.register-shutdown-hook=false
Yaml
logging:
register-shutdown-hook: false
7.4.8. Custom Log Configuration
다양한 로깅 시스템은 클래스패스에 적절한 라이브러리를 포함하여 활성화할 수 있으며 클래스패스의 루트 또는 스프링 환경 프로퍼티인 login.config
에 의해 지정된 위치에 적절한 구성 파일을 제공하여 추가로 커스텀할 수 있다.
org.springframework.boot.logging.LoggingSystem
시스템 프로퍼티을 사용하여 스프링 부트가 특정 로깅 시스템을 사용하도록 강제할 수 있다. 값은 로깅시스템(LoggingSystem)
구현체의 클래스 이름이어야 한다. none
값을 사용하여 스프링 부트의 로깅 구성을 완전히 비활성화할 수도 있다.
애플리케이션컨텍스트(ApplicationContext)
가 생성되기 전에 로깅이 초기화되므로 스프링 @Configuration
파일의 @PropertySources
에서 로깅을 제어하는 것은 불가능하다. 로깅 시스템을 변경하거나 완전히 비활성화하는 유일한 방법은 시스템 프로퍼티를 이용하는 것이다.
로깅 시스템에 따라 다음 파일이 로드된다:
로깅 시스템 | 커스텀 파일 |
---|---|
Logback | logback-spring.xml , logback-spring.groovy , logback.xml , or logback.groovy |
Log4j2 | log4j2-spring.xml or log4j2.xml |
JDK (Java Util Logging) | logging.properties |
가능하다면 로깅 구성에 -spring
변수을 사용하는 것이 좋다(예: logback.xml
대신 logback-spring.xml
). 표준 구성 위치를 사용하는 경우 스프링은 로그 초기화를 완전히 제어할 수 없다.
자바 유틸 로깅(Java Util Logging)
에는 ‘실행 가능한 jar’에서 실행할 때 문제를 일으키는 것으로 알려진 클래스 로딩 문제가 있다. 가능하다면 ‘실행 가능한 jar’에서 실행할 때는 이를 피하는 것이 좋다.
커스텀을 돕기 위해 다음 표에 설명된 대로 일부 다른 프로퍼티가 스프링 환경에서 시스템 프로퍼티로 전송된다:
스프링 환경변수 | 시스템 프로퍼티 | 설명 |
---|---|---|
logging.exception-conversion-word | LOG_EXCEPTION_CONVERSION_WORD | 예외를 기록할 때 사용되는 변환 단어다. |
logging.file.name | LOG_FILE | 정의된 경우 기본 로그 구성에 사용된다. |
logging.file.path | LOG_PATH | 정의된 경우 기본 로그 구성에 사용된다. |
logging.pattern.console | CONSOLE_LOG_PATTERN | 콘솔(stdout)에서 사용할 로그 패턴이다. |
logging.pattern.dateformat | LOG_DATEFORMAT_PATTERN | 로그 날짜 형식에 대한 어펜더 패턴이다. |
logging.charset.console | CONSOLE_LOG_CHARSET | 콘솔 로깅에 사용할 문자 세트다. |
logging.threshold.console | CONSOLE_LOG_THRESHOLD | 콘솔 로깅에 사용할 로그 레벨 임계값(threshold)이다. |
logging.pattern.file | FILE_LOG_PATTERN | 파일에 사용할 로그 패턴이다(LOG_FILE이 활성화된 경우). |
logging.charset.file | FILE_LOG_CHARSET | 파일 로깅에 사용할 문자 세트이다(LOG_FILE이 활성화된 경우). |
logging.threshold.file | FILE_LOG_THRESHOLD | 파일 로깅에 사용할 로그 레벨 임계값입니다. |
logging.pattern.level | LOG_LEVEL_PATTERN | 로그 레벨을 렌더링할 때 사용할 형식(기본값 %5p)이다. |
PID | PID | 현재 프로세스 ID(발견되고 아직 OS 환경 변수로 정의되지 않은 경우). |
로그백을 사용하면 다음 프로퍼티스도 전달된다:
스프링 환경변수 | 시스템 프로퍼티 | 설명 |
---|---|---|
logging.logback.rollingpolicy.file-name-pattern | LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN | 롤오버(rolled-over)된 로그 파일 이름의 패턴(기본값 ${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz). |
logging.logback.rollingpolicy.clean-history-on-start | LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START | 시작 시 보관 로그 파일을 정리할지 여부다. |
logging.logback.rollingpolicy.max-file-size | LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE | 최대 로그 파일 사이즈이다. |
logging.logback.rollingpolicy.total-size-cap | ``LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP` | 보관할 로그 백업의 총 크기다. |
logging.logback.rollingpolicy.max-history | LOGBACK_ROLLINGPOLICY_MAX_HISTORY | 보관할 최대 로그 파일 수다. |
지원되는 모든 로깅 시스템은 구성 파일을 파싱할 때 시스템 프로퍼티스을 참조할 수 있다. 예제는 spring-boot.jar
의 기본 구성을 참고하자. • Logback • Log4j 2 • Java Util logging
로깅 프로퍼티에서 자리 표시자(placeholder)를 사용하려면 기본 프레임워크의 문법이 아닌 스프링 부트의 문법을 사용해야 한다. 특히 로그백을 사용하는 경우 프로퍼티명과 기본값 사이의 구분 기호로 :를 사용해야 하며 :-를 사용하면 안 된다.
LOG_LEVEL_PATTERN
(또는 로그벡으로 login.pattern.level
)만 오버라이드하여 MDC 및 기타 임시 콘텐츠를 로그 줄에 추가할 수 있다. 예를 들어, login.pattern.level=user:%X{user} %5p
를 사용하는 경우 다음 예에 표시된 대로 기본 로그 포맷은 “user”에 대한 MDC 항목이 있는 경우 포함된다.
2019-08-30 12:30:04.031 user:someone INFO 22174 --- [ nio-8080-exec-0]
demo.Controller
Handling authenticated request
7.4.9. Logback Extensions
스프링 부트에는 고급 구성에 도움이 될 수 있는 로그백에 대한 다양한 확장이 포함되어 있다. logback-spring.xml
구성 파일에서 이러한 확장을 사용할 수 있다.
표준 logback.xml
구성 파일이 너무 일찍 로드되므로 해당 파일에서 확장을 사용할 수 없다. logback-spring.xml
을 사용하거나 login.config
프로퍼티를 정의해야 한다.
확장 기능은 로그백의 구성 스캐닝과 함께 사용할 수 없다. 그렇게 하려고 하면 구성 파일을 변경하면 다음 중 하나와 유사한 오류가 나타난다:
ERROR in ch.qos.logback.core.joran.spi.Interpreter@4:71 - no applicable action for
[springProperty], current ElementPath is [[configuration][springProperty]]
ERROR in ch.qos.logback.core.joran.spi.Interpreter@4:71 - no applicable action for
[springProfile], current ElementPath is [[configuration][springProfile]]
Profile-specific Configuration
<springProfile>
태그를 사용하면 활성 스프링 프로필을 기반으로 구성 섹션을 선택적으로 포함하거나 제외할 수 있다. 프로필 섹션은 <configuration>
엘리먼트 내 어디에서나 지원된다. 이름 애트리뷰트를 사용하여 구성 프로필을 지정합니다. <springProfile>
태그에는 프로필명(예: staging) 또는 프로필 표현식이 포함될 수 있다. 프로필 표현을 사용하면 production & (eu-central | eu-west)
와 같이 더 복잡한 프로필 로직를 표현할 수 있다. 자세한 내용은 스프링 프레임워크 레퍼런스 가이드를 확인하자. 다음은 세 가지 샘플 프로필을 보여준다:
<springProfile name="staging">
<!-- configuration to be enabled when the "staging" profile is active -->
</springProfile>
<springProfile name="dev | staging">
<!-- configuration to be enabled when the "dev" or "staging" profiles are active-->
</springProfile>
<springProfile name="!production">
<!-- configuration to be enabled when the "production" profile is not active -->
</springProfile>
Environment Properties
<springProperty>
태그를 사용하면 로그백(Logback) 내에서 사용할 스프링 환경의 프로퍼티스를 노출할 수 있다. 이렇게 하면 로그백 구성의 application.properties
파일 값에 접근하는 경우 유용하다. 태그는 로그백의 표준 <property>
태그와 비슷한 방식으로 작동한다. 그러나 직접 값을 지정하는 대신 환경에서 프로퍼티 소스를 지정한다. 로컬 범위가 아닌 다른 곳에 프로퍼티을 저장해야 하는 경우 범위 특성을 사용할 수 있다. 대체 값이 필요한 경우(프로퍼티가 환경에 설정되지 않은 경우) defaultValue
프로퍼티를 사용할 수 있다. 다음 예에서는 로그백 내에서 사용할 프로퍼티를 노출하는 방법을 보여준다:
<springProperty scope="context" name="fluentHost" source="myapp.fluentd.host" defaultValue="localhost"/>
<appender name="FLUENT" class="ch.qos.logback.more.appenders.DataFluentAppender">
<remoteHost>${fluentHost}</remoteHost>
...
</appender>
소스는 케밥 형식(예: my.property-name)으로 지정해야 한다. 그러나 완화된 규칙(relaxed rules)을 사용하여 환경에 프로퍼티스를 추가할 수 있다.
7.4.10. Log4j2 Extensions
스프링 부트에는 고급 구성에 도움이 될 수 있는 로그4j2에 대한 다양한 확장이 포함되어 있다. 모든 log4j2-spring.xml
구성 파일에서 이러한 확장을 사용할 수 있다.
표준 log4j2.xml
구성 파일이 너무 일찍 로드되므로 해당 파일에서 확장을 사용할 수 없다. log4j2-spring.xml
을 사용하거나 login.config
프로퍼티을 정의해야 한다.
확장은 Log4J에서 제공하는 스프링 부트 지원을 대체한다. 빌드에 org.apache.logging.log4j:log4j-spring-boot
모듈을 포함하지 않도록 해야 한다.
Profile-specific Configuration
<SpringProfile>
태그를 사용하면 활성 스프링 프로필을 기반으로 구성 섹션을 선택적으로 포함하거나 제외할 수 있다. 프로필 섹션은 <Configuration>
엘리먼트 내 어디에서나 지원된다. 이름 애트리뷰트를 사용하여 구성을 허용하는 프로필을 지정한다. <SpringProfile>
태그에는 프로필명(예: staging) 또는 프로필 표현식이 포함될 수 있다. 프로필 표현을 사용하면 production & (eu-central | eu-west)
와 같이 더 복잡한 프로필 로직를 표현할 수 있다. 자세한 내용은 스프링 프레임워크 레퍼런스 가이드를 확인하자. 다음은 세 가지 샘플 프로필을 보여준다:
<SpringProfile name="staging">
<!-- configuration to be enabled when the "staging" profile is active -->
</SpringProfile>
<SpringProfile name="dev | staging">
<!-- configuration to be enabled when the "dev" or "staging" profiles are active-->
</SpringProfile>
<SpringProfile name="!production">
<!-- configuration to be enabled when the "production" profile is not active -->
</SpringProfile>
Environment Properties Lookup
Log4j2 구성 내에서 스프링 환경의 프로퍼티스를 참조하려면 spring:
접두사가 붙은 조회를 사용할 수 있다. 이렇게 하면 Log4j2 구성의 application.properties
파일 값에 접근하려는 경우 유용하다.
다음 예제에서는 스프링 환경에서 spring.application.name
을 읽는 applicationName
이라는 Log4j2 프로퍼티를 설정하는 방법을 보여준다. The following example shows how to set a Log4j2 property named applicationName that reads spring.application.name from the Spring Environment:
<Properties>
<Property name="applicationName">${spring:spring.application.name}</Property>
</Properties>
조회 키는 케밥 케이스(예: my.property-name)로 지정해야 한다.
Log4j2 System Properties
Log4j2는 다양한 항목을 구성하는 데 사용할 수 있는 다양한 시스템 프로퍼티스를 지원한다. 예를 들어 log4j2.skipJansi 시스템 프로퍼티스를 사용하면 콘솔어펜더(ConsoleAppender)
가 윈도우에서 Jansi 출력 스트림을 사용할지를 구성할 수 있다. Log4j2 초기화 이후 로드되는 모든 시스템 프로퍼티스는 스프링 환경에서 얻을 수 있다. 예를 들어, 윈도우에서 콘솔어펜더(ConsoleAppender)
가 Jansi를 사용하도록 하려면 application.properties
파일에 log4j2.skipJansi=false
를 추가할 수 있다.
스프링 환경은 시스템 프로퍼티스와 OS 환경 변수에 로드되는 값이 포함되어 있지 않은 경우에만 고려된다.
Log4j2 초기화 중에 로드된 시스템 프로퍼티스는 스프링 환경을 참조할 수 없다. 예를 들어 Log4j2가 기본 로그4j2 구현체를 사용하는 프로퍼티는 스프링 환경을 사용할 수 있기 전에 선택된다.
7.5. Internationalization
스프링 부트는 지역화된 메시지를 지원하므로 애플리케이션이 다양한 언어를 가진 사용자를 수용할 수 있다. 기본적으로 스프링 부트는 클래스패스 루트에서 메시지 리소스 번들을 찾는다.
구성된 리소스 번들의 기본 프로퍼티 파일(기본적으로 message.properties
)을 사용할 수 있는 경우 자동 구성 된다. 리소스 번들에 언어별 프로퍼티 파일만 포함된 경우 기본값을 추가해야 한다. 구성된 기본 명칭과 일치하는 프로퍼티스 파일이 없으면 자동 구성된 메세지소스(MessageSource)
가 없다.
리소스 번들의 기본 명칭과 기타 여러 애트리뷰트는 다음 예제와 같이 spring.messages
네임스페이스를 사용하여 구성할 수 있다:
프로퍼티스(Properties)
spring.messages.basename=messages,config.i18n.messages
spring.messages.fallback-to-system-locale=false
Yaml
spring:
messages:
basename: "messages,config.i18n.messages"
fallback-to-system-locale: false
spring.messages.basename
은 쉼표로 구분된 위치 목록(패키지 한정자(package qualifier) 또는 클래스패스 루트에서 확인된 리소스)을 지원한다.
지원되는 추가 옵션은 메세지소스프로퍼티스(MessageSourceProperties)를 참고하자.
7.6. JSON
스프링 부트는 세 가지 JSON 매핑 라이브러리와 통합한다:
- Gson
- Jackson
- JSON-B
잭슨(Jackson)
이 선호되는 기본 라이브러리다.
7.6.1. Jackson
잭슨(Jackson)
에 대한 자동 구성이 제공되며 잭슨(Jackson)
은 spring-boot-starter-json
의 일부이다. 잭슨이 클래스패스에 있으면, 오브젝트매퍼(ObjectMapper) 빈이 자동으로 구성된다. 오브젝트매퍼(ObjectMapper)
구성을 커스텀하기 위해 여러 구성 프로퍼티스가 제공된다.
Custom Serializers and Deserializers
잭슨을 사용하여 JSON 데이터를 시리얼라이저(serialize) 및 디시리얼라이저(deserialize)하는 경우 고유한 제이슨시리얼라이저(JsonSerializer)
및 제이슨디시리얼라이저(JsonDeserializer)
클래스를 작성할 수 있다. 커스텀 직렬 컨버터는 일반적으로 모듈을 통해, 잭슨에 등록되지만 스프링 부트는 스프링 빈을 직접 등록하는 것을 더 쉽게 해주는 @JsonComponent
어노테이션을 제공한다.
제이슨시리얼라이저(JsonSerializer)
, 제이슨디시리얼라이저(JsonDeserializer)
또는 키디시리얼라이저(KeyDeserializer)
구현체에서 직접 @JsonComponent
어노테이션을 사용할 수 있다. 다음 예제와 같이 시리얼라이저/디시리얼라이저를 내부(inner) 클래스로 포함하는 클래스에서도 사용할 수 있다:
자바
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import org.springframework.boot.jackson.JsonComponent;
@JsonComponent
public class MyJsonComponent {
public static class Serializer extends JsonSerializer<MyObject> {
@Override
public void serialize(MyObject value, JsonGenerator jgen, SerializerProvider serializers) throws IOException {
jgen.writeStartObject();
jgen.writeStringField("name", value.getName());
jgen.writeNumberField("age", value.getAge());
jgen.writeEndObject();
}
}
public static class Deserializer extends JsonDeserializer<MyObject> {
@Override
public MyObject deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
ObjectCodec codec = jsonParser.getCodec();
JsonNode tree = codec.readTree(jsonParser);
String name = tree.get("name").textValue();
int age = tree.get("age").intValue();
return new MyObject(name, age);
}
}
}
코틀린
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.JsonProcessingException
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.SerializerProvider
import org.springframework.boot.jackson.JsonComponent
import java.io.IOException
@JsonComponent
class MyJsonComponent {
class Serializer : JsonSerializer<MyObject>() {
@Throws(IOException::class)
override fun serialize(value: MyObject, jgen: JsonGenerator, serializers: SerializerProvider) {
jgen.writeStartObject()
jgen.writeStringField("name", value.name)
jgen.writeNumberField("age", value.age)
jgen.writeEndObject()
}
}
class Deserializer : JsonDeserializer<MyObject>() {
@Throws(IOException::class, JsonProcessingException::class)
override fun deserialize(jsonParser: JsonParser, ctxt: DeserializationContext): MyObject {
val codec = jsonParser.codec
val tree = codec.readTree<JsonNode>(jsonParser)
val name = tree["name"].textValue()
val age = tree["age"].intValue()
return MyObject(name, age)
}
}
}
애플리케이션컨텍스트(ApplicationContext)
의 모든 @JsonComponent
빈은 자동으로 잭슨(Jackson)
에 등록된다. @JsonComponent
는 @Component
로 메타 어노테이션을 달기 때문에 일반적인 컴포넌트 스캔이 적용된다.
또한, 스프링 부트는 객체를 직렬화할 때, 표준 잭슨 버전의 유용한 대안 클래스인 제이슨오브젝트시리얼라이저(JsonObjectSerializer)
및 제이슨오브젝트디시리얼라이저(JsonObjectDeserializer)
를 제공한다. 자세한 내용은 자바독(javadoc)의 제이슨오브젝트시리얼라이저(JsonObjectSerializer)
및 제이슨오브젝트디시리얼라이저(JsonObjectDeserializer)
를 참고하자.
위의 예제는 다음과 같이 제이슨오브젝트시리얼라이저/제이슨오브젝트디시리얼라이저를 사용하도록 다시 작성할 수 있다:
자바
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.SerializerProvider;
import org.springframework.boot.jackson.JsonComponent;
import org.springframework.boot.jackson.JsonObjectDeserializer;
import org.springframework.boot.jackson.JsonObjectSerializer;
@JsonComponent
public class MyJsonComponent {
public static class Serializer extends JsonObjectSerializer<MyObject> {
@Override
protected void serializeObject(MyObject value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeStringField("name", value.getName());
jgen.writeNumberField("age", value.getAge());
}
}
public static class Deserializer extends JsonObjectDeserializer<MyObject> {
@Override
protected MyObject deserializeObject(JsonParser jsonParser, DeserializationContext context, ObjectCodec codec, JsonNode tree) throws IOException {
String name = nullSafeValue(tree.get("name"), String.class);
int age = nullSafeValue(tree.get("age"), Integer.class);
return new MyObject(name, age);
}
}
}
코틀린
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.ObjectCodec
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.SerializerProvider
import org.springframework.boot.jackson.JsonComponent
import org.springframework.boot.jackson.JsonObjectDeserializer
import org.springframework.boot.jackson.JsonObjectSerializer
import java.io.IOException
@JsonComponent
class MyJsonComponent {
class Serializer : JsonObjectSerializer<MyObject>() {
@Throws(IOException::class)
override fun serializeObject(value: MyObject, jgen: JsonGenerator, provider: SerializerProvider) {
jgen.writeStringField("name", value.name)
jgen.writeNumberField("age", value.age)
}
}
class Deserializer : JsonObjectDeserializer<MyObject>() {
@Throws(IOException::class)
override fun deserializeObject(jsonParser: JsonParser, context: DeserializationContext, codec: ObjectCodec, tree: JsonNode): MyObject {
val name = nullSafeValue(tree["name"], String::class.java)
val age = nullSafeValue(tree["age"], Int::class.java)
return MyObject(name, age)
}
}
}
Mixins
잭슨은 대상 클래스에 이미 선언된 어노테이션에 추가 어노테이션을 혼합하는 데 사용할 수 있는 믹스인(mixins)을 지원한다. 스프링 부트의 잭슨 자동 구성은 애플리케이션 패키지에서 @JsonMixin
어노테이션이 달린 클래스를 검색하고, 이를 자동 구성된 오브젝트매퍼(ObjectMapper)
에 등록한다. 등록은 스프링 부트의 제이슨믹스인모듈(JsonMixinModule)
에 의해 수행된다.
7.6.2. Gson
지슨(Gson)에 대한 자동 구성이 제공된다. 지슨(Gson)이 클래스패스에 있으면 지슨(Gson) 빈이 자동으로 구성된다. 구성을 커스텀하기 위해 여러 spring.gson.*
구성 프로퍼티스가 제공된다. 더 많은 제어를 위해 하나 이상의 지슨빌더커스터마이저(GsonBuilderCustomizer)
빈을 사용할 수 있다.
7.6.3. JSON-B
JSON-B
에 대한 자동 구성이 제공된다. JSON-B API와 구현이 클래스패스에 있으면 Jsonb 빈이 자동으로 구성된다. 선호되는 JSON-B 구현체는 의존성 관리가 제공되는 이클립스 Yasson
이다.
7.7. Task Execution and Scheduling
컨텍스트에 익스큐터(Executor)
빈이 없으면, 스프링 부트는 비동기(asynchronous) 작업 실행(@EnableAsync) 및 스프링 MVC 비동기 요청 처리에 자동 연결할 수 있는 적절한 기본값으로 스레드풀테스트익스큐터(ThreadPoolTaskExecutor)
를 자동 구성한다.
컨텍스트에서 커스텀 익스큐터(Executor)
를 정의한 경우 일반 작업 실행(@EnableAsync)은 이를 사용하지만, 에이싱크태스크익스큐터(AsyncTaskExecutor)
구현(applicationTaskExecutor
라는 이름)이 필요하므로 스프링 MVC 지원은 구성되지 않는다. 대상 배열에 따라, 익스큐터(Executor)
를 스레드풀태스크익스큐터(ThreadPoolTaskExecutor)
로 변경하거나 커스텀 익스큐터(Executor)
를 래핑하는 스레드풀태스크익스큐터(ThreadPoolTaskExecutor)
와 에이싱크컨피규어러(AsyncConfigurer)
를 모두 정의할 수 있다. 자동 구성된 태스크익스큐터빌더(TaskExecutorBuilder)
를 사용하면 자동 구성이 일반적으로 수행하는 작업을 재현하는 인스턴스를 쉽게 생성할 수 있다.
스레드 풀은 부하에 따라 늘어나고 줄어들 수 있는 8개의 코어 스레드를 사용한다. 이러한 기본 설정은 다음 예제와 같이 spring.task.execution
네임스페이스를 사용하여 미세 조정할 수 있다:
프로퍼티스(Properties)
spring.task.execution.pool.max-size=16
spring.task.execution.pool.queue-capacity=100
spring.task.execution.pool.keep-alive=10s
Yaml
spring:
task:
execution:
pool:
max-size: 16
queue-capacity: 100
keep-alive: "10s"
이렇게 하면 스레드 풀이 제한된 큐(queue)을 사용하도록 변경되어 큐가 가득 차면(100개 작업) 스레드 풀이 최대 16개 스레드로 늘어난다. 스레드가 10초(기본적으로 60초) 동안 유휴 상태(idle)일 때 스레드가 회수되므로 풀 축소는 더욱 공격적이다.
스레드풀태스크스케줄러(ThreadPoolTaskScheduler)
는 예약(schedule)된 작업 실행과 연결되어야 하는 경우 자동 구성될 수도 있다(예: @EnableScheduling
사용). 스레드 풀은 기본적으로 하나의 스레드를 사용하며, 다음 예제와 같이 spring.task.scheduling
네임스페이스를 사용하여 해당 설정을 미세 조정할 수 있다:
프로퍼티스(Properties)
spring.task.scheduling.thread-name-prefix=scheduling-
spring.task.scheduling.pool.size=2
Yaml
spring:
task:
scheduling:
thread-name-prefix: "scheduling-"
pool:
size: 2
커스텀 익스큐터나 스케줄러를 생성해야 하는 경우 태스크익스큐터빌더(TaskExecutorBuilder)
빈과 태스크스케줄러빌더(TaskSchedulerBuilder)
빈 모두 컨텍스트에서 사용할 수 있다.
7.8. Testing
스프링 부트는 애플리케이션을 테스트할 때 도움이 되는 다양한 유틸리티와 어노테이션을 제공한다. 테스트 지원은 두 가지 모듈로 제공된다. spring-boot-test
에는 핵심 아이템이 포함되어 있고, spring-boot-test-autoconfigure
는 테스트에 대한 자동 구성을 지원한다.
대부분의 개발자는 스프링 부트 테스트 모듈과 제이유닛 주피터(JUnit Jupiter), 어설트제이(AssertJ), 햄크레스트(Hamcrest) 및 기타 여러 유용한 라이브러리를 모두 가져오는 spring-boot-starter-test
“스타터(Starter)”를 사용한다.
제이유닛(JUnit) 4를 사용하는 테스트가 있는 경우, 제이유닛(JUnit) 5의 빈티지(vintage) 엔진을 사용하여 실행할 수 있다. 빈티지 엔진을 사용하려면 다음 예와 같이 junit-vintage-engine
에 대한 의존성을 추가하자:
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
hamcrest-core
는 spring-boot-starter-test
의 일부인 org.hamcrest:hamcrest
를 위해 제외된다.
7.8.1. Test Scope Dependencies
spring-boot-starter-test
“스타터”(테스트 범위 내)에는 다음과 같은 제공된 라이브러리가 포함되어 있다:
- 제이유닛(JUnit) 5: 자바 애플리케이션 단위 테스트를 위한 사실상의 표준이다.
- 스프링 테스트 & 스프링 부트 테스트: 스프링 부트 애플리케이션을 위한 유틸리티 및 통합 테스트 지원.
- 어설트제이(AssertJ): 어설트 라이브러리.
- 햄크레스트(Hamcrest): 매처(matcher) 객체의 라이브러리(제약조건(constraint) 또는 조건자(predicate)라고도 함)이다.
- 모키토(Mockito): 자바 모킹(mocking) 프레임워크.
- 제이슨어설트(JSONassert): JSON용 어설션 라이브러리입니다.
- 제이선패스(JsonPath): JSON용 XPath.
일반적으로 테스트를 작성할 때 이러한 공통 라이브러리는 유용하다. 이러한 라이브러리가 요구 사항에 맞지 않으면 자체 테스트 의존성을 추가할 수 있다.
7.8.2. Testing Spring Applications
의존성 주입의 주요 장점 중 하나는 코드의 단위 테스트가 더 쉬워진다는 것이다. 스프링을 사용하지 않고도 new 연산자를 사용하여 객체를 인스턴스화할 수 있다. 실제 의존성 대신 목(mock) 객체를 사용할 수도 있다.
종종, 단위 테스트를 넘어 통합 테스트(스프링 애플리케이션컨텍스트(ApplicationContext)를 사용하여)를 시작해야 한다. 애플리케이션을 배포하거나 다른 인프라에 연결할 필요 없이 통합 테스트를 수행할 수 있으면 유용하다.
스프링 프레임워크에는 이러한 통합 테스트를 위한 전용 테스트 모듈이 포함되어 있다. org.springframework:spring-test
에 대한 의존성을 직접 선언하거나 spring-boot-starter-test
“스타터”를 사용하여 이를 전이적으로(transitively) 가져올 수 있다.
이전에 spring-test
모듈을 사용해 본 적이 없다면, 먼저 스프링 프레임워크 레퍼런스 문서의 관련 장을 읽어야 한다.
7.8.3. Testing Spring Boot Applications
스프링 부트 애플리케이션은 스프링 애플리케이션컨텍스트(ApplicationContext)
이므로 일반적으로 바닐라 스프링 컨텍스트로 수행을 테스트하기 위한 특별한 작업은 없다.
스프링 부트의 외부 프로퍼티스, 로깅 및 기타 기능은 스프링애플리케이션(SpringApplication)
을 사용하여 생성하는 경우에만 기본적으로 컨텍스트에 설치된다.
스프링 부트 기능이 필요할 때 표준 스프링 테스트 @ContextConfiguration
어노테이션 대신 사용할 수 있는 @SpringBootTest
어노테이션을 제공한다. 어노테이션은 스프링애플리케이션(SpringApplication)
을 통해 테스트에 사용되는 애플리케이션컨텍스트(ApplicationContext)
를 생성하여 작동한다. @SpringBootTest
외에도 애플리케이션의 보다 구체적인 부분을 테스트하기 위한 여러 가지 다른 어노테이션도 제공된다.
제이유닛(JUnit) 4를 사용하는 경우 테스트에 @RunWith(SpringRunner.class)
도 추가하는 것을 잊지 말자. 그렇지 않으면 어노테이션이 무시된다. 제이유닛(JUnit) 5를 사용하는 경우 @SpringBootTest
와s 동일한 @ExtendWith(SpringExtension.class)
를 추가할 필요가 없으며 다른 @...Test
어노테이션에는 이미 해당 어노테이션이 달려 있다.
기본적으로, @SpringBootTest
는 서버를 시작하지 않는다. @SpringBootTest
의 webEnvironment
애트리뷰트을 사용하여 테스트 실행 방법을 더욱 구체적으로 알 수 있다:
- MOCK(Default) : 웹
애플리케이션컨텍스트(ApplicationContext)
를 로드하고 목(mock) 웹 환경을 제공한다. 이 어노테이션을 사용할 때 임베디드 서버는 시작되지 않는다. 클래스패스에서 웹 환경을 사용할 수 없는 경우 이 모드는 웹이 아닌 일반애플리케이션컨텍스트(ApplicationContext)
생성으로 투명하게 대체된다. 웹 애플리케이션의 목 기반 테스트를 위해@AutoConfigureMockMvc
또는@AutoConfigureWebTestClient
와 함께 사용할 수 있다. - RANDOM_PORT:
웹서버애플리케이션컨텍스트(WebServerApplicationContext)
를 로드하고 실제 웹 환경을 제공한다. 임베디드 서버가 시작되고 랜덤 포트에서 수신 대기한다. - DEFINED_PORT:
웹서버애플리케이션컨텍스트(WebServerApplicationContext)
를 로드하고 실제 웹 환경을 제공한다. 내장형 서버가 시작되고 정의된 포트(application.properties에서) 또는 기본 포트 8080에서 수신 대기한다. - NONE:
스프링애플리케이션(SpringApplication)
을 사용하여애플리케이션컨텍스트(ApplicationContext)
를 로드하지만 웹 환경(목 또는 기타)을 제공하지 않는다.
테스트가 @Transactional
인 경우 기본적으로 각 테스트 메서드가 끝날 때 트랜잭션을 롤백한다. 그러나 RANDOM_PORT
또는 DEFINED_PORT
와 함께 사용하면 암시적으로 실제 서블릿 환경을 제공하므로 HTTP 클라이언트와 서버는 별도의 스레드에서 실행되므로 별도의 트랜잭션으로 실행된다. 이 경우 서버에서 시작된 트랜잭션은 롤백되지 않는다.
webEnvironment = WebEnvironment.RANDOM_PORT
를 사용하는 @SpringBootTest
는 애플리케이션이 관리 서버에 대해 다른 포트를 사용하는 경우 별도의 랜덤 포트에서 관리 서버를 시작한다.
Detecting Web Application Type
스프링 MVC를 사용할 수 있는 경우, 일반 MVC 기반 애플리케이션 컨텍스트가 구성된다. 스프링 웹플럭스만 있는 경우, 이를 감지하고 대신 웹플럭스 기반 애플리케이션 컨텍스트를 구성한다.
둘 다 존재하면 스프링 MVC가 우선한다. 이 상황에서 반응형(reactive) 웹 애플리케이션을 테스트하려면 spring.main.web-application-type
프로퍼티를 설정해야 한다:
자바
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest(properties = "spring.main.web-application-type=reactive")
class MyWebFluxTests {
// ...
}
코틀린
import org.springframework.boot.test.context.SpringBootTest
@SpringBootTest(properties = ["spring.main.web-application-type=reactive"])
class MyWebFluxTests {
// ...
}
Detecting Test Configuration
스프링 테스트 프레임워크에 익숙하다면 로드할 스프링 @Configuration
을 지정하기 위해 @ContextConfiguration(classes=...)
을 사용하는 데 익숙할 수 있다. 또는 테스트 내에서 중첩된 @Configuration
클래스를 자주 사용했을 수도 있다.
스프링 부트 애플리케이션을 테스트할 때 이는 필요하지 않을 수 있다. 스프링 부트의 @*Test
어노테이션은 그것을 명시적으로 정의하지 않을 때마다 자동으로 기본 구성을 검색한다.
검색 알고리즘은 테스트가 포함된 패키지에서 @SpringBootApplication
또는 @SpringBootConfiguration
이라는 어노테이션이 달린 클래스를 찾을 때까지 동작한다. 코드를 합리적인 방식으로 구성했다면 기본 구성을 찾을 수 있다.
테스트 어노테이션을 사용하여 애플리케이션의 보다 구체적인 부분을 테스트하는 경우 메인(main) 메서드의 애플리케이션 클래스에 특정 영역과 관련된 구성 설정을 추가하지 않아야 한다. @SpringBootApplication
의 컴포넌트 스캔 구성은 슬라이싱(slicing)이 예상대로 작동하는지 확인하는 데 사용되는 제외(exclude) 필터를 정의한다. @SpringBootApplication
어노테이션이 달린 클래스에서 명시적인 @ComponentScan
을 사용하는 경우 해당 필터가 비활성화된다는 점에 유의하자. 슬라이싱을 사용하는 경우 다시 정의해야 한다.
기본 구성을 커스텀하려면 중첩된 @TestConfiguration
클래스를 사용할 수 있다. 애플리케이션의 기본 구성 대신 사용되는 중첩된 @Configuration
클래스와 달리 중첩된 @TestConfiguration
클래스는 애플리케이션의 기본 구성에 추가로 사용된다.
스프링의 테스트 프레임워크는 테스트 사이에 애플리케이션 컨텍스트를 캐시한다. 따라서 테스트가 동일한 구성을 공유하는 한(검색 방법에 관계없이) 컨텍스트를 로드하는 데 시간이 많이 걸릴 수 있는 프로세스는 한 번만 발생한다.
Using the Test Configuration Main Method
일반적으로 @SpringBootTest
가 발견한 테스트 구성은 기본 @SpringBootApplication
이 된다. 잘 구조화된 대부분의 애플리케이션에서 이 구성 클래스에는 애플리케이션을 시작하는 데 사용되는 메인(main) 메서드도 포함된다.
예를 들어, 다음은 일반적인 스프링 부트 애플리케이션에 대한 매우 일반적인 코드 패턴이다:
자바
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
코틀린
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.docs.using.structuringyourcode.locatingthemainclass.MyApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class MyApplication
fun main(args: Array<String>) {
runApplication<MyApplication>(*args)
}
위의 예에서 메인(main) 메소드는 SpringApplication.run
에 위임하는 것 외에는 아무것도 수행하지 않는다. 그러나 SpringApplication.run
을 호출하기 전에 커스텀를 적용하는 더 복잡한 메인(main) 메소드를 갖는 것이 가능하다.
For example, here is an application that changes the banner mode and sets additional profiles:
자바
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(MyApplication.class);
application.setBannerMode(Banner.Mode.OFF);
application.setAdditionalProfiles("myprofile");
application.run(args);
}
}
코틀린
import org.springframework.boot.Banner
import org.springframework.boot.runApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
@SpringBootApplication
class MyApplication
fun main(args: Array<String>) {
runApplication<MyApplication>(*args) {
setBannerMode(Banner.Mode.OFF)
setAdditionalProfiles("myprofile");
}
}
메인(main) 메서드의 커스텀 결과 애플리케이션컨텍스트(ApplicationContext)
에 영향을 미칠 수 있는 메인(main) 메서드를 사용하여 테스트에 사용되는 애플리케이션컨텍스트(ApplicationContext)
를 생성할 수도 있다. 기본적으로 @SpringBootTest
는 메인(main) 메서드를 호출하지 않고 대신 클래스 자체를 소유하여 애플리케이션컨테스트(ApplicationContext)를 생성한다.
이 동작을 변경하려면 @SpringBootTest
의 useMainMethod
애트리뷰트를 UseMainMethod.ALWAYS
또는 UseMainMethod.WHEN_AVAILABLE
로 변경할 수 있다. ALWAYS
로 설정하고 메인(main) 메서드를 찾을 수 없으면 테스트가 실패한다. WHEN_AVAILABLE
로 설정되면 메인(main) 메소드가 사용 가능한 경우 사용되며, 그렇지 않으면 표준 로딩 메커니즘이 사용된다.
예를 들어, 다음 테스트는 애플리케이션컨텍스트(ApplicationContext)
를 생성하기 위해 MyApplication의 메인 메서드를 호출합니다. 메인 메소드가 추가 프로필을 설정하면 애플리케이션컨텍스트(ApplicationContext)가 시작될 때 해당 프로필이 활성화된다.
자바
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.UseMainMethod;
@SpringBootTest(useMainMethod = UseMainMethod.ALWAYS)
class MyApplicationTests {
@Test
void exampleTest() {
// ...
}
}
코틀린
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.SpringBootTest.UseMainMethod
import org.springframework.context.annotation.Import
@SpringBootTest(useMainMethod = UseMainMethod.ALWAYS)
class MyApplicationTests {
@Test
fun exampleTest() {
// ...
}
}
Excluding Test Configuration
애플리케이션이 컴포넌트 스캐닝을 사용하는 경우(예: @SpringBootApplication
또는 @ComponentScan
을 사용하는 경우) 특정 테스트용으로만 만든 최상위 구성 클래스가 실수로 모든 곳에서 선택될 수 있다.
앞서 살펴본 것처럼 @TestConfiguration
을 테스트의 내부(inner) 클래스에서 사용하여 기본 구성을 커스텀할 수 있다. 최상위 클래스에 배치되면 @TestConfiguration
은 src/test/java
의 클래스가 검색을 통해 선택되어서는 안 됨을 나타낸다. 그런 다음 다음 예제와 같이 필요한 경우 해당 클래스를 명시적으로 가져올 수 있다.
자바
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
@SpringBootTest
@Import(MyTestsConfiguration.class)
class MyTests {
@Test
void exampleTest() {
// ...
}
}
코틀린
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.context.annotation.Import
@SpringBootTest
@Import(MyTestsConfiguration::class)
class MyTests {
@Test
fun exampleTest() {
// ...
}
}
@SpringBootApplication
을 통하지 않고 @ComponentScan
을 직접 사용하는 경우 타입익스클루드필터(TypeExcludeFilter)
를 등록해야 한다. 자세한 내용은 자바독(Javadoc)을 참고하자
Using Application Arguments
애플리케이션이 아규먼트를 예상하는 경우, @SpringBootTest
가 args
애트리뷰트을 사용하여 아규먼트를 주입하도록 할 수 있다. If your application expects arguments, you can have @SpringBootTest inject them using the args attribute. 자바
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.test.context.SpringBootTest;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(args = "--app.test=one")
class MyApplicationArgumentTests {
@Test
void applicationArgumentsPopulated(@Autowired ApplicationArguments args) {
assertThat(args.getOptionNames()).containsOnly("app.test");
assertThat(args.getOptionValues("app.test")).containsOnly("one");
}
}
코틀린
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.ApplicationArguments
import org.springframework.boot.test.context.SpringBootTest
@SpringBootTest(args = ["--app.test=one"])
class MyApplicationArgumentTests {
@Test
fun applicationArgumentsPopulated(@Autowired args: ApplicationArguments) {
assertThat(args.optionNames).containsOnly("app.test")
assertThat(args.getOptionValues("app.test")).containsOnly("one")
}
}
Testing With a Mock Environment
기본적으로, @SpringBootTest
는 서버를 시작하지 않고 대신 웹 엔드포인트 테스트를 위한 목 환경을 설정한다.
스프링 MVC를 사용하면, 다음 예제와 같이 MockMvc 또는 웹테스트클라이언트(WebTestClient)
를 사용하여 웹 엔드포인트를 쿼리할 수 있다:
자바
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
class MyMockMvcTests {
@Test
void testWithMockMvc(@Autowired MockMvc mvc) throws Exception {
mvc.perform(get("/")).andExpect(status().isOk()).andExpect(content().string("Hello World"));
}
// 스프링 웹플럭스가 클래스패스에 있는 경우 웹테스트클라이언트(WebTestClient)를 사용하여 MVC 테스트를 실행할 수 있다.
@Test
void testWithWebTestClient(@Autowired WebTestClient webClient) {
webClient
.get().uri("/")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Hello World");
}
}
코틀린
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.test.web.reactive.server.expectBody
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers
@SpringBootTest
@AutoConfigureMockMvc
class MyMockMvcTests {
@Test
fun testWithMockMvc(@Autowired mvc: MockMvc) {
mvc.perform(MockMvcRequestBuilders.get("/")).andExpect(MockMvcResultMatchers.status().isOk)
.andExpect(MockMvcResultMatchers.content().string("Hello World"))
}
// 스프링 웹플럭스가 클래스패스에 있는 경우 웹테스트클라이언트(WebTestClient)를 사용하여 MVC 테스트를 실행할 수 있다.
@Test
fun testWithWebTestClient(@Autowired webClient: WebTestClient) {
webClient
.get().uri("/")
.exchange()
.expectStatus().isOk
.expectBody<String>().isEqualTo("Hello World")
}
}
웹 계층에만 집중하고 전체 애플리케이션컨텍스트(ApplicationContext)
를 시작하지 않으려면 @WebMvcTest
를 사용하는 것이 좋다.
스프링 웹플럭스 엔드포인트를 사용하면, 다음 예제와 같이 웹테스트클라이언트(WebTestClient)
를 사용할 수 있다:
자바
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.reactive.server.WebTestClient;
@SpringBootTest
@AutoConfigureWebTestClient
class MyMockWebTestClientTests {
@Test
void exampleTest(@Autowired WebTestClient webClient) {
webClient
.get().uri("/")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Hello World");
}
}
코틀린
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.test.web.reactive.server.expectBody
@SpringBootTest
@AutoConfigureWebTestClient
class MyMockWebTestClientTests {
@Test
fun exampleTest(@Autowired webClient: WebTestClient) {
webClient
.get().uri("/")
.exchange()
.expectStatus().isOk
.expectBody<String>().isEqualTo("Hello World")
}
}
목 환경 내에서 테스트하는 것은 일반적으로 전체 서블릿 컨테이너로 실행하는 것보다 빠르다. 그러나 모킹(Mocking)은 스프링 MVC 계층에서 발생하므로, 하위 레벨 서블릿 컨테이너 동작에 의존하는 코드는 MockMvc로 직접 테스트할 수 없다.
예를 들어, 스프링 부트의 오류 처리(error handling)는 서블릿 컨테이너에서 제공하는 “오류 페이지”를 기반으로 한다. 즉, MVC 계층에서 예상대로 예외 발생 및 처리를 테스트할 수 있지만, 특정 커스텀 오류 페이지가 렌더링되는지 직접 테스트할 수는 없다. 이러한 하위 레벨 문제를 테스트해야 하는 경우 다음 절에 설명된 대로 완전히 실행되는 서버를 시작할 수 있다.
Testing With a Running Server
전체 실행 서버를 시작해야 하는 경우, 랜덤 포트를 사용하는 것이 좋다. @SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
를 사용하는 경우 테스트가 실행될 때마다 사용 가능한 포트가 무작위로 선택된다.
@LocalServerPort
어노테이션을 사용하여 테스트에 사용되는 실제 포트를 주입할 수 있다. 편의를 위해 시작된 서버에 대한 REST 호출을 수행해야 하는 테스트에서는 웹테스트클라이언트(WebTestClient)
를 추가로 @Autowire
할 수 있다. 이 클라이언트는 다음 예와 같이 실행 중인 서버에 대한 상대 링크를 확인하고 응답을 확인하기 위한 전용 API와 함께 제공된다:
자바
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.test.web.reactive.server.WebTestClient;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyRandomPortWebTestClientTests {
@Test
void exampleTest(@Autowired WebTestClient webClient) {
webClient
.get().uri("/")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Hello World");
}
}
코틀린
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment
import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.test.web.reactive.server.expectBody
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyRandomPortWebTestClientTests {
@Test
fun exampleTest(@Autowired webClient: WebTestClient) {
webClient
.get().uri("/")
.exchange()
.expectStatus().isOk
.expectBody<String>().isEqualTo("Hello World")
}
}
웹테스트클라이언트(WebTestClient)
는 라이브 서버와 목 환경 모두에서 사용할 수 있다.
이 설정에는 클래스패스에 spring-webflux
가 필요하다. webflux
를 추가할 수 없거나 추가하지 않을 경우, 스프링 부트는 테스트레스트템플릿(TestRestTemplate)
기능도 제공한다;
자바
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyRandomPortTestRestTemplateTests {
@Test
void exampleTest(@Autowired TestRestTemplate restTemplate) {
String body = restTemplate.getForObject("/", String.class);
assertThat(body).isEqualTo("Hello World");
}
}
코틀린
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment
import org.springframework.boot.test.web.client.TestRestTemplate
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyRandomPortTestRestTemplateTests {
@Test
fun exampleTest(@Autowired restTemplate: TestRestTemplate) {
val body = restTemplate.getForObject("/", String::class.java)
assertThat(body).isEqualTo("Hello World")
}
}
Customizing WebTestClient
웹테스트클라이언트(WebTestClient)
빈을 커스텀하려면 웹테스트클라이언트빌더커스터마이저(WebTestClientBuilderCustomizer)
빈을 구성하자. 이러한 빈은 웹테스트클라이언트(WebTestClient)
를 생성하는 데 사용되는 WebTestClient.Builder
를 통해 호출된다.
Using JMX
테스트 컨텍스트 프레임워크가 컨텍스트를 캐시하므로 동일한 컴포넌트가 동일한 도메인에 등록되는 것을 방지하기 위해 JMX는 기본적으로 비활성화된다. 이러한 테스트가 MBeanServer
에 접근해야 하는 경우 더티(dirty)로 표시하는 것도 고려해야한다.
자바
import javax.management.MBeanServer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(properties = "spring.jmx.enabled=true")
@DirtiesContext
class MyJmxTests {
@Autowired
private MBeanServer mBeanServer;
@Test
void exampleTest() {
assertThat(this.mBeanServer.getDomains()).contains("java.lang");
// ...
}
}
코틀린
import javax.management.MBeanServer
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.annotation.DirtiesContext
@SpringBootTest(properties = ["spring.jmx.enabled=true"])
@DirtiesContext
class MyJmxTests(@Autowired val mBeanServer: MBeanServer) {
@Test
fun exampleTest() {
assertThat(mBeanServer.domains).contains("java.lang")
// ...
}
}
Using Metrics
클래스패스에 관계없이 @SpringBootTest
를 사용할 때 메모리 내 백업을 제외한 미터(meter) 레지스트리는 자동으로 구성되지 않는다.
통합 테스트의 일부로 메트릭(metric)을 다른 백엔드로 내보내야 하는 경우, @AutoConfigureObservability
로 어노테이션을 추가하자.
Using Tracing
클래스패스에 관계없이 @SpringBootTest
를 사용할 때 트레이싱(tracing)이 자동으로 구성되지 않는다.
통합 테스트의 일부로 트레이싱이 필요한 경우 @AutoConfigureObservability
로 어노테이션을 추가하자.
Mocking and Spying Beans
테스트를 실행할 때, 애플리케이션 컨텍스트 내에서 특정 컴포넌트를 모킹해야 하는 경우가 있다. 예를 들어, 개발 중에 사용할 수 없는 일부 원격 서비스에 대한 퍼사드(facade)가 있을 수 있다. 모킹은 실제 환경에서 트리거하기 어려울 수 있는 오류를 시뮬레이션하려는 경우에도 유용할 수 있다.
스프링 부트에는 애플리케이션컨텍스트(ApplicationContext)
내부 빈에 대한 모키토(Mockito) 목을 정의하는 데 사용할 수 있는 @MockBean
어노테이션이 포함되어 있다. 어노테이션을 사용하여 새 빈을 추가하거나 기존 빈을 바꿀 수 있다. 어노테이션은 테스트 클래스, 테스트 내의 필드 또는 @Configuration
클래스 및 필드에서 직접 사용할 수 있다. 필드에서 사용하면 생성된 목 인스턴스도 주입됩니다. 목빈은 각 테스트 방법 후에 자동으로 리셋된다.
테스트에서 스프링 부트의 테스트 어노테이션(예: @SpringBootTest
) 중 하나를 사용하는 경우 이 기능이 자동으로 활성화된다. 이 기능을 다르게 사용하려면, 다음 예제와 같이 리스너를 명시적으로 추가해야 한다:
자바
import org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener;
import org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
@ContextConfiguration(classes = MyConfig.class)
@TestExecutionListeners({ MockitoTestExecutionListener.class,
ResetMocksTestExecutionListener.class })
class MyTests {
// ...
}
코틀린
import org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener
import org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.TestExecutionListeners
@ContextConfiguration(classes = [MyConfig::class])
@TestExecutionListeners(
MockitoTestExecutionListener::class,
ResetMocksTestExecutionListener::class
)
class MyTests {
// ...
}
다음 예는 기존 리모트서비스(RemoteService)
빈을 목 구현체로 대체한다:
자바
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
@SpringBootTest
class MyTests {
@Autowired
private Reverser reverser;
@MockBean
private RemoteService remoteService;
@Test
void exampleTest() {
given(this.remoteService.getValue()).willReturn("spring");
String reverse = this.reverser.getReverseValue(); // 호출이 주입된 리모트서비스(RemoteService)
assertThat(reverse).isEqualTo("gnirps");
}
}
코틀린
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.mockito.BDDMockito.given
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.mock.mockito.MockBean
@SpringBootTest
class MyTests(@Autowired val reverser: Reverser, @MockBean val remoteService: RemoteService) {
@Test
fun exampleTest() {
given(remoteService.value).willReturn("spring")
val reverse = reverser.reverseValue // 호출이 주입된 리모트서비스(RemoteService)
assertThat(reverse).isEqualTo("gnirps")
}
}
@MockBean
은 애플리케이션 컨텍스트를 새로고침하는 동안 실행되는 빈의 동작을 모킹하는 데 사용할 수 없다. 테스트가 실행될 쯤에는 애플리케이션 컨텍스트 새로고침이 완료되었으며, 목의 동작을 구성하기에는 너무 늦다. 이 상황에서는 목 객체를 생성하고 구성하기 위해 @Bean
메서드를 사용하는 것이 좋다.
또한 @SpyBean
을 사용하여 기존 빈을 모키토(Mockito) 스파이(spy)
로 래핑할 수 있다. 자세한 내용은 자바독(JavaDoc)을 참고하자
범위가 지정된 빈(scoped beans)으로 생성된 것과 같은 CGLib 프록시
는 프록시 메서드를 final
로 선언한다. 이는 기본 구성에서 final
메소드를 모킹하거나 감시(spy)할 수 없기 때문에 모키토(Mockito)가 올바르게 작동하지 못하게 한다. 그러한 빈을 모킹하거나 감시하려면 애플리케이션의 테스트 의존성에 org.mockito:mockito-inline
을 추가하여 인라인 목 메이커(mock maker)를 사용하도록 모키토(Mockito)를 구성하자. 이를 통해 모키토(Mockito)는 final 메소드를 모킹하고 감시할 수 있다.
스프링의 테스트 프레임워크는 테스트 간에 애플리케이션 컨텍스트를 캐시하고 동일한 구성을 공유하는 테스트에 대해 컨텍스트를 재사용하지만 @MockBean
또는 @SpyBean
을 사용하면 캐시 키(cache key)에 영향을 미치므로 컨텍스트 수가 늘어날 가능성이 높다.
이름으로 파라미터를 참조하는 @Cacheable
메소드로 빈을 감시(spy)하기 위해 @SpyBean
을 사용하는 경우 애플리케이션을 -parameters
로 컴파일해야 한다. 이렇게 하면 빈이 감시(spy)된 후 파라미터명을 캐싱 인프라에서 사용할 수 있다.
@SpyBean
을 사용하여 스프링에 의해 프록시된 빈을 감시할 때 특정 상황(예: 주어진 또는 언제(given or when)를 사용하여 기대치를 설정할 때)에서 스프링의 프록시를 제거해야 할 수도 있다. 그렇게 하려면 AopTestUtils.getTargetObject(yourProxiedSpy)
를 사용하자.
Auto-configured Tests
스프링 부트의 자동구성(auto-configuration)은 애플리케이션에 잘 작동하지만 때떄로 테스트엔 너무 과할 수 있다. 애플리케이션의 “슬라이스(slice)”를 테스트하는 데 필요한 구성 부분만 로드하는 것이 도움이 되는 경우가 많다. 예를 들어 스프링 MVC 컨트롤러가 URL을 올바르게 매핑하는지 테스트하고 해당 테스트에 데이터베이스 호출을 포함하고 싶지 않을 수 있다. 또는 JPA 엔터티를 테스트할 때 웹 레이어(layer)엔 관심이 없을 수 있다.
spring-boot-test-autoconfigure
모듈에는 이러한 “슬라이스(slice)”를 자동으로 구성하는 데 사용할 수 있는 여러 어노테이션이 포함되어 있다. 각각은 비슷한 방식으로 작동하며, 애플리케이션컨텍스트(ApplicationContext)
를 로드하는 @...Test
어노테이션과 자동 구성 설정을 커스텀하는 데 사용할 수 있는 하나 이상의 @AutoConfigure...
어노테이션을 제공한다.
각 슬라이스는 컴포넌트 스캔을 적절한 컴포넌트로 제한하고 매우 제한된 자동구성(auto-configuration) 클래스 집합을 로드한다. 그 중 하나를 제외해야 하는 경우 대부분의 @...Test
주석은 excluedAutoConfiguration
애드리뷰트를 제공한다. 또는 @ImportAutoConfiguration#exclude
를 사용할 수 있다.
하나의 테스트에 여러 @...Test
어노테이션을 사용하여 여러 “슬라이스(slices)”를 포함하는 것은 지원되지 않는다. 여러 “슬라이스”가 필요한 경우 @...Test
주석 중 하나를 선택하고 다른 “슬라이스”의 @AutoConfigure...
어노테이션을 직접 포함한다.
표준 @SpringBootTest
어노테이션과 함께 @AutoConfigure...
어노테이션을 사용하는 것도 가능하다. 애플리케이션을 “슬라이싱”하는 데 관심이 없지만 자동 구성된 테스트 빈 중 일부를 원하는 경우에 이 조합을 사용할 수 있다.
Auto-configured JSON Tests
객체 JSON 시리얼라이저 및 디시리얼라이저가 예상대로 작동하는지 테스트하려면 @JsonTest
어노테이션 사용할 수 있다. @JsonTest
는 다음 라이브러리 중 하나일 수 있는 사용 가능한 지원 JSON 매퍼를 자동 구성한다:
- 잭슨 오브젝트매퍼(Jackson ObjectMapper),
@JsonComponent
빈 및 잭슨 모듈 - Gson
- Jsonb
@JsonTest
에 의해 활성화되는 오토컨피규레이션(auto-configurations) 목록은 부록에서 확인할 수 있다.
오토컨피규레이션 엘리먼트를 구성해야 하는 경우 @AutoConfigureJsonTesters
어노테이션을 사용할 수 있다.
스프링 부트에는 제이슨어설트(JSONAssert)
및 제이슨패스(JsonPath)
라이브러리와 함께 작동하여 제이슨(JSON)
이 예상대로 나타나는지 확인하는 어설트제이(AssertJ)
기반 헬퍼(helper)가 포함되어 있다. 잭슨테스터(JacksonTester)
, 지슨테스터(GsonTester)
, 지슨비테스터(JsonbTester)
및 베이직제이슨테스터(BasicJsonTester)
클래스는 각각 잭슨(Jackson), 지슨(Gson), 제이슨비(Jsonb) 및 스트링(Strings)에 사용할 수 있다. @JsonTest
를 사용할 때 테스트 클래스의 모든 헬퍼 필드는 @Autowired
가 될 수 있다. 다음 예는 잭슨(Jackson)에 대한 테스트 클래스를 보여준다:
자바
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.json.JsonTest;
import org.springframework.boot.test.json.JacksonTester;
import static org.assertj.core.api.Assertions.assertThat;
@JsonTest
class MyJsonTests {
@Autowired
private JacksonTester<VehicleDetails> json;
@Test
void serialize() throws Exception {
VehicleDetails details = new VehicleDetails("Honda", "Civic");
// 테스트와 동일한 패키지에 있는 `.json` 파일에 대해 어설션(Assert)
assertThat(this.json.write(details)).isEqualToJson("expected.json");
// 또는 JSON 경로 기반 어설션을 사용하자.
assertThat(this.json.write(details)).hasJsonPathStringValue("@.make");
assertThat(this.json.write(details)).extractingJsonPathStringValue("@.make").isEqualTo("Honda");
}
@Test
void deserialize() throws Exceptio n {
String content = "{\"make\":\"Ford\",\"model\":\"Focus\"}";
assertThat(this.json.parse(content)).isEqualTo(new VehicleDetails("Ford", "Focus"));
assertThat(this.json.parseObject(content).getMake()).isEqualTo("Ford");
}
}
코틀린
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.json.JsonTest
import org.springframework.boot.test.json.JacksonTester
@JsonTest
class MyJsonTests(@Autowired val json: JacksonTester<VehicleDetails>) {
@Test
fun serialize() {
val details = VehicleDetails("Honda", "Civic")
// 테스트와 동일한 패키지에 있는 `.json` 파일에 대해 어설션(Assert)
assertThat(json.write(details)).isEqualToJson("expected.json")
// 또는 JSON 경로 기반 어설션을 사용하자.
assertThat(json.write(details)).hasJsonPathStringValue("@.make")
assertThat(json.write(details)).extractingJsonPathStringValue("@.make").isEqualTo("Honda")
}
@Test
fun deserialize() {
val content = "{\"make\":\"Ford\",\"model\":\"Focus\"}"
assertThat(json.parse(content)).isEqualTo(VehicleDetails("Ford", "Focus"))
assertThat(json.parseObject(content).make).isEqualTo("Ford")
}
}
제이슨(JSON) 헬퍼 클래스는 표준 단위 테스트에서 직접 사용할 수도 있다. 이렇게 하려면 @JsonTest
를 사용하지 않는 경우 @Before
메서드에서 헬퍼의 initFields
메서드를 호출하자.
스프링 부트의 어설션제이(AssertJ)
기반 헬퍼를 사용하여 지정된 제이슨(JSON) 패스의 숫자 값을 어설트(assert)하는 경우 타입에 따라 isEqualTo
를 사용하지 못할 수도 있다. 대신 어설션제이(AssertJ)
의 만족을 사용하여 값이 주어진 조건과 일치하는지 확인할 수 있다. 예를 들어, 다음 예제에서는 실제 숫자가 오프셋 0.01 내에서 0.15에 가까운 부동 소수점 값이라고 어설트(assert)한다.
자바
@Test
void someTest() throws Exception {
SomeObject value = new SomeObject(0.152f);
assertThat(this.json.write(value))
.extractingJsonPathNumberValue("@.test.numberValue")
.satisfies((number) ->
assertThat(number.floatValue()).isCloseTo(0.15f, within(0.01f))
);
}
코틀린
@Test
fun someTest() {
val value = SomeObject(0.152f)
assertThat(json.write(value))
.extractingJsonPathNumberValue("@.test.numberValue")
.satisfies(
ThrowingConsumer { number ->
assertThat(number.toFloat()).isCloseTo(0.15f, within(0.01f))
}
)
}
Auto-configured Spring MVC Tests
스프링 MVC 컨트롤러가 예상대로 작동하는지 테스트하려면, @WebMvcTest
어노테이션을 사용하자. @WebMvcTest
는 스프링 MVC 인프라를 오토 컨피규어(auto-configure)하고 검색된 빈을 @Controller
, @ControllerAdvice
, @JsonComponent
, 컨버터(Converter)
, 제네릭컨버터(GenericConverter)
, 필터(Filter)
, 핸들러인터셉터(HandlerInterceptor)
, 웹Mvc컨피규어러(WebMvcConfigurer)
, 웹Mvc레지스트레이션(WebMvcRegistrations)
및 핸들러메소드아규먼트리졸버(HandlerMethodArgumentResolver)
로 제한한다.
@WebMvcTest
에 의해 활성화된 자동구성(auto-configurations)은 부록에서 확인할 수 있다.
잭슨(Jackson)
모듈과 같은 추가 컴포넌트를 등록해야 하는 경우 테스트에서 @Import
를 사용하여 추가 구성 클래스를 가져올 수 있다.
종종 @WebMvcTest
는 싱글 컨트롤러로 제한되며 @MockBean
과 함께 사용되어 필요한 협력객체에게 목 구현체를 제공한다.
@WebMvcTest
는 또한 목(Mock)Mvc를 오토 컨피규어(auto-configure)한다. 목 MVC는 전체 HTTP 서버를 시작할 필요 없이 MVC 컨트롤러를 빠르게 테스트할 수 있는 강력한 방법을 제공한다.
@AutoConfigureMockMvc
로 어노테이션을 달아 non-@WebMvcTest
(예: @SpringBootTest
)에서 MockMvc
를 오토 컨피규어할 수 있다. 다음 예제에서는 MockMvc
를 사용한다:
자바
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest(UserVehicleController.class)
class MyControllerTests {
@Autowired
private MockMvc mvc;
@MockBean
private UserVehicleService userVehicleService;
@Test
void testExample() throws Exception {
given(this.userVehicleService.getVehicleDetails("sboot"))
.willReturn(new VehicleDetails("Honda", "Civic"));
this.mvc.perform(get("/sboot/vehicle").accept(MediaType.TEXT_PLAIN))
.andExpect(status().isOk())
.andExpect(content().string("Honda Civic"));
}
}
코틀린
import org.junit.jupiter.api.Test
import org.mockito.BDDMockito.given
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers
@WebMvcTest(UserVehicleController::class)
class MyControllerTests(@Autowired val mvc: MockMvc) {
@MockBean
lateinit var userVehicleService: UserVehicleService
@Test
fun testExample() {
given(userVehicleService.getVehicleDetails("sboot"))
.willReturn(VehicleDetails("Honda", "Civic"))
mvc.perform(MockMvcRequestBuilders.get("/sboot/vehicle").accept(MediaType.TEXT_PLAIN))
.andExpect(MockMvcResultMatchers.status().isOk)
.andExpect(MockMvcResultMatchers.content().string("Honda Civic"))
}
}
오토 컨피규어 엘리먼트를 구성해야 하는 경우(예: 서블릿 필터를 적용해야 하는 경우) @AutoConfigureMockMvc
어노테이션의 애트리뷰트를 사용할 수 있다.
HtmlUnit 및 Selenium을 사용하는 경우 오토 컨피규어에서 HtmlUnit 웹클라이언트(WebClient) 빈 및/또는 Selenium 웹드라이버(WebDriver) 빈도 제공한다. 다음 예제에서는 HtmlUnit을 사용한다:
자바
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
@WebMvcTest(UserVehicleController.class)
class MyHtmlUnitTests {
@Autowired
private WebClient webClient;
@MockBean
private UserVehicleService userVehicleService;
@Test
void testExample() throws Exception {
given(this.userVehicleService.getVehicleDetails("sboot")).willReturn(new VehicleDetails("Honda", "Civic"));
HtmlPage page = this.webClient.getPage("/sboot/vehicle.html");
assertThat(page.getBody().getTextContent()).isEqualTo("Honda Civic");
}
}
코틀린
import com.gargoylesoftware.htmlunit.WebClient
import com.gargoylesoftware.htmlunit.html.HtmlPage
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.mockito.BDDMockito.given
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.boot.test.mock.mockito.MockBean
@WebMvcTest(UserVehicleController::class)
class MyHtmlUnitTests(@Autowired val webClient: WebClient) {
@MockBean
lateinit var userVehicleService: UserVehicleService
@Test
fun testExample() {
given(userVehicleService.getVehicleDetails("sboot")).willReturn(VehicleDetails("Honda", "Civic"))
val page = webClient.getPage<HtmlPage>("/sboot/vehicle.html")
assertThat(page.body.textContent).isEqualTo("Honda Civic")
}
}
기본적으로 스프링 부트는 웹드라이버(WebDriver) 빈을 특별한 “범위(“scope”)”에 배치하여 각 테스트 후에 드라이버가 종료되고 새 인스턴스가 주입되도록 한다. 이 동작을 원하지 않으면 웹드라이버 @Bean
에 @Scope("singleton")
을 추가할 수 있다.
스프링 부트에서 생성된
webDriver
스코프는 동일한 이름의 커스텀 스코프를 대체한다. 자신만의webDriver
스코프를 정의한 경우@WebMvcTest
를 사용하면 작동이 중지될 수 있다.
클래스패스에 스프링 시큐리티가 있는 경우 @WebMvcTest
는 웹시큐리티컨피규어러(WebSecurityConfigurer) 빈도 스캔한다. 이러한 테스트에 보안을 완전히 비활성화하는 대신 스프링 시큐리티의 테스트 지원을 사용할 수 있다. 스프링 시큐리티의 목(Mock)Mvc 지원을 사용하는 방법에 대한 자세한 내용은 스프링 시큐리티를 사용한 테스트 방법 절에서 찾을 수 있다.
때로는 스프링 MVC 테스트를 작성하는 것만으로는 충분하지 않다. 스프링 부트는 실제 서버에서 전체 엔드투엔드(end-to-end) 테스트를 실행하는 데 도움이 될 수 있다.
Auto-configured Spring WebFlux Tests
스프링 웹플럭스 컨트롤러가 예상대로 작동하는지 테스트하려면 @WebFluxTest
어노테이션을 사용할 수 있다. @WebFluxTest
는 스프링웹플럭스 인프라를 자동 구성하고 검색된 빈을 @Controller
, @ControllerAdvice
, @JsonComponent
, 컨버터(Converter)
, 제네릭컨버터(GenericConverter)
, 웹필터(WebFilter)
및 웹플럭스컨피규어러(WebFluxConfigurer)
로 제한한다. @WebFluxTest
어노테이션이 사용될 때 일반 @Component
및 @ConfigurationProperties
빈은 검사되지 않는다. @EnableConfigurationProperties
를 사용하여 @ConfigurationProperties
빈을 포함할 수 있다.
@WebFluxTest
에 의해 활성화된 자동구성(auto-configurations)은 부록에서 확인할 수 있다.
잭슨(Jackson)
모듈과 같은 추가 구성 컴포넌트를 등록해야 하는 경우, 테스트에서 @Import
를 사용하여 추가 구성 클래스를 가져올 수 있다.
종종 @WebFluxTest
는 싱글 컨트롤러로 제한되며 @MockBean
어노테이션과 함께 사용되어 필요한 협력객체에게 목 구현체를 제공한다.
@WebFluxTest
는 또한 전체 HTTP 서버를 시작할 필요 없이 웹플럭스(WebFlux) 컨트롤러를 빠르게 테스트할 수 있는 강력한 방법을 제공하는 웹테스트클라이언트(WebTestClient)
를 오토 컨피규어 한다.
@AutoConfigureWebTestClient
로 어노테이션을 달아 non-@WebFluxTest
(예: @SpringBootTest
)에서 웹테스트클라이언트(WebTestClient)
를 오토 컨피규어 할 수 있다. 다음 예제에서는 @WebFluxTest
와 웹테스트클라이언트(WebTestClient)
를 모두 사용하는 클래스를 보여준다:
자바
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.mockito.BDDMockito.given;
@WebFluxTest(UserVehicleController.class)
class MyControllerTests {
@Autowired
private WebTestClient webClient;
@MockBean
private UserVehicleService userVehicleService;
@Test
void testExample() {
given(this.userVehicleService.getVehicleDetails("sboot"))
.willReturn(new VehicleDetails("Honda", "Civic"));
this.webClient.get().uri("/sboot/vehicle").accept(MediaType.TEXT_PLAIN).exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Honda Civic");
}
}
코틀린
import org.junit.jupiter.api.Test
import org.mockito.BDDMockito.given
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.http.MediaType
import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.test.web.reactive.server.expectBody
@WebFluxTest(UserVehicleController::class)
class MyControllerTests(@Autowired val webClient: WebTestClient) {
@MockBean
lateinit var userVehicleService: UserVehicleService
@Test
fun testExample() {
given(userVehicleService.getVehicleDetails("sboot"))
.willReturn(VehicleDetails("Honda", "Civic"))
webClient.get().uri("/sboot/vehicle").accept(MediaType.TEXT_PLAIN).exchange()
.expectStatus().isOk
.expectBody<String>().isEqualTo("Honda Civic")
}
}
목 웹 애플리케이션에서 웹테스트클라이언트(WebTestClient)
를 사용하는 것은 현재 웹플럭스(WebFlux)
에서만 작동하므로 이 설정은 웹플럭스(WebFlux)
애플리케이션에서만 지원된다.
@WebFluxTest
는 기능적 웹 프레임워크를 통해 등록된 경로를 감지할 수 없다. 컨텍스트에서 라우터펑션(RouterFunction)
빈을 테스트하려면 @Import
를 사용하거나 @SpringBootTest
를 사용하여 라우터펑션(RouterFunction)
을 직접 가져오는 것이 좋다.
@WebFluxTest
는 시큐리티웹필터체인(SecurityWebFilterChain)
타입의 @Bean
으로 등록된 커스텀 보안 구성을 감지할 수 없다. 이를 테스트에 포함하려면 @Import
또는 @SpringBootTest
를 사용하여 빈을 등록하는 구성을 가져와야 한다.
때때로 스프링 웹플럭스 테스트를 작성하는 것만으로는 충분하지 않다; 스프링 부트는 실제 서버에서 전체 엔드투엔드(end-to-end) 테스트를 실행하는 데 도움이 될 수 있다.
Auto-configured Spring GraphQL Tests
스프링 그래프QL(GraphQL)은 전용 테스트 지원 모듈을 제공한다; 프로젝트에 별도 추가가 필요하다: Spring GraphQL offers a dedicated testing support module; you’ll need to add it to your project:
메이븐
<dependencies>
<dependency>
<groupId>org.springframework.graphql</groupId>
<artifactId>spring-graphql-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 컴파일 스코프(compile scope)에 존재하지 않으면-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
그레이들
dependencies {
testImplementation("org.springframework.graphql:spring-graphql-test")
// 구성에 존재하지 않으면
testImplementation("org.springframework.boot:spring-boot-starter-webflux")
}
이 테스트 모듈은 그래프Ql테스터(GraphQlTester)
를 제공한다. 테스터는 테스트에 많이 사용되므로 사용법에 익숙해져야한다. 그래프Ql테스터는 여러 변형이 있으며 스프링 부트는 테스트 타입에 따라 이를 자동으로 구성한다:
익스큐션그래프Ql서비스테스터(ExecutionGraphQlServiceTester)
는 클라이언트나 전송 없이 서버 측에서 테스트를 수행한다.- Http그래프Ql테스터(HttpGraphQlTester)는 라이브 서버 유무에 관계없이 서버에 연결하는 클라이언트로 테스트를 수행한다.
스프링 부트는 @GraphQlTest
어노테이션을 사용하여 스프링 그래프QL(GraphQL)
컨트롤러를 테스트하는 데 도움이 된다. @GraphQlTest
는 전송이나 서버를 개입시키지 않고 스프링 그래프QL 인프라를 자동 구성한다. 이는 스캔된 빈을 @Controller
, 런타밍와이어링컨피규어(RuntimeWiringConfigurer)
, 제이슨컴포넌트(JsonComponent)
, 컨버터(Converter)
, 제네릭컨버터(GenericConverter)
, 데이터패쳐익셉션리졸버(DataFetcherExceptionResolver)
, 인스트루먼테이션(Instrumentation)
및 그래프Ql소스빌더커스터마이저(GraphQlSourceBuilderCustomizer)
로 제한한다. @GraphQlTest
어노테이션이 사용될 때, 일반 @Component
및 @ConfigurationProperties
빈은 검색되지 않는다. @EnableConfigurationProperties
를 사용하여 @ConfigurationProperties
빈을 포함할 수 있다.
@GraphQlTest
에 의해 활성화된 자동구성(auto-configurations) 목록은 부록에서 확인할 수 있다.
종종, @GraphQlTest
는 컨트롤러로 제한되고 @MockBean
어노테이션과 함께 사용되어 필요한 협력갹채에게 목 구현체를 제공한다.
자바
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.docs.web.graphql.runtimewiring.GreetingController;
import org.springframework.boot.test.autoconfigure.graphql.GraphQlTest;
import org.springframework.graphql.test.tester.GraphQlTester;
@GraphQlTest(GreetingController.class)
class GreetingControllerTests {
@Autowired
private GraphQlTester graphQlTester;
@Test
void shouldGreetWithSpecificName() {
this.graphQlTester.document("{ greeting(name: \"Alice\") } ")
.execute()
.path("greeting")
.entity(String.class)
.isEqualTo("Hello, Alice!");
}
@Test
void shouldGreetWithDefaultName() {
this.graphQlTester.document("{ greeting } ")
.execute()
.path("greeting")
.entity(String.class)
.isEqualTo("Hello, Spring!");
}
}
코틀린
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.docs.web.graphql.runtimewiring.GreetingController
import org.springframework.boot.test.autoconfigure.graphql.GraphQlTest
import org.springframework.graphql.test.tester.GraphQlTester
@GraphQlTest(GreetingController::class)
internal class GreetingControllerTests {
@Autowired
lateinit var graphQlTester: GraphQlTester
@Test
fun shouldGreetWithSpecificName() {
graphQlTester.document("{ greeting(name: \"Alice\") }")
.execute().path("greeting").entity(String::class.java)
.isEqualTo("Hello, Alice!")
}
@Test
fun shouldGreetWithDefaultName() {
graphQlTester.document("{ greeting }")
.execute().path("greeting").entity(String::class.java)
.isEqualTo("Hello, Spring!")
}
}
@SpringBootTest
테스트는 전체 통합 테스트이며 전체 애플리케이션을 포함한다. 랜덤 또는 정의된 포트를 사용하는 경우 라이브 서버가 구성되고 Http그래프Ql테스터(HttpGraphQlTester)
빈이 자동으로 제공되므로 이를 사용하여 서버를 테스트할 수 있다. 목(MOCK) 환경이 구성되면 @AutoConfigureHttpGraphQlTester
로 테스트 클래스에 어노테이션을 달아 Http그래프Ql테스터(HttpGraphQlTester)
빈을 요청할 수도 있다:
자바
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureHttpGraphQlTester;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.graphql.test.tester.HttpGraphQlTester;
@AutoConfigureHttpGraphQlTester
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
class GraphQlIntegrationTests {
@Test
void shouldGreetWithSpecificName(@Autowired HttpGraphQlTester graphQlTester) {
HttpGraphQlTester authenticatedTester = graphQlTester.mutate()
.webTestClient((client) ->
client.defaultHeaders((headers) ->
headers.setBasicAuth("admin", "ilovespring")
)
).build();
authenticatedTester.document("{ greeting(name: \"Alice\") } ")
.execute()
.path("greeting")
.entity(String.class)
.isEqualTo("Hello, Alice!");
}
}
코틀린
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureHttpGraphQlTester
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.graphql.test.tester.HttpGraphQlTester
import org.springframework.http.HttpHeaders
import org.springframework.test.web.reactive.server.WebTestClient
@AutoConfigureHttpGraphQlTester
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
class GraphQlIntegrationTests {
@Test
fun shouldGreetWithSpecificName(@Autowired graphQlTester: HttpGraphQlTester) {
val authenticatedTester = graphQlTester.mutate()
.webTestClient { client: WebTestClient.Builder ->
client.defaultHeaders { headers: HttpHeaders ->
headers.setBasicAuth("admin", "ilovespring")
}
}.build()
authenticatedTester.document("{ greeting(name: \"Alice\") } ").execute()
.path("greeting").entity(String::class.java).isEqualTo("Hello, Alice!")
}
}
Auto-configured Data Cassandra Tests
@DataCassandraTest
를 사용하여 카산드라(Cassandra)
애플리케이션을 테스트할 수 있다. 기본적으로, 카산드라템플릿(CassandraTemplate)
을 구성하고, @Table
클래스를 검색하고, 스프링 데이터 카산드라 리포지터리를 구성한다. @DataCassandraTest
어노테이션이 사용될 때 일반 @Component
및 @ConfigurationProperties
빈은 검사되지 않는다. @EnableConfigurationProperties
를 사용하여 @ConfigurationProperties
빈을 포함할 수 있다. (스프링 부트와 함께 카산드라를 사용하는 방법에 대한 자세한 내용은 “Cassandra”를 참고하자.)
@DataCassandraTest
에 의해 활성화된 자동구성(auto-configurations) 목록은 부록에서 확인할 수 있다.
다음 예는 스프링 부트에서 카산드라 테스트를 사용하기 위한 일반적인 설정을 보여준다: 자바
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.cassandra.DataCassandraTest;
@DataCassandraTest
class MyDataCassandraTests {
@Autowired
private SomeRepository repository;
}
코틀린
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.data.cassandra.DataCassandraTest
@DataCassandraTest
class MyDataCassandraTests(@Autowired val repository: SomeRepository)
Auto-configured Data Couchbase Tests
@DataCouchbaseTest
를 사용하여 카우치베이스(Couchbase)
애플리케이션을 테스트할 수 있다. 기본적으로 카우치베이스템플릿(CouchbaseTemplate)
또는 리액티브카우치베이스템플릿(ReactiveCouchbaseTemplate)
을 구성하고, @Document
클래스를 검색하고, 스프링 데이터 카우치베이스 리포지터리를 구성한다. @DataCouchbaseTest
어노테이션이 사용될 때 일반 @Component
및 @ConfigurationProperties
빈은 검색되지 않는다. @EnableConfigurationProperties
를 사용하여 @ConfigurationProperties
빈을 포함할 수 있다. (스프링 부트와 함께 카우치베이스(Couchbase)
를 사용하는 방법에 대한 자세한 내용은 이 레퍼런스 뒷부분의 “Couchbase”를 참고하자.)
@DataCouchbaseTest
에 의해 활성화된 자동구성(auto-configurations) 목록은 부록에서 확인할 수 있다.
다음 예제는 스프링 부트에서 카우치베이스(Couchbase)
테스트를 사용하기 위한 일반적인 설정을 보여준다:
자바
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.couchbase.DataCouchbaseTest;
@DataCouchbaseTest
class MyDataCouchbaseTests {
@Autowired
private SomeRepository repository;
// ...
}
코틀린
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.data.couchbase.DataCouchbaseTest
@DataCouchbaseTest
class MyDataCouchbaseTests(@Autowired val repository: SomeRepository) {
// ...
}
Auto-configured Data Elasticsearch Tests
@DataElasticsearchTest
를 사용하여 엘라스틱서치(Elasticsearch)
애플리케이션을 테스트할 수 있다. 기본적으로 엘라스틱서치레스트템플릿(ElasticsearchRestTemplate)
을 구성하고, @Document
클래스를 검색하고, 스프링 데이터 엘라스틱서치(Elasticsearch) 리포지터리를 구성한다. @DataElasticsearchTest
어노테이션이 사용될 때 일반 @Component
및 @ConfigurationProperties
빈은 검색되지 않는다. @EnableConfigurationProperties
를 사용하여 @ConfigurationProperties
빈을 포함할 수 있습니다. (스프링 부트와 함께 엘레스틱서치(Elasticsearch)를 사용하는 방법에 대한 자세한 내용은 이 레퍼런스 뒷부분의 “Elasticsearch”를 참고하자.)
@DataElasticsearchTest
에 의해 활성화되는 자동 구성 설정 목록은 부록에서 확인할 수 있다.
다음 예는 스프링 부트에서 엘라스틱서치(Elasticsearch)
테스트를 사용하기 위한 일반적인 설정을 보여준다:
자바
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.elasticsearch.DataElasticsearchTest;
@DataElasticsearchTest
class MyDataElasticsearchTests {
@Autowired
private SomeRepository repository;
// ...
}
코틀린
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.data.elasticsearch.DataElasticsearchTest
@DataElasticsearchTest
class MyDataElasticsearchTests(@Autowired val repository: SomeRepository) {
// ...
}
Auto-configured Data JPA Tests
@DataJpaTest
어노테이션을 사용하여 JPA 애플리케이션을 테스트할 수 있다. 기본적으로 @Entity
클래스를 검색하고 스프링 데이터 JPA 리포지터리를 구성한다. 클래스패스에 내장된 데이터베이스가 있으면 이를 구성한다. spring.jpa.show-sql
애트리뷰트를 true
로 설정하면 SQL 쿼리가 기본적으로 기록된다. 이는 어노테이션의 showSql
애트리뷰트를 사용하여 비활성화할 수 있다.
@DataJpaTest
에 의해 활성화된 자동구성(auto-configurations) 목록은 부록에서 확인할 수 있다.
기본적으로 데이터 JPA 테스트는 트랜잭션이며 각 테스트가 끝나면 롤백된다. 자세한 내용은 스프링 프레임워크 레퍼런스 문서의 관련 장을 참고하자. 아니면, 다음과 같이 테스트 또는 전체 클래스에 대해 트랜잭션 관리를 비활성화할 수 있다:
자바
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@DataJpaTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyNonTransactionalTests {
// ...
}
코틀린
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional
@DataJpaTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyNonTransactionalTests {
// ...
}
데이터 JPA 테스트는 테스트용으로 특별히 설계된 표준 JPA 엔터티매니저(EntityManager)
에 대한 테스트엔터티매니저(TestEntityManager)
빈을 주입할 수도 있다.
테스트엔터티매니저(TestEntityManager)
는 @AutoConfigureTestEntityManager
를 추가하여 스프링 기반 테스트 클래스에 자동으로 구성될 수도 있다. 그렇게 할 때 테스트 클래스나 메서드에 @Transactional
을 추가하는 등 테스트가 트랜잭션에서 실행되고 있는지 확인하자.
필요한 경우 Jdbc템플릿(JdbcTemplate)
도 사용할 수 있다. 다음 예에서는 사용 중인 @DataJpaTest
어노테이션을 보여준다.
자바
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import static org.assertj.core.api.Assertions.assertThat;
@DataJpaTest
class MyRepositoryTests {
@Autowired
private TestEntityManager entityManager;
@Autowired
private UserRepository repository;
@Test
void testExample() {
this.entityManager.persist(new User("sboot", "1234"));
User user = this.repository.findByUsername("sboot");
assertThat(user.getUsername()).isEqualTo("sboot");
assertThat(user.getEmployeeNumber()).isEqualTo("1234");
}
}
코틀린
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager
@DataJpaTest
class MyRepositoryTests(@Autowired val entityManager: TestEntityManager, @Autowired val repository: UserRepository) {
@Test
fun testExample() {
entityManager.persist(User("sboot", "1234"))
val user = repository.findByUsername("sboot")
assertThat(user?.username).isEqualTo("sboot")
assertThat(user?.employeeNumber).isEqualTo("1234")
}
}
인메모리 임베디드 데이터베이스는 속도가 빠르고 설치가 필요하지 않기 때문에 일반적으로 테스트에 적합하다. 그러나 실제 데이터베이스에 대해 테스트를 실행하려는 경우, 다음 예와 같이 @AutoConfigureTestDatabase
어노테이션을 사용할 수 있다:
자바
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
@DataJpaTest
@AutoConfigureTestDatabase(replace = Replace.NONE)
class MyRepositoryTests {
// ...
}
코틀린
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class MyRepositoryTests {
// ...
}
Auto-configured JDBC Tests
@JdbcTest
는 @DataJpaTest
와 유사하지만 테이터소스(DataSource)
만 필요하고 스프링 데이터 JDBC를 사용하지 않는 테스트이다. 기본적으로 인메모리 임베디드 데이터베이스와 Jdbc템플릿(JdbcTemplate)
을 구성한다. @JdbcTest
어노테이션이 사용될 때 일반 @Component
및 @ConfigurationProperties
빈은 검사되지 않는다. @EnableConfigurationProperties
를 사용하여 @ConfigurationProperties
빈을 포함할 수 있다.
@JdbcTest
에 의해 활성화된 자동구성(auto-configurations) 목록은 부록에서 확인할 수 있다.
기본적으로, JDBC 테스트는 트랜잭션이며 각 테스트가 끝나면 롤백된다. 자세한 내용은 스프링 프레임워크 레퍼런스 문서의 관련 장을 참고하자. 아니면, 다음과 같이 테스트 또는 전체 클래스에 대한 트랜잭션 매니저를 비활성화할 수 있다:
자바
import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@JdbcTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyTransactionalTests { }
코틀린
import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest
import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional
@JdbcTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyTransactionalTests
실제 데이터베이스에 대해 테스트를 실행하려는 경우, 데이터Jpa테스트(DataJpaTest)
와 동일한 방식으로 @AutoConfigureTestDatabase
어노테이션을 사용할 수 있다. (“자동 구성된 데이터 JPA 테스트(Auto-configured Data JPA Tests)” 참고)
Auto-configured Data JDBC Tests
@DataJdbcTest
는 @JdbcTest
와 유사하지만 스프링 데이터 JDBC 리포지터리를 사용하는 테스트용 어노테이션이다. 기본적으로 인메모리 임베디드 데이터베이스, Jdbc템플릿(JdbcTemplate)
및 스프링 데이터 JDBC 리포지터리를 구성한다. @DataJdbcTest
어노테이션이 사용될 때, 앱스트랙트Jdbc컨피규레이션(AbstractJdbcConfiguration)
서브클래스만 스캔되고 일반 @Component
및 @ConfigurationProperties
빈은 스캔되지 않는다. @EnableConfigurationProperties
를 사용하여 @ConfigurationProperties
빈을 포함할 수 있다.
@DataJdbcTest
에 의해 활성화된 자동구성 목록은 부록에서 확인할 수 있다.
기본적으로 데이터 JDBC 테스트는 트랜잭션 방식이며 각 테스트가 끝나면 롤백된다. 자세한 내용은 스프링 프레임워크 레퍼런스 문서의 관련 장을 참고하자. 아니라면, JDBC 예제에 나타난대로 테스트 또는 전체 테스트 클래스에 대해 트랜잭션 관리를 비활성화할 수 있다.
실제 데이터베이스에 대해 테스트를 실행하려는 경우 데이터Jpa테스트(DataJpaTest)
와 동일한 방식으로 @AutoConfigureTestDatabase
어노테이션을 사용할 수 있다. (“자동 구성된 데이터 JPA 테스트(Auto-configured Data JPA Tests)” 참고)
Auto-configured jOOQ Tests
@JooqTest
를 @JdbcTest
와 비슷한 방식으로 사용할 수 있지만, jOOQ
관련 테스트에만 사용할 수 있다. jOOQ
는 데이터베이스 스키마에 해당하는 자바 기반 스키마에 크게 의존하므로 기존 데이터소스(DataSource)
를 사용한다. 이를 인메모리 데이터베이스로 바꾸려면 @AutoConfigureTestDatabase
를 사용하여 해당 설정을 오버라이드할 수 있다. (스프링 부트와 함께 jOOQ
를 사용하는 방법에 대한 자세한 내용은 “Using jOOQ”을 참고하자.) @JooqTest
어노테이션이 사용될 때 일반 @Component
및 @ConfigurationProperties
빈은 검색되지 않는다. @EnableConfigurationProperties
를 사용하여 @ConfigurationProperties
빈을 포함할 수 있다.
@JooqTest
가 활성화하는 자동 구성 목록은 부록에서 확인할 수 있다.
@JooqTest
는 DSL컨텍스트(DSLContext)
를 구성한다. 다음 예에서는 사용 중인 @JooqTest
어노테이션을 보여준다:
자바
import org.jooq.DSLContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jooq.JooqTest;
@JooqTest
class MyJooqTests {
@Autowired
private DSLContext dslContext;
// ...
}
코틀린
import org.jooq.DSLContext
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.jooq.JooqTest
@JooqTest
class MyJooqTests(@Autowired val dslContext: DSLContext) {
// ...
}
JOOQ 테스트는 트랜잭션 방식이며 기본적으로 각 테스트가 끝나면 롤백된다. 아니면, JDBC 예제에 표시된 대로 테스트 또는 전체 테스트 클래스에 대해 트랜잭션 관리를 비활성화할 수 있다.
Auto-configured Data MongoDB Tests
@DataMongoTest
를 사용하여 몽곤DB 애플리케이션을 테스트할 수 있다. 기본적으로 몽고템플릿(MongoTemplate)
을 구성하고, @Document
클래스를 검색하고, 스프링 데이터 몽고DB 리포지터리를 구성한다. @DataMongoTest
어노테이션이 사용될 때 일반 @Component
및 @ConfigurationProperties
빈은 검색되지 않는다.
@EnableConfigurationProperties
를 사용하여 @ConfigurationProperties
빈을 포함할 수 있다. (스프링 부트에서 몽고DB를 사용하는 방법에 대한 자세한 내용은 “MongoDB”를 참고하자.)
@DataMongoTest
에 의해 활성화되는 자동 구성 설정 목록은 부록에서 확인할 수 있다.
다음 클래스는 사용 중인 @DataMongoTest
어노테이션을 보여준다:
자바
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
import org.springframework.data.mongodb.core.MongoTemplate;
@DataMongoTest
class MyDataMongoDbTests {
@Autowired
private MongoTemplate mongoTemplate;
// ...
}
코틀린
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest
import org.springframework.data.mongodb.core.MongoTemplate
@DataMongoTest
class MyDataMongoDbTests(@Autowired val mongoTemplate: MongoTemplate) {
// ...
}
Auto-configured Data Neo4j Tests
@DataNeo4jTest
를 사용하여 네오4j(Neo4j) 애플리케이션을 테스트할 수 있다. 기본적으로 @Node
클래스를 검색하고 스프링 데이터 네오4j(Neo4j)
리포지터리를 구성한다. @DataNeo4jTest
어노테이션이 사용될 때 일반 @Component
및 @ConfigurationProperties
빈은 스캔되지 않는다. @EnableConfigurationProperties
를 사용하여 @ConfigurationProperties
빈을 포함할 수 있다. (스프링 부트와 함께 네오4J(Neo4J)
를 사용하는 방법에 대한 자세한 내용은 “Neo4j”를 참고하자.)
@DataNeo4jTest
에 의해 활성화된 자동 구성 설정 목록은 부록에서 확인할 수 있다.
다음 예제는 스프링 부트에서 네오4j(Neo4J) 테스트를 사용하기 위한 일반적인 설정을 보여준다:
자바
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest;
@DataNeo4jTest
class MyDataNeo4jTests {
@Autowired
private SomeRepository repository;
// ...
}
코틀린
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest
@DataNeo4jTest
class MyDataNeo4jTests(@Autowired val repository: SomeRepository) {
// ...
}
기본적으로, 데이터 네오4j(Neo4j) 테스트는 트랜잭션이며, 각 테스트가 끝나면 롤백된다. 자세한 내용은 스프링 프레임워크 레퍼런스 문서의 관련 장을 참고하자. 아니면, 다음과 같이 테스트 또는 전체 클래스에 대해 트랜잭션 관리를 비활성화할 수 있다:
자바
import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@DataNeo4jTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyDataNeo4jTests { }
코틀린
import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest
import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional
@DataNeo4jTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyDataNeo4jTests
리액티브 접근에서 트랜잭션 테스트가 지원되지 않는다. 이 스타일을 사용하는 경우 위에서 설명한 대로 @DataNeo4jTest
테스트를 구성해야 한다.
Auto-configured Data Redis Tests
@DataRedisTest
를 사용하여 레디스(Redis)
애플리케이션을 테스트할 수 있다. 기본적으로 @RedisHash
클래스를 스캔하고 스프링 데이터 레디스(Redis) 리포지터리를 구성한다. @DataRedisTest
어노테이션이 사용될 때 일반 @Component
및 @ConfigurationProperties
빈은 검색되지 않는다. @EnableConfigurationProperties
를 사용하여 @ConfigurationProperties
빈을 포함할 수 있다. (스프링 부트와 함께 레디스(Redis)를 사용하는 방법에 대한 자세한 내용은 “Redis”를 참고하자.)
@DataRedisTest
에 의해 활성화된 자동 구성 설정 목록은 부록에서 확인할 수 있다.
다음 예에서는 사용 중인 @DataRedisTest
어노테이션을 보여준다:
자바
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.redis.DataRedisTest;
@DataRedisTest
class MyDataRedisTests {
@Autowired
private SomeRepository repository;
// ...
}
코틀린
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.data.redis.DataRedisTest
@DataRedisTest
class MyDataRedisTests(@Autowired val repository: SomeRepository) {
// ...
}
Auto-configured Data LDAP Tests
@DataLdapTest
를 사용하여 LDAP 애플리케이션을 테스트할 수 있다. 기본적으로 인메모리 임베디드 LDAP를 구성하고, Ldap템플릿(LdapTemplate)
을 구성하고, @Entry
클래스를 스캔하고, 스프링 데이터 LDAP 리포지터리를 구성한다. @DataLdapTest
어노테이션이 사용될 때 일반 @Component
및 @ConfigurationProperties
빈은 스캔되지 않는다. @EnableConfigurationProperties
를 사용하여 @ConfigurationProperties
빈을 포함할 수 있다. (스프링 부트에서 LDAP를 사용하는 방법에 대한 자세한 내용은 “LDAP”를 참고하자.)
@DataLdapTest
에 의해 활성화되는 자동 구성 설정 목록은 부록에서 확인할 수 있다.
다음 예에서는 사용 중인 @DataLdapTest
어노테이션을 보여준다:
자바
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.ldap.DataLdapTest;
import org.springframework.ldap.core.LdapTemplate;
@DataLdapTest
class MyDataLdapTests {
@Autowired
private LdapTemplate ldapTemplate;
// ...
}
코틀린
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.data.ldap.DataLdapTest
import org.springframework.ldap.core.LdapTemplate
@DataLdapTest
class MyDataLdapTests(@Autowired val ldapTemplate: LdapTemplate) {
// ...
}
인메모리 임베디드 LDAP는 일반적으로 빠르고 개발자 설치가 필요하지 않으므로 테스트에 적합하다. 그러나 실제 LDAP 서버에 대해 테스트를 실행하려면 다음 예제와 같이 내장된 LDAP 오토컨피규레이션(auto-configuration)을 제외해야 한다:
자바
import org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration;
import org.springframework.boot.test.autoconfigure.data.ldap.DataLdapTest;
@DataLdapTest(excludeAutoConfiguration = EmbeddedLdapAutoConfiguration.class)
class MyDataLdapTests {
// ...
}
코틀린
import org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration
import org.springframework.boot.test.autoconfigure.data.ldap.DataLdapTest
@DataLdapTest(excludeAutoConfiguration = [EmbeddedLdapAutoConfiguration::class])
class MyDataLdapTests {
// ...
}
Auto-configured REST Clients
@RestClientTest
어노테이션을 사용하여 REST 클라이언트를 테스트할 수 있다. 기본적으로 잭슨(Jackson), 지슨(GSON) 및 제이선비(Jsonb)를 자동 구성하고 레스트템플릿빌더(RestTemplateBuilder)
를 구성하며 목레스트서비스서버(MockRestServiceServer)
에 대한 지원을 추가한다. @RestClientTest
어노테이션이 사용될 때 일반 @Component
및 @ConfigurationProperties
빈은 스캔되지 않는다. @EnableConfigurationProperties
를 사용하여 @ConfigurationProperties
빈을 포함할 수 있다.
@RestClientTest
에 의해 활성화되는 자동 구성 설정 목록은 부록에서 확인할 수 있다.
테스트하려는 특정 빈은 다음 예제와 같이 @RestClientTest
의 값 또는 (components)컴포넌트 애트리뷰트을 사용하여 지정해야 한다;
자바
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.client.RestClientTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.client.MockRestServiceServer;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
@RestClientTest(RemoteVehicleDetailsService.class)
class MyRestClientTests {
@Autowired
private RemoteVehicleDetailsService service;
@Autowired
private MockRestServiceServer server;
@Test
void getVehicleDetailsWhenResultIsSuccessShouldReturnDetails() {
this.server.expect(requestTo("/greet/details")).andRespond(withSuccess("hello", MediaType.TEXT_PLAIN));
String greeting = this.service.callRestService();
assertThat(greeting).isEqualTo("hello");
}
}
코틀린
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.client.RestClientTest
import org.springframework.http.MediaType
import org.springframework.test.web.client.MockRestServiceServer
import org.springframework.test.web.client.match.MockRestRequestMatchers
import org.springframework.test.web.client.response.MockRestResponseCreators
@RestClientTest(RemoteVehicleDetailsService::class)
class MyRestClientTests(
@Autowired val service: RemoteVehicleDetailsService,
@Autowired val server: MockRestServiceServer) {
@Test
fun getVehicleDetailsWhenResultIsSuccessShouldReturnDetails(): Unit {
server.expect(MockRestRequestMatchers.requestTo("/greet/details"))
.andRespond(MockRestResponseCreators.withSuccess("hello",MediaType.TEXT_PLAIN))
val greeting = service.callRestService()
assertThat(greeting).isEqualTo("hello")
}
}
Auto-configured Spring REST Docs Tests
@AutoConfigureRestDocs
어노테이션을 사용하면 목(Mock) MVC
, 레스트 어슈어드(REST Assured)
또는 웹테스트클라이언트(WebTestClient)
를 사용한 테스트에서 스프링 레스트 독스(REST Docs)를 사용할 수 있다. 스프링 레스트 독스에서 JUnit
익스텐션은 필요하지 않다.
@AutoConfigureRestDocs
를 사용하면 기본 출력 디렉토리(메이븐을 사용하는 경우 target/generated-snippet
, 그레이들을 사용하는 경우 build/generated-snippets
)를 오버라이드할 수 있다. 문서화된 URI에 나타나는 호스트, 스키마(scheme) 및 포트(port)를 구성하는데 사용할 수 있다.
Auto-configured Spring REST Docs Tests With Mock MVC
@AutoConfigureRestDocs
는 서블릿 기반 웹 애플리케이션을 테스트할 때 스프링 레스트 독스를 사용하도록 목Mvc(MockMvc) 빈을 커스텀한다. 다음 예제와 같이 @Autowired
를 사용하여 이를 주입하고 목 MVC 및 스프링 레스트 독스를 사용할 때 일반적으로 사용하는 것처럼 테스트에서 사용할 수 있다:
자바
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest(UserController.class)
@AutoConfigureRestDocs
class MyUserDocumentationTests {
@Autowired
private MockMvc mvc;
@Test
void listUsers() throws Exception {
this.mvc.perform(get("/users").accept(MediaType.TEXT_PLAIN))
.andExpect(status().isOk())
.andDo(document("list-users"));
}
}
코틀린
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.http.MediaType
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers
@WebMvcTest(UserController::class)
@AutoConfigureRestDocs
class MyUserDocumentationTests(@Autowired val mvc: MockMvc) {
@Test
fun listUsers() {
mvc.perform(MockMvcRequestBuilders.get("/users").accept(MediaType.TEXT_PLAIN))
.andExpect(MockMvcResultMatchers.status().isOk)
.andDo(MockMvcRestDocumentation.document("list-users"))
}
}
@AutoConfigureRestDocs
애트리뷰트가 제공하는 것보다 스프링 레스트 독 구성에 대한 더 많은 제어가 필요한 경우, 다음 예제와 같이 레스트독스목Mvc컨피규레이션커스터마이저(RestDocsMockMvcConfigurationCustomizer)
빈을 사용할 수 있다:
자바
import org.springframework.boot.test.autoconfigure.restdocs.RestDocsMockMvcConfigurationCustomizer;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentationConfigurer;
import org.springframework.restdocs.templates.TemplateFormats;
@TestConfiguration(proxyBeanMethods = false)
public class MyRestDocsConfiguration implements RestDocsMockMvcConfigurationCustomizer {
@Override
public void customize(MockMvcRestDocumentationConfigurer configurer) {
configurer.snippets().withTemplateFormat(TemplateFormats.markdown());
}
}
코틀린
import org.springframework.boot.test.autoconfigure.restdocs.RestDocsMockMvcConfigurationCustomizer
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentationConfigurer
import org.springframework.restdocs.templates.TemplateFormats
@TestConfiguration(proxyBeanMethods = false)
class MyRestDocsConfiguration : RestDocsMockMvcConfigurationCustomizer {
override fun customize(configurer: MockMvcRestDocumentationConfigurer) {
configurer.snippets().withTemplateFormat(TemplateFormats.markdown())
}
}
파라미터화된 출력 디렉토리에 대한 스프링 레스트(REST) 독스 지원를 활용하려는 경우 레스트도큐멘테이션리절트핸들러(RestDocumentationResultHandler)
빈을 생성할 수 있다. 자동 구성은 이 결과 핸들러를 사용하여 AlwaysDo
를 호출하므로 목Mvc(MockMvc)
호출이 자동으로 기본 조각을 생성하게 된다. 다음 예제에서 레스트도큐멘테이션리절트핸들러(RestDocumentationResultHandler)
를 보여준다:
자바
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;
import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler;
@TestConfiguration(proxyBeanMethods = false)
public class MyResultHandlerConfiguration {
@Bean
public RestDocumentationResultHandler restDocumentation() {
return MockMvcRestDocumentation.document("{method-name}");
}
}
코틀린
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation
import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler
@TestConfiguration(proxyBeanMethods = false)
class MyResultHandlerConfiguration {
@Bean
fun restDocumentation(): RestDocumentationResultHandler {
return MockMvcRestDocumentation.document("{method-name}")
}
}
Auto-configured Spring REST Docs Tests With WebTestClient
리액티브 웹 애플리케이션을 테스트할 때 @AutoConfigureRestDocs
를 웹테스트클라이언트(WebTestClient)
와 함께 사용할 수도 있다. 다음 예제와 같이 @Autowired
를 사용하여 이를 주입하고 @WebFluxTest
및 스프링 레스트 독스를 사용할 때 일반적으로 사용하는 것처럼 테스트에서 사용할 수 있다:
자바
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.document;
@WebFluxTest
@AutoConfigureRestDocs
class MyUsersDocumentationTests {
@Autowired
private WebTestClient webTestClient;
@Test
void listUsers() {
this.webTestClient.get().uri("/")
.exchange()
.expectStatus().isOk()
.expectBody().consumeWith(document("list-users"));
}
}
코틀린
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest
import org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation
import org.springframework.test.web.reactive.server.WebTestClient
@WebFluxTest
@AutoConfigureRestDocs
class MyUsersDocumentationTests(@Autowired val webTestClient: WebTestClient) {
@Test
fun listUsers() {
webTestClient.get().uri("/")
.exchange()
.expectStatus().isOk
.expectBody()
.consumeWith(WebTestClientRestDocumentation.document("list-users"))
}
}
@AutoConfigureRestDocs
애트리뷰트가 제공하는 것보다 스프링 레스트 독스 구성에 대한 더 많은 제어가 필요한 경우, 다음 예제와 같이 레스트독스웹테스트클라이언트컨피규레이션커스터마이저(RestDocsWebTestClientConfigurationCustomizer)
빈을 사용할 수 있다:
자바
import org.springframework.boot.test.autoconfigure.restdocs.RestDocsWebTestClientConfigurationCustomizer;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.restdocs.webtestclient.WebTestClientRestDocumentationConfigurer;
@TestConfiguration(proxyBeanMethods = false)
public class MyRestDocsConfiguration implements RestDocsWebTestClientConfigurationCustomizer {
@Override
public void customize(WebTestClientRestDocumentationConfigurer configurer) {
configurer.snippets().withEncoding("UTF-8");
}
}
코틀린
import org.springframework.boot.test.autoconfigure.restdocs.RestDocsWebTestClientConfigurationCustomizer
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.restdocs.webtestclient.WebTestClientRestDocumentationConfigurer
@TestConfiguration(proxyBeanMethods = false)
class MyRestDocsConfiguration : RestDocsWebTestClientConfigurationCustomizer {
override fun customize(configurer: WebTestClientRestDocumentationConfigurer) {
configurer.snippets().withEncoding("UTF-8")
}
}
파라미터화된 출력 디렉토리에 대한 스프링 레스트 독스(Spring REST Docs) 지원을 활용하려는 경우, 웹테스트클라이언트빌더커스터마이저(WebTestClientBuilderCustomizer)
를 사용하여 모든 엔터티 익스체인지(entity exchange) 결과에 대한 컨슈머(consumer)를 구성할 수 있다. 다음 예제에서 웹테스트클라이언트빌더커스터마이저(WebTestClientBuilderCustomizer)
를 보여준다:
자바
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.test.web.reactive.server.WebTestClientBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.document;
@TestConfiguration(proxyBeanMethods = false)
public class MyWebTestClientBuilderCustomizerConfiguration {
@Bean
public WebTestClientBuilderCustomizer restDocumentation() {
return (builder) -> builder.entityExchangeResultConsumer(document("{method-name}"));
}
}
코틀린
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.boot.test.web.reactive.server.WebTestClientBuilderCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation
import org.springframework.test.web.reactive.server.WebTestClient
@TestConfiguration(proxyBeanMethods = false)
class MyWebTestClientBuilderCustomizerConfiguration {
@Bean
fun restDocumentation(): WebTestClientBuilderCustomizer {
return WebTestClientBuilderCustomizer { builder: WebTestClient.Builder ->
builder.entityExchangeResultConsumer(
WebTestClientRestDocumentation.document("{method-name}")
)
}
}
}
Auto-configured Spring REST Docs Tests With REST Assured
@AutoConfigureRestDocs
는 스프링 레스트 독스(Spring REST Docs)를 사용하도록 미리 구성된 리퀘스트스페시피케이션(RequestSpecification) 빈을 테스트에 사용할 수 있도록 만든다. 다음 예제와 같이 @Autowired
를 사용하여 이를 주입하고 레스트 어슈어드(REST Assured) 및 스프링 레스트 독스를 사용할 때 일반적으로 사용하는 것처럼 테스트에서 사용할 수 있다:
자바
import io.restassured.specification.RequestSpecification;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.server.LocalServerPort;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.is;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureRestDocs
class MyUserDocumentationTests {
@Test
void listUsers(@Autowired RequestSpecification documentationSpec, @LocalServerPort int port) {
given(documentationSpec)
.filter(document("list-users"))
.when()
.port(port)
.get("/")
.then().assertThat()
.statusCode(is(200));
}
}
코틀린
import io.restassured.RestAssured
import io.restassured.specification.RequestSpecification
import org.hamcrest.Matchers
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment
import org.springframework.boot.test.web.server.LocalServerPort
import org.springframework.restdocs.restassured.RestAssuredRestDocumentation
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureRestDocs
class MyUserDocumentationTests {
@Test
fun listUsers(@Autowired documentationSpec: RequestSpecification?, @LocalServerPort port: Int) {
RestAssured.given(documentationSpec)
.filter(RestAssuredRestDocumentation.document("list-users"))
.`when`()
.port(port)["/"]
.then().assertThat()
.statusCode(Matchers.`is`(200))
}
}
@AutoConfigureRestDocs
의 애트리뷰트가 제공하는 것보다, 스프링 레스트 독스 구성에 대한 더 많은 제어가 필요한 경우, 다음 예제와 같이 레스트독스레스트어슈어드컨피규레이션커스터마이저(RestDocsRestAssuredConfigurationCustomizer)
빈을 사용할 수 있다:
자바
import org.springframework.boot.test.autoconfigure.restdocs.RestDocsRestAssuredConfigurationCustomizer;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.restdocs.restassured.RestAssuredRestDocumentationConfigurer;
import org.springframework.restdocs.templates.TemplateFormats;
@TestConfiguration(proxyBeanMethods = false)
public class MyRestDocsConfiguration implements RestDocsRestAssuredConfigurationCustomizer {
@Override
public void customize(RestAssuredRestDocumentationConfigurer configurer) {
configurer.snippets().withTemplateFormat(TemplateFormats.markdown());
}
}
코틀린
import org.springframework.boot.test.autoconfigure.restdocs.RestDocsRestAssuredConfigurationCustomizer
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.restdocs.restassured.RestAssuredRestDocumentationConfigurer
import org.springframework.restdocs.templates.TemplateFormats
@TestConfiguration(proxyBeanMethods = false)
class MyRestDocsConfiguration : RestDocsRestAssuredConfigurationCustomizer {
override fun customize(configurer: RestAssuredRestDocumentationConfigurer) {
configurer.snippets().withTemplateFormat(TemplateFormats.markdown())
}
}
Auto-configured Spring Web Services Tests
Auto-configured Spring Web Services Client Tests
@WebServiceClientTest
를 사용하면 스프링 웹 서비스 프로젝트를 사용하여 웹 서비스를 호출하는 애플리케이션을 테스트할 수 있다. 기본적으로 목 웹서비스서버(WebServiceServer)
빈을 구성하고 웹서비스템플릿빌더(WebServiceTemplateBuilder)
를 자동으로 커스텀한다. (스프링 부트에서 웹 서비스를 사용하는 방법에 대한 자세한 내용은 “웹 서비스(Web Services)”를 참고하자.)
@WebServiceClientTest
에 의해 활성화된 자동 구성 설정 목록은 부록에서 확인할 수 있다.
다음 예에서는 사용 중인 @WebServiceClientTest
어노테이션을 보여준다:
자바
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.webservices.client.WebServiceClientTest;
import org.springframework.ws.test.client.MockWebServiceServer;
import org.springframework.xml.transform.StringSource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.ws.test.client.RequestMatchers.payload;
import static org.springframework.ws.test.client.ResponseCreators.withPayload;
@WebServiceClientTest(SomeWebService.class)
class MyWebServiceClientTests {
@Autowired
private MockWebServiceServer server;
@Autowired
private SomeWebService someWebService;
@Test
void mockServerCall() {
this.server
.expect(payload(new StringSource("<request/>")))
.andRespond(withPayload(new StringSource("<response><status>200</status></response>")));
assertThat(this.someWebService.test())
.extracting(Response::getStatus)
.isEqualTo(200);
}
}
코틀린
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.webservices.client.WebServiceClientTest
import org.springframework.ws.test.client.MockWebServiceServer
import org.springframework.ws.test.client.RequestMatchers
import org.springframework.ws.test.client.ResponseCreators
import org.springframework.xml.transform.StringSource
@WebServiceClientTest(SomeWebService::class)
class MyWebServiceClientTests(@Autowired val server: MockWebServiceServer, @Autowired
val someWebService: SomeWebService) {
@Test
fun mockServerCall() {
server
.expect(RequestMatchers.payload(StringSource("<request/>")))
.andRespond(ResponseCreators.withPayload(StringSource("<response><status>200</status></response>")))
assertThat(this.someWebService.test()).extracting(Response::status).isEqualTo(200)
}
}
Auto-configured Spring Web Services Server Tests
@WebServiceServerTest
를 사용하면 스프링 웹 서비스 프로젝트를 사용하여 웹 서비스를 구현하는 애플리케이션을 테스트할 수 있다. 기본적으로, 웹 서비스 엔드포인트를 호출하는 데 사용할 수 있는 목웹서비스클라이언트(MockWebServiceClient) 빈을 구성한다. (스프링 부트에서 웹 서비스를 사용하는 방법에 대한 자세한 내용은 “웹 서비스(Web Services)”를 참고하자.)
@WebServiceServerTest
에 의해 활성화되는 자동 구성 설정 목록은 부록에서 확인할 수 있다.
다음 예에서는 사용 중인 @WebServiceServerTest
어노테이션을 보여준다:
자바
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.webservices.server.WebServiceServerTest;
import org.springframework.ws.test.server.MockWebServiceClient;
import org.springframework.ws.test.server.RequestCreators;
import org.springframework.ws.test.server.ResponseMatchers;
import org.springframework.xml.transform.StringSource;
@WebServiceServerTest(ExampleEndpoint.class)
class MyWebServiceServerTests {
@Autowired
private MockWebServiceClient client;
@Test
void mockServerCall() {
this.client
.sendRequest(RequestCreators.withPayload(newStringSource("<ExampleRequest/>")))
.andExpect(ResponseMatchers.payload(new StringSource("<ExampleResponse>42</ExampleResponse>")));
}
}
코틀린
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.webservices.server.WebServiceServerTest
import org.springframework.ws.test.server.MockWebServiceClient
import org.springframework.ws.test.server.RequestCreators
import org.springframework.ws.test.server.ResponseMatchers
import org.springframework.xml.transform.StringSource
@WebServiceServerTest(ExampleEndpoint::class)
class MyWebServiceServerTests(@Autowired val client: MockWebServiceClient) {
@Test
fun mockServerCall() {
client
.sendRequest(RequestCreators.withPayload(StringSource("<ExampleRequest/>")))
.andExpect(ResponseMatchers.payload(StringSource("<ExampleResponse>42</ExampleResponse>")))
}
}
Additional Auto-configuration and Slicing
각 슬라이스(slice)는 슬라이스의 일부인 자동 구성을 정의하는 하나 이상의 @AutoConfigure...
어노테이션을 제공한다. 커스텀 @AutoConfigure...
어노테이션을 생성하거나 다음 예제와 같이 테스트에 @ImportAutoConfiguration
을 추가하여 테스트별로 추가 자동 구성을 추가할 수 있다:
자바
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration;
import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest;
@JdbcTest
@ImportAutoConfiguration(IntegrationAutoConfiguration.class)
class MyJdbcTests { }
코틀린
import org.springframework.boot.autoconfigure.ImportAutoConfiguration
import org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration
import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest
@JdbcTest
@ImportAutoConfiguration(IntegrationAutoConfiguration::class)
class MyJdbcTests
자동 구성은 스프링 부트에서 특정 방식으로 처리되므로 일반 @Import
어노테이션을 사용하지 말자.
또는, 다음 예제와 같이 META-INF/spring
에 저장된 파일에 슬라이스(slice) 어노테이션을 등록하여 슬라이스 어노테이션 사용에 대한 자동 구성을 추가할 수 있다:
META-INF/spring/org.springframework.boot.test.autoconfigure.jdbc.JdbcTest.imports
com.example.IntegrationAutoConfiguration
이 예제는, @JdbcTest
어노테이션이 달린 모든 테스트에서 com.example.IntegrationAutoConfiguration
이 활성화된다.
이 파일에서는 #으로 주석을 사용할 수 있다.
슬라이스(slice)
또는 @AutoConfigure...
어노테이션은 @ImportAutoConfiguration
으로 메타 어노테이션이 달린 이 방법으로 커스텀할 수 있다.
User Configuration and Slicing
합리적인 방식으로 코드를 구성하면, @SpringBootApplication
클래스가 기본적으로 테스트 구성으로 사용된다.
그런 다음 해당 기능의 특정 영역에대한 특정 구성 설정으로 애플리케이션의 메인(main) 클래스를 낭비하지 않는 것이 중요해진다.
당신이 스프링 데이터 몽고DB를 사용하고 있고, 자동 구성에 의존하며 오디팅(auditing)를 활성화했다고 가정하자. @SpringBootApplication
을 다음과 같이 정의할 수 있다:
자바
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.mongodb.config.EnableMongoAuditing;
@SpringBootApplication
@EnableMongoAuditing
public class MyApplication {
// ...
}
코틀린
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.data.mongodb.config.EnableMongoAuditing
@SpringBootApplication
@EnableMongoAuditing
class MyApplication {
// ...
}
이 클래스는 테스트의 소스 구성이기 때문에 모든 슬라이스 테스트는 실제로 몽고 오디팅(Mongo auditing)를 활성화하려고 시도하지만, 이는 권장하는 작업이 아니다. 권장되는 접근 방식은 다음 예제와 같이 해당 영역별 구성을 애플리케이션과 동일한 레벨의 별도 @Configuration
클래스로 이동하는 것이다.
자바
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.config.EnableMongoAuditing;
@Configuration(proxyBeanMethods = false)
@EnableMongoAuditing
public class MyMongoConfiguration {
// ...
}
코틀린
import org.springframework.context.annotation.Configuration
import org.springframework.data.mongodb.config.EnableMongoAuditing;
@Configuration(proxyBeanMethods = false)
@EnableMongoAuditing
class MyMongoConfiguration {
// ...
}
애플리케이션의 복잡성에 따라, 커스텀를 위한 싱글 @Configuration
클래스가 있거나 도메인 영역당 하나의 클래스가 있을 수 있다. 후자의 접근 방식을 사용하면 필요한 경우 @Import
어노테이션을 사용하여 테스트 중 하나에서 이를 활성화할 수 있다. 슬라이스 테스트를 위해 특정 @Configuration
클래스를 활성화하려는 경우에 대한 자세한 내용은 어떻게(how-to) 섹션
을 참고하자.
테스트 슬라이스는 @Configuration
클래스를 스캐닝에서 제외한다. 예를 들어, @WebMvcTest
의 경우 테스트 슬라이스에 의해 로드된 애플리케이션 컨텍스트에 지정된 웹Mvc컨피규어러(WebMvcConfigurer)
빈을 포함하지 않는다:
자바
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration(proxyBeanMethods = false)
public class MyWebConfiguration {
@Bean
public WebMvcConfigurer testConfigurer() {
return new WebMvcConfigurer() {
// ...
};
}
}
코틀린
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
@Configuration(proxyBeanMethods = false)
class MyWebConfiguration {
@Bean
fun testConfigurer(): WebMvcConfigurer {
return object : WebMvcConfigurer {
// ...
}
}
}
그러나, 아래 구성을 사용하면 커스텀 웹Mvc컨피규어러(WebMvcConfigurer)
가 테스트 슬라이스에 의해 로드된다.
자바
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Component
public class MyWebMvcConfigurer implements WebMvcConfigurer {
// ...
}
코틀린
import org.springframework.stereotype.Component
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
@Component
class MyWebMvcConfigurer : WebMvcConfigurer {
// ...
}
혼란의 또 다른 원인은 클래스패스 스캐닝이다. 코드를 합리적인 방식으로 구성하는 동안 추가 패키지를 스캔해야 한다고 가정해 보자. 애플리케이션은 다음 코드와 유사할 수 있다:
자바
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan({ "com.example.app", "com.example.another" })
public class MyApplication {
// ...
}
코틀린
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.context.annotation.ComponentScan
@SpringBootApplication
@ComponentScan("com.example.app", "com.example.another")
class MyApplication {
// ...
}
이렇게 하면 선택한 슬라이스에 관계없이 두 패키지를 스캔하는 부작용으로 기본 컴포넌트 스캔을 무시한다. 예를 들어 @DataJpaTest
는 갑자기 애플리케이션의 컴포넌트와 사용자 구성을 스캔하는 것처럼 보인다. 다시 말해서, 커스텀 지시문(directive)을 별도의 클래스로 이동하는 것이 이 문제를 해결하는 좋은 방법이다.
옵션이 아닌 경우, 테스트 계층 구조 어딘가에 @SpringBootConfiguration
을 생성하여 대신 사용할 수 있다. 또는, 테스트 소스를 지정하여, 기본 소스 찾기 동작을 비활성화할 수 있다.
Using Spock to Test Spring Boot Applications
스팍(Spock)
2.2 이상을 사용하여 스프링 부트 애플리케이션을 테스트할 수 있다. 그렇게 하려면, 애플리케이션 빌드에 스팍(Spock)의 spock-spring
모듈에 -groovy-4.0
버전 의존성을 추가하자. spock-spring
은 스프링의 테스트 프레임워크를 스팍(Spock)에 통합한다. 자세한 내용은 스팍의 스프링 모듈 문서를 참고하자.
7.8.4. Testcontainers
Testcontainers
라이브러리는 도커 컨테이너 내에서 실행되는 서비스를 관리하는 방법을 제공한다. 제이유닛(JUnit)
과 통합되어 테스트가 실행되기 전에 컨테이너를 시작할 수 있는 테스트 클래스를 작성할 수 있다. Testcontainers
는 MySQL
, MongoDB
, Cassandra
등과 같은 실제 백엔드 서비스와 통신하는 통합 테스트를 작성하는 데 특히 유용하다.
Testcontainers
는 다음과 같이 스프링 부트 테스트에서 사용될 수 있다:
자바
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.Neo4jContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.springframework.boot.test.context.SpringBootTest;
@Testcontainers
@SpringBootTest
class MyIntegrationTests {
@Container
static Neo4jContainer<?> neo4j = new Neo4jContainer<>("neo4j:5");
@Test
void myTest() {
// ...
}
}
코틀린
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
import org.testcontainers.containers.Neo4jContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers
@Testcontainers
@SpringBootTest
class MyIntegrationTests {
@Test
fun myTest() {
// ...
}
companion object {
@Container
val neo4j = Neo4jContainer("neo4j:5")
}
}
테스트가 실행되기 전에 로컬 도커 실행 중인 경우, Neo4j
를 실행하는 도커 컨테이너가 시작된다. 대부분의 경우 컨테이너에서 실행되는 서비스에 연결하려면 애플리케이션을 구성해야 한다.
Service Connections
서비스 커넥션은 원격 서비스에 대한 커넥션이다. 스프링 부트의 오토컨피규레이션(auto-configuration)은 서비스 커넥션 디테일즈(connection details)를 사용하고, 원격 서비스에 대한 커넥션을 설정할 수 있다. 이렇게 하면 커넥션 디테일즈(connection details)가 커넥션 관련 컨피규레이션 프로퍼티스(configuration properties)보다 우선된다.
Testcontainers
를 사용하는 경우, 테스트 클래스의 컨테이너 필드에 어노테이션을 추가하여 컨테이너에서 실행되는 서비스 커넥션 디테일즈(connection details)를 자동으로 생성할 수 있다.
자바
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.Neo4jContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
@Testcontainers
@SpringBootTest
class MyIntegrationTests {
@Container
@ServiceConnection
static Neo4jContainer<?> neo4j = new Neo4jContainer<>("neo4j:5");
@Test
void myTest() {
// ...
}
}
코틀린
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.testcontainers.service.connection.ServiceConnection
import org.testcontainers.containers.Neo4jContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers
@Testcontainers
@SpringBootTest
class MyIntegrationTests {
@Test
fun myTest() {
// ...
}
companion object {
@Container
@ServiceConnection
val neo4j = Neo4jContainer("neo4j:5")
}
}
@ServiceConnection
덕분에, 애플리케이션의 Neo4j
관련 빈이 Testcontainers
가 관리하는 도커 컨테이너 내에서 실행되는 Neo4j
와 통신할 수 있다. 이는 Neo4j
자동 구성에서 네오4j커넥션디테일즈(Neo4jConnectionDetails)
빈을 정의하여 커넥션 관련 컨피규레이션 프로퍼티스(configuration properties)를 오버라이드하여 수행된다.
Testcontainers
와 서비스 커넥션을 사용하려면 spring-boot-testcontainers
모듈을 테스트 의존성으로 추가해야 한다.
서비스 커넥션 어노테이션은 spring.factories
에 등록된 컨테이너커넥션디테일즈팩토리(ContainerConnectionDetailsFactory)
클래스에 의해 처리된다. 컨테이너커넥션디테일즈팩토리(ContainerConnectionDetailsFactory)
는 특정 컨테이너 하위 클래스 또는 도커 이미지명을 기반으로 커넥션디테일즈(ConnectionDetails)
빈을 생성할 수 있다.
spring-boot-testcontainers
jar에는 다음 서비스 커넥션 팩토리가 제공된다.
커넥션 디테일즈 | 매칭타입 |
---|---|
카산드라커넥션디테일즈(CassandraConnectionDetails) | 카산드라컨테이너(CassandraContainer) 타입의 컨테이너 |
카우치베이스커넥션디테일즈(CouchbaseConnectionDetails) | 카우치베이스컨테이너(CouchbaseContainer) 타입의 컨테이너 |
ElasticsearchConnectionDetails | 엘라스틱서치컨테이너(ElasticsearchContainer) 타입의 컨테이너 |
플라이웨이커넥션디테일즈(FlywayConnectionDetails) | Jdbc데이터베이스컨테이너(JdbcDatabaseContainer) 타입의 컨테이너 |
Jdbc커넥션디테일즈(JdbcConnectionDetails) | Jdbc데이터베이스컨테이너(JdbcDatabaseContainer) 타입의 컨테이너 |
카프카커넥션디테일즈(KafkaConnectionDetails) | 카프카컨테이너(KafkaContainer) 또는 레드판다컨테이너(RedpandaContainer) 타입의 컨테이너 |
리퀴베이스(LiquibaseConnectionDetails) | Jdbc데이터베이스컨테이너(JdbcDatabaseContainer) 타입의 컨테이너 |
몽고커넥션디테일즈(MongoConnectionDetails) | 몽고DB컨테이너(MongoDBContainer) 타입의 컨테이너 |
네오4j커넥션디테일즈(Neo4jConnectionDetails) | 네오4j컨테이너(Neo4jContainer) 타입의 컨테이너 |
R2dbc커넥션디테일즈(R2dbcConnectionDetails) | 마리아DB컨테이너(MariaDBContainer) , MSSQL서버컨테이너(MSSQLServerContainer) , MySQL컨테이너(MySQLContainer) , 오라클컨테이너(OracleContainer) 또는 포스트그레SQL컨테이너(PostgreSQLContainer) 타입의 컨테이너 |
레빗커넥션디테일즈(RabbitConnectionDetails) | 레빗MQ컨테이너(RabbitMQContainer) 타입의 컨테이너 |
레디스커넥션디테일즈(RedisConnectionDetails) | “redis”라는 명칭의 컨테이너 |
집킨커넥션디테일즈(ZipkinConnectionDetails) | “openzipkin/zipkin”라는 명칭의 컨테이너 |
기본적으로, 해당 컨테이너에 대해 적용 가능한 모든 커넥션 디테일즈(connection details) 빈이 생성된다. 예를 들어 포스트그레SQL컨테이너(PostgreSQLContainer)
는 Jdbc커넥션디테일즈(JdbcConnectionDetails)
와 R2dbc커넥션디테일즈(R2dbcConnectionDetails)
를 모두 생성한다.
적용 가능한 타입의 하위 집합만 생성하려면, @ServiceConnection
의 타입 애트리뷰트을 사용하면 된다.
기본적으로 Container.getDockerImageName()
은 커넥션 디테일즈(connection details)를 찾기위한 명칭을 얻는 데 사용된다. 커스텀 도커 이미지를 사용하는 경우 @ServiceConnection
의 name
애트리뷰트를 사용하여 이를 오버라이드할 수 있다.
예를 들어, Registry.mycompany.com/mirror/myredis
의 도커 이미지를 사용하는 제네릭컨테이너(GenericContainer)
가 있는 경우 @ServiceConnection(name="redis")
을 사용하여 레디스커넥션디테일즈(RedisConnectionDetails)
가 생성됐는지 확인한다.
Dynamic Properties
서비스 커넥션에 대한 약간 장황하지만 더 유연한 대안은 @DynamicPropertySource
이다. 스태틱(static) @DynamicPropertySource
메소드를 사용하면 스프링 환경에 다이나믹(dynamic) 프로퍼티 값을 추가할 수 있다.
자바
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.Neo4jContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
@Testcontainers
@SpringBootTest
class MyIntegrationTests {
@Container
static Neo4jContainer<?> neo4j = new Neo4jContainer<>("neo4j:5");
@Test
void myTest() {
// ...
}
@DynamicPropertySource
static void neo4jProperties(DynamicPropertyRegistry registry) {
registry.add("spring.neo4j.uri", neo4j::getBoltUrl);
}
}
코틀린
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.DynamicPropertyRegistry
import org.springframework.test.context.DynamicPropertySource
import org.testcontainers.containers.Neo4jContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers
@Testcontainers
@SpringBootTest
class MyIntegrationTests {
@Test
fun myTest() {
// ...
}
companion object {
@Container
val neo4j = Neo4jContainer("neo4j:5")
@DynamicPropertySource
fun neo4jProperties(registry: DynamicPropertyRegistry) {
registry.add("spring.neo4j.uri") { neo4j.boltUrl }
}
}
}
위 구성을 사용하면 애플리케이션의 Neo4j
관련 빈이 테스트컨테이너(Testcontainers)
가 관리하는 도커 컨테이너 내에서 실행되는 Neo4j
와 통신할 수 있다.
Using Testcontainers at Development Time
통합 테스트를 위해 테스트컨테이너(Testcontainer)
를 사용하는 것 외에 개발 시에도 사용할 수 있다. 이 접근 방식을 사용하면 개발자는 애플리케이션이 의존하는 서비스에 대한 컨테이너를 빠르게 시작할 수 있으므로 데이터베이스 서버와 같은 항목을 수동으로 프로비저닝(provision)할 필요가 없다. 이러한 방식으로 테스트컨테이너(Testcontainer)
를 사용하면 컨테이너 구성이 YAML이 아닌 자바로 되어 있다는 점을 제외하면 도커 컴포즈(Compose)와 유사한 기능이 제공된다.
개발 시 테스트컨테이너(Testcontainers)
를 사용하려면 “main”이 아닌 “test” 클래스패스를 사용하여 애플리케이션을 시작해야 한다. 이렇게 하면 선언된 모든 테스트 의존성에 접근할 수 있고 테스트 구성을 작성할 수 있는 자연스러운 장소가 제공된다.
애플리케이션의 테스트 실행 가능 버전을 생성하려면 src/test
디렉토리에 “Application” 클래스를 생성해야 한다. 예를 들어, 메인(main) 애플리케이션이 src/main/java/com/example/MyApplication.java
에 있는 경우 src/test/java/com/example/TestMyApplication.java
를 생성해야 한다.
TestMyApplication
클래스는 SpringApplication.from(...)
메소드를 사용하여 실제 애플리케이션을 시작할 수 있다:
자바
import org.springframework.boot.SpringApplication;
public class TestMyApplication {
public static void main(String[] args) {
SpringApplication.from(MyApplication::main).run(args);
}
}
코틀린
import org.springframework.boot.fromApplication
fun main(args: Array<String>) {
fromApplication<MyApplication>().run(*args)
}
또한 애플리케이션과 함께 시작하려는 컨테이너 인스턴스를 정의해야 합니다. 이렇게 하려면 spring-boot-testcontainers
모듈이 테스트 의존성으로 추가되었는지 확인해야 한다. 이 작업이 완료되면 시작하려는 컨테이너에 대한 @Bean
메서드를 선언하는 @TestConfiguration
클래스를 생성할 수 있다.
커넥션디테일즈(ConnectionDetails)
빈을 생성하기 위해 @ServiceConnection
으로 @Bean
메소드에 어노테이션을 달 수도 있다. 지원되는 기술에 대한 자세한 내용은 서비스 커넥션(Service Connection) 절을 참고하자.
일반적인 테스트컨테이너즈(Testcontainers)
구성은 다음과 같다:
자바
import org.testcontainers.containers.Neo4jContainer;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.context.annotation.Bean;
@TestConfiguration(proxyBeanMethods = false)
public class MyContainersConfiguration {
@Bean
@ServiceConnection
public Neo4jContainer<?> neo4jContainer() {
return new Neo4jContainer<>("neo4j:5");
}
}
코틀린
import org.testcontainers.containers.Neo4jContainer
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.boot.testcontainers.service.connection.ServiceConnection
import org.springframework.context.annotation.Bean;
@TestConfiguration(proxyBeanMethods = false)
class MyContainersConfiguration {
@Bean
@ServiceConnection
fun neo4jContainer(): Neo4jContainer<*> {
return Neo4jContainer("neo4j:5")
}
}
컨테이너 빈의 생명주기은 스프링 부트에 의해 자동으로 관리된다. 컨테이너는 자동으로 시작되고 중지된다.
테스트 구성을 정의한 후에는, with(...)
메서드를 사용하여 이를 테스트 런처(launcher)에 연결할 수 있다:
자바
import org.springframework.boot.SpringApplication;
public class TestMyApplication {
public static void main(String[] args) {
SpringApplication.from(MyApplication::main).with(MyContainersConfiguration.class).run(args);
}
}
코틀린
import org.springframework.boot.fromApplication
import org.springframework.boot.with
fun main(args: Array<String>) {
fromApplication<MyApplication>().with(MyContainersConfiguration::class).run(*args)
}
이제 일반 자바 메인(main) 메소드 애플리케이션과 마찬가지로 TestMyApplication
을 시작하여 애플리케이션과 애플리케이션이 실행해야 하는 컨테이너를 시작할 수 있다.
메이븐 골(goal) spring-boot:test-run
또는 그레이들 태스크 bootTestRun
을 사용하여 커맨드라인에서 이 작업을 수행할 수 있다.
Contributing Dynamic Properties at Development Time
개발 시 컨테이너 @Bean
메소드에서 다이나믹 프로퍼티스를 제공하려면 다이나믹프로퍼티레지스트리(DynamicPropertyRegistry)
를 주입하면 된다. 이는 테스트에 사용할 수 있는 @DynamicPropertySource
어노테이션과 비슷한 방식으로 작동한다. 컨테이너가 시작되면 사용할 수 있는 프로퍼티스를 추가할 수 있다.
일반적인 구성은 다음과 같다:
자바
import org.testcontainers.containers.MongoDBContainer;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.test.context.DynamicPropertyRegistry;
@TestConfiguration(proxyBeanMethods = false)
public class MyContainersConfiguration {
@Bean
public MongoDBContainer monogDbContainer(DynamicPropertyRegistry properties) {
MongoDBContainer container = new MongoDBContainer("mongo:5.0");
properties.add("spring.data.mongodb.host", container::getHost);
properties.add("spring.data.mongodb.port", container::getFirstMappedPort);
return container;
}
}
코틀린
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.annotation.Bean;
import org.springframework.test.context.DynamicPropertyRegistry
import org.testcontainers.containers.MongoDBContainer
@TestConfiguration(proxyBeanMethods = false)
class MyContainersConfiguration {
@Bean
fun monogDbContainer(properties: DynamicPropertyRegistry): MongoDBContainer {
var container = MongoDBContainer("mongo:5.0")
properties.add("spring.data.mongodb.host", container::getHost);
properties.add("spring.data.mongodb.port", container::getFirstMappedPort);
return container
}
}
가능할 때마다 @ServiceConnection
을 사용하는 것이 좋다. 그러나 다이나믹 프로퍼티스는 아직 @ServiceConnection
을 지원하지 않는 기술에 대한 유용한 대체 수단이 될 수 있다.
Importing Testcontainer Declaration Classes
테스트컨테이너즈(Testcontainers)
를 사용할 때 일반적인 패턴은 컨테이너 인스턴스를 스태틱 필드로 선언하는 것이다. 종종 이러한 필드는 테스트 클래스에서 직접 정의된다. 상위 클래스나 테스트에서 구현하는 인터페이스에서 선언할 수도 있다.
예를 들어, 다음 MyContainers
인터페이스는 몽고 및 네오4j 컨테이너를 선언한다:
import org.testcontainers.containers.MongoDBContainer;
import org.testcontainers.containers.Neo4jContainer;
import org.testcontainers.junit.jupiter.Container;
public interface MyContainers {
@Container
MongoDBContainer mongoContainer = new MongoDBContainer("mongo:5.0");
@Container
Neo4jContainer<?> neo4jContainer = new Neo4jContainer<>("neo4j:5");
}
이미 이런 방식으로 정의된 컨테이너가 있거나, 이 스타일을 선호하는 경우 컨테이너를 @Bean
메소드로 정의하는 대신 이러한 선언 클래스를 가져올 수 있다. 이렇게 하려면 테스트 구성 클래스에 @ImportTestcontainers
어노테이션을 추가하자:
자바
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.testcontainers.context.ImportTestcontainers;
@TestConfiguration(proxyBeanMethods = false)
@ImportTestcontainers(MyContainers.class)
public class MyContainersConfiguration { }
코틀린
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.boot.testcontainers.context.ImportTestcontainers
@TestConfiguration(proxyBeanMethods = false)
@ImportTestcontainers(MyContainers::class)
class MyContainersConfiguration { }
컨테이너 필드의 @ServiceConnection
어노테이션을 사용하여 서비스 커넥션을 설정할 수 있다. 선언 클래스에 @DynamicPropertySource
어노테이션이 달린 메서드를 추가할 수도 있다.
Using DevTools with Testcontainers at Development Time
데브툴즈(devtools)
를 사용할 때 @RestartScope
를 사용하여 빈 및 빈 메소드에 어노테이션을 달 수 있다. 이러한 빈은 데브툴즈(devtools)가 애플리케이션을 재시작할 때 다시 생성되지 않는다. 이는 애플리케이션을 재시작해도 상태를 유지하므로 테스트컨테이너(Testcontainer) 컨테이너 빈에 특히 유용하다.
자바
import org.testcontainers.containers.MongoDBContainer;
import org.springframework.boot.devtools.restart.RestartScope;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
@TestConfiguration(proxyBeanMethods = false)
public class MyContainersConfiguration {
@Bean
@RestartScope
public MongoDBContainer monogDbContainer() {
return new MongoDBContainer("mongo:5.0");
}
}
코틀린
import org.springframework.boot.devtools.restart.RestartScope
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.annotation.Bean
import org.testcontainers.containers.MongoDBContainer
@TestConfiguration(proxyBeanMethods = false)
class MyContainersConfiguration {
@Bean
@RestartScope
fun monogDbContainer(): MongoDBContainer {
return MongoDBContainer("mongo:5.0")
}
}
그레이들을 사용 중이고 이 기능을 사용하려면 spring-boot-devtools
의존성 구성을 developmentOnly
에서 testImplementation
으로 변경해야 한다. developmentOnly
의 디폴트 스코프(default scope)를 사용하면 데브툴즈가 활성화되지 않으므로 bootTestRun
태스크가 코드의 변경 사항을 선택하지 않는다.
7.8.5. Test Utilities
애플리케이션을 테스트할 때 일반적으로 유용한 몇 가지 테스트 유틸리티 클래스가 스프링부트
의 일부로 패키지되어 있다.
ConfigDataApplicationContextInitializer
컨피그데이터애플리케이션컨텍스트이니셜라이저(ConfigDataApplicationContextInitializer)
는 스프링부트 application.properties
파일을 로드하기 위해 테스트에 적용할 수 있는 애플리케이션컨텍스트이니셜라이저(ApplicationContextInitializer)
이다. 다음 예제와 같이 @SpringBootTest
에서 제공하는 전체 기능이 필요하지 않은 경우 이를 사용할 수 있다:
자바
import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer;
import org.springframework.test.context.ContextConfiguration;
@ContextConfiguration(classes = Config.class, initializers = ConfigDataApplicationContextInitializer.class)
class MyConfigFileTests {
// ...
}
코틀린
import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer
import org.springframework.test.context.ContextConfiguration
@ContextConfiguration(classes = [Config::class], initializers = [ConfigDataApplicationContextInitializer::class])
class MyConfigFileTests {
// ...
}
컨피그데이터애플리케이션컨텍스트이니셜라이저(ConfigDataApplicationContextInitializer)
를 사용하면 @Value("${...}")
주입이 지원되지 않는다. 유일한 작업은 application.properties
파일이 스프링 환경에 로드되도록 하는 것이다. @Value
지원을 위해서는 프로퍼티소스플레이스홀더컨피규어러(PropertySourcesPlaceholderConfigurer)
를 추가로 구성하거나 자동으로 구성하는 @SpringBootTest
를 사용해야 한다.
TestPropertyValues
테스트프로퍼티스밸류즈(TestPropertyValues)
를 사용하면 컨피규러블인바이런먼트(ConfigurableEnvironment)
또는 컨피규러블애플리케이션컨텍스트(ConfigurableApplicationContext)
에 프로퍼티스를 빠르게 추가할 수 있다. 다음과 같이 key=value
문자열을 사용하여 호출할 수 있다:
자바
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
class MyEnvironmentTests {
@Test
void testPropertySources() {
MockEnvironment environment = new MockEnvironment();
TestPropertyValues.of("org=Spring", "name=Boot").applyTo(environment);
assertThat(environment.getProperty("name")).isEqualTo("Boot");
}
}
코틀린
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.boot.test.util.TestPropertyValues
import org.springframework.mock.env.MockEnvironment
class MyEnvironmentTests {
@Test
fun testPropertySources() {
val environment = MockEnvironment()
TestPropertyValues.of("org=Spring", "name=Boot").applyTo(environment)
assertThat(environment.getProperty("name")).isEqualTo("Boot")
}
}
OutputCapture
아웃풋캡쳐(OutputCapture)
는 System.out
및 System.err
출력을 캡처하는 데 사용할 수 있는 제이유닛(JUnit)
익스텐션(Extension)이다. 이를 사용하려면 다음과 같이 @ExtendWith(OutputCaptureExtension.class)
를 추가하고 캡쳐드아웃풋(CapturedOutput)
을 테스트 클래스 생성자 또는 테스트 메서드에 아규먼트로 주입한다:
자바
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(OutputCaptureExtension.class)
class MyOutputCaptureTests {
@Test
void testName(CapturedOutput output) {
System.out.println("Hello World!");
assertThat(output).contains("World");
}
}
코틀린
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.boot.test.system.CapturedOutput
import org.springframework.boot.test.system.OutputCaptureExtension
@ExtendWith(OutputCaptureExtension::class)
class MyOutputCaptureTests {
@Test
fun testName(output: CapturedOutput?) {
println("Hello World!")
assertThat(output).contains("World")
}
}
TestRestTemplate
테스트레스트템플릿(TestRestTemplate)
은 통합 테스트에 유용한 스프링의 레스트템플릿(RestTemplate)
에 대한 대안이다. 바닐라 템플릿이나 기본 HTTP 인증(사용자 이름과 비밀번호 포함)을 보내는 템플릿을 얻을 수 있다. 두 경우 모두 템플릿의 내결함성(fault tolerant)이 있다. 이는 4xx 및 5xx 오류에 대해 예외를 발생시키지 않음으로써 테스트하기 쉬운 방식으로 작동한다는 것을 의미한다. 대신, 반환된 리스폰스엔터티(ResponseEntity)
및 해당 상태 코드를 통해 이러한 오류를 감지할 수 있다.
스프링 프레임워크 5.0은 웹플럭스 통합 테스트와 웹플럭스 및 MVC 엔드투엔드(end-to-end) 테스트에 작동하는 새로운 웹테스트클라이언트(WebTestClient)
를 제공한다. 테스트레스트템플릿(TestRestTemplate)
과 달리 어설션(assertion)을 위한 유창한 API를 제공한다.
아파치 HTTP 클라이언트(버전 5.1 이상)를 사용하는 것이 권장되지만 필수는 아니다. 클래스패스에 해당 항목이 있는 경우 테스트레스트템플릿(TestRestTemplate)
은 클라이언트를 적절하게 구성하여 응답한다. 아파치의 HTTP 클라이언트를 사용하는 경우 테스트에 적합한 몇 가지 추가 기능이 활성화된다.
- 리디렉션을 따르지 않는다(응답 위치를 어설트(assert)할 수 있도록).
- 쿠키는 무시된다(따라서 템플릿은 상태 비저장(stateless)).
테스트레스트템플릿(TestRestTemplate)
은 다음 예와 같이 통합 테스트에서 직접 인스턴스화할 수 있다:
자바
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.ResponseEntity;
import static org.assertj.core.api.Assertions.assertThat;
class MyTests {
private final TestRestTemplate template = new TestRestTemplate();
@Test
void testRequest() {
ResponseEntity<String> headers = this.template.getForEntity("https://myhost.example.com/example", String.class);
assertThat(headers.getHeaders().getLocation()).hasHost("other.example.com");
}
}
코틀린
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.boot.test.web.client.TestRestTemplate
class MyTests {
private val template = TestRestTemplate()
@Test
fun testRequest() {
val headers = template.getForEntity("https://myhost.example.com/example", String::class.java)
assertThat(headers.headers.location).hasHost("other.example.com")
}
}
또는, WebEnvironment.RANDOM_PORT
또는 WebEnvironment.DEFINED_PORT
와 함께 @SpringBootTest
어노테이션을 사용하는 경우 완전히 구성된 테스트레스트템플릿(TestRestTemplate)
을 주입하고 사용을 시작할 수 있다. 필요한 경우 레스트템플릿빌더(RestTemplateBuilder)
빈을 통해 추가 커스텀을 적용할 수 있다. 다음 예제와 같이 호스트 및 포트를 지정하지 않은 모든 URL은 자동으로 임베디드 서버에 연결된다:
자바
import java.time.Duration;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpHeaders;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MySpringBootTests {
@Autowired
private TestRestTemplate template;
@Test
void testRequest() {
HttpHeaders headers = this.template.getForEntity("/example", String.class).getHeaders();
assertThat(headers.getLocation()).hasHost("other.example.com");
}
@TestConfiguration(proxyBeanMethods = false)
static class RestTemplateBuilderConfiguration {
@Bean
RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder().setConnectTimeout(Duration.ofSeconds(1))
.setReadTimeout(Duration.ofSeconds(1));
}
}
}
코틀린
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.boot.test.web.client.TestRestTemplate
import org.springframework.boot.web.client.RestTemplateBuilder
import org.springframework.context.annotation.Bean
import java.time.Duration
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MySpringBootTests(@Autowired val template: TestRestTemplate) {
@Test
fun testRequest() {
val headers = template.getForEntity("/example", String::class.java).headers
assertThat(headers.location).hasHost("other.example.com")
}
@TestConfiguration(proxyBeanMethods = false)
internal class RestTemplateBuilderConfiguration {
@Bean
fun restTemplateBuilder(): RestTemplateBuilder {
return RestTemplateBuilder().setConnectTimeout(Duration.ofSeconds(1))
.setReadTimeout(Duration.ofSeconds(1))
}
}
}
7.9. Docker Compose Support
도커 컴포즈(Docker Compose)
는 애플리케이션에 필요한 서비스에 대한 여러 컨테이너를 정의하고 관리하는 데 사용할 수 있는 기술이다. compose.yml
파일은 일반적으로 서비스 컨테이너를 정의하고 구성하는 애플리케이션 옆에 생성된다.
도커 컴포즈의 일반적인 워크플로는 docker compose up
을 실행하고, 시작된 서비스에 연결하여 애플리케이션 작업을 한 다음, 완료되면 docker compose down
을 실행하는 것이다.
spring-boot-docker-compose
모듈은 도커 컴포즈를 사용하여 컨테이너 작업을 지원하기 위해 프로젝트에 포함될 수 있다. 메이븐 및 그레이들에 대해 다음에 표시된 대로 빌드에 모듈 의존성을 추가한다.
메이븐
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-docker-compose</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
그레이들
dependencies {
developmentOnly("org.springframework.boot:spring-boot-docker-compose")
}
이 모듈이 의존성으로 포함되면 스프링 부트는 다음 내용을 수행한다:
- 애플리케이션 디렉토리에서
compose.yml
및 기타 일반적인compose
파일명을 검색. - 발견된
compose.yml
로docker compose
를 호출. - 지원되는 각 컨테이너에 대한 서비스 커넥션 빈 생성.
- 애플리케이션이 종료되면
docker compose stop
을 호출.
스프링 부트 지원이 올바르게 작동하려면 docker compose
또는 도커 컴포즈(docker-compose)
CLI 애플리케이션이 경로에 있어야 한다.
7.9.1. Service Connections
서비스 커넥션은 원격 서비스에 대한 커넥션이다. 스트링 부트의 오토 컨피규레이션(auto-configuration)
은 서비스 커넥션 디테일즈를 사용하고 이를 사용하여 원격 서비스에 대한 연결을 설정할 수 있다. 이렇게 하면 커넥션 디테일즈가 커넥션 관련 구성 프로퍼티스보다 우선된다.
스프링 부트의 도커 컴포즈 지원을 사용하면 컨테이너에 매핑된 포트에 서비스 커넥션이 설정된다.
도커 컴포즈는 일반적으로 컨테이너 내부의 포트가 컴퓨터의 임시 포트에 매핑되는 방식으로 사용된다. 예를 들어 포스트그레(Postgres) 서버는 포트 5432를 사용하여 컨테이너 내부에서 실행될 수 있지만 로컬에서는 완전히 다른 포트에 매핑될 수 있다. 서비스 커넥션은 항상 로컬로 매핑된 포트를 검색하고 사용한다.
서비스 커넥션은 컨테이너의 이미지명을 사용하여 설정된다. 현재 지원되는 서비스 커넥션은 다음과 같다:
커넥션 디테일즈 | 매칭타입 |
---|---|
카산드라커넥션디테일즈(CassandraConnectionDetails) | “cassandra”라는 명칭의 컨테이너 |
엘라스틱서치커넥션디테일즈(ElasticsearchConnectionDetails) | “elasticsearch”라는 명칭의 컨테이너 |
Jdbc커넥션디테일즈(JdbcConnectionDetails) | “gvenzl/oracle-xe”, “mariadb”, “mssql/server”, “mysql” 또는 “postgres”라는 명칭의 컨테이너 |
몽고커넥션디테일즈(MongoConnectionDetails) | “mongo”라는 명칭의 컨테이너 |
R2dbc커넥션디테일즈(R2dbcConnectionDetails) | “gvenzl/oracle-xe”, “mariadb”, “mssql/server”, “mysql” 또는 “postgres”라는 명칭의 컨테이너 |
레빗커넥션디테일즈(RabbitConnectionDetails) | “rabbitmq”라는 명칭의 컨테이너 |
레디스커넥션디테일즈(RedisConnectionDetails) | “redis”라는 명칭의 컨테이너 |
집킨커넥션디테일즈(ZipkinConnectionDetails) | “openzipkin/zipkin”라는 명칭의 컨테이너 |
7.9.2. Custom Images
때로는 서비스를 제공하기 위해 자신만의 이미지 버전을 사용해야 할 수도 있다. 표준 이미지와 동일한 방식으로 동작하는 커스텀 이미지를 사용할 수 있다. 특히 표준 이미지가 지원하는 모든 환경 변수는 커스텀 이미지에도 사용해야 한다.
이미지가 다른 이름을 사용하는 경우 스프링 부트가 서비스 커넥션을 제공할 수 있도록 compose.yml
파일의 레이블을 사용할 수 있다. org.springframework.boot.service-connection
이라는 레이블을 사용하여 서비스명을 제공한다.
예제:
services:
redis:
image: 'mycompany/mycustomredis:7.0'
ports:
- '6379'
labels:
org.springframework.boot.service-connection: redis
7.9.3. 특정 컨테이너 스킵(Skipping Specific Containers)
애플리케이션에 연결하지 않으려는 compose.yml
에 정의된 컨테이너 이미지가 있는 경우 레이블을 사용하여 이를 무시할 수 있다. org.springframework.boot.ignore
레이블이 붙은 모든 컨테이너는 스프링 부트에서 무시된다.
예제:
services:
redis:
image: 'redis:7.0'
ports:
- '6379'
labels:
org.springframework.boot.ignore: true
7.9.4. 특정 컴포즈 파일 사용(Using a Specific Compose File)
컴포즈 파일이 애플리케이션과 동일한 디렉토리에 없거나 이름이 다른 경우 application.properties
또는 application.yaml
에서 spring.docker.compose.file
을 사용하여 다른 파일을 가리킬 수 있다. 프로퍼티스는 정확한 패스(Path) 또는 애플리케이션과 관련된 패스로 정의할 수 있다.
예제:
프로퍼티스(Properties)
spring.docker.compose.file=../my-compose.yml
Yaml
spring:
docker:
compose:
file: "../my-compose.yml"
7.9.5. 컨테이너 준비를 기다림(Waiting for Container Readiness)
도커 컴포즈로 시작된 컨테이너는 완전히 준비되는 데 다소 시간이 걸릴 수 있다. 준비 상태를 확인하는 권장 방법은 compose.yml
파일의 서비스 정의 아래에 healthcheck
섹션을 추가하는 것이다.
compose.yml
파일에서 healthcheck
구성이 생략되는 것은 자주 있는 일이므로 스프링 부트는 서비스 준비 상태도 직접 확인한다. 기본적으로 매핑된 포트에 TCP/IP 커넥션을 설정할 수 있으면 컨테이너가 준비된 것으로 간주된다.
compose.yml
파일에 org.springframework.boot.readiness-check.tcp.disable
레이블을 추가하여 컨테이너별로 이를 비활성화할 수 있다.
예제:
services:
redis:
image: 'redis:7.0'
ports:
- '6379'
labels:
org.springframework.boot.readiness-check.tcp.disable: true
application.properties
또는 application.yaml
파일에서 제한시간 값을 변경할 수도 있다.
프로퍼티스(Properties)
spring.docker.compose.readiness.tcp.connect-timeout=10s
spring.docker.compose.readiness.tcp.read-timeout=5s
Yaml
spring:
docker:
compose:
readiness:
tcp:
connect-timeout: 10s
read-timeout: 5s
전체 제한시간은 spring.docker.compose.readiness.timeout
을 사용하여 구성할 수 있다.
7.9.6. 도커 컴포즈 생명주기 제어(Controlling the Docker Compose Lifecycle)
기본적으로 스프링 부트는 애플리케이션이 시작될 때 docker compose up
을 호출하고 애플리케이션이 종료될 때 docker compose stop
을 호출한다. 다양한 생명주기 관리를 선호한다면 spring.docker.compose.lifecycle-management
프로퍼티를 사용할 수 있다.
다음 값이 지원된다:
none
- 도커 컴포즈를 시작하거나 중지하지 않는다.start-only
- 애플리케이션이 시작될 때 도커 컴포즈를 시작하고 실행 상태로 둔다.start-and-stop
- 애플리케이션이 시작되면 도커 컴포즈를 시작하고 JVM이 종료되면 중지한다.
또한 spring.docker.compose.start.command
프로퍼티를 사용하여 docker compose up
또는 docker compose start
를 사용할지 여부를 변경할 수 있다. spring.docker.compose.stop.command
를 사용하면 docker compose down
을 사용할지 docker compose stop
을 사용할지 구성할 수 있다.
다음 예제는 생명주기 매니지먼트 구성 방법을 보여준다.
프로퍼티스(Properties)
spring.docker.compose.lifecycle-management=start-and-stop
spring.docker.compose.start.command=start
spring.docker.compose.stop.command=down
spring.docker.compose.stop.timeout=1m
Yaml
spring:
docker:
compose:
lifecycle-management: start-and-stop
start:
command: start
stop:
command: down
timeout: 1m
7.9.7. 도커 컴포즈 프로필 활성화(Activating Docker Compose Profiles)
도커 컴포즈 프로필은 특정 환경에 맞게 도커 컴포즈 구성을 조정할 수 있다는 점에서 스프링 프로필과 유사하다. 특정 도커 컴포즈 프로필을 활성화하려면 application.properties
또는 application.yaml
파일에서 spring.docker.compose.profiles.active
프로퍼티를 사용할 수 있다:
프로퍼티스(Properties)
spring.docker.compose.profiles.active=myprofile
Yaml
spring:
docker:
compose:
profiles:
active: "myprofile"
7.10. 나만의 자동 구성 만들기(Creating Your Own Auto-configuration)
공유 라이브러리를 개발하는 회사에서 일하거나 오픈 소스 또는 상용 라이브러리에서 작업하는 경우 자체 자동구성을 개발할 수 있다. 자동구성 클래스는 외부 jar에 번들로 묶일 수 있으며 여전히 스프링 부트에서 선택할 수 있다.
자동구성은 자동구성 코드와 함께 사용할 일반적인 라이브러리를 제공하는 “스타터(starter)”와 연결될 수 있다. 먼저 자체 자동구성을 구축하기 위해 알아야 할 사항을 다룬 다음 맞춤형 스타터를 생성하는 데 필요한 일반적인 단계로 넘어간다.
7.10.1. 자동 구성된 빈 이해(Understanding Auto-configured Beans)
자동구성을 구현하는 클래스에는 @AutoConfiguration
이라는 어노테이션이 붙는다. 이 어노테이션 자체는 @Configuration
으로 메타 어노테이션을 달아 자동구성을 표준 @Configuration
클래스로 만든다. 추가 @Conditional
어노테이션은 자동구성을 적용해야 하는 시기를 제한하는 데 사용된다. 일반적으로 자동구성 클래스는 @ConditionalOnClass
및 @ConditionalOnMissingBean
어노테이션을 사용한다. 이렇게 하면 관련 클래스가 발견되고 사용자 고유의 @Configuration
을 선언하지 않은 경우에만 자동 구성이 적용된다.
spring-boot-autoconfigure
의 소스 코드를 탐색하여 스프링이 제공하는 @AutoConfiguration
클래스를 볼 수 있다(META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
파일 참고).
7.10.2. 자동 구성 후보 찾기(Locating Auto-configuration Candidates)
스프링 부트는 게시된 jar 내에 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
파일이 있는지 확인한다. 파일에는 다음 예제와 같이 한 줄에 하나의 클래스명이 포함된 구성 클래스가 나열되어야 한다:
com.mycorp.libx.autoconfigure.LibXAutoConfiguration
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration
# 문자를 사용하여 임포트 파일에 주석을 추가할 수 있다.
자동 구성은 임포트 파일에 이름을 지정하고 로드되어야 한다. 특정 패키지 공간에 정의되어 있는지 확인하고 컴포넌트 스캐닝 대상이 아닌지 확인하자. 또한 자동 구성 클래스는 추가 컴포넌트를 찾기 위해 컴포넌트 스캐닝을 활성화해서는 안 된다. 대신 특정 @Import
어노테이션을 사용해야 한다.
구성을 특정 순서로 적용해야 하는 경우 @AutoConfiguration
어노테이션 또는 전용 @AutoConfigureBefore
및 @AutoConfigureAfter
어노테이션에서 before
, beforeName
, after
및 afterName
어노테이션을 사용할 수 있다. 예를 들어, 웹 관련 구성을 제공하는 경우 웹Mvc오토컨피규레이션(WebMvcAutoConfiguration)
후에 클래스를 적용해야 할 수도 있다.
직접적으로 알 수 없는 특정 자동 구성의 순서를 정하려면, @AutoConfigureOrder
를 사용할 수도 있다. 해당 어노테이션은 일반 @Order
주석과 동일한 의미를 갖지만 자동 구성 클래스에 대한 전용 순서를 제공한다.
표준 @Configuration
클래스와 마찬가지로 자동 구성 클래스가 적용되는 순서는 해당 빈이 정의되는 순서에만 영향을 미친다. 해당 빈이 이후에 생성되는 순서는 영향을 받지 않으며 각 빈의 의존성 및 @DependsOn
관계에 의해 결정된다.
7.10.3. 컨디션 어노테이션(Condition Annotations)
거의 항상 자동 구성 클래스에 하나 이상의 @Conditional
어노테이션을 포함하려고 한다. @ConditionalOnMissingBean
어노테이션은 개발자가 기본값이 만족스럽지 않은 경우 자동 구성을 오버라이드하는 데 사용되는 일반적인 예다.
스프링 부트에는 @Configuration
클래스 또는 개별 @Bean
메소드에 어노테이션을 달아 사용자 코드에서 재사용할 수 있는 다수의 @Conditional
어노테이션이 포함되어 있다. 이러한 어노테이션에는 다음이 포함된다:
- 클래스 조건(Class Conditions)
- 빈 조건(Bean Conditions)
- 프로퍼티 조건(Property Conditions)
- 리소스 조건(Resource Conditions)
- 웹 애플리케이션 조건(Web Application Conditions)
- SqEL 표현식 조건(SpEL Expression Conditions)
클래스 조건(Class Conditions)
@ConditionalOnClass
및 @ConditionalOnMissingClass
어노테이션을 사용하면 특정 클래스의 유무에 따라 @Configuration
클래스를 포함할 수 있다. 어노테이션 메타데이터는 ASM을 사용하여 파싱되므로 해당 클래스가 실행 중인 애플리케이션 클래스패스에 실제로 표시되지 않더라도 value
애트리뷰트을 사용하여 실제 클래스를 참조할 수 있다. 문자열 값을 사용하여 클래스명을 지정하려는 경우 name
애트리뷰트을 사용할 수도 있다.
이 메커니즘은 일반적으로 리턴 타입이 조건의 대상인 @Bean
메소드와 동일한 방식으로 적용되지 않는다. 메소드의 조건이 적용되기 전에 JVM은 클래스 및 잠재적으로 처리된 메소드 참조를 로드하며 존재하지 않고 클래스가 실패하면 실패한다.
이 상황를 처리하려면, 다음 예제와 같이 별도의 @Configuration
클래스를 사용하여 조건을 격리할 수 있다:
자바
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@AutoConfiguration
// 조건들 ...
public class MyAutoConfiguration {
// 자동구성 빈 ...
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(SomeService.class)
public static class SomeServiceConfiguration {
@Bean
@ConditionalOnMissingBean
public SomeService someService() {
return new SomeService();
}
}
}
코틀린
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration(proxyBeanMethods = false)
// 조건들 ...
class MyAutoConfiguration {
// 자동구성 빈 ...
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(SomeService::class)
class SomeServiceConfiguration {
@Bean
@ConditionalOnMissingBean
fun someService(): SomeService {
return SomeService()
}
}
}
@ConditionalOnClass
또는 @ConditionalOnMissingClass
를 메타 어노테이션의 일부로 사용하여 자신만의 어노테이션을 구성하는 경우, 클래스를 참조하므로 이름을 사용해야 한다.
빈 조건(Bean Conditions)
@ConditionalOnBean
및 @ConditionalOnMissingBean
어노테이션을 사용하면 특정 빈의 유무에 따라 빈을 포함할 수 있다. value
애트리뷰트를 사용하여 타입별 빈을 지정하거나 이름별 빈을 지정하려면 이름을 지정할 수 있다. search
애트리뷰트를 사용하면 빈을 검색할 때 고려해야 하는 애플리케이션컨텍스트(ApplicationContext)
계층 구조를 제한할 수 있다.
@Bean
메소드에 배치되면, 대상 타입은 다음 예제와 같이 메소드의 리턴 타입으로 기본 설정된다:
자바
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
@AutoConfiguration
public class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public SomeService someService() {
return new SomeService();
}
}
코틀린
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration(proxyBeanMethods = false)
class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
fun someService(): SomeService {
return SomeService()
}
}
앞의 예제에서는 SomeService
빈이 애플리케이션컨텍스트(ApplicationContext)
에 이미 포함되어 있지 않은 경우 someService
빈이 생성된다.
빈이 추가되는 순서에 매우 주의해야 한다. 이러한 조건은 지금까지 처리된 내용을 기반으로 작동하기 때문이다. 이러한 이유로 자동구성 클래스에는 @ConditionalOnBean
및 @ConditionalOnMissingBean
어노테이션만 사용하는 것이 좋다(이러한 어노테이션은 커스텀 빈 정의가 추가된 후에 로드되도록 보장되기 때문이다).
@ConditionalOnBean
및 @ConditionalOnMissingBean
은 @Configuration
클래스가 생성되는 것을 방지하지 않는다. 클래스 레벨에서 이러한 조건을 사용하는 것과 포함된 각 @Bean
메소드를 어노테이션으로 표시하는 것의 유일한 차이점은 전자가 조건이 일치하지 않으면, @Configuration
클래스를 빈으로 등록하는 것을 방지한다는 것이다.
@Bean
메소드를 선언할 때 메소드의 리턴 타입에 가능한 한 많은 유형 정보를 제공하자. 예를 들어, 빈의 구체적인 클래스가 인터페이스를 구현하는 경우 빈 메소드의 리턴 타입은 인터페이스가 아닌 구체적인 클래스여야 한다. @Bean
메소드에서 가능한 많은 유형 정보를 제공하는 것은 빈 조건을 사용할 때 특히 중요하다. 평가(evaluation)는 메소드 시그니처에서 사용 가능한 유형 정보에만 의존할 수 있기 때문이다.
프로퍼티 조건(Property Conditions)
@ConditionalOnProperty
어노테이션으로 사용하면 스프링 환경 프로퍼티를 기반 구성을 포함할 수 있다. 확인해야 할 프로퍼티를 지정하려면 접두사 및 name 애트리뷰트를 사용하자. 기본적으로 존재하고 false
와 같지 않은 모든 프로퍼티가 일치된다. haveValue
및 matchIfMissing
애트리뷰트를 사용하여 고급 검사를 생성할 수도 있다.
리소스 조건(Resource Conditions)
@ConditionalOnResource
어노테이션을 사용하면 특정 인스턴스가 있는 경우에만 구성을 포함할 수 있다. 다음 예제와 같이 일반적인 스프링 컨벤션을 사용하여 독점할 수 있다: 파일: /home/user/test.dat
.
웹 애플리케이션 조건(Web Application Conditions)
@ConditionalOnWebApplication
및 @ConditionalOnNotWebApplication
어노테이션을 사용하면 애플리케이션이 웹 애플리케이션인지 여부에 따라 구성이 포함될 수 있다. 서블릿 기반 웹 애플리케이션은 스프링 웹애플리케이션컨텍스트(WebApplicationContext)를 사용하거나, 세션 범위를 정의하거나, 컨피규러블웹인바이런먼트(ConfigurableWebEnvironment)
를 갖는 모든 애플리케이션이다. 리액티브 웹 애플리케이션은 리액티브웹애플리케이션컨텍스트(ReactiveWebApplicationContext)
를 사용하거나 컨피규러블리액티브웹인바이런먼트(ConfigurableReactiveWebEnvironment)
를 갖는 모든 애플리케이션이다.
@ConditionalOnWarDeployment
및 @ConditionalOnNotWarDeployment
어노테이션을 사용하면 애플리케이션이 서블릿 컨테이너에 배포되는 기존 WAR 애플리케이션인지 여부에 따라 구성이 포함될 수 있다. 이 조건은 임베디드 웹 서버와 함께 실행되는 애플리케이션에서는 일치하지 않는다.
SpEL 표현식 조건(SpEL Expression Conditions)
@ConditionalOnExpression
어노테이션을 사용하면 SpEL 표현식의 결과에 따라 구성을 포함할 수 있다.
표현식에서 빈을 참조하면 해당 빈이 컨텍스트 새로고침 처리의 초기에 초기화된다. 결과적으로 빈은 사후 처리(예: 구성 프로퍼티스 바인딩)에 적합하지 않으며 해당 상태가 불완전할 수 있다.
7.10.4. 자동 구성 테스트(Testing your Auto-configuration)
자동 구성은 사용자 구성(@Bean
정의 및 환경 커스텀), 조건 평가(condition evaluation)(특정 라이브러리 존재) 등 다양한 요소의 영향을 받을 수 있다. 구체적으로 각 테스트는 해당 커스텀 조합을 나타내는 잘 정의된 애플리케이션컨텍스트(ApplicationContext)
를 생성해야 한다. 애플리케이션컨텍스트러너(ApplicationContextRunner)
는 이를 달성할 수 있는 좋은 방법을 제공한다. 애플리케이션컨텍스트러너(ApplicationContextRunner)
는 일반적으로 기본 공통 구성을 수집하기 위한 테스트 클래스 필드로 정의된다. 다음 예에서는 MyServiceAutoConfiguration
이 항상 호출되는지 확인한다:
자바
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MyServiceAutoConfiguration.class));
코틀린
val contextRunner = ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MyServiceAutoConfiguration::class.java))
여러 자동 구성을 정의해야 하는 경우, 해당 선언은 애플리케이션을 실행할 때와 정확히 동일한 순서로 호출되므로 선언 순서를 지정할 필요가 없다.
각 테스트에서는 러너(runner)를 사용하여 특정 사례를 나타낼 수 있다. 예를 들어 아래 샘플은 커스텀(UserConfiguration
)을 호출하고 자동 구성이 제대로 백오프되는지 확인한다. run
을 호출하면 어설트제이(AssertJ)
와 함께 사용할 수 있는 콜백 컨텍스트가 제공된다.
자바
@Test
void defaultServiceBacksOff() {
this.contextRunner.withUserConfiguration(UserConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(MyService.class);
assertThat(context).getBean("myCustomService").isSameAs(context.getBean(MyService.class));
});
}
@Configuration(proxyBeanMethods = false)
static class UserConfiguration {
@Bean
MyService myCustomService() {
return new MyService("mine");
}
}
코틀린
@Test
fun defaultServiceBacksOff() {
contextRunner.withUserConfiguration(UserConfiguration::class.java)
.run { context: AssertableApplicationContext ->
assertThat(context).hasSingleBean(MyService::class.java)
assertThat(context).getBean("myCustomService")
.isSameAs(context.getBean(MyService::class.java))
}
}
@Configuration(proxyBeanMethods = false)
internal class UserConfiguration {
@Bean
fun myCustomService(): MyService {
return MyService("mine")
}
}
다음 예와 같이 환경을 쉽게 커스텀할 수도 있다:
자바
@Test
void serviceNameCanBeConfigured() {
this.contextRunner.withPropertyValues("user.name=test123").run((context) -> {
assertThat(context).hasSingleBean(MyService.class);
assertThat(context.getBean(MyService.class).getName()).isEqualTo("test123");
});
}
코틀린
@Test
fun serviceNameCanBeConfigured() {
contextRunner.withPropertyValues("user.name=test123").run { context:
AssertableApplicationContext ->
assertThat(context).hasSingleBean(MyService::class.java)
assertThat(context.getBean(MyService::class.java).name).isEqualTo("test123")
}
}
러너(Runner)
를 사용하여 컨디션이밸류에이션리포트(ConditionEvaluationReport)
를 표시할 수도 있다. 리포트는 INFO
또는 DEBUG
레벨로 표시될 수 있다. 다음 예에서는 컨디션이밸류에이션리포트로깅리스너(ConditionEvaluationReportLoggingListener)
를 사용하여 자동 구성 테스트에서 리포트를 표시하는 방법을 보여준다.
자바
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
class MyConditionEvaluationReportingTests {
@Test
void autoConfigTest() {
new ApplicationContextRunner()
.withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO))
.run((context) -> {
// Test something...
});
}
}
코틀린
import org.junit.jupiter.api.Test
import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
import org.springframework.boot.logging.LogLevel
import org.springframework.boot.test.context.assertj.AssertableApplicationContext
import org.springframework.boot.test.context.runner.ApplicationContextRunner
class MyConditionEvaluationReportingTests {
@Test
fun autoConfigTest() {
ApplicationContextRunner()
.withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO))
.run { context: AssertableApplicationContext? -> }
}
}
웹 컨텍스트 시뮬레이션(Simulating a Web Context)
서블릿이나 리액티브 웹 애플리케이션 컨텍스트에서만 작동하는 자동 구성을 테스트해야 하는 경우 각각 웹애플리케이션컨텍스트러너(WebApplicationContextRunner)
또는 리액티브웹애플리케이션컨텍스트러너(ReactiveWebApplicationContextRunner)
를 사용하자.
클래스패스 오버라이딩(Overriding the Classpath)
특정 클래스 및/또는 패키지가 런타임에 존재하지 않을 때 어떤 일이 발생하는지 테스트하는 것도 가능하다. 스프링 부트에는 러너(runner)가 쉽게 사용할 수 있는 필터드클래스로더(FilteredClassLoader)
가 함께 제공된다. 다음 예에서는 MyService
가 없으면 자동 구성이 적절하게 비활성화되는 것을 어설트(assert)한다:
자바
@Test
void serviceIsIgnoredIfLibraryIsNotPresent() {
this.contextRunner.withClassLoader(new FilteredClassLoader(MyService.class))
.run((context) -> assertThat(context).doesNotHaveBean("myService"));
}
코틀린
@Test
fun serviceIsIgnoredIfLibraryIsNotPresent() {
contextRunner.withClassLoader(FilteredClassLoader(MyService::class.java))
.run { context: AssertableApplicationContext? ->
assertThat(context).doesNotHaveBean("myService")
}
}
7.10.5. 나만의 스타터 만들기(Creating Your Own Starter)
일반적인 스프링 부트 스타터에는 “acme”라 부르는 특정 기술의 인프라를 자동 구성하고 커스텀된 코드가 포함되어 있다. 쉽게 확장할 수 있도록 전용 네임스페이스의 여러 구성 키를 환경에 노출할 수 있다. 마지막으로, 사용자가 최대한 쉽게 시작할 수 있도록 단일 “스타터” 의존성이 제공된다.
구체적으로, 커스텀 스타터에는 다음이 포함될 수 있다:
- “acme”에 대한 자동 구성 코드가 포함된 자동 구성 모듈이다.
- 자동 구성 모듈과 “acme” 및 일반적으로 유용한 추가 의존성을 제공하는 시작 모듈이다. 간단히 말해서, 스타터를 추가하면 해당 라이브러리 사용을 시작하는 데 필요한 모든 것이 제공된다.
이렇게 두 모듈을 분리할 필요는 없다. “acme”에 여러 가지 특징, 옵션 또는 옵셔널 기능이 있는 경우, 일부 기능이 옵셔널이라는 사실을 명확하게 표현할 수 있으므로 자동 구성을 분리하는 것이 좋다. 게다가 이러한 선택적 의존성에 대한 의견을 제공하는 스타터를 만들 수도 있다. 동시에 다른 사람들은 자동 구성 모듈에만 의존하여 다른 의견으로 자신만의 스타터를 만들 수 있다.
자동 구성이 상대적으로 간단하고 옵셔널 기능이 없는 경우 스타터에서 두 모듈을 병합하는 것은 선택 사항이다.
네이밍(Naming)
스타터에 적절한 네임스페이스를 제공해야 한다. 다른 메이븐 그룹 ID를 사용하더라도 spring-boot
로 모듈명을 시작하지 말자. 향후 자동 구성에 대한 공식적인 지원을 제공할 수도 있다.
경험상 결합된 모듈명은 스타터 이름을 따라 지정해야 한다. 예를 들어, “acme”에 대한 스타터를 생성하고 자동 구성 모듈명을 acme-spring-boot
로 지정하고 스타터명을 acme-spring-boot-starter
로 지정한다고 가정하자. 두 모듈을 결합한 모듈이 하나만 있는 경우 이름을 acme-spring-boot-starter
로 지정한다.
구성 키(Configuration keys)
스타터가 구성 키를 제공하는 경우 고유한 네임스페이스를 사용하자. 특히, 스프링 부트가 사용하는 네임스페이스(예: 서버, 관리, 스프링 등)에 키를 포함하지 말자. 동일한 네임스페이스를 사용하는 경우 향후 모듈을 손상시키는 방식으로 이러한 네임스페이스를 수정할 수 있다. 경험상 모든 키 앞에는 자신이 소유한 네임스페이스(예: acme)를 붙인다.
다음 예와 같이 각 프로퍼티에 대해 자바독(javadoc) 필드를 추가하여 구성 키가 문서화됐는지 확인하자:
자바
import java.time.Duration;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("acme")
public class AcmeProperties {
/**
* acme 리소스의 위치를 확인할지 여부.
*/
private boolean checkLocation = true;
/**
* acme 서버에 대한 연결 제한시간 설정.
*/
private Duration loginTimeout = Duration.ofSeconds(3);
public boolean isCheckLocation() {
return this.checkLocation;
}
public void setCheckLocation(boolean checkLocation) {
this.checkLocation = checkLocation;
}
public Duration getLoginTimeout() {
return this.loginTimeout;
}
public void setLoginTimeout(Duration loginTimeout) {
this.loginTimeout = loginTimeout;
}
}
코틀린
import org.springframework.boot.context.properties.ConfigurationProperties
import java.time.Duration
@ConfigurationProperties("acme")
class AcmeProperties(
/**
* acme 리소스의 위치를 확인할지 여부.
*/
var isCheckLocation: Boolean = true,
/**
* acme 서버에 대한 연결 제한시간 설정.
*/
var loginTimeout: Duration = Duration.ofSeconds(3))
@ConfigurationProperties
필드 자바독(Javadoc)에는 일반 텍스트만 사용해야 한다. 제이슨(JSON)에 추가되기 전에는 처리되지 않기 때문이다.
설명의 일관성을 유지하기 위해 내부적으로 따르는 몇 가지 규칙은 다음과 같다.
- 설명(description)을 “The” 또는 “A”로 시작하지 말자.
- 부울(boolean) 타입의 경우, “Whether” 또는 “Enable” 로 설명을 시작한다.
- 컬렉션 기반 타입의 경우, “쉼표로 구분된 목록”으로 설명을 시작한다.
long
대신java.time.Duration
을 사용하고 “기간(duration) 접미사가 지정되지 않으면 초(seconds) 사용”와 같이 밀리초(milliseconds)와 다른 경우 기본 단위(default unit)를 설명한다.- 런타임에 결정해야 하는 경우가 아니면 설명(description)에 기본값을 제공하지 말자.
키에 대해서도 IDE 지원을 사용할 수 있도록 메타데이터 생성을 트리거해야 한다. 생성된 메타데이터(META-INF/spring-configuration-metadata.json
)를 검토하여 키가 제대로 문서화되었는지 확인할 수 있다. 호환되는 IDE에서 자신만의 스타터를 사용하는 것도 메타데이터의 품질을 검증하는 좋은 아이디어다.
“자동 구성” 모듈(The “autoconfigure” Module)
자동 구성 모듈에는 라이브러리를 시작하는 데 필요한 모든 것이 포함되어 있다. 또한 구성 키 정의(예: @ConfigurationProperties
)와 컴포넌트 초기화를 추가로 커스텀하는 데 사용할 수 있는 콜백 인터페이스가 포함될 수도 있다.
프로젝트에 자동 구성 모듈을 더 쉽게 포함할 수 있도록 라이브러리에 대한 의존성을 선택 사항으로 표시해야 한다. 그런 식으로 수행하면 라이브러리가 제공되지 않으며 기본적으로 스프링 부트가 백오프된다.
스프링 부트는 어노테이션 프로세서를 사용하여 메타데이터 파일(META-INF/spring-autoconfigure-metadata.properties
)의 자동 구성 조건을 수집한다. 해당 파일이 있는 경우 일치하지 않는 자동 구성을 필터링하므로 시작 시간이 향상된다.
메이븐으로 빌드할 때 자동 구성이 포함된 모듈에 다음 의존성을 추가하는 것이 좋다:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
<optional>true</optional>
</dependency>
애플리케이션에서 자동 구성을 직접 정의한 경우 repackage
골(goal)이 fat jar
에 의존성을 추가하지 못하도록 spring-boot-maven-plugin
을 구성해야 한다:
<project>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
그레이들을 사용하면, 다음 예와 같이 어노테이션프로서세(AnnotationProcessor)
구성에서 의존성을 선언해야 한다:
그레이들
dependencies {
annotationProcessor "org.springframework.boot:spring-boot-autoconfigure-processor"
}
스타터 모듈(Starter Module)
스타터는 실제로 엠프티(empty) jar이다. 유일한 목적은 라이브러리 작업에 필요한 의존성을 제공하는 것이다. 시작하는 데 필요한 것이 무엇인지에 대한 독단적인 견해라고 생각할 수 있다.
스타터가 추가되는 프로젝트에 대해 가정하지 말자. 자동 구성 중인 라이브러리에 일반적으로 다른 스타터가 필요한 경우 해당 라이브러리도 언급해야 한다. 옵셔널 의존성의 수가 많으면 적절한 기본 의존성 세트를 제공하는 것이 어려울 수 있다. 일반적인 라이브러리 사용에 불필요한 의존성을 포함하지 않아야 하기 때문이다. 즉, 옵셔널 의존성을 포함해서는 안 된다.
어느 쪽이든 스타터는 핵심 스프링 부트 스타터(spring-boot-starter)를 직접 또는 간접적으로 참조해야 한다(스타터가 다른 스타터에 의존하는 경우 이를 추가할 필요가 없다). 커스텀 스타터만으로 프로젝트를 생성하는 경우 스프링 부트의 코어 기능은 코어 스타터의 존재로 인해 추가된다.
7.11. 코틀린 지원(Kotlin Support)
코틀린은 JVM(및 기타 플랫폼)을 대상으로 하는 정적 타입 언어로, 자바로 작성된 기존 라이브러리와의 상호 운용성을 제공하면서 간결하고 우아한 코드를 작성할 수 있다.
스프링 부트는 스프링 프레임워크, 스프링 데이터, 리액터와 같은 다른 스프링 프로젝트의 지원을 활용하여 코틀린 지원을 제공한다. 자세한 내용은 스프링 프레임워크 코틀린 지원 문서를 참고하자.
스프링 부트와 코틀린을 시작하는 가장 쉬운 방법은 이 포괄적인 튜토리얼을 따르는 것이다. start.spring.io
를 사용하여 새로운 코틀린 프로젝트를 만들 수 있다. 지원이 필요한 경우 언제든지 코틀린 슬랙의 #spring
채널에 가입하거나 스택 오버플로우(Stack Overflow)
에서 spring
및 kotlin
태그로 질문하자.
7.11.1. 요구사항(Requirements)
스프링 부트에는 코트린 1.7.x 이상이 필요하며 의존성 관리를 통해 적합한 코틀린 버전을 관리한다. 코틀린을 사용하려면 org.jetbrains.kotlin:kotlin-stdlib
및 org.jetbrains.kotlin:kotlin-reflect
가 클래스패스에 있어야 한다. kotlin-stdlib
변형인 kotlin-stdlib-jdk7
및 kotlin-stdlib-jdk8
도 사용할 수 있다.
코틀린 클래스는 기본적으로 최종 클래스이므로 스프링 어노테이션이 달린 클래스를 자동으로 열어 프록시할 수 있도록 kotlin-spring
플러그인을 구성할 수 있다.
코틀린에서 제이슨(JSON)
데이터를 직렬화(serializing)/역직렬화(deserializing)하려면 잭슨(Jackson)의 코틀린 모듈이 필요하다. 클래스패스에서 발견되면 자동으로 등록된다. 잭슨(Jackson)과 코틀린은 있지만 잭슨 코틀린(Jackson Kotlin) 모듈은 없으면 경고 메시지가 나타난다.
이러한 의존성과 플러그인은 start.spring.io
에서 코틀린 프로젝트를 부트스트랩하는 경우 기본적으로 제공된다.
7.11.2. 널 안전성(Null-safety)
코틀린의 주요 기능 중 하나는 널 안전성(null-safety)
이다. 문제를 런타임으로 미루고 널포인터익셉션(NullPointerException)
이 발생하는 대신 컴파일 타임에 null
값을 처리한다. 이는 옵셔널(Optional)
과 같은 래퍼 비용을 지불하지 않고도 일반적인 버그 소스를 제거하는 데 도움이 된다. 코틀린에서는 코틀린의 널 안전성
에 대한 포괄적인 가이드에 설명된 대로 null
허용 값이 있는 기능적 구성을 사용할 수도 있다.
자바는 타입 시스템에서 널 안전성을 표현하는 것을 허용하지 않지만 스프링 프레임워크, 스프링 데이터 및 리액터는 이제 도구 친화적인 어노테이션을 통해 API의 널 안전성을 제공한다. 기본적으로 코틀린에서 사용되는 자바 API의 타입은 null 검사가 완화되는 플랫폼 타입으로 인식된다. null 허용 여부 어노테이션과 결합된 JSR 305 어노테이션에 대한 코틀린 지원은 코틀린의 관련 스프링 API에 null 안전성
을 제공한다.
JSR 305
체크는 -Xjsr305={strict|warn|ignore}
옵션과 함께 -Xjsr305
컴파일러 플래그를 추가하여 구성할 수 있다. 기본 동작은 -Xjsr305=warn
과 동일하다. 스프링 API에서 유추된 코틀린 타입에서 null 안전성을 고려하려면 엄격한 값이 필요하지만 스프링 API null 허용 여부 선언은 마이너 릴리스 간에도 발전할 수 있으며 향후 더 많은 검사가 추가될 수 있다는 점을 염두에 두고 사용해야 한다.
일반 타입 아규먼트, varargs
및 배열 엘리먼트 null 허용 여부는 아직 지원되지 않는다. 최신 정보는 SPR-15942
를 참고하자. 또한 스프링 부트의 자체 API에는 아직 어노테이션이 추가되지 않았다.
7.11.3. 코틀린(Kotlin) API
runApplication
스프링 부트는 다음 예제와 같이 runApplication
코틀린
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class MyApplication
fun main(args: Array<String>) {
runApplication<MyApplication>(*args)
}
이는 SpringApplication.run(MyApplication::class.java, *args)
에 대한 드롭인 대체품(drop-in replacement)이다. 또한 다음 예와 같이 애플리케이션을 커스텀할 수 있다:
코틀린
runApplication<MyApplication>(*args) {
setBannerMode(OFF)
}
확장(Extensions)
코틀린 확장은 추가 기능으로 기존 클래스를 확장하는 기능을 제공한다. 스프링 부트 코틀린 API는 이러한 확장을 사용하여 기존 API에 새로운 코틀린 관련 편의성을 추가한다.
스프링 프레임워크의 레스트오퍼레이션(RestOperations)
용 스프링 프레임워크에서 제공하는 것과 유사한 테스트레스트템플릿(TestRestTemplate)
확장이 제공된다. 무엇보다도 확장 기능을 사용하면 코틀린의 구체화된 타입 파라미터를 활용할 수 있다.
7.11.4. 의존성 관리(Dependency management)
클래스패스에서 다양한 버전의 코틀린 의존성이 혼합되는 것을 방지하기 위해 스프링 부트는 코틀린 BOM을 가져온다.
메이븐을 사용하면 kotlin.version
프로퍼티을 설정하여 코틀린 버전을 맞춤설정할 수 있으며, kotlin-maven-plugin
에 대한 플러그인 관리가 제공된다. 그레이들을 사용하면 스프링 부트 플러그인이 kotlin.version
을 코틀린 플러그인 버전에 자동으로 맞춘다.
스프링 부트는 또한 코틀린 코루틴(Coroutines) BOM을 가져와서 코루틴(Coroutines) 의존성 버전을 관리한다. 버전은 kotlin-coroutines.version
프로퍼티를 설정하여 맞춤설정할 수 있다.
org.jetbrains.kotlinx:kotlinx-coroutines-reactor
의존성은 start.spring.io
에서 하나 이상의 리액티브 의존성을 사용하여 코틀린 프로젝트를 부트스트랩하는 경우 기본적으로 제공된다.
7.11.5. @ConfigurationProperties
@ConfigurationProperties
를 생성자 바인딩(constructor binding)과 함께 사용하면 다음 예제와 같이 변경할 수 없는 val 프로퍼티가 있는 클래스를 지원한다:
코틀린
@ConfigurationProperties("example.kotli")
data class KotlinExampleProperties(
val name: String,
val description: String,
val myService: MyService) {
data class MyService(
val apiToken: String,
val uri: URI
)
}
어노테이션 프로세서를 사용하여 자신만의 메타데이터를 생성하려면 kapt
를 spring-boot-configuration-processor
의존성으로 구성해야 한다. kapt
가 제공하는 모델의 제한으로 인해 일부 기능(예: 기본값 또는 디프리케이트 아이템 감지)이 작동하지 않는다.
7.11.6. 테스팅(Testing)
제이유닛(JUnit) 4를 사용하여 코틀린 코드를 테스트할 수 있지만, 제이유닛(JUnit) 5가 기본적으로 제공되며 권장된다. 제이유닛(JUnit) 5를 사용하면 테스트 클래스를 한 번 인스턴스화하고 모든 클래스 테스트에 재사용할 수 있다. 이를 통해 논스태틱(non-static) 메서드에 @BeforeAll 및 @AfterAll 어노테이션을 사용할 수 있으며 이는 코틀린에 적합하다.
코틀린 클래스를 모킹하려면 목케이(MockK)
를 권장한다. 모키토(Mockito)
특정 @MockBean
및 @SpyBean
어노테이션과 동등한 목케이(MockK)
가 필요한 경우 유사한 @MockkBean
및 @SpykBean
어노테이션을 제공하는 스프링목케이(SpringMockK)
를 사용할 수 있다.
7.11.7. 리소스(Resources)
더 나아가기 위해 읽어볼것
- 코틀린 레퍼런스(Kotlin language reference)
- 코틀린 슬랙(Kotlin Slack (with a dedicated #spring channel))
- 스택 오버플로우(Stack Overflow with
spring
andkotlin
tags) - 브라우저에서 코틀린 사용해 보기(Try Kotlin in your browser)
- 코틀린 블로그(Kotlin blog)
- 어썸 코틀린(Awesome Kotlin)
- 튜토리얼: 스프링 부트와 코틀린을 사용하여 웹 애플리케이션 구축(Tutorial: building web applications with Spring Boot and Kotlin)
- 코틀린을 사용하여 스프링 부트 애플리케이션 개발(Developing Spring Boot applications with Kotlin)
- 코틀린, 스프링부트 및 포스트그레(Postgre)SQL을 사용한 지리공간 메신저(A Geospatial Messenger with Kotlin, Spring Boot and PostgreSQL)
- 스프링 프레임워크 5.0의 코틀린 지원 소개(Introducing Kotlin support in Spring Framework 5.0)
- 스프링 프레임워크 5 코트린 API, 기능적 방식(Spring Framework 5 Kotlin APIs, the functional way)
예제
spring-boot-kotlin-demo
: 일반 스프링 부트 + 스프링 데이터 JPA 프로젝트mixit
: 스프링 부트 2 + 웹플럭스 + 리액티브 스프링 데이터 MongoDBspring-kotlin-fullstack
: 자바스크립트 또는 타입스크립트 대신 프런트엔드용 코틀린2js를 사용하는 웹플럭스 코틀린 풀스택 예시spring-petclinic-kotlin
: 스프링 PetClinic 샘플 애플리케이션의 코틀린 버전spring-kotlin-deepdive
: 부트 1.0 + 자바에서 부트 2.0 + 코틀린으로 단계별 마이그레이션spring-boot-coroutines-demo
: 코루틴 프로젝트 샘플
7.12. SSL
스프링 부트는 보안 통신(secure communications)을 지원하기 위해 여러 타입의 커넥션에 적용할 수 있는 SSL 신뢰 자료(trust material)를 구성하는 기능을 제공한다. 접두사가 spring.ssl.bundle
인 구성 프로퍼티스을 사용하여 명명된 신뢰 자료 및 관련 정보 집합을 지정할 수 있다.
7.12.1. 자바 키스토어 파일로 SSL 구성(Configuring SSL With Java KeyStore Files)
spring.ssl.bundle.jks
접두사가 있는 구성 프로퍼티스를 사용하면 자바 keytool
유틸리티로 생성되고 JKS
또는 PKCS12
포맷의 자바 키스토어(KeyStore) 파일에 저장된 신뢰 자료(trust material) 번들을 구성할 수 있다. 각 번들에는 번들을 참조하는 데 사용할 수 있는 사용자 제공 이름이 있다.
임베디드 웹 서버를 보호하는 데 사용되는 경우 키스토어는 일반적으로 다음 예에 표시된 대로 인증서와 개인 키가 포함된 자바 키스토어로 구성된다:
프로퍼티스(Properties)
spring.ssl.bundle.jks.mybundle.key.alias=application
spring.ssl.bundle.jks.mybundle.keystore.location=classpath:application.p12
spring.ssl.bundle.jks.mybundle.keystore.password=secret
spring.ssl.bundle.jks.mybundle.keystore.type=PKCS12
Yaml
spring:
ssl:
bundle:
jks:
mybundle:
key:
alias: "application"
keystore:
location: "classpath:application.p12"
password: "secret"
type: "PKCS12"
클라이언트 측 커넥션을 보호하는 데 사용되는 경우 트러스트스토어(truststore)
는 일반적으로 다음 예제에 표시된 대로 서버 인증서가 포함된 자바 키스토어(KeyStore)로 구성된다:
프로퍼티스(Properties)
spring.ssl.bundle.jks.mybundle.truststore.location=classpath:server.p12
spring.ssl.bundle.jks.mybundle.truststore.password=secret
Yaml
spring:
ssl:
bundle:
jks:
mybundle:
truststore:
location: "classpath:server.p12"
password: "secret"
지원되는 전체 프로퍼티스 세트는 JksSslBundleProperties
를 참고하자.
7.12.2. PEM 인코딩 인증서로 SSL 구성(Configuring SSL With PEM-encoded Certificates)
접두사(prefix)가 spring.ssl.bundle.pem
인 구성 프로퍼티스를 사용하여 PEM 인코딩된 텍스트 포맷의 신뢰 자료 번들을 구성할 수 있다. 각 번들에는 번들을 참조하는 데 사용할 수 있는 사용자 제공 이름이 있다.
임베디드 웹 서버를 보호하는 데 사용되는 경우, 키스토어는 일반적으로 다음 예에 표시된 대로 인증서와 개인 키로 구성된다:
프로퍼티스(Properties)
spring.ssl.bundle.pem.mybundle.keystore.certificate=classpath:application.crt
spring.ssl.bundle.pem.mybundle.keystore.private-key=classpath:application.key
Yaml
spring:
ssl:
bundle:
pem:
mybundle:
keystore:
certificate: "classpath:application.crt"
private-key: "classpath:application.key"
클라이언트 측 커넥션을 보호하는 데 사용되는 경우 트러스트스토어(truststore)는 일반적으로 다음 예에 표시된 대로 서버 인증서로 구성된다:
프로퍼티스(Properties)
spring.ssl.bundle.pem.mybundle.truststore.certificate=classpath:server.crt
Yaml
spring:
ssl:
bundle:
pem:
mybundle:
truststore:
certificate: "classpath:server.crt"
지원되는 전체 프로퍼티스 세트는 PemSslBundleProperties
를 참고하자.
7.12.3. SSL 번들 적용(Applying SSL Bundles)
프로퍼티스를 사용하여 구성한 후에 스프링 부트에서 자동으로 구성되는 다양한 타입의 커넥션에 대한 구성 프로퍼티스에서 SSL 번들을 이름으로 참조할 수 있다. 자세한 내용은 임베디드 웹 서버, 데이터 기술 및 REST 클라이언트 장을 참고하자.
7.12.4. SSL 번들 사용(Using SSL Bundles)
스프링 부트는 spring.ssl.bundle
프로퍼티스를 사용하여 구성된 명명된 각 번들에 대한 접근를 제공하는 SslBundles
타입의 빈을 자동 구성한다.
SslBundle
은 자동 구성된 SslBundles
빈에서 검색하여 클라이언트 라이브러리에서 SSL 커넥션을 구성하는 데 사용되는 객체를 생성하는 데 사용할 수 있다. SslBundle
은 이러한 SSL 개체를 얻기 위한 계층화된 접근 방식을 제공한다:
- getStores()는 키스토어(KeyStore) 및 트러스트스토어(trust store)
java.security.KeyStore
인스턴스는 물론 필수 키스토어 비밀번호에 대한 접근을 제공한다. - getManagers()는
java.net.ssl.KeyManagerFactory
및java.net.ssl.TrustManagerFactory
인스턴스뿐 아니라 이들이 생성하는java.net.ssl.KeyManager
및java.net.ssl.TrustManager
배열에 대한 접근을 제공한다. - createSslContext()는 새로운
java.net.ssl.SSLContext
인스턴스를 얻는 편리한 방법을 제공한다.
또한, SslBundle
은 사용되는 키, 사용할 프로토콜 및 SSL 엔진에 적용해야 하는 옵션에 대한 세부 정보를 제공한다.
다음 예에서는 SslBundle
을 검색하고 이를 사용하여 SSLContext
를 생성하는 방법을 보여준다:
자바
import javax.net.ssl.SSLContext;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.stereotype.Component;
@Component
public class MyComponent {
public MyComponent(SslBundles sslBundles) {
SslBundle sslBundle = sslBundles.getBundle("mybundle");
SSLContext sslContext = sslBundle.createSslContext();
// 생성된 sslContext로 하는 어떤 작업
}
}
코틀린
import org.springframework.boot.ssl.SslBundles
import org.springframework.stereotype.Component
@Component
class MyComponent(sslBundles: SslBundles) {
init {
val sslBundle = sslBundles.getBundle("mybundle")
val sslContext = sslBundle.createSslContext()
// 생성된 sslContext로 하는 어떤 작업
}
}
7.13. 다음에 읽을 내용(What to Read Next)
이 장에서 논의된 클래스에 대해 자세히 알아보려면 스프링 부트 API 문서를 참고하거나 소스 코드를 직접 찾아볼 수 있다. 구체적인 질문이 있는 경우 하우투(how-to) 장을 참고하자.
스프링 부트의 코어 기능에 익숙하다면 계속해서 프로덕션 지원 기능에 대해 읽어보자.