Spring 如何从 IoC 容器中获取对象?
共 8037字,需浏览 17分钟
·
2021-03-04 20:27
前情回顾
前面几篇文章主要分析了 Spring IoC 容器如何初始化,以及解析和注册我们定义的 bean 信息。
其中,「Spring 中的 IoC 容器」对 Spring 中的容器做了一个概述,「Spring IoC 容器初始化」和「Spring IoC 容器初始化(2)」分析了 Spring 如何初始化 IoC 容器,「Spring 是如何解析 <bean> 标签的?」分析了 Spring 如何解析 <bean> 标签及其子标签,并注册到 BeanFactory。
主要流程如下:
IoC 容器已经建立,而且把我们定义的 bean 信息放入了容器,那么如何从容器中获取对象呢?
本文继续分析。
配置及测试代码
为便于查看,这里再贴一下 bean 配置文件和测试代码。
- 配置文件 application-ioc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="person" class="com.jaxer.spring.ioc.Person">
<property name="pet" ref="dog"/>
</bean>
<bean id="dog" class="com.jaxer.spring.ioc.Dog">
<property name="age" value="1"/>
<property name="owner" ref="person"/>
</bean>
</beans>
- 测试代码
public class IocTests {
@Test
public void test01() {
ApplicationContext context = new ClassPathXmlApplicationContext("application-ioc.xml");
System.out.println(context.getBean("person"));
System.out.println(context.getBean("dog"));
}
}
/*
* 输出结果:
* Person{id=12, name='Jack-12'}
* Dog{age=1}
*/
如何从容器获取对象?
从容器中获取对象是通过 BeanFactory#getBean 方法,它有多个重载的方法,但最终都是通过
AbstractBeanFactory#doGetBean 方法来实现的。doGetBean 方法代码如下:
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
// ...
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
String beanName = transformedBeanName(name);
Object bean;
// 从缓存中获取单例 bean 对象
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
// ...
// 处理 FactoryBean 的场景
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
// 缓存中不存在 bean 对象
else {
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// bean 对象在父容器中,则从父容器中获取 bean 对象
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 {
// 获取 BeanDefinition
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);
// 获取依赖的 bean 对象
// 若创建一个 bean 对象时依赖其他对象,则先创建被依赖对象
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
if (isDependent(beanName, dep)) {
// ...
}
registerDependentBean(dep, beanName);
try {
getBean(dep);
}
catch (NoSuchBeanDefinitionException ex) {
// ...
}
}
}
// 创建 scope 为 singleton(单例)的对象
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// ...
}
});
// 处理 FactoryBean 的场景
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
// 创建 scope 为 prototype 的对象
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);
}
// 处理 FactoryBean 的场景
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
// 创建其他类型对象
else {
String scopeName = mbd.getScope();
if (!StringUtils.hasLength(scopeName)) {
throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'");
}
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);
}
});
// 处理 FactoryBean 的场景
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
catch (IllegalStateException ex) {
// ...
}
}
}
catch (BeansException ex) {
cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
}
// 类型检查
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) {
// ...
}
}
return (T) bean;
}
}
获取 bean 对象主要就是通过这个 doGetBean 方法实现的。
该方法虽然看起来稍微有点长,但是呢,它内部的实现更长、更复杂。不过也是有迹可循的,莫慌。
本文先看下这个方法的整体流程,内部逻辑后面再慢慢研究。先上流程图:
代码虽然有点长,但梳理下来其实也没那么复杂了。
这个方法主要做了什么呢?
当从容器中获取 bean 对象时,首先从缓存中获取。如果缓存中存在,处理 FactoryBean 的场景。
BeanFactory 和 FactoryBean,这哥俩长得很像,也有个别面试题可能会问到。
嗯……以后有机会单独分析?
如果缓存中没有,先去父容器获取,前面创建 BeanFactory 时可以指定 parent 参数,就是那个。
不在父容器中,若 bean 对象依赖了其他对象,则先创建被依赖的 bean 对象,再根据 <bean> 标签的 scope 属性去创建相应的 bean 对象。
是不是有点像我们平时写查询接口时、先从缓存查询,缓存中没的话再查询 DB?
道理是一样的,空间换时间。
小结
先整体,后细节。
本文先从整体上分析了如何从 Spring IoC 容器中获取 bean 对象,内容不多,后文再详细分解吧。
休息会~