TIL

섹션 6. 자동 구성 기반 애플리케이션

메타 애노테이션과 합성 애노테이션

메타 애노테이션

@Retention(RetentionPolicy.RUNTIME) // 런타임에 필요
@Target(ElementType.METHOD) // 메소드에 적용 가능
@Test
@interface UnitTest {
}

public class HelloServiceTest {
  
  @UnitTest
  void simpleHelloService() {
    // ...
  }
}

합성 애노테이션

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Configuration
@ComponentScan
public @interface MySpringbootAnnotation {
}
@MySpringbootAnnotation
public class HellobootApplication {
  //...
}

빈 오브젝트와 역할과 구분

사용자 구성 정보와 자동 구성 정보

인프라 빈 구성 정보의 분리

/*
main
  - java
    - tobyspring 
      - config (자동 구성 정보)
        - TomcatWebServerConfig.java
        - DispatcherServletConfig.java
    - helloboot (컴포넌트 스캔 범위, 사용자 구성 정보)
      - MySpringBootApplication.java
*/

@Configuration
public class TomcatWebServerConfig {
  @Bean
  public ServletWebServerFactory servrWebServerFactory() {
    return new TomcatServletWebServerFactory();
  }
}

@Configuration
public class DispatcherServletConfig {
  @Bean
  public DispatcherServlet dispatcherServlet() {
    return DispatcherServlet();
  }
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({ // 추가
  DispatcherServletConfig.class, 
  TomcatWebServerConfig.class
})
public @interface MySpringbootApplication {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({
  DispatcherServletConfig.class, 
  TomcatWebServerConfig.class
})
public @interface EnableMyAutoConfiguration {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@EnableMyAutoConfiguration
public @interface MySpringbootApplication {
}

동적인 자동 구성 정보 등록

public interface ImportSelecter {
  String[] selectImports(AnnotationMetadata importingClassMetadata);
  
  @Nullable
  default Predicate<String> getExclusionFilter() { return null; }
}
public class MyAutoConfigImportSelecter implements DefaultImportSelecter {
  @Override
  public String[] selectImports(AnnotationMetadata importingClassMetadata) {
    return new Strint[] {
      "tobyspring.config.autoconfig.DispatcherServletConfig",
      "tobyspring.config.autoconfig.TomcatWebServerConfig"
    };
  }
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyAutoConfigImportSelecter.class)
public @interface EnableMyAutoConfiguration {
}

자동 구성 정보 파일 분리

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Configuration
public @interface MyAutoConfiguration {
}
public class MyAutoConfigImportSelecter implements DefaultImportSelecter {
  private final ClassLoader classLoader;
  
  public MyAutoConfigImportSelecter(ClassLoader classLoader) {
    this.classLoader = classLoader;
  }

  @Override
  public String[] selectImports(AnnotationMetadata importingClassMetadata) {
    List<String> autoConfigs = new ArrayList<>();
    
    ImportCandidates.load(MyAutoConfiguration.class, classLoader)
      .forEach(autoConfigs::add);
    
    return autoConfigs.toArray(new String[]);
  }
}
resources
  - META-INF.spring
    - tobyspring.config.MyAutoConfiguration.imports
tobyspring.config.autoconfig.DispatcherServletConfig
tobyspring.config.autoconfig.TomcatWebServerConfig

자동 구성 애노테이션 적용

@MyAutoConfiguration
public class TomcatWebServerConfig {
  @Bean
  public ServletWebServerFactory servrWebServerFactory() {
    return new TomcatServletWebServerFactory();
  }
}

@MyAutoConfiguration
public class DispatcherServletConfig {
  @Bean
  public DispatcherServlet dispatcherServlet() {
    return DispatcherServlet();
  }
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Configuration(proxyBeanMethods = false) // 변경, 기본값은 true
public @interface MyAutoConfiguration {
}

@Configuration과 proxyBeanMethods

@Configuration
static class MyConfig {
  @Bean
  Common common() {
    return new Common();
  }
  
  @Bean
  Bean1 bean1() {
    return new Bean1(common());
  }
  
    @Bean
  Bean1 bean2() {
    return new Bean2(common());
  }
}
@Test
void configuration() {
  MyConfig myConfig = new MyConfig();
  
  Bean1 bean1 = myConfig.bean1();
  Bean2 bean2 = myConfig.bean2();
  
  assertThat(bean1.common).isSamdAs(bean2.common) // 실패
}
@Test
void configuration() {
  AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
  ac.register(MyConfig.class);
  ac.refersh();
  
  Bean1 bean1 = ac.getBean(Bean1.class);
  Bean2 bean2 = ac.getBean(Bean2.class);
  
  assertThat(bean1.common).isSamdAs(bean2.common) // 성공
}
@Configuration
static class MyConfigProxy extends MyConfig {
  private Common common;
  
  @Override
  Common common() {
    if (this.common == null) this.common = super.common();
    
    return this.common;
  }
  
  // ...
}
// @EnableScheduling가 import로 가져오는 설정 클래스
@Configuration(proxyBeanMethods = false)
@Rold(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {
  // ...
}