@AutoConfiguration은 스프링에 있던 기술을 스프링 부트가 효과적으로 활용하여 제공하는 것이다.
Meta-Annotaion.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented @Component // Meta Annotation
public @interface Service { }
애노테이션에 적용한 애노테이션을 메타 애노테이션이라고 한다. 스프링은 코드에서 사용된 애노테이션의 메타 애노테이션의 효력을 적용해 준다.
실제 예시처럼 @Service 애노테이션이 부여된 클래스는 @Service의 메타 애노테이션인 @Component가 적용되어 컴포넌트 스캔 대상이 된다.
Composed-annotation
Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller // Meta Annotation
@ResponseBody // Meta Annotation
public @interface RestController {
...
}
합성 애노테이션은 하나 이상의 메타 애노테이션이 적용된 애노테이션을 말한다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication
@SpringBootApplication이라는 애노테이션은 생각보다 많은 메타 애노테이션이 붙어있다.
@Target
애노테이션의 선언 위치를 이야기한다. ElementType.TYPE은 클래스, 인터페이스 혹은 Enum에 선언할 수 있다.
/** Class, interface (including annotation type), or enum declaration */
TYPE,
@Retention
Retention의 default 값은 Class입니다. 이는 컴파일 타임까지 어노테이션이 유지되지만 실제 런타임에는 어노테이션이 제거됩니다. 런타임까지 해당 어노테이션을 사용하기 위해 RUNTIME으로 설정합니다.
@Documented
@Documented는 javadoc2으로 api 문서를 만들 때 어노테이션에 대한 설명도 포함하도록 지정해 주는 것입니다.
조금 더 알아보기 전에 Bean들도 역할이 존재하고 그 역할에 따라 구분된다는 것을 처음 알았다.
종류는 다음과 같다.
- 애플리케이션 로직 빈
- 애플리케이션 인프라스트럭처 빈
- 컨테이너 인프라스트럭처 빈
애플리케이션 로직 빈
애플리케이션의 비지니스 로직을 담고 있는 클래스로 만들어지는 빈. 컴포넌트 스캐너에 의해서 빈 구성 정보가 생성되고 빈 오브젝트로 등록된다.
이는 개발자가 어떤 빈을 사용하겠다고 명시적으로 구성정보를 제공한 것을 말합니다.
컨테이너 인프라스트럭처 빈
스프링 컨테이너 자신이거나 스프링 컨테이너의 기능을 확장해서 빈의 등록과 생성, 관계설정, 초기화 등의 작업에 참여하는 빈을 컨테이너 인프라 빈이라고 한다.
이는 개발자가 구성한 정보에 의해 생성되는 게 아니라 컨테이너가 직접 만들고 사용하는 빈이기 때문에 애플리케이션 빈과 구분된다. 필요한 경우 일부 컨테이너 인프라 빈은 주입받아서 활용할 수 있다.
예) Evnironment, BeanPostProcessor..
애플리케이션 인프라스트럭처 빈
빈 구성 정보에 의해 컨테이너에 등록되는 빈이지만 애플리케이션의 로직이 아니라 애플리케이션이 동작하는데 꼭 필요한 기술 기반을 제공하는 빈이다.
전통적인 스프령 애플리케이션에서는 빈으로 등록되지 않지만 스프링 부트에서 구성 정보에 의해 빈으로 등록되는 ServletWebServerFactory나 DispatcherServlet 등도 애플리케이션 인프라 빈이라고 볼 수 있다.
스프링 부트의 빈 구성 정보는 컴포넌트 스캔에 의해서 등록되는 빈과 자동 구성에 의해서 등록되는 빈으로 구분된다.
일반적으로 애플리케이션 인프라 빈은 자동 구성에 의해서 등록되지만 개발자가 작성한 코드 구성 정보에 의해서도 등록할 수도 있다.
그럼 자동 구성 정보는 어떠한 방식으로 만들어질까?
이는 애플리케이션에서 사용될 수 있는 애플리케이션 인프라 빈들을 담은 @Configuration 클래스를 만들어 놓는다.
기능으로 구분을 해놓고, 애플리케이션의 필요에 따라 필요한 구성 정보를 골라서 필요한 방식으로 구성해서 자동으로 적용을 해줍니다.
자동 구성 메커니즘을 확장하면 애플리케이션 로직을 담은 라이브러리를 만들어 자동 구성에 의해서 등록되도록 만드는 것도 가능하다.
AutoConfiguration이란 건 미리 준비해둔 Configuration들을 스프링부트가 어떤 게 필요한지 판단을 해서 자동으로 선택해서 사용하게 해주는 것입니다.
그런데 스프링부트는 어떻게 동적으로 구성 정보를 등록하는 것일까?
ImportSelector
public interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata);
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}
ImportSelector는 AnnotationMetadata의 Type을 넘기면 자동 구성할 클래스 이름을 String[]로 리턴하여 이 구성정보를 토대로 스프링 컨테이너가 빈으로 등록합니다.
DeferredImportSelector를 구현 하면 configuration 클래스들을 코드로 동적으로 결정해서 가져올 수 있습니다.
public class MyAutoConfigImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{
"config.auto.DispatcherConfig", "config.auto.TomcatWebServerConfig"
};
}
}
그래서 이를 사용하기 위해선 다음과 같이 @Import를 사용할 수 있습니다.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyAutoConfigImportSelector.class)
public @interface EnableAutoConfiguration {
}
ImportSelector를 구현한 클래스를 Import하게 되면 selectImports라는 메서드를 실행시켜서 반환한 Configuration 클래스만 로딩을 하도록 합니다.
public class MyAutoConfigImportSelector implements DeferredImportSelector {
private final ClassLoader classLoader;
public MyAutoConfigImportSelector(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
ImportCandidates candidates = ImportCandidates.load(MyAutoConfiguration.class, classLoader);
return StreamSupport.stream(candidates.spliterator(), false).toArray(String[]::new);
}
}
ImportCandidates.load()의 메소드는 다음과 같은 역할을 합니다.
클래스패스에서 import될 후보군들의 이름을 읽는데, 이러한 후보군들은 META-INF/spirng/full-qualified-annotation-name.imports라는 곳에서 읽는다고 합니다.
이를 위해 애노테이션을 하나 생성합니다.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Configuration
public @interface MyAutoConfiguration {
}
resources 밑에 다음과 같은 구조를 가지게 합니다.
이후 실행해보면 다음과 같이 실제 빈을 생성한 로그를 확인할 수 있습니다.
MyAutoConfiguration이 단순히 파일이름을 만들기 위해 사용된 것은 아니다.
다음과 같이 해당 설정 파일은 관례를 적용해 동적으로 구성된 설정파일을 나타내도록 할 수 있습니다.
@MyAutoConfiguration
public class DispatcherConfig {
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
}
@Configuration(proxyBeanMethods = false)
이는 MyAutoConfiguration 애노테이션이 붙어서 imports파일을 통해 동적으로 로딩되는 구성정보는 proxyBeanMethods가 false인 설정이 적용됩니다.
proxyBeanMethods 이는 뭐가 다를까?
@Configuration 클래스의 동작 방식을 우선적으로 알아보자.
@Configuration
static class MyConfig{
@Bean
Common common() {
return new Common();
}
@Bean
BeanA beanA() {
return new BeanA(common());
}
@Bean
BeanB beanB() {
return new BeanB(common());
}
}
스프링은 기본적으로 설정이 변경되지 않았다면 Bean을 싱글톤으로 만든다. 하지만 위와 같은 Configuration 상황에서 자바 코드로는 Common 객체를 두 번 호출하게 되고 이로써 동일 객체가 두 개가 생성된다.
이를 자바 코드로만 테스트하면 다음과 같다.
@Test
public void configuration() throws Exception {
BeanA beanA = new MyConfig().beanA();
BeanB beanB = new MyConfig().beanB();
Assertions.assertThat(beanA.common).isSameAs(beanB);
}
이제 스프링 컨테이너를 통해 생성하고 테스트를 진행해 보자.
@Test
public void configuration() throws Exception {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
ac.register(MyConfig.class);
ac.refresh();
BeanA beanA = ac.getBean(BeanA.class);
BeanB beanB = ac.getBean(BeanB.class);
assertThat(beanA.common).isSameAs(beanB.common);
}
여기서 확인할 수 있는 @Configuration의 숨겨진 기능이 있다.
@Configuration이 붙은 클래스는 CGLib을 이용해서 프락시 클래스로 확장을 해서 @Bean이 붙은 메서드의 동작 방식을 변경한다.
@Bean 메서드를 직접 호출해서 다른 빈의 의존 관계를 설정할 때 여러번 호출되더라도 싱글톤 빈처럼 참조할 수 있도록 매번 같은 오브젝트를 리턴하게 한다.
만약 @Bean 메소드 직접 호출로 빈 의존관계 주입을 하지 않는다면 굳이 복잡한 프락시 생성을 할 필요가 없다.
이 경우 proxyBeanMethods를 false로 지정해도 된다.
@Bean 메서드는 평범한 팩토리 메서드처럼 동작한다.
proxyBeanMethods는 스프링 5.2 버전부터 지원되기 시작했고 지금은 스프링과 스프링 부트 의 상당히 많은 @Configuration 클래스 설정에 적용되고 있다.
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {
@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
return new ScheduledAnnotationBeanPostProcessor();
}
}
@Bean은 @Configuration이 빈 클래스에서도 사용될 수 있다.
이를 @Bean 라이트 모드(lite mode)라고 부른다. 빈으로 등록되는 단순 팩토리 메서드로 사용된다.
'Spring|Spring-boot' 카테고리의 다른 글
Environment Properties (0) | 2023.01.30 |
---|---|
Spring boot AutoConfiguration and Conditional (0) | 2023.01.26 |
Standalone Application (0) | 2023.01.22 |
스프링 부트란? (0) | 2023.01.21 |
도메인간 바운디드 컨텍스트 강결합의 대책 "이벤트" (0) | 2023.01.04 |
댓글