====== SpringBoot Properties ======
* [[springframework:springboot|SpringBoot]] 에서 설정 값 외재화
* [[java:jasypt|jasypt]] 를 이용해 비밀번호 계통은 암호화 해서 저장해야 한다.
* [[springframework:springboot:properties_migrator|spring-boot-properties-migrator]]
* 보통 ''yml''을 이용해서 ''application.yml''로 지정한다.
* ''YamlPropertiesFactoryBean''
* ''PropertiesFactoryBean''
* ''YamlPropertySourceLoader''
* ''PropertiesPropertySourceLoader''
===== 기본값 설정 =====
* 이미 지정된 property 이면, 해당 값을 사용하고, 그게 아니면 기본값을 사용하는 구조로
* ''EXTERNAL_USERNAME/EXTERNAL_PASSWORD'' 환경 변수 혹은 ''external.username/external.password'' 프라퍼티가 존재하면 그 값을 사용하고 그게 아니면 '':'' 뒤에 지정된 값을 사용한다.
testing:
my:
username: ${external.username:system}
password: ${external.password:test}
* [[https://stackoverflow.com/a/49644607/1051402|SpEL로 기본값 지정]]. SpEL은 ''@Value''로 값을 읽는 경우에만 지정 가능하며, ''@ConfigurationProperties''를 사용할 때는 안 된다. 혼란스럽기 때문에 안 사용하는게 나을듯 하다.
testing:
my:
username: ${external.username:user}
password: ${external.password:#{' test'}}
@Value("${testing.my.username}")
private String username;
@Value("${testing.my.password}")
private String password;
===== profile 별 설정 =====
* 프로필 별로 ''application-profilename.yml''로 파일을 만들 수도 있고,
* 하나의 파일에서 ''---''로 구분해서 프로필을 지정할 수도 있다.
* ''application.yml''에서 profile을 지정하지 않은 프라퍼티는 공통 디폴트 값 역할을 한다. 각 프로필에서 오버라이드하면 변경되고 아니면 디폴트 값이 사용된다.
a:
b:
c: default value
---
spring.profiles: local
# 기본값 그대로 사용
---
spring.profiles: develop
a:
b:
c: develop-value
---
spring.profiles: production
a:
b:
c: production-value
===== profile include =====
* 프로필을 자동 활성화 한다.
* Spring Boot 2.3 이전에서 사용하는 방식.
* 2.4 부터는 [[https://github.com/kwon37xi/research-spring-boot-2.4/tree/master/profile-group|spring.config.import 와 profile group 기능 사용]]
* ''application.yml''
spring.profiles: production
spring.profiles.include: a,b
# 혹은
spring.profiles: production
spring:
profiles:
include:
- a
- b
- c
* 위 설정은 ''production'' profile 에서 자동으로 ''a'', ''b'', ''c'' 프로필도 활성화 시키는 역할을 한다.
* 그러면서 각 프로필의 설정파일을 ''application-a.yml'', ''application-b.yml'' 형태로 만들어서 ''resources'' 에 두면 이를 읽게 된다.
* 위 설정을 특정 프로필이 아닌 전역으로 지정하면, 마치 그냥 설정 파일을 include 하는 것 처럼 된다. 하지만 ''a'',''b'',''c''라는 프로필이 모르는 사이에 활성화 된다.
* 각 프로필에서 또 나눠서 ''spring.profiles: local'' 이런식으로 특정 프로파일을 지정하고 설정해도 먹지 않고 무시된다. ''a-develop'' 이런식으로 별도 프로필을 만들고 ''active: a-develop'' 처럼 더 추가해줘야만 한다.
* 즉, 최초 시작 설정 파일은 **''application.yml''** 이어야만 include 된 설정 안에서의 Profile 구분이 올바로 작동한다. 애초에 최초 시작 설정 파일을 ''application-prod.yml'' 로 한 상태에서 include는 그 안의 Profile 구분이 무시된다.
===== 별도 Yaml로 PropertySource 적재 방식 =====
* [[https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/YamlPropertySourceLoader.java|YamlPropertySourceLoader]] 를 이용해 외부 YAML 파일을 PropertySource로 적재할 수 있다.
* [[https://www.programcreek.com/java-api-examples/index.php?api=org.springframework.boot.env.YamlPropertySourceLoader|Java Code Examples org.springframework.boot.env.YamlPropertySourceLoader]]
* 여기서 보면 ''env'' 객체에 ''addFirst()'' 혹은 ''addLast()''를 해주고 있는데, 이게 꼭 필요한지 단순히 ''PropertySource'' 객체를 리턴하는 것만으로 충분한지 확인 필요.
* https://stackoverflow.com/a/37349492/1051402 여기서는 ''ApplicationContextInitializer''로 만드는데 꼭 Initializer로 만들어야만 하는가?
// 일반 Bean으로 만들어 주입
@Autowired
private ConfigurableEnvironment env;
@Bean
@Order(-1)
PropertySource consumerPropertySource() throws IOException {
YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
PropertySource propertySource =
loader.load("consumers", new ClassPathResource("consumer.yml"), "develop");
env.getPropertySources().addLast(propertySource);
return propertySource;
}
// ApplicationContextInitializerApplicationContextInitializer 구현
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
try {
Resource resource = applicationContext.getResource("classpath:application.yml");
YamlPropertySourceLoader sourceLoader = new YamlPropertySourceLoader();
PropertySource> yamlTestProperties = sourceLoader.load("yamlTestProperties", resource, null);
applicationContext.getEnvironment().getPropertySources().addLast(yamlTestProperties);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
===== YAML을 Property 객체로 변환하기 =====
* [[https://github.com/spring-projects/spring-framework/blob/master/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlPropertiesFactoryBean.java|YamlPropertiesFactoryBean]] 를 사용하여 yaml을 ''Property'' 객체로 적재할 수 있다. ''PropertySource''로 등록되는 것은 아니므로 헷갈리면 안된다.
* 이 경우 ''@Value("#propertyObjectName['key']")'' 형태로 사용가능해 진다.
===== YAML을 ''@PropertySource''로 사용하기 =====
* [[https://github.com/spring-projects/spring-framework/blob/master/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlPropertiesFactoryBean.java|YamlPropertiesFactoryBean]] 로 명시적으로 지정해줄 수 있다.
* [[https://www.baeldung.com/spring-yaml-propertysource|@PropertySource with YAML Files in Spring Boot | Baeldung]]
@PropertySource(
value = "classpath:foo.yml",
factory = YamlPropertySourceFactory.class
)
public class YamlPropertySourceFactory implements PropertySourceFactory {
@Override
public PropertySource> createPropertySource(String name, EncodedResource encodedResource)
throws IOException {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(encodedResource.getResource());
Properties properties = factory.getObject();
return new PropertiesPropertySource(encodedResource.getResource().getFilename(), properties);
}
}
===== @ConfigurationProperties =====
* ''@EnableConfigurationProperties'' 가 설정돼 있어야 한다.
* [[http://www.baeldung.com/configuration-properties-in-spring-boot|Guide to @ConfigurationProperties in Spring Boot | Baeldung]]
* [[http://javacan.tistory.com/entry/springboot-configuration-properties-class|자바캔(Java Can Do IT) :: 스프링 부트 커스텀 설정 프로퍼티 클래스 사용하기]]
* 아래 의존성을 추가하면 yml IDE 자동 완성이 지원되는 듯
compile "org.springframework.boot:spring-boot-configuration-processor"
* ''@ConfigurationProperties'' 애노테이션이 붙은 클래스에 직접 ''@Configuration''을 붙여서 생성하거나, 별도로 ''@Bean''으로 생성하면 자동으로 값이 차서 Bean 으로 생성된다.
* ''javax.validation'' 애노테이션을 걸어주면 자동 validation이 작동한다.
===== PropertySource과 properties 로그로 남기기 =====
* [[https://stackoverflow.com/questions/44115216/spring-boot-when-application-properties-and-application-yml-are-loaded-by-a-spr|Spring Boot: When application.properties and application.yml are loaded by a spring boot application - Stack Overflow]]
@Component
public class LoadedConfigFileListener implements ApplicationListener, Ordered {
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
MutablePropertySources propertySources = event.getApplicationContext().getEnvironment().getPropertySources();
Iterator> propertySourceIterator = propertySources.iterator();
propertySourceIterator.forEachRemaining(propertySource -> log.info("Successfully loaded: \"{}\" ({}) into application context", propertySource.getName(), propertySource.getSource()));
}
@Override
public int getOrder() {
return ConfigFileApplicationListener.DEFAULT_ORDER + 1;
}
}
===== 모든 properties 로그로 남기기 =====
* [[https://gist.github.com/sandor-nemeth/f6d2899b714e017266cb9cce66bc719d|PropertyLogger.java]]
* 최종적으로 결정된 properties 목록을 확인하고자 할 때, 아래와 같이 ''ContextRefreshEvent'' 를 받는 ''@EventListener'' Bean을 등록해주고, 거기서 로깅하면 된다.
@Component
public class PropertyLogger {
private static final Logger LOGGER = LoggerFactory.getLogger(PropertyLogger.class);
@EventListener
public void handleContextRefresh(ContextRefreshedEvent event) {
final Environment env = event.getApplicationContext().getEnvironment();
LOGGER.info("====== Environment and configuration ======");
LOGGER.info("Active profiles: {}", Arrays.toString(env.getActiveProfiles()));
final MutablePropertySources sources = ((AbstractEnvironment) env).getPropertySources();
StreamSupport.stream(sources.spliterator(), false)
.filter(ps -> ps instanceof EnumerablePropertySource)
.map(ps -> ((EnumerablePropertySource) ps).getPropertyNames())
.flatMap(Arrays::stream)
.distinct()
.filter(prop -> !(prop.contains("credentials") || prop.contains("password")))
.forEach(prop -> LOGGER.info("{}: {}", prop, env.getProperty(prop)));
LOGGER.info("===========================================");
}
}
===== addional property 파일지정 =====
* 기존 프라퍼티 파일들보다 우선순위가 높게 추가 프라퍼티 파일을 지정할 수 있다. **추가**되는 것이므로 기존 값도 계속 함께 읽는다.
spring.config.additional-location=classpath:/custom-config,file:./custom-config
===== JSON을 통한 Override =====
* JSON 으로 명령행에서 properties를 override할 수 있다. JSON 으로 여러 프라퍼티를 지정할 수 있다.
* 시스템 프라퍼티로 : ''spring.application.json={"keyname":"value"}''
* 환경 변수로 : ''SPRING_APPLICATION_JSON={"keyname":"value"}''
===== EnvironmentPostProcessor 를 통한 프라퍼티 값 후처리 =====
* [[https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/env/EnvironmentPostProcessor.html|EnvironmentPostProcessor]] 를 사용하면 읽어들인 프라퍼티 값에 대한 후처리를 할 수 있다.
* 예를들면, 프라퍼티 값을 암호화 해서 저장해 두고, 실제 읽을 때는 복호화해서 읽는 등의 작업이 가능하다.
* [[https://www.baeldung.com/spring-boot-environmentpostprocessor|EnvironmentPostProcessor in Spring Boot | Baeldung]]
* [[https://javacan.tistory.com/entry/activate-some-profile-when-no-active-profiles-in-boot|자바캔(Java Can Do IT) :: 스프링 부트에서 EnvironmentPostProcessor로 기본 프로필 설정하기]]
* [[https://wedul.site/458|Spring Boot application.properties 암호화 내역 복호화 방법]]
* 작성한 코드를 ''META-INF/spring.factories'' 에 등록해야 한다.
org.springframework.boot.env.EnvironmentPostProcessor=
com.baeldung.environmentpostprocessor.PriceCalculationEnvironmentPostProcessor
===== YAML Duration =====
* Spring Boot 2.1 부터 [[https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html|Duration]] 을 지정할 수 있다.
* ''100ms'' 형태로 시간 단위를 문자로 지정하면 된다.
* ''ns'' for nanoseconds
* ''us'' for microseconds
* ''ms'' for milliseconds
* ''s'' for seconds
* ''m'' for minutes
* ''h'' for hours
* ''d'' for days
===== 참고 =====
* [[http://www.springboottutorial.com/spring-boot-application-configuration|Application Configuration with Spring Boot – Spring Boot Tutorial]]
* [[https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-profiles.html|25. Profiles]]
* [[https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html|24. Externalized Configuration]]
* [[https://docs.spring.io/spring-boot/docs/current/reference/html/howto-properties-and-configuration.html|74. Properties and Configuration]]
* [[http://www.baeldung.com/properties-with-spring|Properties with Spring and Spring Boot | Baeldung]]