- 18 “사용” 가이드(“How-to” Guides)
- 18.1. 스프링 부트 애플리케이션(Spring Boot Application)
- 18.1.1. 나만의 FailureAnalyzer 만들기(Create Your Own FailureAnalyzer)
- 18.1.2. 자동 구성 문제 해결(Troubleshoot Auto-configuration)
- 18.1.3. 시작하기 전 환경 또는 ApplicationContext 커스텀(Customize the Environment or ApplicationContext Before It Starts)
- 18.1.4. ApplicationContext 계층 구조 구축(상위 또는 루트 컨텍스트 추가)(Build an ApplicationContext Hierarchy (Adding a Parent or Root Context))
- 18.1.5. 웹이 아닌 애플리케이션 만들기(Create a Non-web Application)
- 18.2. 프로퍼티스와 구성(Properties and Configuration)
- 18.2.1. 빌드 시 자동으로 프로퍼티스 확장(Automatically Expand Properties at Build Time)
- 메이븐을 사용한 자동 프로퍼티 확장(Automatic Property Expansion Using Maven)
- 그레이들을 사용한 자동 프로퍼티 확장(Automatic Property Expansion Using Gradle)
- 18.2.2. SpringApplication 구성 외부화(Externalize the Configuration of SpringApplication)
- 18.2.3. 애플리케이션의 외부 프로퍼티스 위치 변경(Change the Location of External Properties of an Application)
- 18.2.4. ‘짧은’ 커맨드라인 아규먼트 사용(Use ‘Short’ Command Line Arguments)
- 18.2.5. 외부 프로퍼티스에 YAML 사용(Use YAML for External Properties)
- 18.2.6. 활성 스프링 프로필 설정(Set the Active Spring Profiles)
- 18.2.7. 기본 프로필명 설정(Set the Default Profile Name)
- 18.2.8. 환경에 따라 구성 변경(Change Configuration Depending on the Environment)
- 18.2.9. 외부 프로퍼티스에 대한 빌트인 옵션 살펴보기(Discover Built-in Options for External Properties)
- 18.2.1. 빌드 시 자동으로 프로퍼티스 확장(Automatically Expand Properties at Build Time)
- 18.3. 임베디드 웹 서버(Embedded Web Servers)
- 18.3.1. 다른 웹서버 사용(Use Another Web Server)
- 18.3.2. 웹 서버 비활성화(Disabling the Web Server)
- 18.3.3. HTTP 포트 변경(Change the HTTP Port)
- 18.3.4. 할당되지 않은 무작위 HTTP 포트 사용(Use a Random Unassigned HTTP Port)
- 18.3.5. 런타임 시 HTTP 포트 검색(Discover the HTTP Port at Runtime)
- 18.3.6. HTTP 응답 압축 활성화(Enable HTTP Response Compression)
- 18.3.7. SSL 구성(Configure SSL)
- 18.3.8. HTTP/2 구성(Configure HTTP/2)
- HTTP/2와 톰캣(HTTP/2 With Tomcat)
- HTTP/2와 제티(HTTP/2 With Jetty)
- 리액터 네티와 HTTP/2(HTTP/2 With Reactor Netty)
- 언더토우와 HTTP/2(HTTP/2 With Undertow)
- 18.3.9. 웹서버 구성(Configure the Web Server)
- 18.3.10. 애플리케이션에 서블릿, 필터 또는 리스너 추가(Add a Servlet, Filter, or Listener to an Application)
- 스프링 빈을 사용하여 서블릿, 필터 또는 리스너 추가(Add a Servlet, Filter, or Listener by Using a Spring Bean)
- 클래스패스 스캐닝으로 서블릿, 필터 및 리스너 추가(Add Servlets, Filters, and Listeners by Using Classpath Scanning)
- 18.3.11. 접근 로깅 구성(Configure Access Logging)
- 18.3.12. 프런트 엔드 프록시 서버 뒤에서 실행(Running Behind a Front-end Proxy Server)
- 톰캣의 프록시 구성 커스텀(Customize Tomcat’s Proxy Configuration)
- 18.3.13. 톰캣으로 다중 커넥터 활성화(Enable Multiple Connectors with Tomcat)
- 18.3.14. 톰캣의 MBean 레지스트리 활성화(Enable Tomcat’s MBean Registry)
- 18.3.15. 언더토우로 다중 리스너 활성화(Enable Multiple Listeners with Undertow)
- 18.3.16. @ServerEndpoint를 사용하여 웹소켓 엔드포인트 생성(Create WebSocket Endpoints Using @ServerEndpoint)
- 18.4. 스프링 MVC(Spring MVC)
- 18.4.1. JSON REST 서비스 작성(Write a JSON REST Service)
- 18.4.2. XML REST 서비스 작성(Write an XML REST Service)
- 18.4.3. 잭슨 오브젝트매퍼 커스텀(Customize the Jackson ObjectMapper)
- 18.4.4. @ResponseBody 렌더링 커스텀(Customize the @ResponseBody Rendering)
- 18.4.5. 멀티파트 파일 업로드 처리(Handling Multipart File Uploads)
- 18.4.6. 스프링 MVC DispatcherServlet 끄기(Switch Off the Spring MVC DispatcherServlet)
- 18.4.7. 기본 MVC 구성 끄기(Switch off the Default MVC Configuration)
- 18.4.8. 뷰리졸버 커스텀(Customize ViewResolvers)
- 18.5. 저지(Jersey)
- 18.5.1. 스프링 시큐리티로 저지 엔드포인트 보호(Secure Jersey endpoints with Spring Security)
- 18.5.2. 다른 웹 프레임워크와 함께 저지 사용(Use Jersey Alongside Another Web Framework)
- 18.6. HTTP 클라이언트(HTTP Clients)
- 18.6.1. 프록시를 사용하도록 레스트템플릿 구성(Configure RestTemplate to Use a Proxy)
- 18.6.2. 리액터 네티 기반 웹클라이언트에서 사용되는 TcpClient 구성(Configure the TcpClient used by a Reactor Netty-based WebClient)
- 18.7. 로깅(Logging)
- 18.7.1. 로깅을 위한 로그백 구성(Configure Logback for Logging)
- 파일 전용 출력을 위한 로그백 구성(Configure Logback for File-only Output)
- 18.7.2. 로깅을 위해 Log4j 구성(Configure Log4j for Logging)
- YAML 또는 JSON을 사용하여 Log4j 2 구성(Use YAML or JSON to Configure Log4j2)
- 복합 구성을 사용하여 Log4j2 구성(Use Composite Configuration to Configure Log4j2)
- 18.7.1. 로깅을 위한 로그백 구성(Configure Logback for Logging)
- 18.8. 데이터 접근(Data Access)
- 18.8.1. 커스텀 데이터소스 구성(Configure a Custom DataSource)
- 18.8.2. 두 가지 데이터소스 구성(Configure Two DataSources)
- 18.8.3. 스프링 데이터 리포지터리 사용(Use Spring Data Repositories)
- 18.8.4. 스프링 구성에서 @Entity 정의 분리(Separate @Entity Definitions from Spring Configuration)
- 18.8.5. JPA 프로퍼티 구성(Configure JPA Properties)
- 18.8.6. 하이버네이트 네이밍 전략 구성(Configure Hibernate Naming Strategy)
- 18.8.7. 하이버네이트 2차 레벨 캐싱 구성(Configure Hibernate Second-Level Caching)
- 18.8.8. 하이버네이트 컴포넌트에서 의존성 주입 사용(Use Dependency Injection in Hibernate Components)
- 18.8.9. 커스텀 EntityManagerFactory 사용(Use a Custom EntityManagerFactory)
- 18.8.10. 여러 EntityManagerFactory 사용(Using Multiple EntityManagerFactories)
- 18.8.11. 기존 persistence.xml 파일 사용(Use a Traditional persistence.xml File)
- 18.8.12. 스프링 데이터 JPA 및 몽고 리포지터리 사용(Use Spring Data JPA and Mongo Repositories)
- 18.8.13. 스프링 데이터의 웹 지원 커스텀(Customize Spring Data’s Web Support)
- 18.8.14. 스프링 데이터 리포지터리를 REST 엔드포인트로 노출(Expose Spring Data Repositories as REST Endpoint)
- 18.8.15. JPA에서 사용되는 구성 컴포넌트(Configure a Component that is Used by JPA)
- 18.8.16. 두 개의 데이터 소스로 jOOQ 구성(Configure jOOQ with Two DataSources)
- 18.9. 데이터베이스 초기화(Database Initialization)
- 18.9.1. JPA를 사용하여 데이터베이스 초기화(Initialize a Database Using JPA)
- 18.9.2. 하이버네이트를 사용하여 데이터베이스 초기화(Initialize a Database Using Hibernate)
- 18.9.3. 기본 SQL 스크립트를 사용하여 데이터베이스 초기화(Initialize a Database Using Basic SQL Scripts)
- 18.9.4. 스프링 배치 데이터베이스 초기화(Initialize a Spring Batch Database)
- 18.9.5. 상위 레벨 데이터베이스 마이그레이션 도구 사용(Use a Higher-level Database Migration Tool)
- 시작 시 플라이웨이 데이터베이스 마이그레이션 실행(Execute Flyway Database Migrations on Startup)
- 시작 시 리퀴베이스 데이터베이스 마이그레이션 실행(Execute Liquibase Database Migrations on Startup)
- 18.9.6. 초기화된 데이터베이스에 의존(Depend Upon an Initialized Database)
- 데이터베이스 이니셜라이저 감지(Detect a Database Initializer)
- 데이터베이스 초기화에 의존하는 빈 감지(Detect a Bean That Depends On Database Initialization)
- 18.10. NoSQL
- 18.10.1. Lettuce 대신 Jedis를 사용(Use Jedis Instead of Lettuce)
- 18.11. 메세징(Messaging)
- 18.11.1. 트랜잭션된 JMS 세션 비활성화(Disable Transacted JMS Session)
- 18.12. 배치 애플리케이션(Batch Applications)
- 18.12.1. 배치 데이터 소스 지정(Specifying a Batch Data Source)
- 18.12.2. 시작 시 스프링 배치 작업 실행(Running Spring Batch Jobs on Startup)
- 18.12.3. 커맨드라인에서 실행(Running From the Command Line)
- 18.12.4. 잡 리포지터리 저장(Storing the Job Repository)
- 18.13. 액추에이터(Actuator)
- 18.13.1. 액추에이터 엔드포인트의 HTTP 포트 또는 주소 변경(Change the HTTP Port or Address of the Actuator Endpoints)
- 18.13.2. ‘화이트레이블’ 오류 페이지 커스텀(Customize the ‘whitelabel’ Error Page)
- 18.13.3. 민감한 값 삭제(Sanitize Sensitive Values)
- 삭제 커스텀(Customizing Sanitization)
- 18.13.4. 상태 표시기를 마이크로미터 메트릭에 매핑(Map Health Indicators to Micrometer Metrics)
- 18.14. 보안(Security)
- 18.14.1. 스프링 부트 보안 구성 끄기(Switch off the Spring Boot Security Configuration)
- 18.14.2. UserDetailsService 변경 및 사용자 계정 추가(Change the UserDetailsService and Add User Accounts)
- 18.14.3. 프록시 서버 뒤에서 실행할 때 HTTPS 활성화(Enable HTTPS When Running behind a Proxy Server)
- 18.15. 핫 스화핑(Hot Swapping)
- 18.15.1. 정적 콘텐츠 리로드(Reload Static Content)
- 18.15.2. 컨테이너를 재시작하지 않고 템플릿 리로드(Reload Templates without Restarting the Container)
- Thymeleaf 템플릿(Thymeleaf Templates)
- FreeMarker 템플릿(FreeMarker Templates)
- 그루비 템플릿(Groovy Templates)
- 18.15.3. 빠른 애플리케이션 재시작(Fast Application Restarts)
- 18.15.4. 컨테이너를 재시작하지 않고 자바 클래스 리로드(Reload Java Classes without Restarting the Container)
- 18.16. 테스팅(Testing)
- 18.16.1. 스프링 시큐리티와 테스팅(Testing With Spring Security)
- 18.16.2. 슬라이스 테스트에 포함하기 위한 구조 @Configuration 클래스(Structure @Configuration classes for inclusion in slice tests)
- 18.17. 빌드(Build)
- 18.17.1. 빌드 정보 생성(Generate Build Information)
- 18.17.2. 깃 정보 생성(Generate Git Information)
- 18.17.3. 의존성 버전 커스텀(Customize Dependency Versions)
- 18.17.4. 메이븐을 사용하여 실행 가능한 JAR 만들기(Create an Executable JAR with Maven)
- 18.17.5. 스프링 부트 애플리케이션을 의존성으로 사용(Use a Spring Boot Application as a Dependency)
- 18.17.6. 실행 가능한 Jar가 실행될 때 특정 라이브러리 추출(Extract Specific Libraries When an Executable Jar Runs)
- 18.17.7. 제외가 포함된 실행 불가능한 JAR 생성(Create a Non-executable JAR with Exclusions)
- 18.17.8. 메이븐으로 시작된 스프링 부트 애플리케이션 원격 디버그(Remote Debug a Spring Boot Application Started with Maven)
- 18.17.9. spring-boot-antlib를 사용하지 않고 앤트에서 실행 가능한 압축파일 빌드(Build an Executable Archive From Ant without Using spring-boot-antlib)
- 18.18. Ahead of time 처리(Ahead-of-time processing)
- 18.18.1. 컨디션(Conditions)
- 18.19. 기존방식의 배포(Traditional Deployment)
- 18.19.1. 배포 가능한 War 파일 생성(Create a Deployable War File)
- 18.19.2. 기존 애플리케이션을 스프링 부트로 변환(Convert an Existing Application to Spring Boot)
- 18.19.3. WebLogic에 WAR 배포(Deploying a WAR to WebLogic)
- 18.20. 도커 컴포즈(Docker Compose)
- 18.20.1. JDBC URL 커스텀(Customizing the JDBC URL)
- 18.1. 스프링 부트 애플리케이션(Spring Boot Application)
18. “사용” 가이드(“How-to” Guides)
이 장에서는 스프링 부트를 사용할 때 자주 발생하는 몇 가지 일반적인 ‘어떻게 해야 하지…’ 질문에 대한 답변을 제공한다. 완전하진 않지만 꽤 많은 부분을 커버하고 있다.
여기서 다루지 않은 특정 문제가 있는 경우 stackoverflow.com을 확인하여 누군가 이미 답변을 제공했는지 확인할 수 있다. 이곳은 새로운 질문을 하기에도 좋은 곳이다(spring-boot
태그를 사용하자).
우리는 또한 이 장을 확장할 수 있게 되어 매우 기쁘다. ‘사용 방법’을 추가하려면 풀 리퀘스트를 보내보자.
18.1. 스프링 부트 애플리케이션(Spring Boot Application)
이 장에는 스프링 부트 애플리케이션과 직접 관련된 토픽이 포함되어 있다.
18.1.1. 나만의 FailureAnalyzer 만들기(Create Your Own FailureAnalyzer)
FailureAnalyzer
는 시작 시 예외를 가로채서 이를 사람이 읽을 수 있는 메시지로 변환하고 FailureAnalytics
에 래핑하는 훌륭한 방법이다. 스프링 부트는 애플리케이션 컨텍스트 관련 예외, JSR-303 검증 등에 대한 분석기(analyzer)를 제공한다. 자신만의 것을 만들 수도 있다.
AbstractFailureAnalyzer
는 처리할 예외에 지정된 예외 유형이 있는지 확인하는 FailureAnalyzer
의 편리한 확장이다. 구현체가 실제로 존재할 때만 예외를 처리할 수 있도록 이를 확장할 수 있다. 어떤 이유로든 예외를 처리할 수 없는 경우 null
을 반환하여 다른 구현체에서 예외를 처리할 수 있도록 한다.
FailureAnalyzer
구현체는 META-INF/spring.factories
에 등록되어야 한다. 다음 예제에서는 ProjectConstraintViolationFailureAnalyzer
를 등록한다.
org.springframework.boot.diagnostics.FailureAnalyzer=\
com.example.ProjectConstraintViolationFailureAnalyzer
BeanFactory
또는 Environment
에 접근해야 하는 경우 FailureAnalyzer는 각각 BeanFactoryAware
또는 EnvironmentAware
를 구현할 수 있다.
18.1.2. 자동 구성 문제 해결(Troubleshoot Auto-configuration)
스프링 부트 자동 구성은 “올바른 작업”을 수행하기 위해 최선을 다하지만 때로 작업이 실패하고 이유를 분명하게 확인하기 어려울 수 있다.
모든 스프링 부트 ApplicationContext
에는 정말 유용한 ConditionEvaluationReport
가 있다. DEBUG
로깅 출력을 활성화하면 이를 볼 수 있다. spring-boot-actuator
(액추에이터 장 참고)를 사용하는 경우 보고서를 JSON
으로 렌더링하는 조건 엔드포인트도 있다. 해당 엔드포인트를 사용하여 애플리케이션을 디버깅하고 런타임 시 스프링 부트에 의해 추가된 기능(및 추가되지 않은 기능)을 확인하자.
소스 코드와 자바독을 살펴보면 더 많은 질문에 대한 답을 얻을 수 있다. 코드를 읽을 때 다음 법칙을 기억하자.
*AutoConfiguration
이라는 클래스를 찾아 해당 소스를 읽어보자. 어떤 기능을 언제 활성화하는지 알아보려면@Conditional*
어노테이션에 특별한 주의를 기울이자. 커맨드라인에서--debug
를 추가하거나 시스템 프로퍼티-Ddebug
를 추가하여 앱에서 이루어진 모든 자동 구성 결정에 대한 로그를 콘솔에 가져온다. 액추에이터가 활성화된 실행 중인 애플리케이션에서 동일한 정보를 보려면conditions
엔드포인트(/actuator/conditions
또는 이에 상응하는 JMX)를 살펴보자.@ConfigurationProperties
(예:ServerProperties
)인 클래스를 찾아 거기에서 사용 가능한 외부 구성 옵션을 읽어보자.@ConfigurationProperties
어노테이션에는 외부 프로퍼티에 대한 접두사 역할을 하는 이름 애트리뷰트가 있다. 따라서,ServerProperties
에는prefix="server"
가 있고 해당 프로퍼티는server.port
,server.address
등이다. 액추에이터가 활성화된 실행 중인 애플리케이션에서configprops
엔드포인트를 살펴보자.- 편리한 방식으로
Environment
에서 구성 값을 명시적으로 가져오기 위해Binder
에서bind
메서드를 사용하는 방법을 찾아보자. 접두사와 함께 사용되는 경우가 많다. Environment
에 직접 바인딩되는@Value
어노테이션을 찾아보자.- SpEL 표현식에 대한 응답으로 기능을 켜고 끄는
@ConditionalOnExpression
어노테이션을 찾아보자. 일반적으로 환경에서 분석된 자리 표시자(placeholder)로 평가한다.
18.1.3. 시작하기 전 Environment 또는 ApplicationContext 커스텀(Customize the Environment or ApplicationContext Before It Starts)
SpringApplication
에는 컨텍스트나 environment에 커스텀을 적용하는 데 사용되는 ApplicationListener
및 ApplicationContextInitializer
가 있다. 스프링 부트는 META-INF/spring.factories
에서 내부적으로 사용할 수 있도록 이러한 커스텀을 로드한다. 추가 커스텀을 등록하는 방법은 여러 가지가 있다.
- 프로그래밍 방식으로 애플리케이션별
SpringApplication
을 실행하기 전에addListeners
및addInitializers
메서드를 호출한다. - 선언적으로, 애플리케이션별
context.initializer.classes
또는context.listener.classes
프로퍼티스를 설정한다. - 선언적으로, 모든 애플리케이션에 대해
META-INF/spring.factories
를 추가하고 모든 애플리케이션이 라이브러리로 사용하는 jar 파일에 패키징한다.
SpringApplication
은 몇 가지 특별한 ApplicationEvent
를 리스너(컨텍스트가 생성되기 전이라도)에 보낸 다음 ApplicationContext
에 의해 게시된 이벤트에 대한 리스너를 등록한다. 전체 목록은 ‘스프링 부트 기능’ 장의 ‘애플리케이션 이벤트 및 리스너‘를 참고하자.
EnvironmentPostProcessor
를 사용하여 애플리케이션 컨텍스트를 새로 고치기 전에 Environment
을 커스텀하는 것도 가능하다. 다음 예제와 같이 각 구현체는 META-INF/spring.factories
에 등록되어야 한다.
org.springframework.boot.env.EnvironmentPostProcessor=com.example.YourEnvironmentPostProcessor
구현 시 임의의 파일을 로드하고 Environment
에 추가할 수 있다. 다음 예제에서는 클래스패스에서 YAML 구성 파일을 로드한다.
자바
import java.io.IOException;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
public class MyEnvironmentPostProcessor implements EnvironmentPostProcessor {
private final YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
Resource path = new ClassPathResource("com/example/myapp/config.yml");
PropertySource<?> propertySource = loadYaml(path);
environment.getPropertySources().addLast(propertySource);
}
private PropertySource<?> loadYaml(Resource path) {
Assert.isTrue(path.exists(), () -> "Resource " + path + " does not exist");
try {
return this.loader.load("custom-resource", path).get(0);
} catch (IOException ex) {
throw new IllegalStateException("Failed to load yaml configuration from " + path, ex);
}
}
}
코틀린
import org.springframework.boot.SpringApplication
import org.springframework.boot.env.EnvironmentPostProcessor
import org.springframework.boot.env.YamlPropertySourceLoader
import org.springframework.core.env.ConfigurableEnvironment
import org.springframework.core.env.PropertySource
import org.springframework.core.io.ClassPathResource
import org.springframework.core.io.Resource
import org.springframework.util.Assert
import java.io.IOException
class MyEnvironmentPostProcessor : EnvironmentPostProcessor {
private val loader = YamlPropertySourceLoader()
override fun postProcessEnvironment(environment: ConfigurableEnvironment, application: SpringApplication) {
val path: Resource = ClassPathResource("com/example/myapp/config.yml")
val propertySource = loadYaml(path)
environment.propertySources.addLast(propertySource)
}
private fun loadYaml(path: Resource): PropertySource<*> {
Assert.isTrue(path.exists()) { "Resource $path does not exist" }
return try {
loader.load("custom-resource", path)[0]
} catch (ex: IOException) {
throw IllegalStateException("Failed to load yaml configuration from $path", ex)
}
}
}
Environment
은 스프링 부트가 기본적으로 로드하는 모든 일반적인 프로퍼티 소스로 이미 준비되어 있다. 따라서 Environment
에서 파일 위치를 얻는 것이 가능하다. 앞의 예제에서는 일반적으로 다른 위치에 정의된 키가 우선순위를 갖도록 리스트의 끝에 커스텀 리소스 프로퍼티 소스를 추가한다. 커스텀 구현체는 다른 순서를 정의할 수 있다.
@SpringBootApplication
에서 @PropertySource
를 사용하는 것이 Environment
에서 커스텀 리소스를 로드하는 편리한 방법으로 보일 수 있지만 권장하지는 않는다. 이러한 프로퍼티 소스는 애플리케이션 컨텍스트가 새로 고쳐질 때까지 Environment
에 추가되지 않는다. 새로 고침이 시작되기 전에 읽혀지는 logging.*
및 spring.main.*
과 같은 특정 프로퍼티를 구성하기에는 너무 늦다.
18.1.4. ApplicationContext 계층 구조 구축(상위 또는 루트 컨텍스트 추가)(Build an ApplicationContext Hierarchy (Adding a Parent or Root Context))
ApplicationBuilder
클래스를 사용하여 상위/하위 ApplicationContext
계층을 생성할 수 있다. 자세한 내용은 ‘스프링 부트 기능’ 장의 ‘Fluent Builder API‘를 참고하자.
18.1.5. 웹이 아닌 애플리케이션 만들기(Create a Non-web Application)
모든 스프링 애플리케이션이 웹 애플리케이션(또는 웹 서비스)일 필요는 없다. 메인 메서드에서 일부 코드를 실행하고 스프링 애플리케이션을 부트스트랩하여 사용할 인프라를 설정하려는 경우 스프링 부트의 SpringApplication
기능을 사용할 수 있다. SpringApplication
은 웹 애플리케이션 필요 여부에 따라 ApplicationContext
클래스를 변경한다. 이를 돕기 위해 가장 먼저 할 수 있는 일은 서버 관련 의존성(예: 서블릿 API)을 클래스패스에서 벗어나게 하는 것이다. 그렇게 할 수 없는 경우(예를 들어 동일한 코드 베이스에서 두 개의 애플리케이션을 실행하는 경우) SpringApplication
인스턴스에서 명시적으로 setWebApplicationType(WebApplicationType.NONE)
을 호출하거나 (자바 API 또는 외부 속성을 통해) applicationContextClass
프로퍼티를 설정할 수 있다. 비즈니스 로직으로 실행하려는 애플리케이션 코드는 CommandLineRunner
로 구현되고 @Bean
정의로 컨텍스트에 놓을 수 있다.
18.2. 프로퍼티스와 구성(Properties and Configuration)
이 절에는 프로퍼티스와 구성(Configuration)을 설정하고 읽는 방법과 스프링 부트 애플리케이션과의 상호 작용을 다룬다.
18.2.1. 빌드 시 자동으로 프로퍼티스 확장(Automatically Expand Properties at Build Time)
프로젝트의 빌드 구성에도 지정된 일부 프로퍼티스를 하드코딩하는 대신 기존 빌드 구성을 사용하여 자동으로 확장할 수 있다. 이는 메이븐과 그레이들 모두 가능하다.
메이븐을 사용한 자동 프로퍼티 확장(Automatic Property Expansion Using Maven)
리소스 필터링을 사용하여 메이븐 프로젝트에서 프로퍼티스를 자동으로 확장할 수 있다. spring-boot-starter-parent
를 사용하는 경우 다음 예와 같이 @..@
자리 표시자(placeholder)를 사용하여 메이븐 ‘프로젝트 프로퍼티스’를 참고할 수 있다.
프로퍼티스(Properties)
app.encoding=@project.build.sourceEncoding@
app.java.version=@java.version@
Yaml
app:
encoding: "@project.build.sourceEncoding@"
java:
version: "@java.version@"
프로덕션 구성만 해당 방식으로 필터링된다. 즉, src/test/resources
에는 필터링이 적용되지 않는다.
addResources
플래그를 활성화하면 spring-boot:run
골(goal)은 핫 리로딩 목적으로 src/main/resources
를 클래스패스에 직접 추가할 수 있다. 그렇게 하면 리소스 필터링과 이 기능을 우회할 수 있다. 대신 exec:java
골(goal)을 사용하거나 플러그인 구성을 커스텀할 수 있다. 자세한 내용은 플러그인 사용 페이지를 참고하자.
starter parent
를 사용하지 않는 경우, pom.xml
의 <build/>
엘리먼트 내에 다음 엘리먼트를 포함해야 한다.
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
또한 <plugins/>
내에 다음 엘리먼트를 포함해야 한다.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.7</version>
<configuration>
<delimiters>
<delimiter>@</delimiter>
</delimiters>
<useDefaultDelimiters>false</useDefaultDelimiters>
</configuration>
</plugin>
useDefaultDelimiters
프로퍼티는 구성에서 표준 스프링 자리 표시자(예: ${placeholder}
)를 사용하는 경우 중요하다. 해당 프로퍼티가 false
로 설정되지 않은 경우 빌드에 의해 확장할 수 있다.
그레이들을 사용한 자동 프로퍼티 확장(Automatic Property Expansion Using Gradle)
다음 예와 같이 자바 플러그인의 processResources
태스크를 구성하여 그레이들 프로젝트에서 프로퍼티스를 자동으로 확장할 수 있다.
tasks.named('processResources') {
expand(project.properties)
}
그런 다음 다음 예제와 같이 자리 표시자를 사용하여 그레이들 프로젝트의 프로퍼티스를 참조할 수 있다.
프로퍼티스(Properties)
app.name=${name}
app.description=${description}
Yaml
app:
name: "${name}"
description: "${description}"
그레이들의 expand
메소드는 ${..}
토큰을 변환하는 그루비의 SimpleTemplateEngine
을 사용한다. ${..}
스타일은 스프링의 자체 프로퍼티 자리 표시자 메커니즘과 충돌한다. 자동 확장과 함께 스프링 프로퍼티 자리 표시자를 사용하려면 \${..}
와 같이 스프링 프로퍼티 자리 표시자를 이스케이프 처리하자.
18.2.2. SpringApplication 구성 외부화(Externalize the Configuration of SpringApplication)
SpringApplication에는 빈 프로퍼티 세터가 있으므로, 해당 동작을 수정하기 위해 애플리케이션을 생성할 때 자바 API를 사용할 수 있다. 또는 spring.main.*
에서 프로퍼티스를 설정하여 구성을 외부화할 수 있다. 예를 들어 application.properties
에는 다음과 같은 설정이 있을 수 있다.
프로퍼티스(Properties)
spring.main.web-application-type=none
spring.main.banner-mode=off
Yaml
spring:
main:
web-application-type: "none"
banner-mode: "off"
그러면 시작 시 스프링 부트 배너가 인쇄되지 않고 애플리케이션이 내장된 웹 서버를 시작하지 않는다.
외부 구성에 정의된 프로퍼티스는 주요 소스를 제외하고 자바 API로 지정된 값을 오버라이드하고 대체한다. 주요(Primary) 소스는 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.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
@SpringBootApplication
object MyApplication {
@JvmStatic
fun main(args: Array<String>) {
val application = SpringApplication(MyApplication::class.java)
application.setBannerMode(Banner.Mode.OFF)
application.run(*args)
}
}
또는 SpringApplicationBuilder
의 source(...)
메소드에:
자바
import org.springframework.boot.Banner;
import org.springframework.boot.builder.SpringApplicationBuilder;
public class MyApplication {
public static void main(String[] args) {
new SpringApplicationBuilder()
.bannerMode(Banner.Mode.OFF)
.sources(MyApplication.class)
.run(args);
}
}
코틀린
import org.springframework.boot.Banner
import org.springframework.boot.builder.SpringApplicationBuilder
object MyApplication {
@JvmStatic
fun main(args: Array<String>) {
SpringApplicationBuilder()
.bannerMode(Banner.Mode.OFF)
.sources(MyApplication::class.java)
.run(*args)
}
}
위의 예제를 고려하면 다음과 같은 구성이 있다.
프로퍼티스(Properties)
spring.main.sources=com.example.MyDatabaseConfig,com.example.MyJmsConfig
spring.main.banner-mode=console
Yaml
spring:
main:
sources: "com.example.MyDatabaseConfig,com.example.MyJmsConfig"
banner-mode: "console"
실제 애플리케이션은 배너(구성에 의해 오버라이드됨)를 표시하고 ApplicationContext
에 대해 세 가지 소스를 사용한다. 애플리케이션 소스는 다음과 같다.
MyApplication
(코드로부터)MyDatabaseConfig
(외부 구성으로부터)MyJmsConfig
(외부 구성으로부터)
18.2.3. 애플리케이션의 외부 프로퍼티스 위치 변경(Change the Location of External Properties of an Application)
기본적으로, 다양한 소스의 프로퍼티스는 정의된 순서대로 스프링 환경에 추가된다(정확한 순서는 ‘스프링 부트 기능’ 장의 ‘외부화된 구성’ 참고).
다음 시스템 프로퍼티스(또는 환경 변수)을 제공하여 동작을 변경할 수도 있다.
spring.config.name
(SPRING_CONFIG_NAME
): 파일 이름의 루트는 ‘application’으로 기본 설정된다.spring.config.location
(SPRING_CONFIG_LOCATION
): 로드할 파일(예: 클래스패스 리소스 또는 URL)이다. 이 문서에는 별도의Environment
프로퍼티 소스가 설정되어 있으며 시스템 프로퍼티, 환경 변수 또는 커맨드라인으로 오버라이드될 수 있다.
환경에 무엇을 설정했는지에 관계없이 스프링 부트는 위에서 설명한 대로 항상 application.properties
를 로드한다. 기본적으로 YAML을 사용하는 경우 ‘.yaml’ 및 ‘.yml’ 확장자를 가진 파일도 목록에 추가된다.
로드되는 파일에 대한 자세한 정보를 원할 경우 org.springframework.boot.context.config
의 로깅 레벨을 트레이스(trace)로 설정할 수 있다.
18.2.4. ‘짧은’ 커맨드라인 아규먼트 사용(Use ‘Short’ Command Line Arguments)
어떤 사람들은 커맨드라인에서 구성 프로퍼티를 설정하기 위해 --server.port=9000
대신 --port=9000
을 사용하는 것을 좋아한다. 다음 예제와 같이 application.properties
에서 자리 표시자를 사용하여 이 동작을 활성화할 수 있다.
프로퍼티스(Properties)
server.port=${port:8080}
Yaml
server:
port: "${port:8080}"
spring-boot-starter-parent
POM에서 상속받은 경우, maven-resources-plugins
의 기본 필터 토큰이 ${*}
에서 @
(즉, ${maven.token}
대신 @maven.token@
)을 사용하여 스프링 스타일 자리 표시자와의 충돌을 방지한다. application.properties
에 대해 메이븐 필터링을 직접 활성화한 경우 다른 구분 기호를 사용하도록 기본 필터 토큰을 변경할 수도 있다.
이 특정한 경우 포트 바인딩은 헤로쿠(Heroku) 또는 클라우드 파운드리(Cloud Foundry)와 같은 PaaS 환경에서 작동한다. 이 두 플랫폼에서 PORT 환경 변수는 자동으로 설정되며 스프링은 환경 프로퍼티에 대한 대문자 동의어에 바인딩할 수 있다.
18.2.5. 외부 프로퍼티스에 YAML 사용(Use YAML for External Properties)
YAML은 JSON의 상위 집합이므로 다음 예제와 같이 외부 프로퍼티를 계층 포맷으로 저장하기 위한 편리한 문법이다.
spring:
application:
name: "cruncher"
datasource:
driver-class-name: "com.mysql.jdbc.Driver"
url: "jdbc:mysql://localhost/test"
server:
port: 9000
application.yaml
이라는 파일을 생성하여 클래스패스 루트에 넣는다. 그런 다음 의존성에 snakeyaml
을 추가한다(메이븐은 spring-boot-starter
를 사용하는 경우 이미 포함된 org.yaml:snakeyaml
을 사용한다). YAML 파일은 Java Map<String,Object>
(예: JSON 객체)로 파싱하고 스프링 부트는 맵을 평면화하여 많은 사람들이 익숙한 프로퍼티 파일처럼 한 레벨 깊이에 마침표로 구분된 키를 갖도록 한다.
이전 예제 YAML은 다음 application.properties
파일에 해당한다.
spring.application.name=cruncher
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost/test
server.port=9000
YAML에 대한 자세한 내용은 ‘스프링 부트 기능’ 장의 ‘YAML 작업’을 참고하자.
18.2.6. 활성 스프링 프로필 설정(Set the Active Spring Profiles)
스프링 Environment
에는 이를 위한 API가 있지만 일반적으로 시스템 프로퍼티(spring.profiles.active
) 또는 OS 환경 변수(SPRING_PROFILES_ACTIVE
)를 설정한다. 또한 다음과 같이 -D
아규먼트를 사용하여 애플리케이션을 시작할 수 있다(메인 클래스 또는 jar 압축파일 앞에 놓는 것을 기억하자).
$ java -jar -Dspring.profiles.active=production demo-0.0.1-SNAPSHOT.jar
스프링 부트에서는 다음 예제와 같이 application.properties
에서 활성 프로필을 설정할 수도 있다.
프로퍼티스(Properties)
spring.profiles.active=production
Yaml
spring:
profiles:
active: "production"
이 방법으로 설정된 값은 시스템 프로퍼티나 환경 변수 설정으로 대체되지만 SpringApplicationBuilder.profiles()
메서드로는 대체되지 않는다. 따라서 후자의 자바 API를 사용하면 기본값을 변경하지 않고 프로필을 보강할 수 있다.
자세한 내용은 “스프링 부트 기능” 장의 “프로필”을 참고하자.
18.2.7. 기본 프로필명 설정(Set the Default Profile Name)
기본 프로필은 활성화된 프로필이 없는 경우 활성화되는 프로필이다. 기본적으로 기본 프로필 이름은 default
이지만 시스템 프로퍼티(spring.profiles.default
) 또는 OS 환경 변수(SPRING_PROFILES_DEFAULT
)를 사용하여 변경할 수 있다.
스프링 부트에서는, 다음 예제와 같이 application.properties
에서 기본 프로필명을 설정할 수도 있다.
프로퍼티스(Properties)
spring.profiles.default=dev
Yaml
spring:
profiles:
default: "dev"
자세한 내용은 “스프링 부트 기능” 장의 “프로필”을 참고하자.
18.2.8. 환경에 따라 구성 변경(Change Configuration Depending on the Environment)
스프링 부트는 활성 프로필을 조건부로 활성화할 수 있는 다중 문서 YAML 및 프로퍼티 파일(자세한 내용은 다중 문서 파일 작업 참고)을 지원한다.
문서에 spring.config.activate.on-profile
키가 포함되어 있으면 프로필 값(쉼표로 구분된 프로필 목록 또는 프로필 표현식)이 스프링 Environment.acceptsProfiles()
메서드에 제공된다. 다음 예제와 같이 프로필 식이 일치하면 해당 문서가 최종 병합에 포함된다(그렇지 않으면 포함되지 않음).
프로퍼티스(Properties)
server.port=9000
#---
spring.config.activate.on-profile=development
server.port=9001
#---
spring.config.activate.on-profile=production
server.port=0
Yaml
server:
port: 9000
---
spring:
config:
activate:
on-profile: "development"
server:
port: 9001
---
spring:
config:
activate:
on-profile: "production"
server:
port: 0
앞의 예제에서 기본 포트는 9000이다. 그러나 ‘development’라는 스프링 프로필이 활성화된 경우 포트는 9001이다. ‘production’이 활성화된 경우 포트는 0이다.
문서는 발견된 순서대로 병합된다. 나중 값은 이전 값을 오버라이드 한다.
18.2.9. 외부 프로퍼티스에 대한 빌트인 옵션 살펴보기(Discover Built-in Options for External Properties)
스프링 부트는 런타임 시 application.properties
(또는 YAML 파일 및 기타 위치)의 외부 프로퍼티를 애플리케이션에 바인딩한다. 클래스패스에 있는 추가 jar 파일에서 기여가 이루어질 수 있기 때문에 단일 위치에 지원되는 모든 프로퍼티의 전체 목록이 없으며 기술적으로 그럴 수도 없다.
액추에이터 기능을 사용하여 실행 중인 애플리케이션에는 @ConfigurationProperties
를 통해 사용 가능한 모든 바인딩 및 바인딩 가능한 프로퍼티를 표시하는 configprops
엔드포인트가 있다.
부록에는 스프링 부트에서 지원하는 가장 일반적인 프로퍼티스 목록과 함께 application.properties
예제가 포함되어 있다. 최종 목록은 소스 코드에서 @ConfigurationProperties
및 @Value
어노테이션을 검색하고 가끔씩 Binder
를 사용하여 얻은 것이다. 프로퍼티스 로드의 정확한 순서에 대한 자세한 내용은 “외부화된 구성”을 참고하자.
18.3. 임베디드 웹 서버(Embedded Web Servers)
각 스프링 부트 웹 애플리케이션에는 임베디드 웹 서버가 포함되어 있다. 이 기능은 임베디드 서버를 변경하는 방법과 임베디드 서버를 구성하는 방법을 포함하여 다양한 방법에 대한 질문으로 이어진다. 이 장에서는 이러한 질문에 답변한다.
18.3.1. 다른 웹서버 사용(Use Another Web Server)
많은 스프링 부트 스타터에는 기본 임베디드 컨테이너가 포함되어 있다.
- 서블릿 스택 애플리케이션의 경우
spring-boot-starter-web
에는spring-boot-starter-tomcat
을 포함하여 톰캣이 포함되어 있지만,spring-boot-starter-jetty
또는spring-boot-starter-undertow
을 사용할 수도 있다. - 리액티브 스택 애플리케이션의 경우
spring-boot-starter-webflux
에는spring-boot-starter-reactor-netty
를 포함하여 리액터 네티가 포함되어 있지만spring-boot-starter-tomcat
,spring-boot-starter-jetty
또는spring-boot-starter-undertow
를 대신 사용할 수도 있다.
다른 HTTP 서버로 전환할 때 필요한 기본 의존성을 대신 교체해야 한다. 이 프로세스를 돕기 위해 스프링 부트는 지원되는 각 HTTP 서버에 대해 별도의 스타터를 제공한다.
다음 메이븐 예제에서는 톰캣을 제외하고 스프링 MVC용 제티(Jetty)를 포함하는 방법을 보여준다.
<properties>
<servlet-api.version>3.1.0</servlet-api.version>
</properties>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- 톰캣 의존성 제외 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 대신 Jetty 사용 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
톰캣 9 및 언더토우 2와 달리 제티 9.4는 서블릿 4.0을 지원하지 않으므로 서블릿 API 버전이 오버라이드 됐다.
서블릿 4.0을 지원하는 제티 10을 사용하려면 다음 예제에 표시된 대로 수행할 수 있다.
<properties>
<jetty.version>10.0.8</jetty.version>
</properties>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- 톰캣 의존성 제외 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 대신 Jetty 사용 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
<exclusions>
<!-- 제티-9 일부 의존성 제외 -->
<exclusion>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-server</artifactId>
</exclusion>
<exclusion>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>javax-websocket-server-impl</artifactId>
</exclusion>
</exclusions>
</dependency>
톰캣 스타터를 제외하는 것과 함께 몇 가지 제티9 관련 의존성도 제외해야 한다.
다음 그레이들 예제에서는 스프링 웹플럭스용 리액터 네티 대신 언더토우를 사용하기 위해 필요한 의존성과 모듈 교체를 구성한다.
dependencies {
implementation "org.springframework.boot:spring-boot-starter-undertow"
implementation "org.springframework.boot:spring-boot-starter-webflux"
modules {
module("org.springframework.boot:spring-boot-starter-reactor-netty") {
replacedBy("org.springframework.boot:spring-boot-starter-undertow", "Use Undertow instead of Reactor Netty")
}
}
}
spring-boot-starter-reactor-netty
는 웹클라이언트(WebClient)
클래스를 사용하는 데 필요하므로 다른 서버를 포함해야 하는 경우에도 네티(Netty)에 대한 의존성을 유지해야 할 수도 있다.
18.3.2. 웹 서버 비활성화(Disabling the Web Server)
클래스패스에 웹 서버를 시작하는 데 필요한 비트가 포함되어 있으면 스트링 부트가 시작한다. 이 동작을 비활성화하려면 다음 예와 같이 application.properties
에서 WebApplicationType
을 구성하면 된다.
프로퍼티스(Properties)
spring.main.web-application-type=none
Yaml
spring:
main:
web-application-type: "none"
18.3.3. HTTP 포트 변경(Change the HTTP Port)
독립 실행형 애플리케이션에서 기본 HTTP 포트는 기본적으로 8080이지만, server.port
를 사용하여 설정할 수 있다(예: application.properties
또는 시스템 프로퍼티). 환경 값의 느린 바인딩(relaxed binding) 덕분에 SERVER_PORT
(예: OS 환경 변수)를 사용할 수도 있다.
HTTP 엔드포인트를 완전히 끄되 여전히 WebApplicationContext
를 생성하려면 server.port=-1
을 사용하자(그렇게 하면 테스트에 유용할 때도 있음).
자세한 내용은 ‘스프링 부트 기능’ 장의 “임베디드 서블릿 컨테이너 커스텀” 또는 ServerProperties
소스 코드를 참고하자.
18.3.4. 할당되지 않은 무작위 HTTP 포트 사용(Use a Random Unassigned HTTP Port)
사용 가능한 포트를 검색하려면(충돌을 방지하기 위해 OS 기본 사용) server.port=0
을 사용하자.
18.3.5. 런타임 시 HTTP 포트 검색(Discover the HTTP Port at Runtime)
로그 출력이나 WebServer
를 통해 WebServerApplicationContext
에서 서버가 실행 중인 포트에 접근할 수 있다. 이를 가져오고 초기화되었는지 확인하는 가장 좋은 방법은 ApplicationListener<WebServerInitializedEvent>
타입의 @Bean
을 추가하고 게시될 때 이벤트에서 컨테이너를 꺼내는 것이다.
@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
를 사용하는 테스트는 다음 예제와 같이 @LocalServerPort
어노테이션을 사용하여 필드에 실제 포트를 주입할 수도 있다.
자바
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.server.LocalServerPort;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyWebIntegrationTests {
@LocalServerPort
int port;
// ...
}
코틀린
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment
import org.springframework.boot.test.web.server.LocalServerPort
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyWebIntegrationTests {
@LocalServerPort
var port = 0
// ...
}
@LocalServerPort
는 @Value("${local.server.port}")
에 대한 메타 어노테이션이다. 일반 애플리케이션에 포트를 삽입하지 말자. 방금 본 것처럼 값은 컨테이너가 초기화된 후에만 설정된다. 테스트와 달리 애플리케이션 코드 콜백은 초기(값이 실제로 사용 가능해지기 전)에 처리된다.
18.3.6. HTTP 응답 압축 활성화(Enable HTTP Response Compression)
HTTP 응답 압축은(response compression) 제티, 톰캣, 리액터 네티 및 언더토우에서 지원된다. 다음과 같이 application.properties
에서 활성화할 수 있다.
프로퍼티스(Properties)
server.compression.enabled=true
Yaml
server:
compression:
enabled: true
기본적으로, 압축을 수행하려면 응답 길이가 2048바이트 이상이어야 한다. server.compression.min-response-size
프로퍼티를 설정하여 이 동작을 구성할 수 있다.
기본적으로 응답은 콘텐츠 타입(content type)이 다음 중 하나인 경우에만 압축된다.
text/html
text/xml
text/plain
text/css
text/javascript
application/javascript
application/json
application/xml
server.compression.mime-types
프로퍼티를 설정하여 이 동작을 구성할 수 있다.
18.3.7. SSL 구성(Configure SSL)
SSL은 일반적으로 application.properties
또는 application.yaml
에서 다양한 server.ssl.*
프로퍼티스를 설정하여 선언적으로 구성할 수 있다. 다음 예제에서는 자바 KeyStore
파일을 사용하여 SSL 프로퍼티스를 설정하는 방법을 보여준다.
프로퍼티스(Properties)
server.port=8443
server.ssl.key-store=classpath:keystore.jks
server.ssl.key-store-password=secret
server.ssl.key-password=another-secret
Yaml
server:
port: 8443
ssl:
key-store: "classpath:keystore.jks"
key-store-password: "secret"
key-password: "another-secret"
다음 예제에서는 PEM
으로 인코딩된 인증서와 개인 키 파일을 사용하여 SSL 프로퍼티스를 설정하는 방법을 보여준다.
프로퍼티스(Properties)
server.port=8443
server.ssl.certificate=classpath:my-cert.crt
server.ssl.certificate-private-key=classpath:my-cert.key
server.ssl.trust-certificate=classpath:ca-cert.crt
Yaml
server:
port: 8443
ssl:
certificate: "classpath:my-cert.crt"
certificate-private-key: "classpath:my-cert.key"
trust-certificate: "classpath:ca-cert.crt"
또는, 다음 예제와 같이 SSL 신뢰 자료(trust material)를 SSL 번들로 구성하고 웹 서버에 적용할 수 있다.
프로퍼티스(Properties)
server.port=8443
server.ssl.bundle=example
Yaml
server:
port: 8443
ssl:
bundle: "example"
지원되는 모든 프로퍼티스에 대한 자세한 내용은 Ssl을 참고하자.
이전 예제 같은 구성을 사용한다는 것은 애플리케이션이 더 이상 포트 8080
에서 일반 HTTP 커넥터를 지원하지 않는다는 것을 의미한다. 스프링 부트는 application.properties
를 통해 HTTP 커넥터와 HTTPS 커넥터의 구성을 모두 지원하지 않는다. 둘 다 갖고 싶다면 프로그래밍 방식으로 둘 중 하나를 구성해야 한다. HTTP 커넥터가 프로그래밍 방식으로 구성하기가 더 쉽기 때문에 application.properties
를 사용하여 HTTPS를 구성하는 것이 좋다.
18.3.8. HTTP/2 구성(Configure HTTP/2)
server.http2.enabled
구성 프로퍼티를 사용하여 스프링 부트 애플리케이션에서 HTTP/2
지원을 활성화할 수 있다. h2(TLS를 통한 HTTP/2)
및 h2c(TCP를 통한 HTTP/2)
가 모두 지원된다. h2
를 사용하려면 SSL도 활성화해야 한다. SSL이 활성화되지 않은 경우 h2c가 사용된다. 예를 들어 애플리케이션이 TLS
종료를 수행하는 프록시 서버 뒤에서 실행 중일 때 h2c를 사용하려고 할 수 있다.
HTTP/2와 톰캣(HTTP/2 With Tomcat)
스프링 부트는 기본적으로 h2c
및 h2
를 지원하는 톰캣 10.1.x와 함께 제공된다. 또는 라이브러리와 해당 종속 항목이 호스트 운영 체제에 설치된 경우 h2
지원을 위해 libtcnative
를 사용할 수 있다.
라이브러리 디렉토리는 아직 사용 가능하지 않은 경우 JVM 라이브러리 경로에서 사용할 수 있어야 한다. -Djava.library.path=/usr/local/opt/tomcat-native/lib
와 같은 JVM 아규먼트를 사용하여 이를 수행할 수 있다. 이에 대한 자세한 내용은 공식 톰캣 문서를 참고하자.
HTTP/2와 제티(HTTP/2 With Jetty)
HTTP/2
지원을 위해 제티에는 추가 org.eclipse.jetty.http2:http2-server
의존성이 필요하다. h2c
를 사용하려면 다른 의존성이 필요하지 않다. h2
를 사용하려면 배포에 따라 다음 의존성 중 하나를 선택해야 한다.
org.eclipse.jetty:jetty-alpn-java-server
JDK 빌트인 지원 사용org.eclipse.jetty:jetty-alpn-conscrypt-server
및Conscrypt
라이브러리
리액터 네티와 HTTP/2(HTTP/2 With Reactor Netty)
spring-boot-webflux-starter
는 기본적으로 리액터 네티를 서버로 사용한다. 리액터 네티는 기본적으로 h2c
및 h2
를 지원한다. 최적의 런타임 성능을 위해 이 서버는 기본 라이브러리가 있는 h2
도 지원한다. 이를 활성화하려면 애플리케이션에 추가 의존성이 있어야 한다.
스프링 부트는 모든 플랫폼에 대한 기본 라이브러리를 포함하는 io.netty:netty-tcnative-boringssl-static
“uber jar”의 버전을 관리한다. 개발자는 클래시파이어(classifier)를 사용하여 필요한 의존성만 가져오도록 선택할 수 있습니다(네티 공식 문서 참고).
언더토우와 HTTP/2(HTTP/2 With Undertow)
언더토우는 기본적으로 h2c
와 h2
를 지원한다.
18.3.9. 웹서버 구성(Configure the Web Server)
일반적으로, 먼저 여러 구성 키 중 하나를 사용하고 application.properties
또는 application.yaml
파일에 새 항목(entries)을 추가하여 웹 서버를 커스텀해야 한다. “외부 프로퍼티에 대한 빌트인 옵션 검색“을 참고하자. server.*
네임스페이스는 매우 유용하며, 여기에는 서버별 기능을 위한 server.tomcat.*
, server.jetty.*
등과 같은 네임스페이스가 포함되어 있다. 일반 애플리케이션 프로퍼티스 목록을 참고하자.
이전 절에서는 압축, SSL, HTTP/2 등 이미 많은 일반적인 사례를 다루었다. 그러나 사례에 맞는 구성 키가 없으면 WebServerFactoryCustomizer
를 살펴봐야 한다. 이러한 컴포넌트를 선언하고 선택한 것과 관련된 서버 팩토리에 접근할 수 있다. 선택한 서버(Tomcat, Jetty, Reactor Netty, Undertow) 및 선택한 웹 스택(서블릿 또는 리액티브)에 대한 변수을 선택해야 한다.
아래 예제는 spring-boot-starter-web(서블릿 스택)
이 있는 톰캣에 대한 것이다.
자바
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;
@Component
public class MyTomcatWebServerCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
@Override
public void customize(TomcatServletWebServerFactory factory) {
// 여기에 커스텀 팩토리를 작성
}
}
코틀린
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory
import org.springframework.boot.web.server.WebServerFactoryCustomizer
import org.springframework.stereotype.Component
@Component
class MyTomcatWebServerCustomizer :
WebServerFactoryCustomizer<TomcatServletWebServerFactory?> {
override fun customize(factory: TomcatServletWebServerFactory?) {
// 여기에 커스텀 팩토리를 작성
}
}
스프링 부트는 해당 인프라를 내부적으로 사용하여 서버를 자동 구성한다. 자동 구성된 WebServerFactoryCustomizer
빈의 순서는 0
이며 명시적인 순서가 없는 한 사용자가 정의한 커스텀보다 먼저 처리된다.
커스텀을 사용하여 WebServerFactory
에 접근하면 이를 사용하여 커넥터, 서버 리소스 또는 서버 자체와 같은 특정 부분을 구성할 수 있다. 모두 서버별 API를 사용한다.
또한 스프링 부트는 다음 내용을 제공한다.
서버 | 서블릿 스택 | 리액티브 스택 |
---|---|---|
톰캣 | TomcatServletWebServerFactory | TomcatReactiveWebServerFactory |
제티 | JettyServletWebServerFactory | JettyReactiveWebServerFactory |
언더토우 | UndertowServletWebServerFactory | UndertowReactiveWebServerFactory |
리액터 | N/A | NettyReactiveWebServerFactory |
최후의 수단으로 스프링 부트에서 제공하는 빈을 오버라이드하는 자신만의 WebServerFactory
빈을 선언할 수도 있다. 그렇게 하면 자동 구성된 커스텀이 여전히 커스텀 팩토리에 적용되므로 해당 옵션을 주의해서 사용하자.
18.3.10. 애플리케이션에 서블릿, 필터 또는 리스너 추가(Add a Servlet, Filter, or Listener to an Application)
spring-boot-starter-web
을 사용하는 서블릿 스택 애플리케이션에는 Servlet
, Filter
, ServletContextListener
및 Servlet
API가 지원하는 기타 리스너를 애플리케이션에 추가하는 두 가지 방법이 있다.
- 스프링 빈을 사용하여 서블릿, 필터 또는 리스너 추가
- 클래스패스 스캐닝하여 서블릿, 필터 및 리스너 추가
스프링 빈을 사용하여 서블릿, 필터 또는 리스너 추가(Add a Servlet, Filter, or Listener by Using a Spring Bean)
스프링 빈을 사용하여 서블릿(Servlet)
, 필터(Filter)
또는 서블릿 *Listener
를 추가하려면 이에 대한 @Bean
정의를 제공해야 한다. 이렇게 하면 구성이나 의존성을 주입하려는 경우 매우 유용할 수 있다. 그러나 너무 많은 다른 빈을 즉시 초기화(eager initialization)하지 않도록 매우 주의해야 한다. 왜냐하면 애플리케이션 생명주기 초기에 컨테이너에 구성해야 하기 때문이다. 예를 들어 DataSource 또는 JPA 구성에 의존되게 하는 것은 좋은 생각이 아니다. 초기화 대신 처음 사용할 때 빈을 지연 초기화하여 이러한 제한 사항을 해결할 수 있다.
필터 및 서블릿의 경우 기본 컴포넌트 대신 또는 기본 컴포넌트에 추가로 FilterRegistrationBean
또는 ServletRegistrationBean
을 추가하여 매핑 및 초기화 파라미터를 추가할 수도 있다.
필터 등록에 DispatcherType
이 지정되지 않은 경우 REQUEST
가 사용된다. 이는 서블릿 사양의 기본 디스패처 타입과 일치한다.
다른 스프링 빈과 마찬가지로 서블릿 필터 빈의 순서를 정의할 수 있다. “서블릿, 필터, 리스너를 스프링 비으로 등록” 절을 확인하자.
서블릿 또는 필터 등록 비활성화
앞에서 설명한 대로 모든 서블릿 또는 필터 빈은 서블릿 컨테이너에 자동으로 등록된다. 특정 필터 또는 서블릿 빈의 등록을 비활성화하려면 다음 예제와 같이 해당 등록 빈을 생성하고 비활성화된 것으로 표시한다.
자바
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class MyFilterConfiguration {
@Bean
public FilterRegistrationBean<MyFilter> registration(MyFilter filter) {
FilterRegistrationBean<MyFilter> registration = new FilterRegistrationBean<>(filter);
registration.setEnabled(false);
return registration;
}
}
코틀린
import org.springframework.boot.web.servlet.FilterRegistrationBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration(proxyBeanMethods = false)
class MyFilterConfiguration {
@Bean
fun registration(filter: MyFilter): FilterRegistrationBean<MyFilter> {
val registration = FilterRegistrationBean(filter)
registration.isEnabled = false
return registration
}
}
클래스패스 스캐닝으로 서블릿, 필터 및 리스너 추가(Add Servlets, Filters, and Listeners by Using Classpath Scanning)
@WebServlet
, @WebFilter
및 @WebListener
어노테이션이 달린 클래스는 @ServletComponentScan
으로 @Configuration
클래스에 어노테이션을 달고 등록하려는 컴포넌트가 포함된 패키지를 지정하여 임베디드 서블릿 컨테이너에 자동으로 등록될 수 있다. 기본적으로, @ServletComponentScan
은 어노테이션이 달린 클래스를 패키지에서 스캔한다.
18.3.11. 접근 로깅 구성(Configure Access Logging)
각각의 네임스페이스를 통해 톰캣, 언더토우 및 제티에 대한 접근 로그를 구성할 수 있다.
예를 들어, 다음 설정은 커스텀 패턴을 사용하여 톰캣에 대한 접근을 기록한다.
프로퍼티스(Properties)
server.tomcat.basedir=my-tomcat
server.tomcat.accesslog.enabled=true
server.tomcat.accesslog.pattern=%t %a %r %s (%D ms)
Yaml
server:
tomcat:
basedir: "my-tomcat"
accesslog:
enabled: true
pattern: "%t %a %r %s (%D ms)"
로그의 기본 위치는 톰캣의 기본 디렉토리에 상대적인 로그 디렉토리입니다. 기본적으로 로그 디렉토리는 임시 디렉토리이므로 톰캣의 기본 디렉토리를 수정하거나 로그에 대한 절대 경로를 사용할 수 있다. 앞의 예제에서 로그는 애플리케이션의 작업 디렉토리를 기준으로 my-tomcat/logs
에서 사용할 수 있다.
언더토우에 대한 접근 로깅은 다음 예제와 같은 방식으로 구성할 수 있다.
프로퍼티스(Properties)
server.undertow.accesslog.enabled=true
server.undertow.accesslog.pattern=%t %a %r %s (%D ms)
server.undertow.options.server.record-request-start-time=true
Yaml
server:
undertow:
accesslog:
enabled: true
pattern: "%t %a %r %s (%D ms)"
options:
server:
record-request-start-time: true
접근 로깅을 활성화하고 패턴을 구성하는 것 외에도 레코딩 시작 시간도 활성화됐다. 이는 접근 로그 패턴에 응답 시간(%D
)을 포함할 때 필요하다. 로그는 애플리케이션의 작업 디렉토리에 상대적인 경로의 logs
디렉토리에 저장된다. server.undertow.accesslog.dir
프로퍼티를 설정하여 이 위치를 커스텀할 수 있다.
마지막으로 제티에 대한 접근 로깅을 다음과 같이 구성할 수도 있다.
프로퍼티스(Properties)
server.jetty.accesslog.enabled=true
server.jetty.accesslog.filename=/var/log/jetty-access.log
Yaml
server:
jetty:
accesslog:
enabled: true
filename: "/var/log/jetty-access.log"
기본적으로, 로그는 System.err
로 리다이렉션된다. 자세한 내용은 제티 문서를 참고하자.
18.3.12. 프론트엔드 프록시 서버 뒤에서 실행(Running Behind a Front-end Proxy Server)
애플리케이션이 프록시, 로드 밸런서 또는 클라우드 뒤에서 실행되는 경우 요청 정보(호스트, 포트, 체계 등)가 도중에 변경될 수 있다. 애플리케이션이 10.10.10.10:8080
에서 실행 중일 수 있지만 HTTP 클라이언트에는 example.org
만 표시되어야 한다.
RFC7239 “전달된 헤더”는 전달된 HTTP 헤더를 정의한다. 프록시는 이 헤더를 사용하여 원래 요청에 대한 정보를 제공할 수 있다. 해당 헤더를 읽고 링크를 생성하여 HTTP 302
응답, JSON 문서 또는 HTML 페이지로 클라이언트에 보낼 때 해당 정보를 자동으로 사용하도록 애플리케이션을 구성할 수 있다. X-Forwarded-Host
, X-Forwarded-Port
, X-Forwarded-Proto
, X-Forwarded-Ssl
및 X-Forwarded-Prefix
와 같은 비표준 헤더도 있다.
프록시가 일반적으로 사용되는 X-Forwarded-For
및 X-Forwarded-Proto
헤더를 추가하는 경우 server.forward-headers-strategy
를 NATIVE
로 설정하면 이를 지원한다. 이 옵션을 사용하면 웹 서버 자체가 이 기능을 지원한다. 특정 문서를 확인하여 특정 동작에 대해 알아볼 수 있다.
이것이 충분하지 않은 경우 스프링 프레임워크는 ForwardedHeaderFilter
를 제공한다. server.forward-headers-strategy
를 FRAMEWORK
로 설정하여 애플리케이션에서 서블릿 필터로 등록할 수 있다.
톰캣을 사용하고 프록시에서 SSL을 종료하는 경우 server.tomcat.redirect-context-root
를 false
로 설정해야 한다. 이를 통해 리다이렉션이 수행되기 전에 X-Forwarded-Proto
헤더를 적용할 수 있다.
애플리케이션이 클라우드 파운드리 또는 헤로쿠(Heroku)에서 실행되는 경우 server.forward-headers-strategy
프로퍼티는 기본적으로 NATIVE
로 설정된다. 다른 모든 경우에는 기본값이 NONE
이다.
톰캣의 프록시 구성 커스텀(Customize Tomcat’s Proxy Configuration)
톰캣을 사용하는 경우 다음 예제와 같이 “포워드”된 정보를 전달하는 데 사용되는 헤더 이름을 추가로 구성할 수 있다.
프로퍼티스(Properties)
server.tomcat.remoteip.remote-ip-header=x-your-remote-ip-header
server.tomcat.remoteip.protocol-header=x-your-protocol-header
Yaml
server:
tomcat:
remoteip:
remote-ip-header: "x-your-remote-ip-header"
protocol-header: "x-your-protocol-header"
또한 톰캣은 신뢰할 수 있는 내부 프록시와 일치하는 정규식으로 구성된다. 기본값은 부록의 server.tomcat.remoteip.internal-proxies
항목을 참고하자. 다음 예제와 같이 application.properties
에 항목을 추가하여 밸브 구성을 커스텀할 수 있다.
프로퍼티스(Properties)
server.tomcat.remoteip.internal-proxies=192\\.168\\.\\d{1,3}\\.\\d{1,3}
Yaml
server:
tomcat:
remoteip:
internal-proxies: "192\\.168\\.\\d{1,3}\\.\\d{1,3}"
내부 프록시를 공백으로 설정하면 모든 프록시를 신뢰할 수 있다. 그러나 프로덕션에서는 사용하지 말자.
자동을 끄고(server.forward-headers-strategy=NONE
설정) WebServerFactoryCustomizer
빈을 사용하여 새 valve
인스턴스를 추가하여 톰캣의 RemoteIpValve
구성을 완전히 제어할 수 있다.
18.3.13. 톰캣으로 다중 커넥터 활성화(Enable Multiple Connectors with Tomcat)
다음 예제와 같이 HTTP 및 HTTPS 커넥터를 포함한 여러 커넥터를 허용할 수 있는 org.apache.catalina.connector.Connector
를 TomcatServletWebServerFactory
에 추가할 수 있다.
자바
import org.apache.catalina.connector.Connector;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class MyTomcatConfiguration {
@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> connectorCustomizer() {
return (tomcat) -> tomcat.addAdditionalTomcatConnectors(createConnector());
}
private Connector createConnector() {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setPort(8081);
return connector;
}
}
코틀린
import org.apache.catalina.connector.Connector
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory
import org.springframework.boot.web.server.WebServerFactoryCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration(proxyBeanMethods = false)
class MyTomcatConfiguration {
@Bean
fun connectorCustomizer(): WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
return WebServerFactoryCustomizer { tomcat: TomcatServletWebServerFactory ->
tomcat.addAdditionalTomcatConnectors(createConnector())
}
}
private fun createConnector(): Connector {
val connector = Connector("org.apache.coyote.http11.Http11NioProtocol")
connector.port = 8081
return connector
}
}
18.3.14. 톰캣의 MBean 레지스트리 활성화(Enable Tomcat’s MBean Registry)
내장된 톰캣의 MBean 레지스트리는 기본적으로 비활성화되어 있다. 이는 톰캣의 메모리 사용량을 최소화한다. 예를 들어 톰캣의 MBean을 사용하여 마이크로미터에서 메트릭을 노출하는 데 사용할 수 있도록 하려면 다음 예제에 표시된 대로 server.tomcat.mbeanregistry.enabled
프로퍼티를 사용해야 한다.
프로퍼티스(Properties)
server.tomcat.mbeanregistry.enabled=true
Yaml
server:
tomcat:
mbeanregistry:
enabled: true
18.3.15. 언더토우로 다중 리스너 활성화(Enable Multiple Listeners with Undertow)
다음 예제와 같이 UndertowServletWebServerFactory
에 UndertowBuilderCustomizer
를 추가하고 빌더에 리스너를 추가한다.
자바
import io.undertow.Undertow.Builder;
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class MyUndertowConfiguration {
@Bean
public WebServerFactoryCustomizer<UndertowServletWebServerFactory> undertowListenerCustomizer() {
return (factory) -> factory.addBuilderCustomizers(this::addHttpListener);
}
private Builder addHttpListener(Builder builder) {
return builder.addHttpListener(8080, "0.0.0.0");
}
}
코틀린
import io.undertow.Undertow
import org.springframework.boot.web.embedded.undertow.UndertowBuilderCustomizer
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory
import org.springframework.boot.web.server.WebServerFactoryCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration(proxyBeanMethods = false)
class MyUndertowConfiguration {
@Bean
fun undertowListenerCustomizer(): WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
return WebServerFactoryCustomizer { factory: UndertowServletWebServerFactory ->
factory.addBuilderCustomizers(UndertowBuilderCustomizer { builder: Undertow.Builder -> addHttpListener(builder) })
}
}
private fun addHttpListener(builder: Undertow.Builder): Undertow.Builder {
return builder.addHttpListener(8080, "0.0.0.0")
}
}
18.3.16. @ServerEndpoint를 사용하여 웹소켓 엔드포인트 생성(Create WebSocket Endpoints Using @ServerEndpoint)
임베디드 컨테이너를 사용한 스프링 부트 애플리케이션에서 @ServerEndpoint
를 사용하려면 다음 예제와 같이 단일 ServerEndpointExporter
@Bean
을 선언해야 한다.
자바
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration(proxyBeanMethods = false)
public class MyWebSocketConfiguration {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
코틀린
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.socket.server.standard.ServerEndpointExporter
@Configuration(proxyBeanMethods = false)
class MyWebSocketConfiguration {
@Bean
fun serverEndpointExporter(): ServerEndpointExporter {
return ServerEndpointExporter()
}
}
이전 예제에 표시된 빈은 @ServerEndpoint
어노테이션이 달린 빈을 기본 WebSocket
컨테이너에 등록한다. 독립형 서블릿 컨테이너에 배포되면 이 역할은 서블릿 컨테이너 이니셜라이저에 의해 수행되며 ServerEndpointExporter
빈은 필요하지 않습니다.
18.4. 스프링 MVC(Spring MVC)
스프링 부트에는 스프링 MVC를 포함하는 다양한 스타터가 있다. 일부 스타터에는 스프링 MVC를 직접 포함하지 않고 의존성을 포함한다는 점에 유의하자. 이 절에서는 스프링 MVC 및 스프링 부트에 대한 일반적인 질문에 답변한다.
18.4.1. JSON REST 서비스 작성(Write a JSON REST Service)
스프링 부트 애플리케이션의 모든 스프링 @RestController
는 다음 예제와 같이 Jackson2
가 클래스패스에 있는 한 기본적으로 JSON 응답을 렌더링한다.
자바
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyController {
@RequestMapping("/thing")
public MyThing thing() {
return new MyThing();
}
}
코틀린
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
class MyController {
@RequestMapping("/thing")
fun thing(): MyThing {
return MyThing()
}
}
MyThing
이 Jackson2
에 의해 직렬화될 수 있으면(일반 POJO
또는 Groovy
객체의 경우 해당) localhost:8080/thing
은 기본적으로 이에 대한 JSON 표현을 제공한다. 브라우저는 XML을 선호하는 승인 헤더를 보내는 경향이 있기 때문에 브라우저에서 때때로 XML 응답을 볼 수 있다.
18.4.2. XML REST 서비스 작성(Write an XML REST Service)
클래스패스에 잭슨(Jackson) XML 확장(jackson-dataformat-xml)이 있는 경우 이를 사용하여 XML 응답을 렌더링할 수 있다. JSON에 사용한 이전 예제가 작동한다. Jackson XML 렌더러를 사용하려면 프로젝트에 다음 의존성을 추가하자.
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
잭슨(Jackson)의 XML 확장을 사용할 수 없고 JAXB를 사용할 수 있는 경우 다음 예제와 같이 MyThing
에 @XmlRootElement
어노테이션을 추가하여 XML을 렌더링할 수 있다.
자바
import jakarta.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class MyThing {
private String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
코틀린
import jakarta.xml.bind.annotation.XmlRootElement
@XmlRootElement
class MyThing {
var name: String? = null
}
예를 들어 다음을 추가하여 JAXB 라이브러리가 프로젝트의 일부인지 확인해야 한다.
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
</dependency>
서버가 JSON 대신 XML을 렌더링하도록 하려면 Accept: text/xml
헤더를 보내야 할 수도 있다(또는 브라우저를 사용해야 할 수도 있다).
18.4.3. 잭슨 오브젝트매퍼 커스텀(Customize the Jackson ObjectMapper)
스프링 MVC(클라이언트 및 서버측)는 HttpMessageConverters
를 사용하여 HTTP 교환 중 콘텐츠를 변환한다. 잭슨(Jackson)이 클래스패스에 있는 경우 Jackson2ObjectMapperBuilder
에서 제공하는 기본 컨버터를 이미 얻은 것이다. 이 컨버터의 인스턴스는 자동으로 구성된다.
ObjectMapper(또는 Jackson XML 변환기용 XmlMapper) 인스턴스(기본적으로 생성됨)에는 다음과 같은 커스텀의 프로퍼티가 있다.
MapperFeature.DEFAULT_VIEW_INCLUSION
이 비활성화됐다.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
가 비활성화됐다.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS
가 비활성화됐다.
스프링 부트에는 이 동작을 더 쉽게 커스텀할 수 있는 몇 가지 기능도 있다.
환경(environment) 변수를 사용하여 ObjectMapper
및 XmlMapper
인스턴스를 구성할 수 있다. 잭슨(Jackson)은 처리의 다양한 측면을 구성하는 데 사용할 수 있는 광범위한 켜기/끄기 기능을 제공한다. 이러한 기능은 환경(environment) 프로퍼티에 매핑되는 6개의 enum (Jackson)에 설명되어 있다.
Enum | 프로퍼티 | 값 |
---|---|---|
com.fasterxml.jackson.databind.DeserializationFeature | spring.jackson.deserialization.<feature_name> | true, false |
com.fasterxml.jackson.core.JsonGenerator.Feature | spring.jackson.generator.<feature_name> | true, false |
com.fasterxml.jackson.databind.MapperFeature | spring.jackson.mapper.<feature_name> | true, false |
com.fasterxml.jackson.core.JsonParser.Feature | spring.jackson.parser.<feature_name> | true, false |
com.fasterxml.jackson.databind.SerializationFeature | spring.jackson.serialization.<feature_name> | true, false |
com.fasterxml.jackson.annotation.JsonInclude.Include | spring.jackson.default-property-inclusion | always, non_null, non_absent,non_default, non_empty |
예를 들어, 프리티 프린트(pretty print)를 활성화하려면 spring.jackson.serialization.indent_output=true
를 설정하자. 유연한 바인딩 사용 덕분에 indent_output
의 대소문자는 해당 enum 상수인 INDENT_OUTPUT
의 대소문자와 일치할 필요가 없다.
이 환경 변수 기반 구성은 자동 구성된 Jackson2ObjectMapperBuilder
빈에 적용되며 자동 구성된 ObjectMapper
빈을 포함하여 빌더를 사용하여 생성된 모든 매퍼에 적용된다.
컨텍스트의 Jackson2ObjectMapperBuilder
는 하나 이상의 Jackson2ObjectMapperBuilderCustomizer
빈으로 커스텀할 수 있다. 이러한 커스텀 빈은 순서를 정할 수 있으며(부트의 자체 커스텀의 순서는 0) 부트 커스텀 전후에 추가 커스텀을 적용할 수 있다.
com.fasterxml.jackson.databind.Module
타입의 모든 빈은 자동 구성된 Jackson2ObjectMapperBuilder
에 자동으로 등록되고 생성된 모든 ObjectMapper
인스턴스에 적용된다. 이는 애플리케이션에 새로운 기능을 추가할 때 커스텀 모듈을 제공하기 위한 전역 메커니즘을 제공한다.
기본 ObjectMapper
를 완전히 바꾸려면 해당 타입의 @Bean
을 정의하고 @Primary
로 표시하거나, 빌더 기반 접근 방식을 선호하는 경우 Jackson2ObjectMapperBuilder
@Bean
을 정의하자. 두 경우 모두 그렇게 하면 ObjectMapper
의 모든 자동 구성이 비활성화된다.
MappingJackson2HttpMessageConverter
타입의 @Beans
를 제공하면 MVC 구성의 기본값이 대체된다. 또한 HttpMessageConverters
타입의 빈이 제공된다(기본 MVC 구성을 사용하는 경우 항상 사용 가능). 기본 및 사용자 강화 메시지 컨버터에 접근하는 몇 가지 유용한 방법이 있다.
자세한 내용은 “@ResponseBody
렌더링 커스텀” 장과 WebMvcAutoConfiguration
소스 코드를 참고하자.
18.4.4. @ResponseBody 렌더링 커스텀(Customize the @ResponseBody Rendering)
스프링은 HttpMessageConverters
를 사용하여 @ResponseBody
(또는 @RestController
의 응답)를 렌더링한다. 스프링 부트 컨텍스트에 적절한 타입의 빈을 추가하여 추가 컨버터를 제공할 수 있다. 추가한 빈이 기본적으로 포함된 타입(예: JSON 컨버터의 경우 MappingJackson2HttpMessageConverter
)인 경우 기본값을 대체한다. HttpMessageConverters
유형의 빈이 제공되며 기본 MVC 구성을 사용하는 경우 항상 사용할 수 있다. 여기에는 기본 및 사용자 강화 메시지 컨버터에 접근하는 몇 가지 유용한 방법이 있다(예를 들어 커스텀 RestTemplate
에 수동으로 삽입하려는 경우 유용할 수 있다).
일반적인 MVC 사용에서와 마찬가지로 사용자가 제공하는 모든 WebMvcConfigurer
빈은 configureMessageConverters
메소드를 오버라이드하여 커스텀에 기여할 수도 있다. 그러나 일반 MVC와 달리 필요한 추가 커스텀만 제공할 수 있다(스프링 부트는 동일한 메커니즘을 사용하여 기본값을 제공하기 때문이다). 마지막으로, 자체 @EnableWebMvc
구성을 제공하여 스프링 부트 기본 MVC 구성을 선택 해제하면 WebMvcConfigurationSupport
의 getMessageConverters
를 사용하여 완전히 제어하고 모든 작업을 수동으로 수행할 수 있다.
자세한 내용은 WebMvcAutoConfiguration
소스 코드를 참고하자.
18.4.5. 멀티파트 파일 업로드 처리(Handling Multipart File Uploads)
스프링 부트는 파일 업로드를 지원하기 위해 서블릿 5 jakarta.servlet.http.Part
API를 수용한다. 기본적으로 스프링 부트는 단일 요청에서 파일당 최대 1MB 크기와 최대 10MB의 파일 데이터로 스프링 MVC를 구성한다. MultipartProperties
클래스에 노출된 프로퍼티를 사용하여 이러한 값, 중간 데이터가 저장되는 위치(예: /tmp
디렉터리) 및 데이터가 디스크에 플러시되는 임계값을 오버라이드할 수 있다. 예를 들어 파일을 무제한으로 지정하려면 spring.servlet.multipart.max-file-size
프로퍼티를 -1
로 설정하자.
멀티파트 지원은 스프링 MVC 컨트롤러 핸들러 메소드에서 MultipartFile
타입의 @RequestParam
어노테이션 파라미터로 멀티파트로 인코딩된 파일 데이터를 수신하려는 경우에 유용하다.
자세한 내용은 MultipartAutoConfiguration
소스를 참고하자.
Apache Commons File Upload
와 같은 추가 의존성을 도입하는 대신 컨테이너에 빌트인 멀티파트 업로드 지원을 사용하는 것이 좋다.
18.4.6. 스프링 MVC DispatcherServlet 끄기(Switch Off the Spring MVC DispatcherServlet)
기본적으로 모든 콘텐츠는 애플리케이션 루트(/)에서 제공된다. 다른 경로에 매핑하려면 다음과 같이 구성할 수 있다.
프로퍼티스(Properties)
spring.mvc.servlet.path=/mypath
Yaml
spring:
mvc:
servlet:
path: "/mypath"
추가 서블릿이 있는 경우 각각에 대해 Servlet
또는 ServletRegistrationBean
타입의 @Bean
을 선언할 수 있으며 스프링 부트는 이를 컨테이너에 투명하게 등록한다. 서블릿은 그런 방식으로 등록되기 때문에 DispatcherServlet
을 호출하지 않고도 DispatcherServlet
의 하위 컨텍스트에 매핑될 수 있다.
DispatcherServlet
을 직접 구성하는 것은 드문 일이지만 실제로 수행해야 하는 경우 DispatcherServletPath
타입의 @Bean
을 제공하여 커스텀 DispatcherServlet
의 경로를 제공해야 한다.
18.4.7. 기본 MVC 구성 끄기(Switch off the Default MVC Configuration)
MVC 구성을 완벽하게 제어하는 가장 쉬운 방법은 @EnableWebMvc
어노테이션을 사용하여 고유한 @Configuration
을 제공하는 것이다. 그렇게 하면 모든 MVC 구성이 사용자의 손 달려있게 된다.
18.4.8. 뷰리졸버 커스텀(Customize ViewResolvers)
ViewResolver
는 @Controller
의 뷰 이름을 실제 뷰 구현으로 변환하는 스프링 MVC의 핵심 컴포넌트다. ViewResolver
는 REST 스타일 서비스(@ResponseBody
를 렌더링하는 데 뷰가 사용되지 않음)보다는 UI 애플리케이션에서 주로 사용된다. 선택할 수 있는 ViewResolver
구현이 많이 있으며 스프링 자체에서는 어떤 구현을 사용해야 하는지에 대한 자체 의견은 없다. 반면에 스프링 부트는 클래스패스와 애플리케이션 컨텍스트에서 찾은 내용에 따라 하나 또는 두 개를 설치한다. DispatcherServlet은
애플리케이션 컨텍스트에서 찾은 모든 확인자를 사용하여 결과를 얻을 때까지 각 확인자를 차례로 시도해 본다. 직접 추가하는 경우 리졸버(resolver)가 추가되는 순서와 위치를 알고 있어야 한다.
WebMvcAutoConfiguration
은 다음 ViewResolver
를 컨텍스트에 추가한다.
‘defaultViewResolver’로
InternalResourceViewResolver
가 있다. 이는DefaultServlet
을 사용하여 렌더링할 수 있는 물리적 리소스(정적 리소스 및 JSP 페이지를 사용하는 경우 포함)를 찾는다. 뷰 이름에 접두사와 접미사를 적용한 다음 서블릿 컨텍스트에서 해당 경로가 있는 물리적 리소스를 찾는다. 기본값은 둘 다 비어 있지만spring.mvc.view.prefix
및spring.mvc.view.suffix
를 통해 외부 구성에 접근할 수 있다. 동일한 타입의 빈을 제공하여 이를 대체할 수 있다.‘beanNameViewResolver’로
BeanNameViewResolver
가 있다. 이는 뷰 리졸버(resolver) 체인의 멤버이며 해석되는 뷰와 동일한 이름을 가진 빈을 선택한다. 이를 오버라이드하거나 교체할 필요는 없다.‘viewResolver’인
ContentNegotiatingViewResolver
는 실제로View
타입의 빈이 있는 경우에만 추가된다. 이는 다른 모든 사용자에게 위임하고 클라이언트가 보낸 ‘Accept’ HTTP 헤더와 일치하는 항목을 찾으려고 시도하는 컴포짓 리졸버(composite resolver)이다. 자세한 내용을 알아보기 위해 연구하고 싶을 만한ContentNegotiatingViewResolver
에 대한 유용한 블로그가 있으며 자세한 내용은 소스 코드를 살펴볼 수도 있다. ‘viewResolver’라는 빈을 정의하여 자동 구성된ContentNegotiatingViewResolver
를 끌 수 있다.Thymeleaf
를 사용하는 경우 ‘thymeleafViewResolver’라는ThymeleafViewResolver
도 있다. 뷰 이름을 접두사와 접미사로 묶어 리소스를 찾는다. 접두사는spring.thymeleaf.prefix
이고 접미사는spring.thymeleaf.suffix
다. 접두사와 접미사 값의 기본값은 각각 ‘classpath:/templates/’ 및 ‘.html’이다. 동일한 이름의 빈을 제공하여ThymeleafViewResolver
를 오버라이드할 수 있다.FreeMarker
를 사용하는 경우 ‘freeMarkerViewResolver’라는FreeMarkerViewResolver
도 있다. 접두사와 접미사로 뷰 이름을 둘러싸서 로더 경로(spring.freemarker.templateLoaderPath
로 외부화되고 기본값이 ‘classpath:/templates/’)에서 리소스를 찾는다. 접두사는spring.freemarker.prefix
로 외부화되고 접미사는spring.freemarker.suffix
로 외부화된다. 접두사와 접미사의 기본값은 각각 비어 있고 ‘.ftlh’다. 동일한 이름의 빈을 제공하여FreeMarkerViewResolver
를 오버라이드할 수 있다.그루비 템플릿을 사용하는 경우(실제로
groovy-templates
가 클래스패스에 있는 경우) ‘groovyMarkupViewResolver’라는GroovyMarkupViewResolver
도 있다. 접두사와 접미사(spring.groovy.template.prefix
및spring.groovy.template.suffix
로 외부화됨)로 뷰 이름을 둘러싸서 로더 경로에서 리소스를 찾는다. 접두사와 접미사의 기본값은 각각 ‘classpath:/templates/’ 및 ‘.tpl’다. 동일한 이름의 빈을 제공하여GroovyMarkupViewResolver
를 오버라이드할 수 있다.Mustache
를 사용하는 경우 ‘mustacheViewResolver’라는MustacheViewResolver
도 있다. 뷰 이름을 접두사와 접미사로 묶어 리소스를 찾는다. 접두사는spring.mustache.prefix
이고 접미사는spring.mustache.suffix
다. 접두사 및 접미사 값의 기본값은 각각 ‘classpath:/templates/’ 및 ‘.mustache’다. 동일한 이름의 빈을 제공하여MustacheViewResolver
를 오버라이드할 수 있다.
자세한 내용은 다음 장을 참고하자.
- WebMvcAutoConfiguration
- ThymeleafAutoConfiguration
- FreeMarkerAutoConfiguration
- GroovyTemplateAutoConfiguration
18.5. 저지(Jersey)
18.5.1. 스프링 시큐리티로 저지 엔드포인트 보호(Secure Jersey endpoints with Spring Security)
스프링 시큐리티는 스프링 MVC 기반 웹 애플리케이션을 보호하는 데 사용할 수 있는 것과 거의 동일한 방식으로 저지 기반 웹 애플리케이션을 보호하는 데 사용할 수 있다. 그러나 저지(Jersey)와 함께 스프링 시큐리티의 메서드 레벨 보안을 사용하려면 sendError(int)
대신 setStatus(int)
를 사용하도록 저지(Jersey)를 구성해야 한다. 이는 스프링 시큐리티가 클라이언트에 인증 또는 권한 부여 실패를 보고하기 전에 저지가 응답을 커밋하는 것을 방지한다.
다음 예제와 같이 jersey.config.server.response.setStatusOverSendError
프로퍼티는 애플리케이션의 ResourceConfig
빈에서 true
로 설정되어야 한다.
import java.util.Collections;
import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.stereotype.Component;
@Component
public class JerseySetStatusOverSendErrorConfig extends ResourceConfig {
public JerseySetStatusOverSendErrorConfig() {
register(Endpoint.class);
setProperties(Collections.singletonMap("jersey.config.server.response.setStatusOverSendError", true));
}
}
18.5.2. 다른 웹 프레임워크와 함께 저지 사용(Use Jersey Alongside Another Web Framework)
스프링 MVC와 같은 다른 웹 프레임워크와 함께 저지(Jersey)를 사용하려면 다른 프레임워크가 처리할 수 없는 요청을 처리할 수 있도록 구성해야 한다. 먼저 spring.jersey.type
애플리케이션 프로퍼티를 필터 값으로 구성하여 서블릿 대신 필터를 사용하도록 저지를 구성한다. 둘째, 다음 예와 같이 404가 발생하는 요청을 전달하도록 ResourceConfig
를 구성한다.
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletProperties;
import org.springframework.stereotype.Component;
@Component
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
register(Endpoint.class);
property(ServletProperties.FILTER_FORWARD_ON_404, true);
}
}
18.6. HTTP 클라이언트(HTTP Clients)
스프링 부트는 HTTP 클라이언트와 함께 작동하는 다양한 스타터를 제공한다. 이 장에서는 사용과 관련된 질문에 답한다.
18.6.1. 프록시를 사용하도록 레스트템플릿 구성(Configure RestTemplate to Use a Proxy)
RestTemplate 커스텀에 설명된 대로 RestTemplateBuilder
와 함께 RestTemplateCustomizer
를 사용하여 커스텀된 RestTemplate
을 빌드할 수 있다. 이는 프록시를 사용하도록 구성된 RestTemplate
을 생성하는 데 권장되는 접근 방식이다.
프록시 구성의 정확한 세부사항은 사용 중인 기본 클라이언트 요청 팩토리에 따라 다르다.
18.6.2. 리액터 네티 기반 웹클라이언트에서 사용되는 TcpClient 구성(Configure the TcpClient used by a Reactor Netty-based WebClient)
리액터 네티가 클래스패스에 있으면 리액터 네티 기반 웹클라이언트(WebClient)가 자동 구성된다. 클라이언트의 네트워크 연결 처리를 커스텀하려면 ClientHttpConnector
빈을 제공하자. 다음 예제에서는 연결 시간 제한을 60초로 구성하고 ReadTimeoutHandler
를 추가한다.
자바
import io.netty.channel.ChannelOption;
import io.netty.handler.timeout.ReadTimeoutHandler;
import reactor.netty.http.client.HttpClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.client.reactive.ReactorResourceFactory;
@Configuration(proxyBeanMethods = false)
public class MyReactorNettyClientConfiguration {
@Bean
ClientHttpConnector clientHttpConnector(ReactorResourceFactory resourceFactory) {
HttpClient httpClient = HttpClient.create(resourceFactory.getConnectionProvider())
.runOn(resourceFactory.getLoopResources())
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 60000)
.doOnConnected((connection) -> connection.addHandlerLast(new ReadTimeoutHandler(60)));
return new ReactorClientHttpConnector(httpClient);
}
}
코틀린
import io.netty.channel.ChannelOption
import io.netty.handler.timeout.ReadTimeoutHandler
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.client.reactive.ClientHttpConnector
import org.springframework.http.client.reactive.ReactorClientHttpConnector
import org.springframework.http.client.reactive.ReactorResourceFactory
import reactor.netty.http.client.HttpClient
@Configuration(proxyBeanMethods = false)
class MyReactorNettyClientConfiguration {
@Bean
fun clientHttpConnector(resourceFactory: ReactorResourceFactory): ClientHttpConnector {
val httpClient = HttpClient.create(resourceFactory.connectionProvider)
.runOn(resourceFactory.loopResources)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 60000)
.doOnConnected { connection -> connection.addHandlerLast(ReadTimeoutHandler(60)) }
return ReactorClientHttpConnector(httpClient)
}
}
연결 공급자(connection provider) 및 이벤트 루프 리소스에 ReactorResourceFactory
를 사용하는 것에 유의하자. 이를 통해 요청을 받는 서버와 요청을 하는 클라이언트의 리소스를 효율적으로 공유할 수 있다.
18.7. 로깅(Logging)
스프링 부트에는 일반적으로 스프링 프레임워크의 spring-jcl
모듈에서 제공되는 Commons Logging API
를 제외하고 필수 로깅 의존성이 없다. Logback
을 사용하려면 classpath
에 spring-jcl
과 logback
을 포함시켜야 한다. 권장되는 방법은 모두 spring-boot-starter-logging
에 의존하는 스타터를 이용하는 것이다. 웹 애플리케이션의 경우, 로깅 스타터에 전이적으로 의존하므로 spring-boot-starter-web
만 필요하다. 메이븐을 사용하는 경우 다음 의존성이 로깅을 추가한다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
스프링 부트에는 클래스패스의 내용을 기반으로 로깅을 구성하려고 시도하는 LoggingSystem
추상화가 있다. Logback
을 사용할 수 있는 경우 이것이 첫 번째 선택이 된다.
로깅에 대해 수행해야 하는 유일한 변경 사항이 다양한 로거의 레벨을 설정하는 것이라면 다음 예제와 같이 “logging.level” 접두사를 사용하여 application.properties
에서 이를 수행할 수 있다.
프로퍼티스(Properties)
logging.level.org.springframework.web=debug
logging.level.org.hibernate=error
Yaml
logging:
level:
org.springframework.web: "debug"
org.hibernate: "error"
log.file.name
을 사용하여 콘솔 로그를 기록할 파일의 위치를 설정할 수도 있다.
로깅 시스템의 보다 세부적인 설정을 구성하려면, 해당 LoggingSystem
에서 지원하는 기본 구성 포맷(native configuration format)을 사용해야 한다. 기본적으로, 스프링 부트는 시스템의 기본 위치(예: Logback의 경우 classpath:logback.xml)에서 기본 구성을 선택하지만, login.config
프로퍼티를 사용하여 구성 파일의 위치를 설정할 수 있다.
18.7.1. 로깅을 위한 로그백 구성(Configure Logback for Logging)
application.properties
로 달성할 수 있는 것 이상으로 로그백에 커스텀를 적용해야 하는 경우 표준 로그백 구성 파일을 추가해야 한다. logback
을 찾으려면 클래스패스 루트에 logback.xml
파일을 추가할 수 있다. 스프링 부트 Logback
확장을 사용하려는 경우 logback-spring.xml
을 사용할 수도 있다.
Logback 문서에는 구성을 세부적으로 다루는 전용 장이 있다.
스프링 부트는 사용자 고유 구성에 포함될 수 있는 다양한 로그백 구성을 제공한다. 여기에는 특정 일반적인 스프링 부트 컨벤션을 다시 적용할 수 있도록 설계됐다.
다음 파일은 org/springframework/boot/logging/logback/
아래에 제공된다:
defaults.xml
- 변환 규칙, 패턴 프로퍼티 및 공통 로거 구성을 제공한다.console-appender.xml
-CONSOLE_LOG_PATTERN
을 사용하여ConsoleAppender
를 추가한다.file-appender.xml
- 적절한 설정과 함께FILE_LOG_PATTERN
및ROLLING_FILE_NAME_PATTERN
을 사용하여RollingFileAppender
를 추가한다.
또한, 이전 버전의 스프링 부트와의 호환성을 위해 레거시 base.xml
파일이 제공된다.
일반적인 커스텀 logback.xml
파일은 다음과 같다.
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
<root level="INFO">
<appender-ref ref="CONSOLE" />
</root>
<logger name="org.springframework.web" level="DEBUG"/>
</configuration>
로그백 구성 파일은 LoggingSystem
이 자동으로 생성하는 시스템 프로퍼티를 활용할 수도 있다.
- ${PID}: 현재 프로세스 ID이다.
- ${LOG_FILE}:
Logging.file.name
이 부트 외부 구성에 설정되었는지 여부다. - ${LOG_PATH}:
log.file.path
(로그 파일이 상주할 라이브러리를 구성하는 장치)가 부트의 외부 구성에 설정되었는지 여부. - ${LOG_EXCEPTION_CONVERSION_WORD}:
logging.exception-conversion-word
가 부트 외부 구성에 설정되었는지 여부다. - ${ROLLING_FILE_NAME_PATTERN}: 부트 외부 구성에
logging.pattern.rolling-file-name
이 설정되었는지 여부다.
스프링 부트는 또한 커스텀 Logback 컨버터를 사용하여 콘솔(로그 파일은 아님)에서 멋진 ANSI 색상 터미널 출력을 제공한다. 예제는 defaults.xml
구성의 CONSOLE_LOG_PATTERN
을 참고한다.
그루비가 클래스패스에 있으면 logback.groovy
를 사용하여 로그백(Logback)을 구성할 수도 있다. 존재하는 경우 이 설정이 우선적으로 적용된다.
그루비 구성에서는 스프링 확장이 지원되지 않는다. logback-spring.groovy
파일은 감지되지 않는다.
파일 전용 출력을 위한 로그백 구성(Configure Logback for File-only Output)
콘솔 로깅을 비활성화하고 파일에만 출력을 쓰려면 다음 예제와 같이 file-appender.xml
을 가져오지만 console-appender.xml
은 가져오지 않는 커스텀 logback-spring.xml
이 필요하다.
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}/}spring.log}"/>
<include resource="org/springframework/boot/logging/logback/file-appender.xml" />
<root level="INFO">
<appender-ref ref="FILE" />
</root>
</configuration>
또한 다음 예제에 표시된 대로 application.properties
또는 application.yaml
에 login.file.name
을 추가해야 한다.
프로퍼티스(Properties)
logging.file.name=myapplication.log
Yaml
logging:
file:
name: "myapplication.log"
18.7.2. 로깅을 위해 Log4j 구성(Configure Log4j for Logging)
스프링 부트는 클래스패스에 있으면 로깅 구성을 위해 Log4j 2를 지원한다. 의존성 조립을 위해 스타터를 사용하는 경우 Logback
을 제외하고 대신 Log4j 2
를 포함해야 한다. 스타터를 사용하지 않는 경우 Log4j 2
외에 spring-jcl
을 (적어도) 제공해야 한다.
약간의 흔들림이 필요하더라도, 권장 사항은 스타터를 사용하는 것이다. 다음 예제에서는 메이븐에서 스타터를 설정하는 방법을 보여준다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
그레이들은 스타터를 설정하는 몇 가지 방법을 제공한다. 한 가지 방법은 모듈 교체를 사용하는 것이다. 이렇게 하려면 다음 예제와 같이 Log4j 2
스타터에 대한 의존성을 선언하고 기본 로깅 스타터가 발생하면 Log4j 2
스타터로 대체되어야 한다고 그레이들에 알린다.
dependencies {
implementation "org.springframework.boot:spring-boot-starter-log4j2"
modules {
module("org.springframework.boot:spring-boot-starter-logging") {
replacedBy("org.springframework.boot:spring-boot-starter-log4j2", "Use Log4j2 instead of Logback")
}
}
}
Log4j
스타터는 일반적인 로깅 요구 사항(예: 톰캣에서 java.util.logging
을 사용하지만 Log4j 2
를 사용하여 출력 구성)에 대한 의존성을 함께 수집한다.
java.util.logging
을 사용하여 수행된 디버그 로깅이 Log4j 2
로 라우팅되도록 하려면 java.util.logging.manager
시스템 프로퍼티를 org.apache.logging.log4j.jul.LogManager
로 설정하여 JDK 로깅 어댑터를 구성하자.
YAML 또는 JSON을 사용하여 Log4j 2 구성(Use YAML or JSON to Configure Log4j2)
기본 XML 구성 포맷 외에도 Log4j 2
는 YAML
및 JSON
구성 파일도 지원한다. 대체 구성 파일 포맷을 사용하도록 Log4j 2
를 구성하려면 다음 예제와 같이 클래스패스에 적절한 의존성을 추가하고 선택한 파일 포맷과 일치하도록 구성 파일 이름을 지정한다.
포맷 | 의존성 | 파일명 |
---|---|---|
YAML | com.fasterxml.jackson.core:jackson-databind + com.fasterxml.jackson.dataformat:jackson-dataformat-yaml | log4j2.yaml + log4j2.yml |
JSON | com.fasterxml.jackson.core:jackson-databind | log4j2.json + log4j2.jsn |
복합 구성을 사용하여 Log4j2 구성(Use Composite Configuration to Configure Log4j2)
Log4j 2
는 여러 구성 파일을 단일 복합 구성으로 결합하는 기능을 지원한다. 스프링 부트에서 이 지원을 사용하려면 하나 이상의 보조 구성 파일 위치로 login.log4j2.config.override
를 구성하자. 보조 구성 파일은 기본 소스가 스프링 부트의 기본값, log4j.xml
과 같은 표준 위치 또는 logging.config
프로퍼티로 구성된 위치인지 여부에 관계없이 기본 구성과 병합된다.
18.8. 데이터 접근(Data Access)
스프링 부트에는 데이터 소스 작업을 위한 다양한 스타터가 포함되어 있다. 이 절에서는 이와 관련된 질문에 답한다.
18.8.1. 커스텀 데이터소스 구성(Configure a Custom DataSource)
자신만의 DataSource
를 구성하려면 구성에서 해당 타입의 @Bean
을 정의하자. 스프링 부트는 데이터베이스 초기화를 포함하여 필요한 모든 곳에서 DataSource
를 재사용한다. 일부 설정을 외부화해야 하는 경우 DataSource
를 환경에 바인딩할 수 있다(“서드파티 구성” 참고).
다음 예제에서는 빈으로 데이터 소스를 정의하는 방법을 보여준다.
자바
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class MyDataSourceConfiguration {
@Bean
@ConfigurationProperties(prefix = "app.datasource")
public SomeDataSource dataSource() {
return new SomeDataSource();
}
}
코틀린
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration(proxyBeanMethods = false)
class MyDataSourceConfiguration {
@Bean
@ConfigurationProperties(prefix = "app.datasource")
fun dataSource(): SomeDataSource {
return SomeDataSource()
}
}
다음 예제에서는 프로퍼티를 설정하여 데이터 소스를 정의하는 방법을 보여준다.
프로퍼티스(Properties)
app.datasource.url=jdbc:h2:mem:mydb
app.datasource.username=sa
app.datasource.pool-size=30
Yaml
app:
datasource:
url: "jdbc:h2:mem:mydb"
username: "sa"
pool-size: 30
SomeDataSource
에 URL, 사용자명 및 풀(pool) 크기에 대한 일반 JavaBean
프로퍼티가 있다면, DataSource
를 다른 컴포넌트가 사용하기 전 자동으로 바인딩된다.
스프링 부트는 표준 데이터 소스 중 하나를 생성하는 데 사용할 수 있는 DataSourceBuilder
라는 유틸리티 빌더 클래스도 제공한다(클래스패스에 있는 경우). 빌더는 클래스패스에서 사용 가능한 항목을 기반으로 사용할 항목을 감지할 수 있다. 또한 JDBC URL을 기반으로 드라이버를 자동 감지한다.
다음 예제에서는 DataSourceBuilder
를 사용하여 데이터 소스를 생성하는 방법을 보여준다.
자바
import javax.sql.DataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class MyDataSourceConfiguration {
@Bean
@ConfigurationProperties("app.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
}
코틀린
import javax.sql.DataSource
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.jdbc.DataSourceBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration(proxyBeanMethods = false)
class MyDataSourceConfiguration {
@Bean
@ConfigurationProperties("app.datasource")
fun dataSource(): DataSource {
return DataSourceBuilder.create().build()
}
}
해당 DataSource
로 앱을 실행하려면 커넥션 정보만 있으면 된다. 풀별 설정도 제공할 수 있다. 자세한 내용은 런타임에 사용될 구현을 확인하자.
다음 예제에서는 프로퍼티스를 설정하여 JDBC 데이터 소스를 정의하는 방법을 보여준다.
프로퍼티스(Properties)
app.datasource.url=jdbc:mysql://localhost/test
app.datasource.username=dbuser
app.datasource.password=dbpass
app.datasource.pool-size=30
Yaml
app:
datasource:
url: "jdbc:mysql://localhost/test"
username: "dbuser"
password: "dbpass"
pool-size: 30
그러나, 위 설정은 문제가 있다. 커넥션 풀의 실제 타입이 노출되지 않기 때문에, 커스텀 DataSource
에 대한 메타데이터에 키가 생성되지 않으며 IDE에서 자동 완성 기능을 사용할 수 없다 (DataSource 인터페이스가 프로퍼티스를 노출하지 않기 때문에). 또한 클래스패스에 히카리(Hikari)가 있는 경우 히카리에는 url
프로퍼티가 없지만 jdbcUrl
프로퍼티가 있기 때문에 이 기본 설정이 작동하지 않는다. 이 경우 다음과 같이 구성을 재작성해야 한다.
프로퍼티스(Properties)
app.datasource.jdbc-url=jdbc:mysql://localhost/test
app.datasource.username=dbuser
app.datasource.password=dbpass
app.datasource.pool-size=30
Yaml
app:
datasource:
jdbc-url: "jdbc:mysql://localhost/test"
username: "dbuser"
password: "dbpass"
pool-size: 30
커넥션 풀이 DataSource
가 아닌 전용 구현체을 사용하고 반환하도록 강제하여 이 문제를 해결할 수 있다. 런타임 시 구현체를 변경할 수 없지만, 옵션 목록은 명시적이다.
다음 예제에서는 DataSourceBuilder
를 사용하여 HikariDataSource
를 생성하는 방법을 보여준다.
자바
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class MyDataSourceConfiguration {
@Bean
@ConfigurationProperties("app.datasource")
public HikariDataSource dataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
}
코틀린
import com.zaxxer.hikari.HikariDataSource
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.jdbc.DataSourceBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration(proxyBeanMethods = false)
class MyDataSourceConfiguration {
@Bean
@ConfigurationProperties("app.datasource")
fun dataSource(): HikariDataSource {
return DataSourceBuilder.create().type(HikariDataSource::class.java).build()
}
}
DataSourceProperties
의 기능을 활용하면 더 많은 작업을 수행할 수 있다. 즉, URL이 제공되지 않는 경우 적절한 사용자명과 비밀번호가 포함된 기본 임베디드 데이터베이스를 제공하면 된다. DataSourceProperties
객체의 상태에서 DataSourceBuilder
를 쉽게 초기화할 수 있으므로 스프링 부트가 자동으로 생성하는 DataSource
를 주입할 수도 있다. 그러나 이렇게 하면 구성이 두 개의 네임스페이스, 즉 spring.datasource
의 url
, 사용자명, 비밀번호, 타입 및 드라이버로 분할되고 나머지는 커스텀 네임스페이스(app.datasource
)로 분할된다. 이를 방지하려면 다음 예제와 같이 커스텀 네임스페이스에서 커스텀 DataSourceProperties
를 오버라이드할 수 있다.
자바
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration(proxyBeanMethods = false)
public class MyDataSourceConfiguration {
@Bean
@Primary
@ConfigurationProperties("app.datasource")
public DataSourceProperties dataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@ConfigurationProperties("app.datasource.configuration")
public HikariDataSource dataSource(DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
}
코틀린
import com.zaxxer.hikari.HikariDataSource
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Primary
@Configuration(proxyBeanMethods = false)
class MyDataSourceConfiguration {
@Bean
@Primary
@ConfigurationProperties("app.datasource")
fun dataSourceProperties(): DataSourceProperties {
return DataSourceProperties()
}
@Bean
@ConfigurationProperties("app.datasource.configuration")
fun dataSource(properties: DataSourceProperties): HikariDataSource {
return properties.initializeDataSourceBuilder().type(HikariDataSource::class.java).build()
}
}
이 설정을 사용하면 전용 커넥션 풀이 코드에서 선택되고, 해당 설정이 app.datasource.configuration
하위 네임스페이스에 노출된다는 점을 제외하면, 기본적으로 스프링 부트가 수행하는 작업과 동기화된다. DataSourceProperties
가 url/jdbcUrl
변환을 처리하므로 다음과 같이 구성할 수 있다.
프로퍼티스(Properties)
app.datasource.url=jdbc:mysql://localhost/test
app.datasource.username=dbuser
app.datasource.password=dbpass
app.datasource.configuration.maximum-pool-size=30
Yaml
app:
datasource:
url: "jdbc:mysql://localhost/test"
username: "dbuser"
password: "dbpass"
configuration:
maximum-pool-size: 30
스프링 부트는 히카리(Hikari) 관련 설정을 spring.datasource.hikari
에 노출한다. 이 예제에서는 여러 데이터 소스 구현을 지원하지 않으므로 보다 일반적인 구성 하위 네임스페이스를 사용한다.
커스텀 구성이 히카리를 사용하도록 선택했기 때문에 app.datasource.type
은 효과가 없다. 실제로 빌더는 설정한 값으로 초기화된 다음 .type()
호출로 오버라이드된다.
자세한 내용은 “스프링 기능” 장의 “데이터소스 구성”과 DataSourceAutoConfiguration
클래스를 참고하자.
18.8.2. 두 가지 데이터소스 구성(Configure Two DataSources)
여러 데이터 소스을 구성해야 하는 경우 이전 절에서 설명한 것과 동일한 방법을 적용할 수 있다. 그러나 DataSource
인스턴스 중 하나를 @Primary
로 표시해야 한다. 왜냐하면 앞으로 다양한 자동 구성을 통해 타입별 인스턴스를 얻을 수 있을 것이기 때문이다.
자체 DataSource
를 생성하면 자동 구성이 취소된다. 다음 예제에서는 기본 데이터 소스에서 자동 구성이 제공하는 것과 정확히 동일한 기능을 제공한다.
자바
import com.zaxxer.hikari.HikariDataSource;
import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration(proxyBeanMethods = false)
public class MyDataSourcesConfiguration {
@Bean
@Primary
@ConfigurationProperties("app.datasource.first")
public DataSourceProperties firstDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@Primary
@ConfigurationProperties("app.datasource.first.configuration")
public HikariDataSource firstDataSource(DataSourceProperties firstDataSourceProperties) {
return firstDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
@Bean
@ConfigurationProperties("app.datasource.second")
public BasicDataSource secondDataSource() {
return DataSourceBuilder.create().type(BasicDataSource.class).build();
}
}
코틀린
import com.zaxxer.hikari.HikariDataSource
import org.apache.commons.dbcp2.BasicDataSource
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.jdbc.DataSourceBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Primary
@Configuration(proxyBeanMethods = false)
class MyDataSourcesConfiguration {
@Bean
@Primary
@ConfigurationProperties("app.datasource.first")
fun firstDataSourceProperties(): DataSourceProperties {
return DataSourceProperties()
}
@Bean
@Primary
@ConfigurationProperties("app.datasource.first.configuration")
fun firstDataSource(firstDataSourceProperties: DataSourceProperties): HikariDataSource {
return firstDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource::class.java).build()
}
@Bean
@ConfigurationProperties("app.datasource.second")
fun secondDataSource(): BasicDataSource {
return DataSourceBuilder.create().type(BasicDataSource::class.java).build()
}
}
이니셜라이저를 사용하는 경우, 데이터베이스 이니셜라이저 기능이 복사본을 사용하도록 firstDataSourceProperties
를 @Primary
로 플래그 지정해야 한다.
두 데이터 소스 모두 고급 커스텀에도 적용된다. 예를 들어 다음과 같이 구성할 수 있다.
프로퍼티스(Properties)
app.datasource.first.url=jdbc:mysql://localhost/first
app.datasource.first.username=dbuser
app.datasource.first.password=dbpass
app.datasource.first.configuration.maximum-pool-size=30
app.datasource.second.url=jdbc:mysql://localhost/second
app.datasource.second.username=dbuser
app.datasource.second.password=dbpass
app.datasource.second.max-total=30
Yaml
app:
datasource:
first:
url: "jdbc:mysql://localhost/first"
username: "dbuser"
password: "dbpass"
configuration:
maximum-pool-size: 30
second:
url: "jdbc:mysql://localhost/second"
username: "dbuser"
password: "dbpass"
max-total: 30
다음 예제와 같이 보조 DataSource
에도 동일한 개념을 적용할 수 있다.
자바
import com.zaxxer.hikari.HikariDataSource;
import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration(proxyBeanMethods = false)
public class MyCompleteDataSourcesConfiguration {
@Bean
@Primary
@ConfigurationProperties("app.datasource.first")
public DataSourceProperties firstDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@Primary
@ConfigurationProperties("app.datasource.first.configuration")
public HikariDataSource firstDataSource(DataSourceProperties firstDataSourceProperties) {
return firstDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
@Bean
@ConfigurationProperties("app.datasource.second")
public DataSourceProperties secondDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@ConfigurationProperties("app.datasource.second.configuration")
public BasicDataSource secondDataSource(@Qualifier("secondDataSourceProperties") DataSourceProperties secondDataSourceProperties) {
return secondDataSourceProperties.initializeDataSourceBuilder().type(BasicDataSource.class).build();
}
}
코틀린
import com.zaxxer.hikari.HikariDataSource
import org.apache.commons.dbcp2.BasicDataSource
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Primary
@Configuration(proxyBeanMethods = false)
class MyCompleteDataSourcesConfiguration {
@Bean
@Primary
@ConfigurationProperties("app.datasource.first")
fun firstDataSourceProperties(): DataSourceProperties {
return DataSourceProperties()
}
@Bean
@Primary
@ConfigurationProperties("app.datasource.first.configuration")
fun firstDataSource(firstDataSourceProperties: DataSourceProperties): HikariDataSource {
return firstDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource::class.java).build()
}
@Bean
@ConfigurationProperties("app.datasource.second")
fun secondDataSourceProperties(): DataSourceProperties {
return DataSourceProperties()
}
@Bean
@ConfigurationProperties("app.datasource.second.configuration")
fun secondDataSource(secondDataSourceProperties: DataSourceProperties): BasicDataSource {
return secondDataSourceProperties.initializeDataSourceBuilder().type(BasicDataSource::class.java).build()
}
}
이전 예제에서는 스프링 부트가 자동 구성에 사용하는 것과 동일한 로직를 사용하여 커스텀 네임스페이스에 두 개의 데이터 소스를 구성한다. 각 구성 하위 네임스페이스는 선택한 구현에 따라 고급 설정을 제공한다.
18.8.3. 스프링 데이터 리포지터리 사용(Use Spring Data Repositories)
스프링 데이터는 다양한 종류의 @Repository
인터페이스 구현체를 생성할 수 있다. @Repositories
가 @EnableAutoConfiguration
클래스의 동일한 패키지(또는 하위 패키지)에 포함되어 있으면 스프링 부트는 이 모든 것을 처리한다.
많은 애플리케이션에서 필요한 것은 클래스패스에 올바른 스프링 데이터 의존성을 배치하는 것뿐이다. JPA용 spring-boot-starter-data-jpa
, Mongodb
용 spring-boot-starter-data-mongodb
및 지원되는 기술에 대한 다양한 기타 스타터가 있다. 시작하려면 @Entity
객체를 처리할 리포지터리 인터페이스를 만들자.
스프링 부트는 찾은 @EnableAutoConfiguration
을 기반으로 @Repository
정의 위치를 추측하려 한다. 더 많은 제어가 필요하면, @EnableJpaRepositories
어노테이션(스프링 데이터 JPA)을 사용하자.
스프링 데이터에 대한 자세한 내용은 스프링 데이터 프로젝트 페이지를 참고하자.
18.8.4. 스프링 구성에서 @Entity 정의 분리(Separate @Entity Definitions from Spring Configuration)
스프링 부트는 찾은 @EnableAutoConfiguration
을 기반으로 @Entity
정의의 위치를 찾으려 시도한다. 더 많은 제어가 필요하면 다음 예제와 같이 @EntityScan
어노테이션을 사용할 수 있다.
자바
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
@EnableAutoConfiguration
@EntityScan(basePackageClasses = City.class)
public class MyApplication {
// ...
}
코틀린
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.boot.autoconfigure.domain.EntityScan
import org.springframework.context.annotation.Configuration
@Configuration(proxyBeanMethods = false)
@EnableAutoConfiguration
@EntityScan(basePackageClasses = [City::class])
class MyApplication {
// ...
}
18.8.5. JPA 프로퍼티 구성(Configure JPA Properties)
스프링 데이터 JPA는 이미 일부 벤더 독립적 구성 옵션(예: SQL 로깅용 옵션)을 제공하고 있으며, 스프링 부트는 이러한 옵션과 하이버네이트용 추가 옵션을 외부 구성 프로퍼티로 노출한다. 그 중 일부는 상황에 따라 자동으로 감지되므로 별도로 설정할 필요가 없다.
spring.jpa.hibernate.ddl-auto
는 런타임 조건에 따라 기본값이 다르기 때문에 특별한 경우다. 임베디드 데이터베이스가 사용되고 스키마 매니저(예: Liquibase 또는 Flyway)가 DataSource
를 처리하지 않는 경우 기본값은 생성-삭제(create-drop)이다. 다른 모든 경우에는 기본값이 없음(none)이다.
사용할 다이어렉트(dialect)은 JPA 프로바이더에 의해 감지된다. 다이어렉트(dialect)을 직접 설정하려면 spring.jpa.database-platform
프로퍼티를 설정하자.
설정하는 가장 일반적인 옵션은 다음 예제에 나와 있다.
프로퍼티스(Properties)
spring.jpa.hibernate.naming.physical-strategy=com.example.MyPhysicalNamingStrategy
spring.jpa.show-sql=true
Yaml
spring:
jpa:
hibernate:
naming:
physical-strategy: "com.example.MyPhysicalNamingStrategy"
show-sql: true
또한, spring.jpa.properties.*
의 모든 프로퍼티는 로컬 EntityManagerFactory
가 생성될 때 일반 JPA 프로퍼티(접두사가 제거됨)으로 전달된다.
warning
spring.jpa.properties.*
에 정의된 이름이 JPA 프로바이더가 예상하는 이름과 정확히 일치하는지 확인해야 한다. 스프링 부트는 이러한 항목에 대해 어떤 종류의 완화된 바인딩도 시도하지 않는다.
예를 들어, 하이버네이트의 배치 크기를 구성하려면 spring.jpa.properties.hibernate.jdbc.batch_size
를 사용해야 한다. batchSize
또는 batch-size
와 같은 다른 형식을 사용하는 경우 하이버네이트(Hibernate)는 설정을 적용하지 않는다.
하이버네이트 프로퍼티스에 고급 커스텀를 적용해야 하는 경우 EntityManagerFactory
를 생성하기 전에 호출될 HibernatePropertiesCustomizer
빈을 등록하는 것을 고려하자. 이는 자동 구성에 의해 적용되는 모든 항목보다 우선시 된다.
18.8.6. 하이버네이트 네이밍 전략 구성(Configure Hibernate Naming Strategy)
하이버네이트는 객체 모델명을 해당 데이터베이스명에 매핑하기 위해 두 가지 다른 명명 전략을 사용한다. 물리적 및 암시적 전략 구현의 완전한 클래스명은 각각 spring.jpa.hibernate.naming.physical-strategy
및 spring.jpa.hibernate.naming.implicit-strategy
프로퍼티스를 설정하여 구성할 수 있다. 대안으로, ImplicitNamingStrategy
또는 PhysicalNamingStrategy
빈이 애플리케이션 컨텍스트에서 사용 가능하다면 하이버네이트는 이를 사용하도록 자동으로 구성된다.
기본적으로, 스프링 부트는 CamelCaseToUnderscoresNamingStrategy
를 사용하여 물리적 명명 전략을 구성한다. 이 전략을 사용하면 모든 점(dot)이 밑줄(underscore)로 대체되고 카멜 케이스도 밑줄로 대체된다. 또한 기본적으로 모든 테이블 이름은 소문자로 생성된다. 예를 들어 TelephoneNumber
엔터티는 Telephone_number
테이블에 매핑된다. 스키마에 대소문자 혼합 식별자가 필요한 경우 다음 예제와 같이 커스텀 CamelCaseToUnderscoresNamingStrategy
빈을 정의하자.
자바
import org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class MyHibernateConfiguration {
@Bean
public CamelCaseToUnderscoresNamingStrategy caseSensitivePhysicalNamingStrategy() {
return new CamelCaseToUnderscoresNamingStrategy() {
@Override
protected boolean isCaseInsensitive(JdbcEnvironment jdbcEnvironment) {
return false;
}
};
}
}
코틀린
import org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration(proxyBeanMethods = false)
class MyHibernateConfiguration {
@Bean
fun caseSensitivePhysicalNamingStrategy(): CamelCaseToUnderscoresNamingStrategy {
return object : CamelCaseToUnderscoresNamingStrategy() {
override fun isCaseInsensitive(jdbcEnvironment: JdbcEnvironment): Boolean {
return false
}
}
}
}
대신 하이버네이트의 기본값을 사용하려면 다음 프로퍼티를 설정하자.
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
또는, 다음 빈을 구성할 수 있다.
자바
import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
class MyHibernateConfiguration {
@Bean
PhysicalNamingStrategyStandardImpl caseSensitivePhysicalNamingStrategy() {
return new PhysicalNamingStrategyStandardImpl();
}
}
코틀린
import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration(proxyBeanMethods = false)
internal class MyHibernateConfiguration {
@Bean
fun caseSensitivePhysicalNamingStrategy(): PhysicalNamingStrategyStandardImpl {
return PhysicalNamingStrategyStandardImpl()
}
}
자세한 내용은 HibernateJpaAutoConfiguration
및 JpaBaseConfiguration
을 참고하자.
18.8.7. 하이버네이트 2차 레벨 캐싱 구성(Configure Hibernate Second-Level Caching)
하이버네이트 2차 레벨 캐시는 다양한 캐시 프로바이더로 구성될 수 있다. 캐시 프로바이더를 다시 조회하도록 하이버네이트를 구성하는 것보다 가능할 때마다 컨텍스트에서 사용 가능한 것을 제공하는 것이 더 좋다.
JCache
로 이를 수행하려면 먼저 org.hibernate.orm:hibernate-jcache
가 클래스패스에서 사용 가능한지 확인하자. 그런 다음 다음 예제에 표시된 대로 HibernatePropertiesCustomizer
빈을 추가하자.
자바
import org.hibernate.cache.jcache.ConfigSettings;
import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
import org.springframework.cache.jcache.JCacheCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class MyHibernateSecondLevelCacheConfiguration {
@Bean
public HibernatePropertiesCustomizer hibernateSecondLevelCacheCustomizer(JCacheCacheManager cacheManager) {
return (properties) -> properties.put(ConfigSettings.CACHE_MANAGER, cacheManager.getCacheManager());
}
}
코틀린
import org.hibernate.cache.jcache.ConfigSettings
import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer
import org.springframework.cache.jcache.JCacheCacheManager
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration(proxyBeanMethods = false)
class MyHibernateSecondLevelCacheConfiguration {
@Bean
fun hibernateSecondLevelCacheCustomizer(cacheManager: JCacheCacheManager): HibernatePropertiesCustomizer {
return HibernatePropertiesCustomizer { properties ->
properties[ConfigSettings.CACHE_MANAGER] = cacheManager.cacheManager
}
}
}
이 커스터마이저는 애플리케이션이 사용하는 것과 동일한 CacheManager
를 사용하도록 하이버네이트를 구성한다. 별도의 CacheManager
인스턴스를 사용하는 것도 가능하다. 자세한 내용은 하이버네이트 사용자 가이드를 참고하자.
18.8.8. 하이버네이트 컴포넌트에서 의존성 주입 사용(Use Dependency Injection in Hibernate Components)
기본적으로 스프링 부트는 컨버터와 엔터티 리스너가 정기적인 의존성 주입을 사용할 수 있도록 BeanFactory
를 사용하는 BeanContainer
구현체를 등록한다.
hibernate.resource.beans.container
프로퍼티를 제거하거나 변경하는 HibernatePropertiesCustomizer
를 등록하여 이 동작을 비활성화하거나 조정할 수 있다.
18.8.9. 커스텀 EntityManagerFactory 사용(Use a Custom EntityManagerFactory)
EntityManagerFactory
구성을 완전히 제어하려면, ‘entityManagerFactory’라는 @Bean
을 추가해야 한다. 스프링 부트 자동 구성은 해당 타입의 빈이 있는 경우 해당 엔티티 매니저를 끈다.
18.8.10. 여러 EntityManagerFactory 사용(Using Multiple EntityManagerFactories)
여러 데이터 소스에 대해 JPA를 사용해야 하는 경우 데이터 소스당 하나의 EntityManagerFactory
가 필요할 수 있다. 스프링 ORM의 LocalContainerEntityManagerFactoryBean
을 사용하면 필요에 맞게 EntityManagerFactory
를 구성할 수 있다. 다음 예제와 같이 JpaProperties
를 재사용하여 각 EntityManagerFactory
에 대한 설정을 바인딩할 수도 있다.
자바
import javax.sql.DataSource;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
@Configuration(proxyBeanMethods = false)
public class MyEntityManagerFactoryConfiguration {
@Bean
@ConfigurationProperties("app.jpa.first")
public JpaProperties firstJpaProperties() {
return new JpaProperties();
}
@Bean
public LocalContainerEntityManagerFactoryBean firstEntityManagerFactory(DataSource firstDataSource, JpaProperties firstJpaProperties) {
EntityManagerFactoryBuilder builder = createEntityManagerFactoryBuilder(firstJpaProperties);
return builder.dataSource(firstDataSource).packages(Order.class).persistenceUnit("firstDs").build();
}
private EntityManagerFactoryBuilder createEntityManagerFactoryBuilder(JpaProperties jpaProperties) {
JpaVendorAdapter jpaVendorAdapter = createJpaVendorAdapter(jpaProperties);
return new EntityManagerFactoryBuilder(jpaVendorAdapter, jpaProperties.getProperties(), null);
}
private JpaVendorAdapter createJpaVendorAdapter(JpaProperties jpaProperties) {
// ... 필요에 따라 JPA 프로퍼티스 매핑
return new HibernateJpaVendorAdapter();
}
}
코틀린
import javax.sql.DataSource
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.orm.jpa.JpaVendorAdapter
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter
@Configuration(proxyBeanMethods = false)
class MyEntityManagerFactoryConfiguration {
@Bean
@ConfigurationProperties("app.jpa.first")
fun firstJpaProperties(): JpaProperties {
return JpaProperties()
}
@Bean
fun firstEntityManagerFactory(
firstDataSource: DataSource?,
firstJpaProperties: JpaProperties
): LocalContainerEntityManagerFactoryBean {
val builder = createEntityManagerFactoryBuilder(firstJpaProperties)
return builder.dataSource(firstDataSource).packages(Order::class.java).persistenceUnit("firstDs").build()
}
private fun createEntityManagerFactoryBuilder(jpaProperties: JpaProperties): EntityManagerFactoryBuilder {
val jpaVendorAdapter = createJpaVendorAdapter(jpaProperties)
return EntityManagerFactoryBuilder(jpaVendorAdapter, jpaProperties.properties, null)
}
private fun createJpaVendorAdapter(jpaProperties: JpaProperties): JpaVendorAdapter {
// ... 필요에 따라 JPA 프로퍼티스 매핑
return HibernateJpaVendorAdapter()
}
}
위의 예제에서는 firstDataSource
라는 DataSource
빈을 사용하여 EntityManagerFactory
를 생성한다. Order
와 동일한 패키지에 있는 엔터티를 스캔한다. app.first.jpa
네임스페이스를 사용하여 추가 JPA 프로퍼티를 매핑할 수 있다.
LocalContainerEntityManagerFactoryBean
에 대한 빈을 직접 생성하면, 자동 구성된 LocalContainerEntityManagerFactoryBean
생성 중에 적용된 모든 커스텀이 손실된다. 예를 들어, 하이버네이트의 경우 spring.jpa.hibernate
접두사 아래의 모든 프로퍼티는 LocalContainerEntityManagerFactoryBean
에 자동으로 적용되지 않는다. 명명 전략이나 DDL 모드 등을 구성하기 위해 이러한 프로퍼티를 사용했다면 LocalContainerEntityManagerFactoryBean
빈을 생성할 때 이를 명시적으로 구성해야 한다.
JPA 접근이 필요한 추가 데이터 소스에 대해 유사한 구성을 제공해야 한다. 그림을 완성하려면, 각 EntityManagerFactory
에 대해 JpaTransactionManager
도 구성해야 한다. 또는 두 가지 모두를 포괄하는 JTA 트랜잭션 매니저를 사용할 수도 있다.
스프링 데이터를 사용하는 경우 다음 예제와 같이 @EnableJpaRepositories
를 적절하게 구성해야 한다.
자바
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@Configuration(proxyBeanMethods = false)
@EnableJpaRepositories(basePackageClasses = Order.class, entityManagerFactoryRef = "firstEntityManagerFactory")
public class OrderConfiguration {
}
코틀린
import org.springframework.context.annotation.Configuration
import org.springframework.data.jpa.repository.config.EnableJpaRepositories
@Configuration(proxyBeanMethods = false)
@EnableJpaRepositories(basePackageClasses = [Order::class], entityManagerFactoryRef = "firstEntityManagerFactory")
class OrderConfiguration
자바
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@Configuration(proxyBeanMethods = false)
@EnableJpaRepositories(basePackageClasses = Customer.class, entityManagerFactoryRef = "secondEntityManagerFactory")
public class CustomerConfiguration {
}
코틀린
import org.springframework.context.annotation.Configuration
import org.springframework.data.jpa.repository.config.EnableJpaRepositories
@Configuration(proxyBeanMethods = false)
@EnableJpaRepositories(basePackageClasses = [Customer::class], entityManagerFactoryRef = "secondEntityManagerFactory")
class CustomerConfiguration
18.8.11. 기존 persistence.xml 파일 사용(Use a Traditional persistence.xml File)
스프링 부트는 기본적으로 META-INF/persistence.xml
을 검색하거나 사용하지 않는다. 기존 persistence.xml
을 사용하려는 경우, LocalEntityManagerFactoryBean
타입(‘entityManagerFactory’ ID 사용)의 고유한 @Bean
을 정의하고 거기에 영속성 단위명을 설정해야 한다.
기본 설정은 JpaBaseConfiguration
을 참고하자.
18.8.12. 스프링 데이터 JPA 및 몽고 리포지터리 사용(Use Spring Data JPA and Mongo Repositories)
스프링 데이터 JPA와 스프링 데이터 몽고는 둘 다 자동으로 리포지터리 구현을 생성할 수 있다. 둘 다 클래스패스에 있는 경우 생성할 리포지터리를 스프링 부트에 알리기 위해 몇 가지 추가 구성을 수행해야 할 수도 있다. 이를 수행하는 가장 명시적인 방법은 표준 스프링 데이터 @EnableJpaRepositories
및 @EnableMongoRepositories
어노테이션을 사용하고 리포지터리 인터페이스의 위치를 제공하는 것이다.
외부 구성에서 자동 구성된 리포지터리를 켜고 끄는 데 사용할 수 있는 플래그(spring.data.*.repositories.enabled
및 spring.data.*.repositories.type
)도 있다. 예를 들어 몽고 리포지터리를 끄고 자동 구성된 MongoTemplate
을 사용하려는 경우 이렇게 하는것이 유용하다.
자동 구성된 다른 스프링 데이터 리포지터리 타입(Elasticsearch
, Redis
등)에도 동일한 장애물과 기능이 존재한다. 이를 사용하려면 그에 따라 어노테이션 및 플래그명을 변경하자.
18.8.13. 스프링 데이터의 웹 지원 커스텀(Customize Spring Data’s Web Support)
스프링 데이터는 웹 애플리케이션에서 스프링 데이터 리포지터리의 사용을 단순화하는 웹 지원을 제공한다. 스프링 부트는 해당 구성을 커스텀하기 위해 spring.data.web
네임스페이스에 프로퍼티스를 제공한다. 스프링 데이터 REST를 사용하는 경우 대신 spring.data.rest
네임스페이스의 프로퍼티스를 사용해야 한다.
18.8.14. 스프링 데이터 리포지터리를 REST 엔드포인트로 노출(Expose Spring Data Repositories as REST Endpoint)
스프링 데이터 REST는 애플리케이션에 대해 스프링 MVC가 활성화된 경우 리포지터리 구현체를 REST 엔트포인트로 노출할 수 있다.
스프링 부트는 RepositoryRestConfiguration
을 커스텀하는 유용한 프로퍼티스(spring.data.rest
네임스페이스)를 노출한다. 추가 커스텀를 제공해야 하는 경우 RepositoryRestConfigurer
빈을 사용해야 한다.
커스텀 RepositoryRestConfigurer
에서 순서를 지정하지 않으면, 스프링 부트가 내부적인 순서 후에 실행된다. 순서를 지정해야 하는 경우 0보다 큰지 확인하자.
18.8.15. JPA에서 사용되는 구성 컴포넌트(Configure a Component that is Used by JPA)
JPA가 사용하는 컴포넌트를 구성하려면 컴포넌트가 JPA보다 먼저 초기화되었는지 확인해야 한다. 컴포넌트가 자동 구성되면 스프링부트가 이를 자동으로 처리한다. 예를 들어, Flyway가 자동 구성되면 하이버네이트는 하이버네이트가 데이터베이스를 사용하기 전에 Flyway가 데이터베이스를 초기화할 수 있도록 Flyway에 의존하도록 구성된다.
컴포넌트를 직접 구성하는 경우 필요한 의존성을 설정하는 편리한 방법으로 EntityManagerFactoryDependsOnPostProcessor
하위 클래스를 사용할 수 있다. 예를 들어, 엘라스틱서치를 인덱스 매니저로 사용하여 하이버네이트 서치를 사용하는 경우 다음 예제에 표시된 것처럼 모든 EntityManagerFactory
빈은 elasticsearchClient
빈에 종속되도록 구성되어야 한다.
자바
import jakarta.persistence.EntityManagerFactory;
import org.springframework.boot.autoconfigure.orm.jpa.EntityManagerFactoryDependsOnPostProcessor;
import org.springframework.stereotype.Component;
/**
* {@link EntityManagerFactoryDependsOnPostProcessor}
* {@link EntityManagerFactory} 빈은 {@code elasticsearchClient} 빈에 의존한다.
*/
@Component
public class ElasticsearchEntityManagerFactoryDependsOnPostProcessor extends EntityManagerFactoryDependsOnPostProcessor {
public ElasticsearchEntityManagerFactoryDependsOnPostProcessor() {
super("elasticsearchClient");
}
}
코틀린
import org.springframework.boot.autoconfigure.orm.jpa.EntityManagerFactoryDependsOnPostProcessor
import org.springframework.stereotype.Component
@Component
class ElasticsearchEntityManagerFactoryDependsOnPostProcessor : EntityManagerFactoryDependsOnPostProcessor("elasticsearchClient")
18.8.16. 두 개의 데이터 소스로 jOOQ 구성(Configure jOOQ with Two DataSources)
여러 데이터 소스와 함께 jOOQ를 사용해야 하는 경우 각각에 대해 고유한 DSLContext
를 만들어야 한다. 자세한 내용은 JooqAutoConfiguration
을 참고하자.
특히 JooqExceptionTranslator
및 SpringTransactionProvider
를 재사용하여 단일 DataSource
로 자동 구성이 수행하는 것과 유사한 기능을 제공할 수 있다.
18.9. 데이터베이스 초기화(Database Initialization)
SQL 데이터베이스는 스택이 무엇인지에 따라 다양한 방법으로 초기화될 수 있다. 물론, 데이터베이스가 별도의 프로세스라면 수동으로 수행할 수도 있다. 스키마 생성에는 단일 메커니즘을 사용하는 것이 좋다.
18.9.1. JPA를 사용하여 데이터베이스 초기화(Initialize a Database Using JPA)
JPA에는 DDL 생성 기능이 있으며, 이러한 기능은 데이터베이스에 대해 시작 시 실행되도록 설정할 수 있다. 이는 두 가지 외부 프로퍼티를 통해 제어된다.
spring.jpa.generate-ddl
(boolean) 기능을 켜고 끌 수 있으며 벤더에 독립적이다.spring.jpa.hibernate.ddl-auto
(enum)는 보다 세밀한 방식으로 동작을 제어하는 하이버네이트 기능이다. 이 기능은 이 가이드의 뒷부분에서 자세히 설명한다.
18.9.2. 하이버네이트를 사용하여 데이터베이스 초기화(Initialize a Database Using Hibernate)
spring.jpa.hibernate.ddl-auto
를 명시적으로 설정할 수 있으며 표준 하이버네이트 프로퍼티는 none
, verify
, update
, create
및 create-drop
이다. 스프링 부트는 데이터베이스 임베디드 여부에 따라 기본값을 선택한다. 스키마 매니저가 감지되지 않은 경우 기본값은 create-drop
이고 다른 모든 경우에는 none
이다. 임베디드 데이터베이스는 커넥션 타입과 JDBC URL을 확인하여 감지한다. hsqldb
, h2
및 derby
를 후보로 두고, 다른 것들은 후보로 두지 않는다. 인메모리에서 ‘실제’ 데이터베이스로 전환할 때, 새 플랫폼에 테이블과 데이터가 없을 수도 있으니 주의하자. ddl-auto
를 명시적으로 설정하거나 다른 메커니즘 중 하나를 사용하여 데이터베이스를 초기화해야 한다.
org.hibernate.SQL
로거를 활성화하여 스키마 생성을 출력할 수 있다. 디버그 모드를 활성화하면 이 작업은 자동으로 수행된다.
또한, 하이버네이트가 처음부터 스키마를 생성하는 경우(즉, ddl-auto
프로퍼티가 create
또는 create-drop
으로 설정된 경우) 시작 시 클래스패스 루트에 있는 import.sql
이라는 파일이 실행된다. 주의하면 데모와 테스트에 유용할 수 있지만, 프로덕션 환경에서 클래스패스에 포함하고 싶지는 않을 것이다. 이는 하이버네이트 기능이다(스프링과는 아무 관련이 없다).
18.9.3. 기본 SQL 스크립트를 사용하여 데이터베이스 초기화(Initialize a Database Using Basic SQL Scripts)
스프링 부트는 JDBC 데이터소스(DataSource) 또는 R2DBC 케넥션팩토리(ConnectionFactory)의 스키마(DDL 스크립트)를 자동으로 생성하고 초기화(DML 스크립트)할 수 있다. 이는 표준 루트 클래스패스(각각 schema.sql
및 data.sql
)에서 SQL을 로드한다. 또한 스프링 부트는 schema-${platform}.sql
및 data-${platform}.sql
파일(있는 경우)을 처리한다. 여기서 platform
은 spring.sql.init.platform
의 값이다. 필요할 경우 이를 통해 데이터베이스별 스크립트로 전환할 수 있다. 예를 들어 데이터베이스 벤더명(hsqldb
, h2
, oracle
, mysql
, postgresql
등)으로 설정하도록 선택할 수 있다. 기본적으로 SQL 데이터베이스 초기화는 임베디드 메모리 데이터베이스를 사용할 때만 수행된다. 타입에 관계없이 SQL 데이터베이스를 항상 초기화하려면, spring.sql.init.mode
를 always
로 설정하자. 마찬가지로 초기화를 비활성화하려면, spring.sql.init.mode
를 never
로 설정하자. 기본적으로 스프링 부트는 스크립트 기반 데이터베이스 이니셜라이저(initializer)의 빠른 실패(fail-fast) 기능을 활성화한다. 즉, 스크립트로 인해 예외가 발생하면 애플리케이션이 시작되지 않는다. spring.sql.init.continue-on-error
를 설정하여 해당 동작을 조정할 수 있다.
스크립트 기반 데이터소스(DataSource) 초기화는 기본적으로 JPA EntityManagerFactory
빈이 생성되기 전에 수행된다. schema.sql
을 사용하여 JPA가 관리하는 엔터티에 대한 스키마를 생성하고 data.sql
을 사용하여 데이터를 채울 수 있다. 우리는 다중 데이터 소스 초기화 기술을 사용하는 것을 권장하지 않지만, 스크립트 기반 데이터소스(DataSource) 초기화가 하이버네이트에 의해 수행된 스키마 생성을 기반으로 구축될 수 있도록 하려면 spring.jpa.defer-datasource-initialization
을 true
로 설정하자. 이는 EntityManagerFactory
빈이 생성되고 초기화될 때까지 데이터 소스 초기화를 연기한다. 그런 다음 schema.sql
을 사용하여 하이버네이트에 의해 수행된 스키마 생성에 추가할 수 있으며 data.sql
을 사용하여 이를 채울 수 있다.
Flyway
또는 Liquibase
와 같은 상위 레벨 데이터베이스 마이그레이션 도구를 사용하는 경우, 해당 도구만 사용하여 스키마를 생성하고 초기화해야 한다. Flyway
또는 Liquibase
와 함께 기본 schema.sql
및 data.sql
스크립트를 사용하는 것은 권장되지 않으며 향후 릴리스에서는 지원이 제거될 예정이다.
18.9.4. 스프링 배치 데이터베이스 초기화(Initialize a Spring Batch Database)
스프링 배치를 사용하는 경우 가장 널리 사용되는 데이터베이스 플랫폼에 대한 SQL 초기화 스크립트가 사전 패키지되어 제공된다. 스프링 부트는 데이터베이스 타입을 감지하고 시작 시 해당 스크립트를 실행할 수 있다. 임베디드 데이터베이스를 사용하는 경우 이는 기본적으로 실행된다. 다음 예제와 같이 모든 데이터베이스 타입에 대해 이를 활성화할 수도 있다.
프로퍼티스(Properties)
spring.batch.jdbc.initialize-schema=always
Yaml
spring:
batch:
jdbc:
initialize-schema: "always"
spring.batch.jdbc.initialize-schema
를 never
로 설정하여 초기화를 명시적으로 끌 수도 있다.
18.9.5. 상위 레벨 데이터베이스 마이그레이션 도구 사용(Use a Higher-level Database Migration Tool)
스프링 부트는 Flyway
와 Liquibase
라는 두 가지 상위 레벨 마이그레이션 도구를 지원한다.
시작 시 플라이웨이 데이터베이스 마이그레이션 실행(Execute Flyway Database Migrations on Startup)
시작 시 Flyway
데이터베이스 마이그레이션을 자동으로 실행하려면 클래스패스에 org.flywaydb:flyway-core
를 추가하자.
일반적으로 마이그레이션은 V<VERSION>__<NAME>.sql
(<VERSION>
은 밑줄로 구분된 버전(예: ‘1’ 또는 ‘2_1’) 포함) 형식의 스크립트다. 기본적으로 classpath:db/migration
이라는 디렉터리에 있지만 spring.flyway.locations
를 설정하여 해당 위치를 수정할 수 있다. 이는 하나 이상의 classpath:
또는 filesystem:
위치를 쉼표로 구분한 목록이다. 예를 들어, 다음 구성은 기본 클래스패스 위치와 /opt/migration
디렉토리 모두에서 스크립트를 검색한다.
프로퍼티스(Properties)
spring.flyway.locations=classpath:db/migration,filesystem:/opt/migration
Yaml
spring:
flyway:
locations: "classpath:db/migration,filesystem:/opt/migration"
벤더별 스크립트를 사용하려면 특별한 {vendor}
자리 표시자(placeholder)를 추가할 수도 있다. 다음 예제를 보자.
프로퍼티스(Properties)
spring.flyway.locations=classpath:db/migration/{vendor}
Yaml
spring:
flyway:
locations: "classpath:db/migration/{vendor}"
db/migration
을 사용하는 대신 앞선 구성에서는 데이터베이스 타입(예: MySQL의 경우 db/migration/mysql
)에 따라 사용할 디렉터리를 설정한다. 지원되는 데이터베이스 목록은 DatabaseDriver
에서 확인할 수 있다.
마이그레이션은 자바로 작성할 수도 있다. Flyway
는 JavaMigration
을 구현하는 모든 빈으로 자동 구성된다.
FlywayProperties
는 대부분의 Flyway
설정과 마이그레이션을 비활성화하거나 위치 확인을 끄는 데 사용할 수 있는 작은 추가 프로퍼티를 제공한다. 구성에 대한 추가 제어가 필요한 경우 FlywayConfigurationCustomizer
빈 등록을 고려하자.
스프링 부트는 Flyway.migration()
을 호출하여 데이터베이스 마이그레이션을 수행한다. 더 많은 제어가 필요한 경우 FlywayMigrationStrategy
를 구현하는 @Bean
을 제공하자.
Flyway
는 SQL 및 자바 콜백을 지원한다. SQL 기반 콜백을 사용하려면 콜백 스크립트를 classpath:db/migration
디렉터리에 배치하자. 자바 기반 콜백을 사용하려면 콜백을 구현하는 하나 이상의 빈을 생성하자. 이러한 빈은 자동으로 Flyway
에 등록된다. @Order
를 사용하거나 Ordered
를 구현하여 순서를 정할 수 있다. 더 이상 사용되지 않는 FlywayCallback
인터페이스를 구현하는 빈도 감지할 수 있지만 콜백 빈과 함께 사용할 수는 없다.
기본적으로, Flyway
는 컨텍스트에서 (@Primary) DataSource
를 오토와이어(autowire)하고 이를 마이그레이션에 사용한다. 다른 DataSource
를 사용하고 싶다면 하나를 만들고 해당 @Bean
을 @FlywayDataSource
로 표시할 수 있다. 그렇게 하고 두 개의 데이터 소스를 원하는 경우, 다른 데이터 소스를 만들고 @Primary
로 표시해야 한다. 또는 외부 프로퍼티에서 spring.flyway.[url,user,password]
를 설정하여 Flyway
의 네이티브 DataSource
를 사용할 수 있다. spring.flyway.url
또는 spring.flyway.user
를 설정하면 Flyway
가 자체 DataSource
를 사용하기에 충분하다. 세 가지 프로퍼티 중 하나라도 설정되지 않은 경우 해당 spring.datasource
프로퍼티 값이 사용된다.
Flyway
를 사용하여 특정 시나리오에 대한 데이터를 제공할 수도 있다. 예를 들어 테스트 관련 마이그레이션을 src/test/resources
에 배치할 수 있으며 이는 테스트를 위해 애플리케이션이 시작될 때만 실행된다. 또한 프로필별 구성을 사용하여 spring.flyway.locations
를 커스텀하여 특정 프로필이 활성화된 경우에만 특정 마이그레이션이 실행되도록 할 수 있다. 예를 들어 application-dev.properties
에서 다음 설정을 지정할 수 있다.
프로퍼티스(Properties)
spring.flyway.locations=classpath:/db/migration,classpath:/dev/db/migration
Yaml
spring:
flyway:
locations: "classpath:/db/migration,classpath:/dev/db/migration"
해당 설정을 사용하면 dev/db/migration
의 마이그레이션은 dev
프로필이 활성화된 경우에만 실행된다.
시작 시 리퀴베이스 데이터베이스 마이그레이션 실행(Execute Liquibase Database Migrations on Startup)
시작 시 Liquibase
데이터베이스 마이그레이션을 자동으로 실행하려면 클래스패스에 org.liquibase:liquibase-core
를 추가하자.
org.liquibase:liquibase-core
를 클래스패스에 추가하면 기본적으로 애플리케이션 시작 중과 테스트 실행 전에 데이터베이스 마이그레이션이 실행된다. 이 동작은 spring.liquibase.enabled
프로퍼티를 사용하여 기본 및 테스트 구성에서 다른 값을 설정하여 커스텀할 수 있다. 데이터베이스를 초기화하는 데 두 가지 다른 방법을 사용할 수 없다(예: 애플리케이션 시작을 위한 Liquibase, 테스트 실행을 위한 JPA).
기본적으로, 마스터 변경 로그는 db/changelog/db.changelog-master.yaml
에서 읽혀지지만 spring.liquibase.change-log
를 설정하여 위치를 변경할 수 있다. YAML 외에도 Liquibase는 JSON
, XML
및 SQL
변경 로그 포맷도 지원한다.
기본적으로, Liquibase
는 컨텍스트에서 (@Primary
) DataSource
를 자동 연결하고 이를 마이그레이션에 사용한다. 다른 DataSource
를 사용해야 하는 경우 하나를 만들고 해당 @Bean
을 @LiquibaseDataSource
로 표시할 수 있다. 그렇게 하고 두 개의 데이터 소스가 필요한 경우 다른 데이터 소스를 만들고 @Primary
로 표시해야 한다. 또는 외부 프로퍼티스에 spring.liquibase.[driver-class-name,url,user,password]
를 설정하여 Liquibase
의 네이티브 DataSource
를 사용할 수 있습니다. spring.liquibase.url
또는 spring.liquibase.user
를 설정하면 Liquibase
가 자체 DataSource
를 사용하게 하기에 충분하다. 세 가지 프로퍼티스 중 하나라도 설정되지 않은 경우 해당 spring.datasource
프로퍼티 값이 사용된다.
컨텍스트, 기본 스키마 등과 같은 사용 가능한 설정에 대한 자세한 내용은 LiquibaseProperties
를 참고하자.
18.9.6. 초기화된 데이터베이스에 의존(Depend Upon an Initialized Database)
데이터베이스 초기화는 애플리케이션 컨텍스트 새로고침의 한 과정으로 애플리케이션이 시작되는 동안 수행된다. 시작 중에 초기화된 데이터베이스에 접근할 수 있도록 하기 위해 데이터베이스 이니셜라이저 역할을 하는 빈과 데이터베이스 초기화가 필요한 빈이 자동으로 감지됩니다. 초기화되는 데이터베이스에 따라 초기화되는 빈은 이를 초기화하는 데이터베이스에 종속되도록 구성된다. 시작 중에 애플리케이션이 데이터베이스에 접근하려고 시도하지만 데이터베이스가 초기화되지 않은 경우 데이터베이스를 초기화하고 데이터베이스 초기화를 요구하는 빈을 추가 감지할 수 있다.
데이터베이스 이니셜라이저 감지(Detect a Database Initializer)
스프링 부트는 SQL 데이터베이스를 초기화하는 다음 유형의 빈을 자동으로 감지한다.
DataSourceScriptDatabaseInitializer
EntityManagerFactory
Flyway
FlywayMigrationInitializer
R2dbcScriptDatabaseInitializer
SpringLiquibase
데이터베이스 초기화 라이브러리에 서드파티 스타터를 사용하는 경우 다른 타입의 빈도 자동으로 감지하도록 디텍터(detector)를 제공할 수 있다. 다른 빈을 감지하려면 META-INF/spring.factories
에 DatabaseInitializerDetector
구현을 등록하자.
데이터베이스 초기화에 의존하는 빈 감지(Detect a Bean That Depends On Database Initialization)
스프링 부트는 데이터베이스 초기화에 따라 다음 타입의 빈을 자동으로 감지한다.
AbstractEntityManagerFactoryBean
(spring.jpa.defer-datasource-initialization
이true
로 설정되지 않은 경우)DSLContext (jOOQ)
EntityManagerFactory
(spring.jpa.defer-datasource-initialization
이true
로 설정되지 않은 경우)JdbcOperations
NamedParameterJdbcOperations
서드파티 스타터 데이터 액세스 라이브러리를 사용하는 경우 다른 타입의 빈도 자동으로 감지하도록 디텍터를 제공할 수 있다. 다른 빈을 감지하려면 META-INF/spring.factories
에 DefinesOnDatabaseInitializationDetector
구현체를 등록하자. 또는 @DependsOnDatabaseInitialization
을 사용하여 빈의 클래스 또는 해당 @Bean
메소드에 어노테이션을 달자.
18.10. NoSQL
스프링 부트는 NoSQL 기술을 지원하는 다양한 스타터를 제공한다. 이 절에서는 스프링 부트에서 NoSQL을 사용할 때 발생하는 질문에 답한다.
18.10.1. Lettuce 대신 Jedis를 사용(Use Jedis Instead of Lettuce)
기본적으로 스프링 부트 스타터(spring-boot-starter-data-redis)는 Lettuce를 사용한다. 해당 의존성을 제외하고 대신 Jedis 의존성을 포함해야 한다. 스프링 부트는 이러한 의존성을 모두 관리하므로 버전을 지정하지 않고도 Jedis로 전환할 수 있다.
다음 예제에서는 메이븐에서 이를 수행하는 방법을 보여준다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
다음 예에서는 그레이들에서 이를 수행하는 방법을 보여준다.
dependencies {
implementation('org.springframework.boot:spring-boot-starter-data-redis') {
exclude group: 'io.lettuce', module: 'lettuce-core'
}
implementation 'redis.clients:jedis'
// ...
}
18.11. 메세징(Messaging)
스프링 부트는 메시징을 지원하기 위한 다양한 스타터를 제공한다. 이 절에서는 스프링 부트에서 메시징을 사용할 때 발생하는 질문에 답한다.
18.11.1. 트랜잭션된 JMS 세션 비활성화(Disable Transacted JMS Session)
JMS 브로커가 트랜잭션 세션을 지원하지 않는 경우 트랜잭션 지원을 모두 비활성화해야 한다. 나만의 JmsListenerContainerFactory
를 생성하는 경우 기본적으로 트랜잭션이 불가능하므로 아무것도 하지 않아도 된다. 스프링 부트의 기본값을 재사용하기 위해 DefaultJmsListenerContainerFactoryConfigurer
를 사용하려는 경우 다음과 같이 트랜잭션된 세션을 비활성화할 수 있다.
자바
import jakarta.jms.ConnectionFactory;
import org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
@Configuration(proxyBeanMethods = false)
public class MyJmsConfiguration {
@Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory listenerFactory = new DefaultJmsListenerContainerFactory();
configurer.configure(listenerFactory, connectionFactory);
listenerFactory.setTransactionManager(null);
listenerFactory.setSessionTransacted(false);
return listenerFactory;
}
}
코틀린
import jakarta.jms.ConnectionFactory
import org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.jms.config.DefaultJmsListenerContainerFactory
@Configuration(proxyBeanMethods = false)
class MyJmsConfiguration {
@Bean
fun jmsListenerContainerFactory(connectionFactory: ConnectionFactory?, configurer: DefaultJmsListenerContainerFactoryConfigurer): DefaultJmsListenerContainerFactory {
val listenerFactory = DefaultJmsListenerContainerFactory()
configurer.configure(listenerFactory, connectionFactory)
listenerFactory.setTransactionManager(null)
listenerFactory.setSessionTransacted(false)
return listenerFactory
}
}
앞의 예제는 기본 팩토리를 오버라이드하며, 애플리케이션이 정의하는 다른 팩토리가 있는 경우 이를 적용해야 한다.
18.12. 배치 애플리케이션(Batch Applications)
사람들이 스프링 부트 애플리케이션 내에서 스프링 배치를 사용할 때 많은 질문이 자주 발생한다. 이 절에서는 이러한 질문을 다룬다.
18.12.1. 배치 데이터 소스 지정(Specifying a Batch Data Source)
기본적으로, 배치 애플리케이션에는 잡 세부 정보를 저장하기 위한 DataSource
가 필요하다. 스프링 배치는 기본적으로 단일 DataSource
를 기대한다. 애플리케이션의 기본 DataSource
가 아닌 다른 DataSource
를 사용하려면 DataSource
빈을 선언하고 해당 @Bean
메소드에 @BatchDataSource
어노테이션을 추가한다. 그렇게 하고 두 개의 데이터 소스를 원하는 경우 다른 하나를 @Primary
로 표시해야 한다. 더 효과적으로 제어하려면 @Configuration
클래스 중 하나에 @EnableBatchProcessing
을 추가하거나 DefaultBatchConfiguration
을 상속해야한다. 자세한 내용은 @EnableBatchProcessing
및 DefaultBatchConfiguration
의 자바독을 참고하자.
스프링 배치에 대한 자세한 내용은 스프링 배치 프로젝트 페이지를 참고하자.
18.12.2. 시작 시 스프링 배치 작업 실행(Running Spring Batch Jobs on Startup)
스프링 배치 자동 구성은 애플리케이션의 클래스패스에 spring-boot-starter-batch
를 추가하여 활성화된다. 애플리케이션 컨텍스트에서 단일 잡이 발견되면 시작 시 실행된다(자세한 내용은 JobLauncherApplicationRunner
참고). 잡 빈이 여러 개 발견되면 spring.batch.job.name
을 사용하여 실행해야 할 잡을 지정해야 한다.
애플리케이션 컨텍스트에서 발견된 잡 실행을 비활성화하려면, spring.batch.job.enabled
를 false
로 설정하자.
자세한 내용은 BatchAutoConfiguration을 참고하자.
18.12.3. 커맨드라인에서 실행(Running From the Command Line)
스프링 부트는 --
로 시작하는 모든 커맨드라인 아규먼트를 환경 변수에 추가할 프로퍼티로 변환한다. 커맨드라인 프로퍼티 접근를 참고하자. 배치 잡에 아규먼트를 전달하는 데 사용하면 안 된다. 커맨드라인에서 배치 아규먼트를 지정하려면 다음 예제와 같이 일반 포맷(--
없이)을 사용한다.
$ java -jar myapp.jar someParameter=someValue anotherParameter=anotherValue
커맨드라인에서 환경 프로퍼티을 지정하면 잡에서 해당 프로퍼티가 무시된다. 다음 명령을 고려해보자.
$ java -jar myapp.jar --server.port=7070 someParameter=someValue
이는 배치 잡에 someParameter=someValue
라는 하나의 아규먼트만 제공한다.
18.12.4. 잡 리포지터리 저장(Storing the Job Repository)
스프링 배치에는 잡 리포지터리용 데이터 스토어가 필요하다. 스프링 부트를 사용한다면 실제 데이터베이스를 사용해야 한다. 이는 메모리 내 데이터베이스일 수 있다. 잡 리포티저리 구성을 참고하자.
18.13. 액추에이터(Actuator)
Spring Boot includes the Spring Boot Actuator. This section answers questions that often arise from its use.
18.13.1. 액추에이터 엔드포인트의 HTTP 포트 또는 주소 변경(Change the HTTP Port or Address of the Actuator Endpoints)
독립형(standalone) 애플리케이션에서 액추에이터 HTTP 포트는 기본적으로 기본 HTTP 포트와 동일하다. 애플리케이션이 다른 포트에서 수신하도록 하려면 외부 프로퍼티인 management.server.port
를 설정해야 한다. 완전히 다른 네트워크 주소(예: 관리용 내부 네트워크와 사용자 애플리케이션용 외부 네트워크가 있는 경우)를 수신하려면 management.server.address
를 서버가 바인딩할 수 있는 유효한 IP 주소로 설정할 수도 있다.
자세한 내용은 ManagementServerProperties
소스 코드 및 “프로덕션 지원 기능” 장의 “관리 서버 포트 커스텀”를 참고하자.
18.13.2. ‘화이트레이블’ 오류 페이지 커스텀(Customize the ‘whitelabel’ Error Page)
스프링 부트는 서버 오류가 발생할 경우 브라우저 클라이언트에 표시되는 ‘화이트레이블’ 오류 페이지를 설치한다(JSON 및 기타 미디어 타입을 사용하는 머신 클라이언트는 올바른 오류 코드와 함께 응답을 확인해야 합니다).
기본 오류 페이지를 끄려면 server.error.whitelabel.enabled=false
를 설정하자. 이렇게 하면 사용 중인 서블릿 컨테이너의 기본값이 복원된다. 스프링 부트는 여전히 오류 보기를 시도하므로 완전히 비활성화하기보다는 자체 오류 페이지를 추가해야 할 수도 있다.
오류 페이지를 자신만의 것으로 오버라이드하는 것은 사용하는 템플릿 기술에 따라 다르다. 예를 들어 Thymeleaf
를 사용하는 경우 error.html
템플릿을 추가할 수 있다. FreeMarker
를 사용하는 경우 error.ftlh
템플릿을 추가할 수 있다. 일반적으로 오류 이름으로 해결되는 뷰나 /error
경로를 처리하는 @Controller
가 필요하다. 기본 구성 중 일부를 바꾸지 않는 한 ApplicationContext
에서 BeanNameViewResolver
를 찾아야 하므로 @Bean
명명된 오류가 이를 수행하는 한 가지 방법이 될 것이다. 자세한 옵션은 ErrorMvcAutoConfiguration
을 참고하자.
서블릿 컨테이너에 핸들러를 등록하는 방법에 대한 자세한 내용은 “오류 처리” 절을 참고하자.
18.13.3. 민감한 값 삭제(Sanitize Sensitive Values)
/env
, /configprops
및 /quartz
엔드포인트에서 반환된 정보는 다소 민감할 수 있다. 모든 값은 기본적으로 삭제된다(******
로 대체됨). 삭제되지 않은 형식으로 원래 값을 보는 것은 해당 엔드포인트의 showValues
프로퍼티를 사용하여 엔드포인트별로 구성할 수 있다. 이 프로퍼티는 다음 값을 갖도록 구성할 수 있다.
ALWAYS
- 모든 값은 모든 사용자에게 삭제되지 않은 형식으로 표시된다.NEVER
- 모든 값은 항상 삭제된다(**로 대체됨).WHEN_AUTHORIZED
- 모든 값은 승인된 사용자에게 삭제되지 않은 형식으로 표시된다.
HTTP 엔드포인트의 경우, 사용자가 인증을 받았고 엔드포인트의 역할 속성에 의해 구성된 역할이 있는 경우 사용자에게 권한이 부여된 것으로 간주된다. 기본적으로 인증된 모든 사용자에게 권한이 부여된다. JMX 엔드포인트의 경우, 모든 사용자에게 항상 권한이 부여된다.
프로퍼티스(Properties)
management.endpoint.env.show-values=WHEN_AUTHORIZED
management.endpoint.env.roles=admin
Yaml
management:
endpoint:
env:
show-values: WHEN_AUTHORIZED
roles: "admin"
위 구성을 사용하면 관리자 역할을 가진 모든 사용자가 /env
엔드포인트에서 원래 형식으로 모든 값을 볼 수 있다.
값 표시가 ALWAYS
또는 WHEN_AUTHORIZED
로 설정되면 SanitizingFunction
에 의해 적용된 모든 삭제가 계속 적용된다.
삭제 커스텀(Customizing Sanitization)
삭제를 제어하려면, SanitizingFunction
빈을 정의하자. 함수가 호출되는 SanitizedData
는 키와 값은 물론 해당 항목이 나온 PropertySource
에 대한 접근을 제공한다. 예를 들어, 이를 통해 특정 속성 소스에서 나오는 모든 값을 정리할 수 있다. 각 SanitizingFunction
은 함수가 삭제 가능한 데이터의 값을 변경할 때까지 순서대로 호출된다.
18.13.4. 상태 표시기를 마이크로미터 메트릭에 매핑(Map Health Indicators to Micrometer Metrics)
스프링 부트 상태 표시기(health indicator)는 전체 시스템 상태를 나타내는 Status
타입을 반환한다. 특정 애플리케이션의 상태 레벨을 모니터링하거나 경고하려는 경우 마이크로미터를 사용하여 이러한 상태를 메트릭으로 내보낼 수 있다. 기본적으로 상태 코드 “UP”, “DOWN”, “OUT_OF_SERVICE” 및 “UNKNOWN”은 스프링 부트에서 사용된다. 이를 내보내려면 마이크로미터 게이지(Micrometer Gauge)와 함께 사용할 수 있도록 이러한 상태를 일부 숫자들로 변환해야 한다.
다음 예제에서는 이러한 내보내기를 작성하는 한 가지 방법을 보여준다.
자바
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.Status;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class MyHealthMetricsExportConfiguration {
public MyHealthMetricsExportConfiguration(MeterRegistry registry, HealthEndpoint healthEndpoint) {
// 이 예제에서는 공통 태그(예: 앱)가 다른 곳에 적용된다고 가정한다.
Gauge.builder("health", healthEndpoint, this::getStatusCode)
.strongReference(true)
.register(registry);
}
private int getStatusCode(HealthEndpoint health) {
Status status = health.health().getStatus();
if (Status.UP.equals(status)) { return 3; }
if (Status.OUT_OF_SERVICE.equals(status)) { return 2; }
if (Status.DOWN.equals(status)) { return 1; }
return 0;
}
}
코틀린
import io.micrometer.core.instrument.Gauge
import io.micrometer.core.instrument.MeterRegistry
import org.springframework.boot.actuate.health.HealthEndpoint
import org.springframework.boot.actuate.health.Status
import org.springframework.context.annotation.Configuration
@Configuration(proxyBeanMethods = false)
class MyHealthMetricsExportConfiguration(registry: MeterRegistry, healthEndpoint:
HealthEndpoint) {
init {
// 이 예제에서는 공통 태그(예: 앱)가 다른 곳에 적용된다고 가정한다.
Gauge.builder("health", healthEndpoint) { health -> getStatusCode(health).toDouble() }
.strongReference(true)
.register(registry)
}
private fun getStatusCode(health: HealthEndpoint): Int {
val status = health.health().status
if (Status.UP == status) { return 3 }
if (Status.OUT_OF_SERVICE == status) { return 2 }
if (Status.DOWN == status) { return 1 }
return 0
}
}
18.14. 보안(Security)
이 장에서는 스프링 부트와 함께 스프링 시큐리티를 사용하면서 발생하는 질문을 포함하여 스프링 부트로 작업할 때 보안에 대한 질문을 다룬다.
스프링 시큐리티에 대한 자세한 내용은 스프링 시큐리티 프로젝트 페이지를 참고하자.
18.14.1. 스프링 부트 보안 구성 끄기(Switch off the Spring Boot Security Configuration)
애플리케이션에서 SecurityFilterChain
빈을 사용하여 @Configuration
을 정의하면 스프링 부트에서 기본 웹앱 보안 설정이 꺼진다.
18.14.2. UserDetailsService 변경 및 사용자 계정 추가(Change the UserDetailsService and Add User Accounts)
AuthenticationManager
, AuthenticationProvider
또는 UserDetailsService
타입의 @Bean
을 제공하는 경우 InMemoryUserDetailsManager
에 대한 기본 @Bean
이 생성되지 않는다. 이는 스프링 시큐리티의 전체 기능(예: 다양한 인증 옵션)을 사용할 수 있음을 의미한다.
사용자 계정을 추가하는 가장 쉬운 방법은 자체 UserDetailsService
빈을 제공하는 것이다.
The easiest way to add user accounts is to provide your own UserDetailsService bean.
18.14.3. 프록시 서버 뒤에서 실행할 때 HTTPS 활성화(Enable HTTPS When Running behind a Proxy Server)
모든 기본 엔드포인트를 HTTPS를 통해서만 사용할 수 있도록 하는 것은 모든 애플리케이션에서 중요한 일이다. 톰캣을 서블릿 컨테이너로 사용하는 경우, 스프링 부트는 일부 환경 설정을 감지하면 톰캣의 자체 RemoteIpValve
를 자동으로 추가하고 HttpServletRequest
를 사용하여 보안 여부를 보고할 수 있어야 한다(심지어 프록시 서버의 다운스트림에서도 실제 SSL 종료를 처리한다). 표준 동작은 일반적인 특정 요청 헤더(x-forwarded-for
및 x-forwarded-proto
)의 유무에 따라 결정되므로 대부분의 프론트엔드 프록시에서 작동한다. 다음 예제와 같이 application.properties
에 일부 항목을 추가하여 밸브를 켤 수 있다.
프로퍼티스(Properties)
server.tomcat.remoteip.remote-ip-header=x-forwarded-for
server.tomcat.remoteip.protocol-header=x-forwarded-proto
Yaml
server:
tomcat:
remoteip:
remote-ip-header: "x-forwarded-for"
protocol-header: "x-forwarded-proto"
(해당 프로퍼티스 중 하나가 있으면 밸브에서 전환된다. 또는 WebServerFactoryCustomizer
빈을 사용하여 TomcatServletWebServerFactory
를 커스텀하여 RemoteIpValve
를 추가할 수 있다.)
모든(또는 일부) 요청에 대해 보안 채널을 요구하도록 스프링 시큐리티를 구성하려면 다음 HttpSecurity
구성을 추가하는 자체 SecurityFilterChain
빈을 추가하는 것을 고려하자.
자바
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class MySecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// 애플리케이션 보안 커스텀 ...
http.requiresChannel((channel) -> channel.anyRequest().requiresSecure());
return http.build();
}
}
코틀린
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.web.SecurityFilterChain
@Configuration
class MySecurityConfig {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
// 애플리케이션 보안 커스텀 ...
http.requiresChannel { requests -> requests.anyRequest().requiresSecure() }
return http.build()
}
}
18.15. 핫 스화핑(Hot Swapping)
스프링 부트는 핫 스와핑을 지원한다. 이 장에서는 작동 방식에 대한 질문에 답한다.
18.15.1. 정적 콘텐츠 리로드(Reload Static Content)
핫 리로드에는 여러 가지 옵션이 있다. 빠른 애플리케이션 재시작 및 실시간 리로드 지원은 물론 합리적인 개발 구성(예: 템플릿 캐싱)과 같은 추가 개발 기능을 제공하는, spring-boot-devtools
를 사용하는 것이 합리적이다. Devtools
는 클래스패스의 변경 사항을 모니터링하여 작동한다. 이는 변경 사항을 적용하려면, 정적 리소스 변경 사항을 “빌드”해야 함을 의미한다. 기본적으로 변경 사항을 저장할 때, 이클립스에서는 자동으로 처리된다. 인텔리J IDEA에서는 Make Project
명령이 필요한 빌드를 트리거한다. 기본적으로 재시작 제외로 인해 정적 리소스를 변경해도 애플리케이션이 재시작되지 않는다. 그러나, 이 기능을 사용하면 실시간 리로드 적용한다.
또는, IDE에서 실행하는 것(특히 디버깅을 켠 상태에서)은 개발을 수행하는 좋은 방법이다(모든 최신 IDE는 정적 리소스 리로드를 허용하고 일반적으로 자바 클래스 변경 사항의 핫스왑도 허용한다).
마지막으로, 소스에서 직접 정적 파일을 리로드하여 커맨드라인에서 실행하는 것을 지원하도록 메이븐 및 그레이들 플러그인을 구성할 수 있다(addResources 프로퍼티 참고). 더 높은 레벨의 도구로 해당 코드를 작성하는 경우 외부 CSS/js
컴파일러 프로세스에서 이를 사용할 수 있다.
18.15.2. 컨테이너를 재시작하지 않고 템플릿 리로드(Reload Templates without Restarting the Container)
스프링 부트에서 지원하는 대부분의 템플릿 기술에는 캐싱을 비활성화하는 구성 옵션이 포함되어 있다(이 문서의 뒷부분에서 설명). spring-boot-devtools
모듈을 사용하는 경우 이러한 프로퍼티는 개발 시 자동으로 구성된다.
Thymeleaf 템플릿(Thymeleaf Templates)
Thymeleaf
를 사용하는 경우 spring.thymeleaf.cache
를 false
로 설정하자. 다른 Thymeleaf
커스텀 옵션은 ThymeleafAutoConfiguration
을 참고하자.
FreeMarker 템플릿(FreeMarker Templates)
FreeMarker를 사용하는 경우 spring.freemarker.cache
를 false
로 설정하자. 다른 FreeMarker
커스텀 옵션은 FreeMarkerAutoConfiguration
을 참고하자.
그루비 템플릿(Groovy Templates)
그루비 템플릿을 사용하는 경우 spring.groovy.template.cache
를 false
로 설정하자. 다른 그루비 커스텀 옵션은 GroovyTemplateAutoConfiguration
을 참고하자.
18.15.3. 빠른 애플리케이션 재시작(Fast Application Restarts)
spring-boot-devtools
모듈에는 자동 애플리케이션 재시작 지원이 포함되어 있다. JRebel
과 같은 기술만큼 빠르지는 않지만 일반적으로 “콜드 스타트”보다 훨씬 빠르다. 이 문서의 뒷부분에서 설명하는 좀 더 복잡한 리로드 옵션을 조사하기 전에 먼저 시도해 보아야 할 것이다.
자세한 내용은 개발자 도구 장을 참고하자.
18.15.4. 컨테이너를 재시작하지 않고 자바 클래스 리로드(Reload Java Classes without Restarting the Container)
많은 최신 IDE(Eclipse, IDEA 등)는 바이트코드의 핫 스와핑을 지원한다. 결과적으로 클래스나 메서드 시그니처에 영향을 주지 않는 변경을 수행하는 경우 부작용 없이 깔끔하게 리로드되어야 한다.
18.16. 테스팅(Testing)
스프링 부트에는 다양한 테스트 유틸리티와 지원 클래스는 물론 공통 테스트 의존성을 제공하는 전용 스타터도 포함되어 있다. 이 장에서는 테스트에 대한 일반적인 질문에 답변한다.
18.16.1. 스프링 시큐리티와 테스팅(Testing With Spring Security)
스프링 시큐리티는 특정 사용자로 테스트를 실행하기 위한 지원을 제공한다. 예를 들어 아래 코드 조각의 테스트는 ADMIN
역할이 있는 인증된 사용자를 사용하여 실행됩니다.
자바
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.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@WebMvcTest(UserController.class)
class MySecurityTests {
@Autowired
private MockMvc mvc;
@Test
@WithMockUser(roles = "ADMIN")
void requestProtectedUrlWithUser() throws Exception {
this.mvc.perform(get("/"));
}
}
코틀린
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.security.test.context.support.WithMockUser
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
@WebMvcTest(UserController::class)
class MySecurityTests(@Autowired val mvc: MockMvc) {
@Test
@WithMockUser(roles = ["ADMIN"])
fun requestProtectedUrlWithUser() {
mvc.perform(MockMvcRequestBuilders.get("/"))
}
}
스프링 시큐리티는 스프링 MVC 테스트와 포괄적인 통합을 제공하며 이는 @WebMvcTest
슬라이스 및 MockMvc
를 사용하여 컨트롤러를 테스트할 때도 사용할 수 있다.
스프링 시큐리티의 테스트 지원에 대한 자세한 내용은 스프링 시큐리티의 레퍼런스 문서를 참고하자.
18.16.2. 슬라이스 테스트에 포함하기 위한 구조 @Configuration 클래스(Structure @Configuration classes for inclusion in slice tests)
슬라이스 테스트는 스프링 프레임워크의 컴포넌트 스캔을 타입에 따라 일부 컴포넌트의 집합으로 제한하여 작동한다. @Bean
어노테이션을 사용하여 생성된 빈과 같이 컴포넌트 스캔을 통해 생성되지 않은 빈의 경우, 슬라이스 테스트는 해당 빈을 애플리케이션 컨텍스트에 포함/제외할 수 없다. 다음 예제를 고려해보자.
import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration(proxyBeanMethods = false)
public class MyConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated());
return http.build();
}
@Bean
@ConfigurationProperties("app.datasource.second")
public BasicDataSource secondDataSource() {
return DataSourceBuilder.create().type(BasicDataSource.class).build();
}
}
위의 @Configuration
클래스가 있는 애플리케이션에 대한 @WebMvcTest
의 경우 컨트롤러 엔드포인트가 제대로 보호되는지 테스트할 수 있도록 애플리케이션 컨텍스트에 SecurityFilterChain
빈이 있을 것으로 예상할 수 있다. 그러나 MyConfiguration
은 필터에 지정된 타입과 일치하지 않기 때문에 @WebMvcTest
의 컴포넌트 스캔 필터에 의해 선택되지 않는다. @Import(MyConfiguration.class)
로 테스트 클래스에 어노테이션을 달아 구성을 명시적으로 포함할 수 있다. 그러면 웹 계층을 테스트할 때 필요하지 않은 BasicDataSource
빈을 포함하여 MyConfiguration
의 모든 빈이 로드된다. 구성 클래스를 두 개로 분할하면 보안 구성만 가져올 수 있다.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration(proxyBeanMethods = false)
public class MySecurityConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated());
return http.build();
}
}
import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class MyDatasourceConfiguration {
@Bean
@ConfigurationProperties("app.datasource.second")
public BasicDataSource secondDataSource() {
return DataSourceBuilder.create().type(BasicDataSource.class).build();
}
}
특정 도메인의 빈을 슬라이스 테스트에 포함해야 하는 경우 단일 구성 클래스를 갖는 것은 비효율적일 수 있다. 대신, 특정 도메인에 대한 빈이 포함된 여러 세분화된 클래스로 애플리케이션 구성을 구성하면 특정 슬라이스 테스트에 대해서만 해당 구성을 가져올 수 있다.
18.17. 빌드(Build)
스프링 부트에는 메이븐 및 그레이들용 빌드 플러그인이 포함되어 있다. 이 장에서는 이러한 플러그인에 대한 일반적인 질문에 답변한다.
18.17.1. 빌드 정보 생성(Generate Build Information)
메이븐 플러그인과 그레이들 플러그인 모두 프로젝트의 좌표, 이름 및 버전이 포함된 빌드 정보를 생성할 수 있다. 구성을 통해 추가 프로퍼티을 추가하도록 플러그인을 구성할 수도 있다. 이러한 파일이 있으면 스프링 부트는 BuildProperties
빈을 자동 구성한다.
메이븐을 사용하여 빌드 정보를 생성하려면 다음 예제와 같이 build-info
골(goal)을 추가하자.
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
자세한 내용은 스프링 부트 메이븐 플러그인 문서를 참고하자.
다음 예제는 그래이들에서 동일한 작업을 수행한다.
springBoot {
buildInfo()
}
자세한 내용은 스프링 부트 그레이들 플러그인 문서를 참고하자.
18.17.2. 깃 정보 생성(Generate Git Information)
메이븐과 그레이들 모두 프로젝트가 빌드될 때 깃 소스 코드 리포지터리의 상태에 대한 정보가 포함된 git.properties
파일을 생성할 수 있다.
메이븐 사용자의 경우 spring-boot-starter-parent
POM에는 git.properties
파일을 생성하기 위해 사전 구성된 플러그인이 포함되어 있다. 이를 사용하려면 POM에 깃 커밋 ID 플러그인에 대한 다음 선언을 추가하자.
<build>
<plugins>
<plugin>
<groupId>io.github.git-commit-id</groupId>
<artifactId>git-commit-id-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
그레이들 사용자는 다음 예제와 같이 gradle-git-properties
플러그인을 사용하여 동일한 결과를 얻을 수 있다.
plugins {
id "com.gorylenko.gradle-git-properties" version "2.4.1"
}
메이븐 및 그레이들 플러그인 모두 git.properties
에 포함된 프로퍼티를 구성할 수 있다.
git.properties
의 커밋 시간은 yyyy-MM-dd'T'HH:mm:ssZ
형식과 일치해야 한다. 이는 위에 나열된 두 플러그인의 기본 포맷이다. 이 포맷을 사용하면 시간을 Date
로 파싱하고 해당 포맷을 JSON으로 직렬화할 때 Jackson
의 날짜 직렬화 구성 설정에 따라 제어할 수 있다.
18.17.3. 의존성 버전 커스텀(Customize Dependency Versions)
spring-boot-dependents
POM은 공통 의존성 버전을 관리한다. 메이븐 및 그레이들용 스프링 부트 플러그인을 사용하면 빌드 프로퍼티을 사용하여 이러한 관리되는 의존성 버전을 커스텀할 수 있다.
각 스프링 부트 릴리스는 이러한 특정 서드파티 의존성 세트에 대해 설계되고 테스트됐다. 버전을 오버라이드하면 호환성 문제가 발생할 수 있다.
메이븐으로 의존성 버전을 오버라이드하려면 메이븐 플러그인 설명서의 이 장을 참고하자.
그레이들로 의존성 버전을 오버라이드하려면 메이븐 플러그인 설명서의 이 장을 참고하자.
18.17.4. 메이븐을 사용하여 실행 가능한 JAR 만들기(Create an Executable JAR with Maven)
spring-boot-maven-plugin
은 실행 가능한 팻(fat) JAR을 생성하는 데 사용될 수 있다. spring-boot-starter-parent
POM을 사용하는 경우 플러그인을 선언할 수 있으며 jar는 다음과 같이 리패키징된다.
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
상위 POM을 사용하지 않는 경우에도 플러그인을 사용할 수 있다. 그러나 다음과 같이 <executions>
섹션을 추가로 추가해야 한다.
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
전체 사용법에 대한 자세한 내용은 플러그인 문서를 참고하자.
18.17.5. 스프링 부트 애플리케이션을 의존성으로 사용(Use a Spring Boot Application as a Dependency)
war
파일과 마찬가지로 스프링 부트 애플리케이션은 의존성으로 사용되지 않는다. 애플리케이션에 다른 프로젝트와 공유하려는 클래스가 포함되어 있는 경우 권장되는 접근 방식은 해당 코드를 별도의 모듈로 이동하는 것이다. 그런 다음 별도의 모듈은 애플리케이션 및 기타 프로젝트에 따라 달라질 수 있다.
위에서 권장한 대로 코드를 재정렬할 수 없는 경우 의존성으로 사용하기에 적합한 별도의 아티팩트를 생성하도록 스프링부트의 메이븐 및 그레이들 플러그인을 구성해야 한다. 실행 가능한 압축파일은 BOOT-INF/classes
의 실행 가능한 jar
포맷 패키지 애플리케이션 클래스와 같은 의존성으로 사용될 수 없다. 이는 실행 가능한 jar가 의존성으로 사용될 때 찾을 수 없음을 의미한다.
두 개의 아티팩트(의존성으로 사용할 수 있는 아티팩트와 실행 가능한 아티팩트)를 생성하려면 분류자(classifier)를 지정해야 한다. 이 분류자는 실행 가능한 아카이브의 이름에 적용되며 기본 아카이브는 의존성으로 사용된다.
메이븐에서 exec
분류자를 구성하려면 다음과 같이 구성할 수 있다.
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<classifier>exec</classifier>
</configuration>
</plugin>
</plugins>
</build>
18.17.6. 실행 가능한 Jar가 실행될 때 특정 라이브러리 추출(Extract Specific Libraries When an Executable Jar Runs)
실행 가능한 jar에 포함된 대부분의 중첩 라이브러리는 실행하기 위해 압축을 풀 필요가 없다. 그러나 특정 라이브러리에는 문제가 있을 수 있다. 예를 들어 JRuby
에는 jruby-complete.jar
이 항상 자체적으로 파일로 직접 사용 가능하다고 가정하는 자체 중첩 jar 지원이 포함되어 있다.
문제가 있는 라이브러리를 처리하기 위해 실행 가능한 jar가 처음 실행될 때 특정 중첩 jar이 자동으로 압축 해제되도록 플래그를 지정할 수 있다. 이러한 중첩된 jar는 java.io.tmpdir
시스템 프로퍼티로 식별되는 임시 디렉터리 아래에 기록된다.
애플리케이션이 계속 실행되는 동안 임시 디렉터리에 압축이 풀린 jar를 삭제하지 않도록 운영 체제가 구성되어 있는지 확인하는 데 주의를 기울여야 한다.
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<requiresUnpack>
<dependency>
<groupId>org.jruby</groupId>
<artifactId>jruby-complete</artifactId>
</dependency>
</requiresUnpack>
</configuration>
</plugin>
</plugins>
</build>
18.17.7. 제외가 포함된 실행 불가능한 JAR 생성(Create a Non-executable JAR with Exclusions)
실행 파일과 실행 불가능한 jar가 두 개의 별도 빌드 제품으로 있는 경우 실행 파일 버전에는 라이브러리 jar에 필요하지 않은 추가 구성 파일이 있는 경우가 많다. 예를 들어, application.yaml
구성 파일은 실행 불가능한 JAR에서 제외될 수 있다.
메이븐에서는 실행 가능한 jar가 주요 아티팩트여야 하며 다음과 같이 라이브러리에 대해 분류된 jar를 추가할 수 있다.
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<id>lib</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<classifier>lib</classifier>
<excludes>
<exclude>application.yaml</exclude>
</excludes>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
18.17.8. 메이븐으로 시작된 스프링 부트 애플리케이션 원격 디버그(Remote Debug a Spring Boot Application Started with Maven)
메이븐으로 시작된 스프링 부트 애플리케이션에 원격 디버거를 연결하려면 메이븐 플러그인의 jvmArguments
프로퍼티를 사용할 수 있다.
자세한 내용은 이 예제를 참고하자.
18.17.9. spring-boot-antlib를 사용하지 않고 앤트에서 실행 가능한 압축파일 빌드(Build an Executable Archive From Ant without Using spring-boot-antlib)
앤트로 빌드하려면 의존성을 확보하고 컴파일한 다음 jar 또는 war 압축파일을 생성해야 한다. 실행 가능하게 만들려면 spring-boot-antlib
모듈을 사용하거나 다음 가이드를 따를 수 있다.
jar
을 빌드하는 경우 중첩된BOOT-INF/classes
디렉토리에 애플리케이션의 클래스와 리소스를 패키징한다.war
를 빌드하는 경우 평소와 같이 중첩된WEB-INF/classes
디렉터리에 애플리케이션의 클래스를 패키징한다.jar
의 경우 중첩된BOOT-INF/lib
디렉터리에,war
의 경우WEB-INF/lib
디렉터리에 런타임 의존성을 추가한다. 압축파일의의 항목을 압축하지 말자.jar
의 경우 중첩된BOOT-INF/lib
디렉토리에,war
의 경우WEB-INF/lib-provided
에 제공된(임베디드 컨테이너) 의존성을 추가한다. 압축파일의 항목을 압축하지 말자.- 아카이브 루트에
spring-boot-loader
클래스를 추가한다(Main-Class를 사용할 수 있도록). - 매니페스트의
Main-Class
애티튜드로 적절한 런쳐(launcher)(예: jar 파일의 JarLauncher)을 사용하고 매니페스트 항목으로 필요한 다른 프로퍼티를 지정한다(주로 Start-Class 프로퍼티를 설정).
다음 예에서는 앤트를 사용하여 실행 가능한 압축파일를 빌드하는 방법을 보여준다.ㄴ
<target name="build" depends="compile">
<jar destfile="target/${ant.project.name}-${spring-boot.version}.jar" compress="false">
<mappedresources>
<fileset dir="target/classes" />
<globmapper from="*" to="BOOT-INF/classes/*"/>
</mappedresources>
<mappedresources>
<fileset dir="src/main/resources" erroronmissingdir="false"/>
<globmapper from="*" to="BOOT-INF/classes/*"/>
</mappedresources>
<mappedresources>
<fileset dir="${lib.dir}/runtime" />
<globmapper from="*" to="BOOT-INF/lib/*"/>
</mappedresources>
<zipfileset src="${lib.dir}/loader/spring-boot-loader-jar-${spring-boot.version}.jar" />
<manifest>
<attribute name="Main-Class" value="org.springframework.boot.loader.JarLauncher" />
<attribute name="Start-Class" value="${start-class}" />
</manifest>
</jar>
</target>
18.18. Ahead of time 처리(Ahead-of-time processing)
사람들이 스프링 부트 애플리케이션의 ahead of time 처리를 사용할 때 많은 질문이 자주 발생한다. 이 장에서는 이러한 질문을 다룬다.
18.18.1. 컨디션(Conditions)
Ahead of time 처리는 애플리케이션을 최적화하고 빌드 시 환경(environment)을 기반으로 컨디션을 평가한다. 프로필(Profile)은 컨디션(Condition)을 통해 구현되므로 영향을 받는다.
ahead-of-time 처리에 최적화된 애플리케이션의 컨디션에 따라 생성된 빈을 원한다면 애플리케이션을 빌드할 때 환경(environment)을 설정해야 한다. 빌드 시 ahead-of-time 처리 중 생성된 빈은 애플리케이션을 실행할 때 항상 생성되며 끌 수 없다. 이를 위해 애플리케이션을 구축할 때 사용해야 하는 프로필을 설정할 수 있다.
메이븐의 경우 spring-boot-maven-plugin:process-aot
execution의 profiles
를 구성하고 작동한다.
<profile>
<id>native</id>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<id>process-aot</id>
<configuration>
<profiles>profile-a,profile-b</profiles>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
</profile>
그레이들의 경우 ProcessAot
태스크를 구성해야 한다.
tasks.withType(org.springframework.boot.gradle.tasks.aot.ProcessAot).configureEach {
args('--spring.profiles.active=profile-a,profile-b')
}
컨디션에 영향을 주지 않는 프로퍼티만 변경하는 프로필은 ahead-of-time가 최적화된 애플리케이션을 실행할 때 제한 없이 지원된다.
18.19. 기존방식의 배포(Traditional Deployment)
스프링 부트는 기존방식의 배포뿐만 아니라 보다 현대적인 배포 형태도 지원한다. 이 절에서는 기존방식의 배포에 대한 일반적인 질문에 답변한다.
18.19.1. 배포 가능한 War 파일 생성(Create a Deployable War File)
{ :warning } 스프링 웹플럭스는 서블릿 API에 엄격하게 의존하지 않고 기본적으로 임베디드 리액터 네티 서버에 배포되므로 웹플럭스 애플리케이션에는 War
배포가 지원되지 않는다.
배포 가능한 war 파일을 생성하는 첫 번째 단계는 SpringBootServletInitializer
하위 클래스를 제공하고 해당 구성 메서드를 오버라이드하는 것이다. 이렇게 하면 스프링 프레임워크의 서블릿 3.0 지원을 활용하고 서블릿 컨테이너에서 애플리케이션을 시작할 때 애플리케이션을 구성할 수 있다. 일반적으로 다음 예제와 같이 SpringBootServletInitializer
를 상속하도록 애플리케이션의 메인 클래스를 업데이트해야 한다.
자바
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
@SpringBootApplication
public class MyApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(MyApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
코틀린
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.builder.SpringApplicationBuilder
import org.springframework.boot.runApplication
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer
@SpringBootApplication
class MyApplication : SpringBootServletInitializer() {
override fun configure(application: SpringApplicationBuilder): SpringApplicationBuilder {
return application.sources(MyApplication::class.java)
}
}
fun main(args: Array<String>) {
runApplication<MyApplication>(*args)
}
다음 단계는 프로젝트가 jar 파일이 아닌 war 파일을 생성하도록 빌드 구성을 업데이트하는 것이다. 메이븐과 spring-boot-starter-parent
(메이븐의 war 플러그인 구성)를 사용하는 경우 pom.xml
을 수정하여 다음과 같이 패키징을 war로 변경하면 된다.
<packaging>war</packaging>
그레이들을 사용하는 경우 war 플러그인을 프로젝트에 적용하려면 다음과 같이 build.gradle
을 수정해야 한다.
apply plugin: 'war'
프로세스의 마지막 단계는 임베디드 서블릿 컨테이너가 war 파일이 배포되는 서블릿 컨테이너를 방해하지 않는지 확인하는 것이다. 그렇게 하려면 포함된 서블릿 컨테이너 의존성을 제공됨으로 표시해야 한다.
다음 예제처럼 메이븐을 사용하는 경우, 서블릿 컨테이너(이 경우 톰캣)가 제공되는 것(provided)으로 표시한다.
<dependencies>
<!-- ... -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<!-- ... -->
</dependencies>
다음 예제처럼 그레이들을 사용하는 경우, 서블릿 컨테이너(이 경우 톰캣)가 제공되는 것(provided)으로 표시한다.
dependencies {
// ...
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
// ...
}
providedRuntime
은 그레이들의 compileOnly
구성보다 선호된다. 다른 제한 사항 중에서 compileOnly
의존성은 테스트 클래스패스에 없으므로 모든 웹 기반 통합 테스트가 실패한다.
스프링 부트 빌드 툴을 사용하는 경우 임베디드 서블릿 컨테이너 의존성을 제공된 것으로 표시하면 lib-provided
디렉터리에 패키징된 의존성과 함께 실행 가능한 war
파일이 생성된다. 즉, 서블릿 컨테이너에 배포할 수 있을 뿐만 아니라 커맨드라인에서 java -jar
을 사용하여 애플리케이션을 실행할 수도 있다.
18.19.2. 기존 애플리케이션을 스프링 부트로 변환(Convert an Existing Application to Spring Boot)
웹이 아닌 스프링 애플리케이션을 스프링 부트 애플리케이션으로 변환하려면 ApplicationContext
를 생성하는 코드를 바꾸고 이를 SpringApplication
또는 SpringApplicationBuilder
에 대한 호출로 바꾸자. 스프링 MVC 웹 애플리케이션은 일반적으로 먼저 배포 가능한 war 애플리케이션을 생성한 다음 나중에 실행 가능한 war 또는 jar로 마이그레이션하는 것이 가능하다. jar를 war로 변환하는 방법은 시작하기 가이드를 참고하자.
SpringBootServletInitializer
(예: Application 클래스)를 상속하고 스프링 부트 @SpringBootApplication
어노테이션을 추가하여 배포 가능한 war을 생성하려면 다음 예제에 표시된 것과 같이 코드를 작성하자.
자바
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
@SpringBootApplication
public class MyApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
// application을 커스텀하거나 application.sources(...)를 호출하여 소스를 추가하자.
// 우리의 예제는 그 자체로 @SpringBootApplication를 사용한 @Configuration 클래스이기 때문에
// 실제로는 이 메서드를 오버라이드할 필요가 없다.
return application;
}
}
코틀린
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.builder.SpringApplicationBuilder
import org.springframework.boot.runApplication
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer
@SpringBootApplication
class MyApplication : SpringBootServletInitializer() {
override fun configure(application: SpringApplicationBuilder): SpringApplicationBuilder {
// application을 커스텀하거나 application.sources(...)를 호출하여 소스를 추가하자.
// 우리의 예제는 그 자체로 @SpringBootApplication를 사용한 @Configuration 클래스이기 때문에
// 실제로는 이 메서드를 오버라이드할 필요가 없다.
return application
}
}
sources
에 넣은 것은 단지 스프링 애플리케이션컨텍스트(ApplicationContext)
일 뿐이라는 점을 기억하자. 일반적으로 작동하는 모든 것이 여기서 동작해야 한다. 나중에 제거하고 스프링 부트가 자체 기본값을 제공하도록 할 수 있는 일부 빈이 있을 수 있지만 그렇게 하기 전에 작업을 수행하는 것이 가능해야 한다.
정적 리소스는 클래스패스 루트의 /public
(또는 /static
, /resources
또는 /META-INF/resources
)으로 이동할 수 있다. message.properties
에도 동일하게 적용된다(스프링 부트가 클래스패스 루트에서 자동으로 감지함).
스프링 DispatcherServlet
및 스프링 시큐리티의 바닐라 사용에는 더 이상의 변경이 필요하지 않다. 애플리케이션에 다른 기능이 있는 경우(예: 다른 서블릿 또는 필터 사용) web.xml
에서 해당 엘리먼트를 다음과 같이 대체하여 애플리케이션 컨텍스트에 일부 구성을 추가해야 할 수 있다.
서블릿(Servlet)
또는ServletRegistrationBean
타입의@Bean
은 마치web.xml
의<servlet/>
및<servlet-mapping/>
인 것처럼 컨테이너에 해당 빈을 설치한다.필터(Filter)
또는FilterRegistrationBean
타입의@Bean
은<filter/>
및<filter-mapping/>
과 유사하게 작동한다.- XML 파일의
ApplicationContext
는 애플리케이션의@ImportResource
를 통해 추가될 수 있다. 또는 어노테이션 구성이 이미 많이 사용되는 경우를@Bean
정의로 몇 줄로 다시 생성할 수 있다.
war 파일이 작동하면, 다음 예제와 같이 애플리케이션에 메인 메서드를 추가하여 이를 실행 가능하게 만들 수 있다.
자바
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
코틀린
fun main(args: Array<String>) {
runApplication<MyApplication>(*args)
}
애플리케이션을 war
또는 실행 가능한 애플리케이션으로 시작하려는 경우, SpringBootServletInitializer
콜백에서 사용 가능한 메서드와 메인 메서드에서 빌더의 커스텀을 공유해야 한다.
자바
import org.springframework.boot.Banner;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
@SpringBootApplication
public class MyApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return customizerBuilder(builder);
}
public static void main(String[] args) {
customizerBuilder(new SpringApplicationBuilder()).run(args);
}
private static SpringApplicationBuilder customizerBuilder(SpringApplicationBuilder builder) {
return builder.sources(MyApplication.class).bannerMode(Banner.Mode.OFF);
}
}
코틀린
import org.springframework.boot.Banner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.builder.SpringApplicationBuilder
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer
@SpringBootApplication
class MyApplication : SpringBootServletInitializer() {
override fun configure(builder: SpringApplicationBuilder): SpringApplicationBuilder {
return customizerBuilder(builder)
}
companion object {
@JvmStatic
fun main(args: Array<String>) {
customizerBuilder(SpringApplicationBuilder()).run(*args)
}
private fun customizerBuilder(builder: SpringApplicationBuilder): SpringApplicationBuilder {
return builder.sources(MyApplication::class.java).bannerMode(Banner.Mode.OFF)
}
}
}
애플리케이션은 둘 이상의 범주에 속할 수 있다.
web.xml
이 없는 서블릿(Servlet) 3.0+ 애플리케이션.web.xml
이 있는 애플리케이션.- 컨텍스트 계층 구조가 있는 애플리케이션.
- 컨텍스트 계층 구조가 없는 애플리케이션.
이들 모두는 상호 변환이 가능해야 하지만, 각각 약간씩 다른 기술이 필요할 수 있다.
서블릿(Servlet) 3.0+ 애플리케이션이 이미 스프링 서블릿 3.0+ 초기화 지원 클래스(initializer support classes)를 사용하고 있다면 꽤 쉽게 변환할 수 있다. 일반적으로 기존 WebApplicationInitializer
의 모든 코드는 SpringBootServletInitializer
로 이동할 수 있다. 기존 애플리케이션에 둘 이상의 ApplicationContext
가 있는 경우(예: AbstractDispatcherServletInitializer
를 사용하는 경우) 모든 컨텍스트 소스를 단일 SpringApplication
으로 결합할 수 있다. 발생할 수 있는 주요 문제는 결합이 작동하지 않고 컨텍스트 계층을 유지해야 하는 경우다. 예제는 계층 구조 작성 항목을 참고하자. 웹 특정 기능을 포함하는 기존 상위 컨텍스트는 일반적으로 모든 ServletContextAware
컴포넌트가 하위 컨텍스트에 있도록 분할되어야 한다.
아직 스프링 애플리케이션이 아닌 애플리케이션은 스프링 부트 애플리케이션으로 변환될 수 있으며, 앞에서 언급한 가이드가 도움이 될 수 있다. 그러나 아직 문제가 발생할 수 있다. 그런 경우에는 spring-boot
태그를 사용하여 스택 오버플로우에 질문하는 것이 좋다.
18.19.3. WebLogic에 WAR 배포(Deploying a WAR to WebLogic)
스프링 부트 애플리케이션을 WebLogic에 배포하려면 서블릿 이니셜라이저가 WebApplicationInitializer
를 직접 구현하는지 확인해야 한다(이미 이를 구현한 기본 클래스에서 상속하는 경우에도).
WebLogic의 일반적인 초기화(initializer)는 다음 예제와 같다.
자바
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.web.WebApplicationInitializer;
@SpringBootApplication
public class MyApplication extends SpringBootServletInitializer implements WebApplicationInitializer {
}
코틀린
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer
import org.springframework.web.WebApplicationInitializer
@SpringBootApplication
class MyApplication : SpringBootServletInitializer(), WebApplicationInitializer
로그백(Logback)을 사용하는 경우 WebLogic에 서버와 함께 설치된 버전이 아닌 패키지 버전을 선호하도록 지시해야 한다. 다음 내용이 포함된 WEB-INF/weblogic.xml
파일을 추가하면 된다.
<?xml version="1.0" encoding="UTF-8"?>
<wls:weblogic-web-app
xmlns:wls="http://xmlns.oracle.com/weblogic/weblogic-web-app"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
https://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd
http://xmlns.oracle.com/weblogic/weblogic-web-app
https://xmlns.oracle.com/weblogic/weblogic-web-app/1.4/weblogic-web-app.xsd">
<wls:container-descriptor>
<wls:prefer-application-packages>
<wls:package-name>org.slf4j</wls:package-name>
</wls:prefer-application-packages>
</wls:container-descriptor>
</wls:weblogic-web-app>
18.20. 도커 컴포즈(Docker Compose)
이 절에는 스프링 부트의 도커 컴포즈 지원과 관련된 토픽이 포함되어 있다.
18.20.1. JDBC URL 커스텀(Customizing the JDBC URL)
예를 들어, 도커 컴포즈(Docker Compose)와 함께 JdbcConnectionDetails
를 사용하는 경우 org.springframework.boot.jdbc.parameters
레이블을 서비스에 적용하여 JDBC URL의 파라미터를 커스텀할 수 있다.
services:
postgres:
image: 'postgres:15.3'
environment:
- 'POSTGRES_USER=myuser'
- 'POSTGRES_PASSWORD=secret'
- 'POSTGRES_DB=mydb'
ports:
- '5432:5432'
labels:
org.springframework.boot.jdbc.parameters: 'ssl=true&sslmode=require'
이 도커 컴포즈(Docker Compose) 파일이 있으면 사용되는 JDBC URL은 jdbc:postgresql://127.0.0.1:5432/mydb?ssl=true&sslmode=require
이다.