1. 개요
이 사용방법(예제)에서는 Spring 애플리케이션에서 속성을 다시 로드하는 방법을 배웁니다 .
2. Spring에서 속성 읽기
Spring에서 속성에 액세스할 수 있는 여러 가지 옵션이 있습니다.
- 환경 — 환경을 주입한 다음 주어진 속성을 읽기 위해 Environment#getProperty를 사용할 수 있습니다. 환경에는 시스템 속성, -D 매개변수 및 application.properties(.yml) 와 같은 다양한 속성 소스가 포함됩니다 . @PropertySource를 사용하여 환경 에 추가 속성 소스를 추가할 수도 있습니다 .
- Properties — 속성 파일을 Properties 인스턴스 에 로드한 다음 properties.get("property")을 호출하여 bean에서 사용할 수 있습니다.
- @Value — @Value(${'property'}) 어노테이션을 사용하여 빈에 특정 속성을 주입할 수 있습니다.
- @ConfigurationProperties — @ConfigurationProperties를 사용하여 빈에서 계층적 속성을 로드할 수 있습니다.
3. 외부 파일에서 속성 다시 로드
런타임 중에 파일의 속성을 변경하려면 해당 파일을 jar 외부에 배치해야 합니다. 그런 다음 명령줄 매개 변수 –spring.config.location=file://{파일 경로}를 사용하여 Spring에 위치를 알려줍니다 . 또는 application.properties에 넣을 수 있습니다 .
파일 기반 속성에서 파일을 다시 로드하는 방법을 선택해야 합니다. 예를 들어 엔드포인트 또는 스케줄러를 개발하여 파일을 읽고 속성을 업데이트할 수 있습니다.
파일을 다시 로드하는 편리한 라이브러리 중 하나는 Apache의 commons-configuration 입니다 . 다른 ReloadingStrategy 와 함께 PropertiesConfiguration을 사용할 수 있습니다 .
pom.xml 에 commons-configuration을 추가해봅시다 :
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
<version>1.10</version>
</dependency>
그런 다음 나중에 사용할 PropertiesConfiguration 빈을 생성하는 메서드를 추가합니다 .
@Bean
@ConditionalOnProperty(name = "spring.config.location", matchIfMissing = false)
public PropertiesConfiguration propertiesConfiguration(
@Value("${spring.config.location}") String path) throws Exception {
String filePath = new File(path.substring("file:".length())).getCanonicalPath();
PropertiesConfiguration configuration = new PropertiesConfiguration(
new File(filePath));
configuration.setReloadingStrategy(new FileChangedReloadingStrategy());
return configuration;
}
위의 코드에서 FileChangedReloadingStrategy를 기본 새로 고침 지연이 있는 다시 로드 전략으로 설정했습니다 . 이는 PropertiesConfiguration이 마지막 확인이 5000ms 이전인 경우 파일 수정 날짜를 확인함을 의미합니다 .
FileChangedReloadingStrategy#setRefreshDelay를 사용하여 지연을 사용자 정의할 수 있습니다 .
3.1. 환경 속성 다시 로드
환경 인스턴스를 통해 로드된 속성을 다시 로드하려면 PropertySource를 확장한 다음 PropertiesConfiguration을 사용하여 외부 속성 파일에서 새 값을 반환해야 합니다 .
PropertySource 확장부터 시작하겠습니다 .
public class ReloadablePropertySource extends PropertySource {
PropertiesConfiguration propertiesConfiguration;
public ReloadablePropertySource(String name, PropertiesConfiguration propertiesConfiguration) {
super(name);
this.propertiesConfiguration = propertiesConfiguration;
}
public ReloadablePropertySource(String name, String path) {
super(StringUtils.hasText(name) ? path : name);
try {
this.propertiesConfiguration = new PropertiesConfiguration(path);
this.propertiesConfiguration.setReloadingStrategy(new FileChangedReloadingStrategy());
} catch (Exception e) {
throw new PropertiesException(e);
}
}
@Override
public Object getProperty(String s) {
return propertiesConfiguration.getProperty(s);
}
}
PropertiesConfiguration#getProperty 에 Delegation하기 위해 getProperty 메서드를 재정의했습니다 . 따라서 새로 고침 지연에 따라 간격으로 업데이트된 값을 확인합니다.
이제 ReloadablePropertySource를 Environment 의 속성 소스 에 추가합니다 .
@Configuration
public class ReloadablePropertySourceConfig {
private ConfigurableEnvironment env;
public ReloadablePropertySourceConfig(@Autowired ConfigurableEnvironment env) {
this.env = env;
}
@Bean
@ConditionalOnProperty(name = "spring.config.location", matchIfMissing = false)
public ReloadablePropertySource reloadablePropertySource(PropertiesConfiguration properties) {
ReloadablePropertySource ret = new ReloadablePropertySource("dynamic", properties);
MutablePropertySources sources = env.getPropertySources();
sources.addFirst(ret);
return ret;
}
}
동일한 키로 기존 속성을 재정의하기를 원하기 때문에 새 속성 소스를 첫 번째 항목으로 추가했습니다 .
Environment 에서 속성을 읽는 빈을 만들어 봅시다 .
@Component
public class EnvironmentConfigBean {
private Environment environment;
public EnvironmentConfigBean(@Autowired Environment environment) {
this.environment = environment;
}
public String getColor() {
return environment.getProperty("application.theme.color");
}
}
다른 재로드 가능한 외부 속성 소스를 추가해야 하는 경우 먼저 사용자 정의 PropertySourceFactory를 구현해야 합니다 .
public class ReloadablePropertySourceFactory extends DefaultPropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String s, EncodedResource encodedResource)
throws IOException {
Resource internal = encodedResource.getResource();
if (internal instanceof FileSystemResource)
return new ReloadablePropertySource(s, ((FileSystemResource) internal)
.getPath());
if (internal instanceof FileUrlResource)
return new ReloadablePropertySource(s, ((FileUrlResource) internal)
.getURL()
.getPath());
return super.createPropertySource(s, encodedResource);
}
}
그런 다음 @PropertySource를 사용하여 구성 요소의 클래스에 어노테이션을 달 수 있습니다 .
@PropertySource(value = "file:path-to-config", factory = ReloadablePropertySourceFactory.class)
3.2. 속성 인스턴스 다시 로드
환경은 Properties 보다 더 나은 선택입니다 . 특히 파일에서 속성을 다시 로드해야 하는 경우에 그렇습니다. 그러나 필요한 경우 java.util.Properties를 확장할 수 있습니다 .
public class ReloadableProperties extends Properties {
private PropertiesConfiguration propertiesConfiguration;
public ReloadableProperties(PropertiesConfiguration propertiesConfiguration) throws IOException {
super.load(new FileReader(propertiesConfiguration.getFile()));
this.propertiesConfiguration = propertiesConfiguration;
}
@Override
public String getProperty(String key) {
String val = propertiesConfiguration.getString(key);
super.setProperty(key, val);
return val;
}
// other overrides
}
getProperty 및 해당 오버로드를 재정의한 다음 PropertiesConfiguration 인스턴스에 Delegation 했습니다 . 이제 우리는 이 클래스의 빈을 생성하고 컴포넌트에 주입할 수 있습니다.
3.3. @ConfigurationProperties 로 빈 다시 로드하기
@ConfigurationProperties 로 동일한 효과를 얻으려면 인스턴스를 재구성해야 합니다. 그러나 Spring은 프로토타입 또는 요청 범위 를 가진 구성 요소의 새 인스턴스만 생성합니다 .
결과적으로 환경을 다시 로드하는 우리의 기술은 그들에게도 작동하지만 싱글톤의 경우 bean을 파괴하고 재생성하기 위해 Endpoints을 구현하거나 bean 자체 내에서 속성 다시 로드를 처리하는 것 외에 선택의 여지가 없습니다.
3.4. @Value를 사용하여 Bean 다시 로드
@Value 어노테이션은 @ConfigurationProperties 와 동일한 제한 사항을 나타냅니다 .
4. 액추에이터와 클라우드에 의한 속성 재로딩
Spring Actuator는 상태, 메트릭 및 구성에 대해 서로 다른 Endpoints을 제공하지만 새로 고침 빈에 대해서는 아무것도 제공하지 않습니다. 따라서 /refresh 엔드포인트를 추가하려면 Spring Cloud가 필요합니다. 이 엔드포인트는 Environment 의 모든 속성 소스를 다시 로드한 다음 EnvironmentChangeEvent 를 게시합니다 .
Spring Cloud는 @RefreshScope 도 도입했으며 구성 클래스 또는 빈에 사용할 수 있습니다. 결과적으로 기본 범위는 싱글톤 대신 새로고침이 됩니다 .
새로 고침 범위를 사용하여 Spring은 EnvironmentChangeEvent 에서 이러한 구성 요소의 내부 캐시를 지웁니다 . 그런 다음 Bean에 대한 다음 액세스에서 새 인스턴스가 작성됩니다.
pom.xml 에 spring-boot-starter-actuator를 추가하여 시작하겠습니다 .
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
그런 다음 spring-cloud-dependencies를 가져옵니다 .
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<properties>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
다음으로 spring-cloud-starter를 추가합니다 .
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
마지막으로 새로 고침 Endpoints을 활성화합니다.
management.endpoints.web.exposure.include=refresh
Spring Cloud를 사용할 때 구성 서버를 설정하여 속성을 관리할 수 있지만 외부 파일을 계속 사용할 수도 있습니다. 이제 속성을 읽는 두 가지 다른 방법인 @Value 및 @ConfigurationProperties를 처리할 수 있습니다 .
4.1. @ConfigurationProperties 로 빈 새로 고침
@RefreshScope 와 함께 @ConfigurationProperties를 사용하는 방법을 보여드리겠습니다 .
@Component
@ConfigurationProperties(prefix = "application.theme")
@RefreshScope
public class ConfigurationPropertiesRefreshConfigBean {
private String color;
public void setColor(String color) {
this.color = color;
}
//getter and other stuffs
}
우리 빈은 루트 "application" 에서 " color" 속성을 읽고 있습니다 . 테마” 속성 . Spring 문서에 따라 setter 메서드가 필요합니다.
외부 구성 파일에서 " application.theme.color " 값을 변경한 후 /refresh를 호출하여 다음에 액세스할 때 bean에서 새 값을 가져올 수 있습니다.
4.2. @Value 로 빈 새로 고침
샘플 구성 요소를 만들어 보겠습니다.
@Component
@RefreshScope
public class ValueRefreshConfigBean {
private String color;
public ValueRefreshConfigBean(@Value("${application.theme.color}") String color) {
this.color = color;
}
//put getter here
}
새로 고침 과정은 위와 동일합니다.
그러나 /refresh는 명시적 싱글톤 범위 가 있는 Bean에 대해 작동하지 않는다는 점에 유의해야 합니다 .
5. 결론
이 기사에서는 Spring Cloud 기능을 사용하거나 사용하지 않고 속성을 다시 로드하는 방법을 배웠습니다. 또한 각 기술의 함정과 예외를 설명했습니다.
전체 코드는 GitHub 프로젝트에서 사용할 수 있습니다 .