====== 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]]