Environment 추상화와 프로퍼티
imports 를 selector 가 로딩을하고 class level @Conditional 을 확인하고 method level(@Bean을 생성하는 팩토리메소드) 의 @Conditional을 확인하게 된다.
custom bean구성정보는 개발자가 추가한 (custom tomcat) 팩토리 빈 메소드를 의미한다.
기본 tomcat port를 바꾸고 싶다든지 다양한 property를 변경을 가능하게 한다. 읽어와서 사용을 할 수 있다.
getProperty에 넣는 인자는 다음 사진처럼 4가지로 spring boot가 알아서 converting 해서 인식을 하게 된다.
자동 구성에 Environment 프로퍼티 적용
@MySpringBootApplication
public class HellobootApplication {
ApplicationRunner test(Environment environment) {
return args -> {
System.out.println(environment.getProperty("my.name"));
};
}
public static void main(String[] args) {
SpringApplication.run(HellobootApplication.class, args);
}
}
스프링에서 제공해주는 Environment는 jvm option(System property) > Environment property(환경변수) > application.properties 순으로 우선순위를 가지게 된다.
key를 동일하게 my.name으로 해주는 경우를 말한다.
@MyAutoConfiguration
@ConditionalMyOnClass("org.apache.catalina.startup.Tomcat")
public class TomcatWebServerConfig {
@Bean("tomcatWebServerFactory")
@ConditionalOnMissingBean
public ServletWebServerFactory servletWebServerFactory(Environment env) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.setContextPath(env.getProperty("contextPath"));
return factory;
}
}
contextpath는 해당 servlet container의 시작지점을 의미한다.
localhost:8080/hello 에서 localhost:8080/app/hello 처럼 작성해줘야 제대로 요청을 처리하게 된다.
@Value 와 PropertySourcesPlaceholderConfigurer
@Value("${contextPath}")
String contextPath;
application.properties에 있는 값을 $ 로 읽어오는 기술을 spring에서 바로제공해주는것은아니다.
@MyAutoConfiguration
public class PropertyPlaceholderConfig {
@Bean PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
BeanPostProcess에서 확장된 것이다. 해당 클래스를 빈으로 등록하면 위에 @Value값을 property 로부터
제대로 읽어올 수 있다.
Springboot에서는 자동 구성에 포함된 녀석이다.
지금은 Springboot의 autoconfiguration을 사용하지 않으니
imports에 적어서 부팅시 해당 클래스를 자동으로 빈으로 등록할 수 있게 한다.
프로퍼티 클래스의 분리
@MyAutoConfiguration
@ConditionalMyOnClass("org.apache.catalina.startup.Tomcat")
public class TomcatWebServerConfig {
@Bean("tomcatWebServerFactory")
@ConditionalOnMissingBean
public ServletWebServerFactory servletWebServerFactory(ServerProperties properties) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.setContextPath(properties.getContextPath());
factory.setPort(properties.getPort());
return factory;
}
}
@MyAutoConfiguration
public class ServerPropertiesConfig {
@Bean
public ServerProperties serverProperties(Environment environment) {
return Binder.get(environment).bind("", ServerProperties.class).get();
}
}
properties 에서 가져올 설정들이 많으면 별도의 클래스를 지정해서 getter 로 가져오는것이 용이하다.
Binder를 통해 ServerProperties 클래스안에 있는 필드들의 getter,setter를 통해서 자동으로 알아서 binding을해준다.
프로퍼티 빈의 후처리기 도입
@MyAutoConfiguration
@ConditionalMyOnClass("org.apache.catalina.startup.Tomcat")
@EnableMyConfigurationProperties(ServerProperties.class)
public class TomcatWebServerConfig {
@Bean("tomcatWebServerFactory")
@ConditionalOnMissingBean
public ServletWebServerFactory servletWebServerFactory(ServerProperties properties) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.setContextPath(properties.getContextPath());
factory.setPort(properties.getPort());
return factory;
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyConfigurationPropertiesImportSelector.class)
public @interface EnableMyConfigurationProperties {
Class<?> value();
}
주로 Enable~ Annotation을 사용하는 이유는 meta annotation으로 @Import를 사용하여 가져오는것을 한번 숨기는 역할을 하게 된다.
public class MyConfigurationPropertiesImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
MultiValueMap<String, Object> attr = importingClassMetadata.getAllAnnotationAttributes(EnableMyConfigurationProperties.class.getName());
Class propertyClass = (Class) attr.getFirst("value");
return new String[] { propertyClass.getName() };
}
}
인자로 들어온 ServerProperties 클래스를 여기서 읽어들어와 bean으로 등록하게 된다.
단순히 SeverProperties를 빈으로 등록하는 것만으로는 ServerProperties에 우리가 원하는 property 값이 채워져 있지 않다.
밑에나오는 BeanPostProcessor 를 통해서 채워 넣는 과정이 필요하다.
@MyConfigurationProperties(prefix = "server")
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;
}
}
prefix를 통해 server.port 이런식으로 어떤 맥락에서 사용되는지 알 수 있게 된다.
@MyAutoConfiguration
public class PropertyPostProcessorConfig {
@Bean BeanPostProcessor propertyPostProcessor(Environment env) {
return new BeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
MyConfigurationProperties annotation = findAnnotation(bean.getClass(), MyConfigurationProperties.class);
if (annotation == null) return bean;
Map<String, Object> attrs = getAnnotationAttributes(annotation);
String prefix = (String) attrs.get("prefix");
return Binder.get(env).bindOrCreate(prefix, bean.getClass());
}
};
}
}
Environment 를 통해서 property들을 읽어와서 ServerProperties에 맵핑해준다.
@MyConfigurationProperties이 달려있는 (marker annotation) bean에만 후처리기가 동작하게 된다.
Binder를 통해서 맵핑이 진행된다. prefix 와 bean 클래스정보를 넘겨주면 알아서 prefix를 앞에 붙인상태로 class 의 field property와 일치하는것을 찾는다.
prefix = server이면 server.port , server.ip 가 application.yml 에 적혀있는지 찾게 된다.
'WEB > Spring Boot' 카테고리의 다른 글
웹 서버와 서블릿 컨테이너 (0) | 2023.05.08 |
---|---|
스프링 부트 자세히 살펴보기 (0) | 2023.02.14 |
Spring JDBC 자동 구성 개발 (0) | 2023.02.14 |
자동 구성 기반 애플리케이션 + 조건부 자동 구성 (0) | 2023.02.09 |
독립 실행형 서블릿 애플리케이션 (0) | 2023.02.02 |