TIL

섹션 8 외부 설정을 이용한 자동 구성

Environment 추상화와 프로퍼티

  1. 자동 구성 후보 로딩
    1. MyAotoConfiguration.Imports
  2. @Conditional Class 조건 체크
    1. 특정 Class가 존재하는가로 체크
    2. Starter, Dependency Classpath Class
  3. @Conditional @Bean 조건 체크
    1. Configuration 클래스가 적용이 되면 하위 @Bean 메서드의 조건 체크
    2. MissingBean = false
      1. 커스텀 @Bean 구성정보가 있다면 사용
      2. 자동 구성 정보에 있는 빈은 무시
    3. MissingBean = true
      1. @MyAutoConfiguration + @Bean 구성 정보

Environment Abstraction - Properties

자동 구성에 Environment 프로퍼티 적용

@FunctionalInterface
public interface ApplicationRunner {
  void run(ApplicationArguments args) throws Exception;
}
@MySpringBootApplication
public class HellobootApplication {
  @Bean
  ApplicationRunner applicationRunner(Environment env) {
    return args -> {
      String name = env.getProperty("my.name");
      System.out.println("my.name: " + name);
    };
  }

  public static void main(String[] args) {
    SpringApplication.run(HellobootApplication.class, args);
  }
}

자동 구성의 설정 값 변경하기

@MyAutoConfiguration
@ConditionalMyOnClass("org.apache.catalina.startup.Tomcat")
public class TomcatWebServerConfig {
  @Bean("tomcatWebServerFactory")
  @ConditionalOnMissingBean
  public ServletWebServerFactory servrWebServerFactory() {
    TomcatServletServerFactory factory = new TomcatServletWebServerFactory();
    factory.setContextPath("/app");
    return factory;
  }
}
@MyAutoConfiguration
@ConditionalMyOnClass("org.apache.catalina.startup.Tomcat")
public class TomcatWebServerConfig {
  @Bean("tomcatWebServerFactory")
  @ConditionalOnMissingBean
  public ServletWebServerFactory servrWebServerFactory(Environment env) {
    TomcatServletServerFactory factory = new TomcatServletWebServerFactory();
    factory.setContextPath(env.getProperty("contextPath");
    return factory;
  }
}

@Value와 PropertySourcesPlaceholderConfigurer

@MyAutoConfiguration
@ConditionalMyOnClass("org.apache.catalina.startup.Tomcat")
public class TomcatWebServerConfig {
  @Value("${contextPath}")
  String contextPath;

  @Bean("tomcatWebServerFactory")
  @ConditionalOnMissingBean
  public ServletWebServerFactory servrWebServerFactory(Environment env) {
    TomcatServletServerFactory factory = new TomcatServletWebServerFactory();
    factory.setContextPath(this.contextPath);
    return factory;
  }
}
@MyAutoConfiguration
public class PropertyPlaceholderConfig {
  @Bean
  PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
    return new PropertySourcesPlaceholderConfigurer();
  }
}
tobyspring.config.autoconfig.DispatcherServletConfig
tobyspring.config.autoconfig.TomcatWebServerConfig
tobyspring.config.autoconfig.JettyWebServerConfig
tobyspring.config.autoconfig.PropertyPlaceholderConfig

프로퍼티 클래스 분리

@MyAutoConfiguration
@ConditionalMyOnClass("org.apache.catalina.startup.Tomcat")
public class TomcatWebServerConfig {
  @Value("${contextPath}")
  String contextPath;
  
  @Value("${port:8080}") // 디폴트값이 필요한 경우
  int port;
  
  // ... 더 많은 프로퍼티?

  @Bean("tomcatWebServerFactory")
  @ConditionalOnMissingBean
  public ServletWebServerFactory servrWebServerFactory(Environment env) {
    TomcatServletServerFactory factory = new TomcatServletWebServerFactory();

    factory.setContextPath(this.contextPath);
    factory.setContextPath(this.port);
    
    return factory;
  }
}
@Getter @Setter
public class ServerProperties {
  private String contextPath;
  
  private int port;
}
@MyAutoConfiguration
@ConditionalMyOnClass("org.apache.catalina.startup.Tomcat")
public class TomcatWebServerConfig {

  @Bean("tomcatWebServerFactory")
  @ConditionalOnMissingBean
  public ServletWebServerFactory servrWebServerFactory(ServerProperties properties) {
    TomcatServletServerFactory factory = new TomcatServletWebServerFactory();

    factory.setContextPath(properties.getContextPath());
    factory.setContextPath(properties.getPort());
    
    return factory;
  }
}
import org.springframework.boot.context.properties.bind.Binder;

@MyAutoConfiguration
public class ServerPropertiesConfig {
  @Bean
  public ServerProperties serverProperteis(Environment environment) {
    return Binder.get(environment).bind("", ServerProperteis.class).get();
  }
}

프로퍼티 빈의 후처리기 도입

@MyAutoConfiguration
public class PropertyPostProcessorConfig {
  @Bean
  BeanPostProcessor propertyPostProcessor(Environmnet 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;
        
        return Binder.get(env).bindOrCreate("", bean.getClass());
      }
    }
  }
}
@Getter @Setter
@MyConfigurationProperties // 내부에 @Component도 존재
public class ServerProperties {
  private String contextPath;
  
  private int port;
}
@Getter @Setter
@MyConfigurationProperties(prefix = "server")
public class ServerProperties {
  private String contextPath;
  
  private int port;
}
@MyAutoConfiguration
public class PropertyPostProcessorConfig {
  @Bean
  BeanPostProcessor propertyPostProcessor(Environmnet 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());
      }
    };
  }
}