In previous articles, we've covered the foundational steps for bean preparation, including bean definition processing and FactoryBean identification. Building upon this knowledge, we can now dive deeper into the implementation logic of the getBean method and gain better insights into how the createBean method works.
Usage of getBean
Before diving into the getBean method implementation, let's explore its common usage patterns:
// Initialize a Spring context
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
UserService bean1 = applicationContext.getBean(UserService.class);
UserService bean2 = (UserService) applicationContext.getBean("userService");
UserService bean3 = applicationContext.getBean("userService", UserService.class);
UserService bean4 = (UserService) applicationContext.getBean("userService", new OrderService());
bean1.test();
bean2.test();
bean3.test();
bean4.test();
The first two approaches are widely used. The third one checks whether the returned bean matches the specified type, and if so, performs a type conversion. The fourth approach uses constructor inference to select an appropriate constructor when creating the bean instance.
To make the fourth approach effective, you may want to use a prototype scope by setting the @Scope("prototype") annotation. This ensures that each call to getBean creates a new instance, allowing parameterized constructors to be invoked:
@Component
@Scope("prototype")
public class UserService {
public UserService() {
System.out.println(0);
}
public UserService(OrderService orderService) {
System.out.println(1);
}
public void test() {
System.out.println(11);
}
}
General Flow of getBean
Due to the extensive code involved, I will not show the full source here but instead provide a simplified pseudo-code outline for clarity. Then, we'll examine each phase in detail:
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
String beanName = transformedBeanName(name);
Object beanInstance;
// First, check the singleton cache for existing singletons
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
// Handle FactoryBean instances
} else {
// Check if bean definition exists in this factory, otherwise delegate to parent
try {
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
// Resolve dependencies
}
if (mbd.isSingleton()) {
// Call createBean
} else if (mbd.isPrototype()) {
// Call createBean with prototype-specific logic
} else {
// Handle other scopes
}
} catch (Exception e) {
...
}
}
return adaptBeanInstance(name, beanInstance, requiredType);
}
Singleton Cache
Regardless of whether a bean is singleton or prototype, Spring first attempts to retrieve it from the singleton cache. While some might consider this inefficient, Spring optimizes for the majority of beans being singletons. This overhead is minimal and resembles Java’s class loader delegation model — checking local caches before delegating upwards.
Parent Bean Factory
When analyzing bean definition creation, we can ignore singleton caching and focus on how definitions are processed. Evenif a parent factory exists, it can be bypassed in simple setups. The code snippet below shows how delegation occurs:
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
String nameToLookup = originalBeanName(name);
if (parentBeanFactory instanceof AbstractBeanFactory) {
return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
nameToLookup, requiredType, args, typeCheckOnly);
} else if (args != null) {
return (T) parentBeanFactory.getBean(nameToLookup, args);
} else if (requiredType != null) {
return parentBeanFactory.getBean(nameToLookup, requiredType);
} else {
return (T) parentBeanFactory.getBean(nameToLookup);
}
}
dependsOn Handling
After merging the bean definition, Spring resolves any dependsOn annotations. Here’s how the process works:
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
if (isDependent(beanName, dep)) {
throw new BeanCreationException(...);
}
registerDependentBean(dep, beanName);
getBean(dep); // Create dependency first
}
}
This mechanism does not resolve circular dependencies but uses two maps to track dependencies:
dependentBeanMap: tracks which beans depend on othersdependenciesForBeanMap: tracks what beans a given bean depends on
Singleton Creation
For singleton beans, the process involves calling getSingleton with a lambda expression that delegates to createBean:
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
} catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
});
Prototype Creation
Prototype beans are handled differently:
if (mbd.isPrototype()) {
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
} finally {
afterPrototypeCreation(beanName);
}
beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
Other Scopes
Spring supports various scope annotations like @RequestScope, @SessionScope, etc., which are essentially aliases for the base @Scope annotation:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {
@AliasFor(annotation = Scope.class)
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}
Each scope is managed through a custom Scope implementation. For example, session-scoped beans are stored in HTTP session attributes:
Object scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
} finally {
afterPrototypeCreation(beanName);
}
});
The scope's get method retrieves or stores the bean in the appropriate scope context (e.g., HTTP session), ensuring proper lifecycle management.
Conclusion
The getBean method primarily follows these steps:
- First, attempt to retrieve the bean from the singleton cache.
- If not found, check for the bean definition in the current container; if missing, delegate to the parent.
- Once the definition is located, create the bean instance according to its scope.
- Finally, return the constructed bean instance.
This overview helps clarify the internal flow of getBean, aiding further understanding of createBean's behavior. For additional questions or clarifications, feel free to reach out.