@Component
@Controller
@Service
Junit
에서 @Test
와 비슷한 기능을 하는 애노테이션 만들어보는 예제다.@Retention(RetentionPolicy.RUNTIME) // 런타임에 필요
@Target(ElementType.METHOD) // 메소드에 적용 가능
@Test
@interface UnitTest {
}
public class HelloServiceTest {
@UnitTest
void simpleHelloService() {
// ...
}
}
ex) @RestController
= @Controller
+ @ResponseBody
@Configuration
+ @ComponentScan
으로 동작시켰던 애플리케이션도 합성 애노테이션으로 조합할 수 있다.@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Configuration
@ComponentScan
public @interface MySpringbootAnnotation {
}
@MySpringbootAnnotation
public class HellobootApplication {
//...
}
HelloController
SimpleHelloService
DataSource
JpaEntityManagerFactory
JdbcTransactionManager
ApplicationContext
/BeanFactory
Environment
BeanPostProcessor
BeanFactoryPostProcessor
DefaultAdvisorAutoProxyCreator
@ComponentScan
을 통해 자동으로 자바 코드와 어노테이션에서 구성 정보를 읽어서 빈을 등록한다.@Configuration
클래스들이 있고 스프링부트가 필요한 구성을 찾아 자동 적용한다는 것이 기본 동작 원리이다./*
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();
}
}
@MySpringApplicationApplication
어노테이션에 다음 설정을 추가하면 된다.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({ // 추가
DispatcherServletConfig.class,
TomcatWebServerConfig.class
})
public @interface MySpringbootApplication {
}
EnableMyAutoConfiguration
설정 클래스로 합쳐보자
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({
DispatcherServletConfig.class,
TomcatWebServerConfig.class
})
public @interface EnableMyAutoConfiguration {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@EnableMyAutoConfiguration
public @interface MySpringbootApplication {
}
EnableMyAutoConfiguration
을 만들 때 인프라 빈을 수동으로 Import
했다.ImportSelecter
인터페이스를 사용할 수 있다.public interface ImportSelecter {
String[] selectImports(AnnotationMetadata importingClassMetadata);
@Nullable
default Predicate<String> getExclusionFilter() { return null; }
}
ImportSelecter
를 확장한 DefaultImportSelecter
를 사용하면 Configuration
이 붙은 클래스를 동적으로 등록할 수 있다.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 {
}
MyAutoConfiguration
이라는 메타 어노테이션을 만든다.@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Configuration
public @interface MyAutoConfiguration {
}
MyAutoConfigImportSelecter
를 아래와 같이 수정한다.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[]);
}
}
ImportCandidates.load()
META-INF/spring/
경로 밑에 {full-qualified-annotation-name}.imports
파일에서 클래스 정보를 읽어온다.resources
- META-INF.spring
- tobyspring.config.MyAutoConfiguration.imports
tobyspring.config.MyAutoConfiguration.imports
파일 내부에 인프라 빈 정보를 작성한다.tobyspring.config.autoconfig.DispatcherServletConfig
tobyspring.config.autoconfig.TomcatWebServerConfig
ImportCandidates.load
메서드는 MyAutoConfiguration
네이밍이 된 파일에서 빈 정보를 찾는다.DispatcherServletConfig
와 TomcatWebServerConfig
에 적용되어 있던 @Configuration
을 @MyAutoConfiguration
으로 변경한다.
MyAutoConfiguration.imports
파일에서 읽어오는 정보라는 것을 명시하는 관례다.@MyAutoConfiguration
public class TomcatWebServerConfig {
@Bean
public ServletWebServerFactory servrWebServerFactory() {
return new TomcatServletWebServerFactory();
}
}
@MyAutoConfiguration
public class DispatcherServletConfig {
@Bean
public DispatcherServlet dispatcherServlet() {
return DispatcherServlet();
}
}
@MyAutoConfiguration
내부 @Configuration
의 엘리먼트를 하나 변경한다.@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Configuration(proxyBeanMethods = false) // 변경, 기본값은 true
public @interface MyAutoConfiguration {
}
@Configuration
을 학습하기 위해 학습 테스트를 만들어보겠다
Bean1
과 Bean2
가 Common
이라는 같은 빈을 의존하는 상황이라 가정@Configuration
static class MyConfig {
@Bean
Common common() {
return new Common();
}
@Bean
Bean1 bean1() {
return new Bean1(common());
}
@Bean
Bean1 bean2() {
return new Bean2(common());
}
}
Bean1
과 Bean2
는 다른 common
인스턴스를 의존할 것이다.@Test
void configuration() {
MyConfig myConfig = new MyConfig();
Bean1 bean1 = myConfig.bean1();
Bean2 bean2 = myConfig.bean2();
assertThat(bean1.common).isSamdAs(bean2.common) // 실패
}
MyConfig
가 스프링 컨테이너의 구성 정보로 사용되면 동작 방식이 달라지게 된다.@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) // 성공
}
proxyBeanMethods = true
인 경우 (기본값)
Configuration
이 붙은 설정 클래스 빈이 직접 등록되는 것이 아닌 프록시로 등록된다.@Configuration
static class MyConfigProxy extends MyConfig {
private Common common;
@Override
Common common() {
if (this.common == null) this.common = super.common();
return this.common;
}
// ...
}
proxyBeanMethods
를 false
로 변경하는 것이 가능해져 Configuration
빈이 프록시로 대체되지 않는다.
// @EnableScheduling가 import로 가져오는 설정 클래스
@Configuration(proxyBeanMethods = false)
@Rold(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {
// ...
}