Properties를 등록하기 위해 설정 정보를 담고 있는 클래스를 만든다.
@Component
public class ServerProperties {
private String contextPath;
private int port;
public String getContextPath() {
return contextPath;
}
public void setContextPath(String contextPath) {
this.contextPath = contextPath;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}
이는 ServletWebServerFactory Bean의 속성 정보로 사용될 프로퍼티 값이다.
이를 주입받기 위해서 우리는 다음과 같이 Bean을 정의할 수 있다.
@Bean(name = "tomcatWebServerFactory")
@ConditionalOnMissingBean
public ServletWebServerFactory servletWebServerFactory(ServerProperties serverProperties) {
TomcatServletWebServerFactory tomcatServletWebServerFactory = new TomcatServletWebServerFactory();
tomcatServletWebServerFactory.setPort(serverProperties.getPort());
return tomcatServletWebServerFactory;
}
그런데 ServerProperties Class에서는 프로퍼티 파일에서 값을 읽어오지 못했다. 이를 읽어오는 방법을 알아보자.
우선 Configuration Properties으로 사용되는 클래스임을 마킹하기 위해 다음과 같이 어노테이션을 정의합니다.
@Component
@MyConfigurationProperties
public class ServerProperties{..}
@MyConfigurationProperties라는 Marker 애노테이션은 빈 후처리기에서 프로퍼티를 바인딩해 주는 작업의 대상을 필터링하는 데 사용됩니다.
이제 BeanPostProcessor를 추가할 것 입니다. 이는 Bean을 최종적으로 생성한 이후 조작할 수 있는 확장 기능입니다.
@Bean
BeanPostProcessor postProcessor(Environment env) {
return new BeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
MyConfigurationProperties annotation = AnnotationUtils.findAnnotation(bean.getClass(), MyConfigurationProperties.class);
if (annotation == null) return bean;
return Binder.get(env).bindOrCreate("", bean.getClass());
}
};
}
이제 프로퍼티에 prefix를 붙여보자 .
프로퍼티에 대한 namespace역할을 하여 다른 기술 프로퍼티와 중첩되지 않게 하는 방법이다.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Component
public @interface MyConfigurationProperties {
String prefix();
}
@Bean
BeanPostProcessor postProcessor(Environment env) {
return new BeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
MyConfigurationProperties annotation = AnnotationUtils.findAnnotation(bean.getClass(), MyConfigurationProperties.class);
if (annotation == null) return bean;
Map<String, Object> attrs = AnnotationUtils.getAnnotationAttributes(annotation);
String prefix = (String) attrs.get("prefix");
return Binder.get(env).bindOrCreate(prefix, bean.getClass());
}
};
}
조금 더 욕심을 내서 @Import 구문 대신 스프링 부트가 사용하는 EnableConfigurationProperties처럼 동작하도록 만들어 보자.
@MyAutoConfiguration
@ConditionalMyOnClass("org.apache.catalina.startup.Tomcat")
@EnableMyConfigurationProperties(ServerProperties.class)
public class TomcatWebServerConfig {...}
@EnableMyConfigurationProperties(ServerProperties.class)
다음과 같이 기존 Import 구문이었던 것은 제외하고 애노테이션을 추가하고 사용할 프로퍼티 클래스를 받아보자.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyConfigurationPropertiesImportSelector.class)
public @interface EnableMyConfigurationProperties {
Class<?> value();
}
클래스 타입의 value를 정의해 주고 @Import 구문에서 ImportSelector를 구현하는 클래스를 임포트 하자.
public class MyConfigurationPropertiesImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
MultiValueMap<String, Object> attrs = importingClassMetadata.getAllAnnotationAttributes(EnableMyConfigurationProperties.class.getName());
Class propertyClass = (Class) attrs.getFirst("value");
return new String[] { propertyClass.getName()};
}
}
다음과 같이 DefeerdImportSelector의 selectImports() 메서드를 구현하자.
이렇게 설정하면 흐름이 다음과 같이 변한다.
1. 조건 검사.
@ConditionalMyOnClass("org.apache.catalina.startup.Tomcat")
2. 조건이 통과되면 ServletWebServerFactory, ServerProperties Bean을 생성을 시도한다.
@Bean(name = "tomcatWebServerFactory")
@ConditionalOnMissingBean
public ServletWebServerFactory servletWebServerFactory(ServerProperties serverProperties) {
TomcatServletWebServerFactory tomcatServletWebServerFactory = new TomcatServletWebServerFactory();
tomcatServletWebServerFactory.setPort(serverProperties.getPort());
return tomcatServletWebServerFactory;
}
3. @EnableMyConfigurationProperties(ServerProperties.class)에 대해서 ImportSelector가 동작하고
EnableMyConfigurationProperties의 모든 속성값을 가져와서 value 값에 들어 있는 클래스를 가져온 이후 클래스 이름을 반환한다. 이때 반환 값은 ServerProperties이다.
public class MyConfigurationPropertiesImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
System.out.println("sdasd");
MultiValueMap<String, Object> attrs = importingClassMetadata.getAllAnnotationAttributes(EnableMyConfigurationProperties.class.getName());
Class propertyClass = (Class) attrs.getFirst("value");
return new String[] { propertyClass.getName()};
}
}
4. ServerProperties.class를 @Import 하여서 빈이 생성된다.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyConfigurationPropertiesImportSelector.class)
public @interface EnableMyConfigurationProperties {
Class<?> value();
}
5. 빈이 생성된 이후 빈후처리 프로세서가 동작한다.
@MyConfigurationProperties(prefix = "server")
public class ServerProperties{}
@MyAutoConfiguration
public class PropertyPostProcessorConfig {
@Bean
BeanPostProcessor postProcessor(Environment env) {
return new BeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
MyConfigurationProperties annotation = AnnotationUtils.findAnnotation(bean.getClass(), MyConfigurationProperties.class);
if (annotation == null) return bean;
Map<String, Object> attrs = AnnotationUtils.getAnnotationAttributes(annotation);
String prefix = (String) attrs.get("prefix");
return Binder.get(env).bindOrCreate(prefix, bean.getClass());
}
};
}
}
@MyConfigurationProperties 어노테이션을 가지고 있는 Bean을 찾아서 프로퍼티 파일을 읽거나 환경변수 등등 값을 초기화한다.
6. 마지막으로 ServletWebServerFactory 빈을 생성하는데 생성된 ServerProperties를 주입받는다. 이후 프로퍼티 값을 설정한다.
@Bean(name = "tomcatWebServerFactory")
@ConditionalOnMissingBean
public ServletWebServerFactory servletWebServerFactory(ServerProperties serverProperties) {
TomcatServletWebServerFactory tomcatServletWebServerFactory = new TomcatServletWebServerFactory();
tomcatServletWebServerFactory.setPort(serverProperties.getPort());
return tomcatServletWebServerFactory;
}
'Spring|Spring-boot' 카테고리의 다른 글
Spring Boot Actuator (0) | 2023.02.16 |
---|---|
Spring Boot 좀 더 자세히. (2) | 2023.02.04 |
Environment Properties (0) | 2023.01.30 |
Spring boot AutoConfiguration and Conditional (0) | 2023.01.26 |
Spring Boot @AutoConfiguration (0) | 2023.01.24 |
댓글