application.yml에 선언해서 관리하는 게 코드 유지보수나 보안적(깃허브 업로드 때 제외할 수 있음)으로도 좋을 것 같아서 적용하기 위해 알아보게 됨annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'jwt:
header: Authorization
secret: secretsecretsecretsecret123secretsecretsecretsecret123===
access-validity-in-seconds: 500
refresh-validity-in-seconds: 6048000application.yml의 값을 바인딩할 객체 생성하고 @ConfigurationProperties 어노테이션 추가@Getter
@Setter
@ConfigurationProperties(prefix = "jwt")
public class JwtProperties {
private String header;
private String secret;
private long accessValidityInSeconds;
private long refreshValidityInSeconds;
private long authValidityInSeconds;
}@Getter는 해당 값들을 사용하기 위해 필요@Setter는 Spring에서 @ConfigurationProperties 클래스를 사용할 때, setter 메서드로 값을 바인딩하는 방식이라 설정하지 않으면 값이 바인딩되지 않음(프로젝트 빌드 시 오류 발생)@ConfigurationPropertiesScan(basePackages = "com.example") 어노테이션 추가@SpringBootApplication
@EnableJpaAuditing
@ConfigurationPropertiesScan(basePackages = "com.example")
public class TravelApplication {
public static void main(String[] args) {
SpringApplication.run(TravelApplication.class, args);
}
}@ConfigurationPropertiesScan@ConfigurationProperties로 선언된 클래스를 스캔해서 자동으로 빈으로 등록해주는 역할basePackages 속성에 지정한 패키지 하위에서 @ConfigurationProperties가 붙은 클래스를 찾아 등록@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface ConfigurationProperties {
@AliasFor("prefix")
String value() default "";
@AliasFor("value")
String prefix() default "";
boolean ignoreInvalidFields() default false;
boolean ignoreUnknownFields() default true;
}@ConfigurationProperties는 단순히 메타정보만 담고 있음ConfigurationPropertiesBindingPostProcessor라는 빈 후처리기가 값을 바인딩@ConfigurationProperties 어노테이션이 붙은 클래스를 스캔@ConfigurationProperties의 prefix 값을 기준으로 application.yml에서 매칭되는 값을 찾고, 그걸 해당 필드에 바인딩setter 호출하거나 생성자를 통해 값을 주입(생성자를 통해 넣으려면 별도 설정 등 필요)@SpringBootApplication이나 @ConfigurationPropertiesScan 같은 어노테이션을 통해 해당 클래스를 빈으로 등록하면, Spring이 실행 중에 @ConfigurationProperties가 붙은 클래스를 자동으로 감지해서 처리ConfigurationPropertiesBindingPostProcessor 내부public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProcessor, PriorityOrdered, ApplicationContextAware, InitializingBean {
public static final String BEAN_NAME = ConfigurationPropertiesBindingPostProcessor.class.getName();
private ApplicationContext applicationContext;
private BeanDefinitionRegistry registry;
private ConfigurationPropertiesBinder binder;
public ConfigurationPropertiesBindingPostProcessor() {
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public void afterPropertiesSet() throws Exception {
this.registry = (BeanDefinitionRegistry)this.applicationContext.getAutowireCapableBeanFactory();
this.binder = ConfigurationPropertiesBinder.get(this.applicationContext);
}
public int getOrder() {
return -2147483647;
}
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (!this.hasBoundValueObject(beanName)) {
this.bind(ConfigurationPropertiesBean.get(this.applicationContext, bean, beanName));
}
return bean;
}
private boolean hasBoundValueObject(String beanName) {
return BindMethod.VALUE_OBJECT.equals(BindMethodAttribute.get(this.registry, beanName));
}
private void bind(ConfigurationPropertiesBean bean) {
if (bean != null) {
Assert.state(bean.asBindTarget().getBindMethod() != BindMethod.VALUE_OBJECT, "Cannot bind @ConfigurationProperties for bean '" + bean.getName() + "'. Ensure that @ConstructorBinding has not been applied to regular bean");
try {
this.binder.bind(bean);
} catch (Exception var3) {
Exception ex = var3;
throw new ConfigurationPropertiesBindException(bean, ex);
}
}
}
public static void register(BeanDefinitionRegistry registry) {
Assert.notNull(registry, "Registry must not be null");
if (!registry.containsBeanDefinition(BEAN_NAME)) {
BeanDefinition definition = BeanDefinitionBuilder.rootBeanDefinition(ConfigurationPropertiesBindingPostProcessor.class).getBeanDefinition();
definition.setRole(2);
registry.registerBeanDefinition(BEAN_NAME, definition);
}
ConfigurationPropertiesBinder.register(registry);
}
}
setApplicationContext(ApplicationContext applicationContext)
afterPropertiesSet()
registry : 빈 정의를 등록/조회할 수 있는 객체(BeanDefinitionRegistry)binder : 실제로 바인딩을 수행할 핵심 객체(ConfigurationPropertiesBinder)postProcessBeforeInitialization(Object bean, String beanName)
@ConfigurationProperties가 붙은 것인지 판단application.yml, properties)의 값을 해당 bean에 바인딩hasBoundValueObject(String beanName)
@ConstructorBinding이 붙은 클래스인지 체크해서 중복 바인딩을 피하려는 목적도 있음bind(ConfigurationPropertiesBean bean)
binder.bind(bean) 호출이 핵심Binder라는 객체가 동작해서 application.yml의 값들을 가져오고, POJO의 setter에 주입(또는 생성자 기반이면 생성자로)register(BeanDefinitionRegistry registry)
PostProcessor를 Bean으로 등록하지 않았다면 자동 등록해주는 static 메서드📢 순서 정리
@ConfigurationProperties
↓
Spring이 클래스 인식
↓
ConfigurationPropertiesBindingPostProcessor 클래스
postProcessBeforeInitialization()
↓
binder.bind() 호출
↓
Binder가 application.yml 파싱 후
↓
필드에 값 주입(setter 또는 생성자)