Spring Context Bean이 등록되는 과정을 한번 보자 우선 ApplicationContext의 구현체인 AnnotationConfigApplicationContext를 보자
이 구현채는 기존 xml로 하나하나 빈으로 등록해주던 동작을 @Configuration 어노테이션이 적용된 자바 설정 파일을 통해 빈을 등록이 가능하도록 해준 클래스이고 @Component, @Service, @Repository, @Controller와 같은 어노테이션이 붙은 클래스들을 자동으로 감지하여 빈으로 등록할 수 있도록 해준다.
AnnotationConfigApplicationContext는 Spring의 기본 컨테이너인 ApplicationContext의 구현체로, 자바 설정 파일을 기반으로 동작합니다. 이 컨텍스트는 BeanFactory를 포함하여 빈의 라이프사이클을 관리해준다.
public void register(Class<?>... componentClasses) {
Assert.notEmpty(componentClasses, "At least one component class must be specified");
StartupStep registerComponentClass = this.getApplicationStartup().start("spring.context.component-classes.register").tag("classes", () -> {
return Arrays.toString(componentClasses);
});
this.reader.register(componentClasses);
registerComponentClass.end();
}
코드를 간단하게 설명하자면 스프링 애플리케이션이 시작되면 @Bean이 붙은 모든 클래스를 불러와서 register를 통해 Bean을 등록한다.
AnnotationConfigApplicationContext에서 받아온 Class들을 AnnotatedBeanDefinitionReader register를 통해 Bean에 등록하는 작업을 해준다.
이제 AnnotatedBeanDefinitionReader register 의 동작을 한번 보자
public void register(Class<?>... componentClasses) {
Class[] var2 = componentClasses;
int var3 = componentClasses.length;
for(int var4 = 0; var4 < var3; ++var4) {
Class<?> componentClass = var2[var4];
this.registerBean(componentClass);
}
}
public void registerBean(Class<?> beanClass) {
this.doRegisterBean(beanClass, (String)null, (Class[])null, (Supplier)null, (BeanDefinitionCustomizer[])null);
}
private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name, @Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier, @Nullable BeanDefinitionCustomizer[] customizers) {
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
if (!this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
abd.setAttribute(ConfigurationClassUtils.CANDIDATE_ATTRIBUTE, Boolean.TRUE);
abd.setInstanceSupplier(supplier);
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
abd.setScope(scopeMetadata.getScopeName());
String beanName = name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry);
AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
int var10;
int var11;
if (qualifiers != null) {
Class[] var9 = qualifiers;
var10 = qualifiers.length;
for(var11 = 0; var11 < var10; ++var11) {
Class<? extends Annotation> qualifier = var9[var11];
if (Primary.class == qualifier) {
abd.setPrimary(true);
} else if (Lazy.class == qualifier) {
abd.setLazyInit(true);
} else {
abd.addQualifier(new AutowireCandidateQualifier(qualifier));
}
}
}
if (customizers != null) {
BeanDefinitionCustomizer[] var13 = customizers;
var10 = customizers.length;
for(var11 = 0; var11 < var10; ++var11) {
BeanDefinitionCustomizer customizer = var13[var11];
customizer.customize(abd);
}
}
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}
}
AnnotationConfigApplicationContext에서 넘어온 클래스 목록이 doRegister에 의해 등록된다.
코드를 보면서 내려오다 보면 Primary, Lazy가 보인다. Primary는 우선순위를 설정하는 것이고 Lazy는 애플리케이션 시작 때 생성이 아닌 필요시에 생성되기때문에 따로 설정을 해준다.
그 외에는 Qualifier 어노테이션 같은 것이 처리된다. AutowireCandidateQualifier 객체로 래핑하여 해당 어노테이션을 추가하고 이는 빈이 자동 주입될 때 어떤 조건으로 주입할지 결정하는 데 사용한다.
그리고 마지막줄 3줄을 보면 AnnotatedGenericBeanDefinition,ScopeMetadata,BeanDefinitionHolder가 있다.
무슨 역할을 하는지 보자
AnnotatedGenericBeanDefinition은 BeanDefinition의 구현체, Bean의 속성, 생성자 등의 정보를 갖는다.
ScopeMetadata는 Bean Scope의 정보이다.
BeanDefinitionHolder BeanDefinition와 name, alias 정보를 추가적으로 갖는다.
위 동작을 수행 후 BeanDefinitionReaderUtils의 registerBeanDefinition에 BeanDefinitionHolder와 registry를 인자로 메서드를 호출하면 Bean이 등록된다.
public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
String[] var4 = aliases;
int var5 = aliases.length;
for(int var6 = 0; var6 < var5; ++var6) {
String alias = var4[var6];
registry.registerAlias(beanName, alias);
}
}
}
메서드를 호출하면 넘어온 registry에 의해 등록된다. registry는 어떤 값인지 알아보자
private final BeanDefinitionRegistry registry;
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
this(registry, getOrCreateEnvironment(registry));
}
우선 BeanDefinitionReaderUtils의 registerBeanDefinition 호출 시에 넘어가는 registry에 어떤값이 들어가는지 보자
AnnotatedBeanDefinitionReader를 생성 할때 주입 받는다. 이건 아마 AnnotationConfigApplicationContext에서 생성자로 생성을 할때 registry를 넘겨주는듯 하다.
AnnotationConfigApplicationContext를 보면 생성자에서 자기 자신을 registry로 넣어서 AnnotatedBeanDefinitionReader를 생성한다.
private final AnnotatedBeanDefinitionReader reader;
public AnnotationConfigApplicationContext() {
StartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create");
this.reader = new AnnotatedBeanDefinitionReader(this);
createAnnotatedBeanDefReader.end();
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
AnnotationConfigApplicationContext 생성자를 보면 AnnotatedBeanDefinitionReader 생성에 자기 자신(AnnotationConfigApplicationContext)을 인자로 한다.
이건 AnnotationConfigApplicationContext가 BeanDefinitionRegistry(GenericApplicationContext) 의구현체이기도 하기 때문이다.
public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {
.
.
.
}
그럼 마지막으로 GenericApplicationContext에 registerBeanDefinition로직을 보자
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
private final DefaultListableBeanFactory beanFactory;
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {
this.beanFactory.registerBeanDefinition(beanName, beanDefinition);
}
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
if (beanDefinition instanceof AbstractBeanDefinition abd) {
try {
abd.validate();
} catch (BeanDefinitionValidationException var8) {
BeanDefinitionValidationException ex = var8;
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Validation of bean definition failed", ex);
}
}
BeanDefinition existingDefinition = (BeanDefinition)this.beanDefinitionMap.get(beanName);
if (existingDefinition != null) {
if (!this.isBeanDefinitionOverridable(beanName)) {
throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
}
if (existingDefinition.getRole() < beanDefinition.getRole()) {
if (this.logger.isInfoEnabled()) {
this.logger.info("Overriding user-defined bean definition for bean '" + beanName + "' with a framework-generated bean definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]");
}
} else if (!beanDefinition.equals(existingDefinition)) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Overriding bean definition for bean '" + beanName + "' with a different definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]");
}
} else if (this.logger.isTraceEnabled()) {
this.logger.trace("Overriding bean definition for bean '" + beanName + "' with an equivalent definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]");
}
this.beanDefinitionMap.put(beanName, beanDefinition);
} else {
if (this.isAlias(beanName)) {
String aliasedName = this.canonicalName(beanName);
if (!this.isBeanDefinitionOverridable(aliasedName)) {
if (this.containsBeanDefinition(aliasedName)) {
throw new BeanDefinitionOverrideException(beanName, beanDefinition, this.getBeanDefinition(aliasedName));
}
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Cannot register bean definition for bean '" + beanName + "' since there is already an alias for bean '" + aliasedName + "' bound.");
}
this.removeAlias(beanName);
}
if (this.hasBeanCreationStarted()) {
synchronized(this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
this.removeManualSingletonName(beanName);
}
} else {
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
this.removeManualSingletonName(beanName);
}
this.frozenBeanDefinitionNames = null;
}
if (existingDefinition == null && !this.containsSingleton(beanName)) {
if (this.isConfigurationFrozen()) {
this.clearByTypeCache();
}
} else {
this.resetBeanDefinition(beanName);
}
}
}
beanDefinitionMap에 beanName, beanDefinition을 저장한다.
이제 Bean 등록까진 되었으니 아마 다음은 Bean이 실제로 생성되는 부분을 뜯어보지 않을까싶다.
'Dev > Spring' 카테고리의 다른 글
[Spring] Maven과 Gradle (0) | 2022.01.06 |
---|---|
[Spring] JPA+Spring으로 카테고리 로직 구현 - 3 (0) | 2021.12.10 |
[Spring] JPA+Spring으로 카테고리 로직 구현 - 2 (0) | 2021.12.10 |
[Spring] JPA+Spring으로 카테고리 로직 구현 - 1 (0) | 2021.12.10 |
[Spring] 어노테이션으로 로그인 여부 확인(Interceptor) (0) | 2021.09.21 |