Bean 생성 원리와 저장 위치
Spring에서 bean 생성 전략은 두 가지로 나뉜다.
일반적으로 singleton을 사용하지만 상황에 따라 prototype bean을 사용하기도 한다.
그렇기 때문에 Spring에서는 bean의 정의를 따로 저장해둔다.
지난 포스트에선 딱 여기까지 진행했었다.
이번엔 실제로 생성된 bean이 어디에 저장되는지를 탐구해보려고 한다.
소스 코드 작성
Spring framework의 코드만 보고 bean의 위치를 직접 찾아가기는 어렵다.
목적에 맞는 소스 코드를 작성한다.
SomeBean
@ToString public class SomeBean { private String name = "someBean"; }
BeanConfiguration
public class BeanConfiguration { @Bean public SomeBean someBean() { return new SomeBean(); } }
main
public class DemoApplication { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(BeanConfiguration.class); SomeBean someBean = (SomeBean) ctx.getBean("someBean"); System.out.println(someBean.toString()); } }
추적
Annotation 기반의 컨텍스트를 생성했다(AnnotationConfigApplicationContext
).
그리고 someBean을 getBean 메소드로 받아온다.
getBean에 브레이크 포인트를 걸고 따라가보자.
AbstractApplicationContext.getBean(String name)
으로 이동된다.
@Override
public Object getBean(String name) throws BeansException {
assertBeanFactoryActive();
return getBeanFactory().getBean(name);
}
getBean 메소드에선 beanFactory에 저장된 bean을 가져와서 리턴하고 있다.
BeanFactory
가 눈에 띈다.
여기서 잠깐 AnnotationConfigApplicationContext
의 의존 관계 UML을 확인해보자.
Spring framework 소스 코드를 받아와서 IDE로 열어보면 위와 같은 UML을 확인해볼 수 있다.
참조 : Spring framework 소스 코드 읽어보기 첫 단계 - download, build
BeanFactory
AnnotationConfigApplicationContext
는 제일 하단에 있고 getBean 메소드는 조금 상단에 존재하는 AbstractApplicationContext
클래스에 위치하고 있다.
UML 사진 최상단에 BeanFactory
인터페이스를 확인할 수 있을 것이다.
ApplicationContext
도 BeanFactory
의 하위에 존재하는 인터페이스이다.
여튼 계속 추적해보자.
getBeanFactory의 정의를 쫓아가면 GenericApplicationContext
의 필드에 DefaultListableBeanFactory
타입으로 선언된 beanFactory를 볼 수 있다.
getBean
getBean 메소드는 BeanFactory
를 상속받는 AbstractBeanFactory
클래스에 존재한다.
아래는 getBean 메소드를 따라가면 나오는 doGetBean의 원본이다.
/**
* Return an instance, which may be shared or independent, of the specified bean.
* @param name the name of the bean to retrieve
* @param requiredType the required type of the bean to retrieve
* @param args arguments to use when creating a bean instance using explicit arguments
* (only applied when creating a new instance as opposed to retrieving an existing one)
* @param typeCheckOnly whether the instance is obtained for a type check,
* not for actual use
* @return an instance of the bean
* @throws BeansException if the bean could not be created
*/
@SuppressWarnings("unchecked")
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
final String beanName = transformedBeanName(name);
Object bean;
// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
if (logger.isTraceEnabled()) {
if (isSingletonCurrentlyInCreation(beanName)) {
logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
"' that is not fully initialized yet - a consequence of a circular reference");
}
else {
logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
}
}
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else {
// Fail if we're already creating this bean instance:
// We're assumably within a circular reference.
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// Check if bean definition exists in this factory.
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
// Not found -> check parent.
String nameToLookup = originalBeanName(name);
if (parentBeanFactory instanceof AbstractBeanFactory) {
return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
nameToLookup, requiredType, args, typeCheckOnly);
}
else if (args != null) {
// Delegation to parent with explicit args.
return (T) parentBeanFactory.getBean(nameToLookup, args);
}
else if (requiredType != null) {
// No args -> delegate to standard getBean method.
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
else {
return (T) parentBeanFactory.getBean(nameToLookup);
}
}
if (!typeCheckOnly) {
markBeanAsCreated(beanName);
}
try {
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);
// Guarantee initialization of beans that the current bean depends on.
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
if (isDependent(beanName, dep)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
}
registerDependentBean(dep, beanName);
try {
getBean(dep);
}
catch (NoSuchBeanDefinitionException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
}
}
}
// Create bean instance.
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
else {
String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
Object scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
});
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
catch (IllegalStateException ex) {
throw new BeanCreationException(beanName,
"Scope '" + scopeName + "' is not active for the current thread; consider " +
"defining a scoped proxy for this bean if you intend to refer to it from a singleton",
ex);
}
}
}
catch (BeansException ex) {
cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
}
// Check if required type matches the type of the actual bean instance.
if (requiredType != null && !requiredType.isInstance(bean)) {
try {
T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
if (convertedBean == null) {
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
}
return convertedBean;
}
catch (TypeMismatchException ex) {
if (logger.isTraceEnabled()) {
logger.trace("Failed to convert bean '" + name + "' to required type '" +
ClassUtils.getQualifiedName(requiredType) + "'", ex);
}
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
}
}
return (T) bean;
}
최하단에 return (T) bean;
이 보일 것이다.
이 로직 내부에서 bean에 인스턴스를 저장하고 내보내는 것으로 추측된다.
실제로 내용을 살펴보면 조건에 맞는 경우 bean에 인스턴스를 할당하고 넘기는 형태로 되어 있다.
다시 위로 올라가서 살펴보면 첫 문장이 눈에 띈다.
Object sharedInstance = getSingleton(beanName);
bean의 scope를 따로 정해주지 않았으니 당연히 singleton 타입일 것이다.
저 메소드도 따라가보자.
getSingleton
이 메소드는 DefaultSingletonBeanRegistry
에 위치하고 있다.
/**
* Return the (raw) singleton object registered under the given name.
* <p>Checks already instantiated singletons and also allows for an early
* reference to a currently created singleton (resolving a circular reference).
* @param beanName the name of the bean to look for
* @param allowEarlyReference whether early references should be created or not
* @return the registered singleton object, or {@code null} if none found
*/
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
beanName으로 생성된 싱글턴 오브젝트를 리턴한다고 한다.
그렇다면 첫 줄의 singletonObjects
이 무엇인지 확인해보자.
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
이번에도 Map이다. bean의 이름 기반으로 저장된다는 것 같다.
이 곳에 저장된 someBean을 받고 바로 doGetBean 메소드가 종료된다.
일단 싱글톤 타입의 bean instance가 어느 위치에 저장되는지를 알아냈다.
그렇다면 언제 생성해서 이 곳에 넣는지를 알아보자.
bean instance 저장 시점
DefaultSingletonBeanRegistry
의 필드, singletonObjects(Map)에 인스턴스가 저장된 것을 확인했다.
이 클래스 내부에 singletonObjects.put
으로 검색하면 딱 두 군데가 나온다.
모두 브레이크 포인트를 찍은 뒤 main문에 있는 브레이크 포인트는 제거해보자.
put이 있는 지점에 몇 번 브레이크 포인트가 반복해서 걸린다.
Spring이 시작되면 기본적으로 생성되는 bean이 있으니 beanName이 someBean인 곳을 찾아야 한다.
찾고 난 뒤 메소드를 계속 빠져나오다 보면 AbstractApplicationContext.finishRefresh()
에 커서가 위치한다.
finishRefresh 메소드보다 한 단계 전에 실행하는 메소드에서 빈이 생성되었을 것이라고 추측할 수 있다.
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
finishRefresh 전에 실행되는 메소드는 finishBeanFactoryInitialization이다.
finishBeanFactoryInitialization 메소드의 주석을 읽어보면 남은 모든 싱글톤을 초기화한다고 쓰여 있다.
bean 인스턴스를 생성하고 저장하는 시점도 찾았다.
들어가서 추적하다보면 또다시 getBean으로 진입하게 된다.
doGetBean
getBean을 추적하다보면 아까랑 똑같이 doGetBean으로 가게 된다.
진짜 필요한 부분만 간추려보았다.
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
...
if (sharedInstance != null && args == null) {
...
} else {
...
try {
...
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);
...
// Create bean instance.
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
...
}
catch (BeansException ex) {
cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
}
...
return (T) bean;
}
인스턴스 mbd는 RootBeanDefinition
타입의 변수인데, RootBeanDefinition
는 BeanDefinition
의 자손 클래스이다.
그렇다면 bean의 정의를 담고 있는 것이다.
조금 더 내리면 if (mbd.isSingleton()) { ... }
이 보인다.
이름만 봐도 singleton이면 true를 리턴할 것처럼 생겼다.
/**
* Return whether this a <b>Singleton</b>, with a single shared instance
* returned from all calls.
* @see #SCOPE_SINGLETON
*/
@Override
public boolean isSingleton() {
return SCOPE_SINGLETON.equals(this.scope) || SCOPE_DEFAULT.equals(this.scope);
}
그렇다. 예상처럼 싱글톤 스코프인지 검사하는 메소드였다.
싱글톤으로 선언했으니 이 if문 내부로 들어가야 한다.
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
getSingleton의 인자로 beanName과 인터페이스의 구현체를 넘겨주고 있다.
그리고 그 구현체엔 createBean으로 bean을 생성하여 전달하는 것을 볼 수 있다.
createBean을 추적하다 보면 인스턴스는 BeanWrapper
로 한 번 포장하여 생성함을 확인할 수 있다.
/**
* Return the (raw) singleton object registered under the given name,
* creating and registering a new one if none registered yet.
* @param beanName the name of the bean
* @param singletonFactory the ObjectFactory to lazily create the singleton
* with, if necessary
* @return the registered singleton object
*/
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
if (this.singletonsCurrentlyInDestruction) {
throw new BeanCreationNotAllowedException(beanName,
"Singleton bean creation not allowed while singletons of this factory are in destruction " +
"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
}
if (logger.isDebugEnabled()) {
logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
}
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<>();
}
try {
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
catch (IllegalStateException ex) {
// Has the singleton object implicitly appeared in the meantime ->
// if yes, proceed with it since the exception indicates that state.
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
throw ex;
}
}
catch (BeanCreationException ex) {
if (recordSuppressedExceptions) {
for (Exception suppressedException : this.suppressedExceptions) {
ex.addRelatedCause(suppressedException);
}
}
throw ex;
}
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
afterSingletonCreation(beanName);
}
if (newSingleton) {
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
이 곳에서 맨 밑 부분에 있는 addSingleton 메소드 내부 로직엔 singletonObjects.put
이 들어 있다.
새로운 싱글턴 인스턴스인 경우 여기서 생성하고 저장을 담당하는 것이다.
Map 타입의 필드 singletonObjects에 인스턴스를 삽입하게 되면 bean 생성 과정은 종료된다.
'Java > Spring framework' 카테고리의 다른 글
Spring(boot)에서 초기화 코드를 작성하는 방법 (0) | 2020.05.11 |
---|---|
Spring boot 자동 설정 분석 (0) | 2020.03.15 |
Spring framework 소스 코드 읽어보기 - Bean 생성 원리 (2) (1) | 2020.01.27 |
Spring framework core (12) - Spring AOP (0) | 2020.01.24 |
Spring framework core (11) - SpEL (0) | 2020.01.24 |