深入浅出,从源码搞清Bean的加载过程
前言
Bean的加载过程算是面试中的老生常谈了,今天我们就来从源码层面深入去了解一下Spring中是如何进行Bean的加载的
Spring
先看示例代码:
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:/spring/text.xml");
System.out.println(context.getBean("stringutil"));
}
我们就从这个getBean去探索Spring如何进行类的加载。首先我们先看看ClassPathXmlApplicationContex的继承关系:
而DefaultResourceLoader又是实现的ResourceLoader接口
我们通过不断去溯源new ClassPathXmlApplicationContext
这个构造方法
我们先通过super一直溯源到最后
我们再来看看setParent的源码:
发现这里仅仅是给ApplicationContex设设置上下文。
所以真正的重点还是上面的refresh
那里。
refresh
先看源码,源码有点长,但是我加了注解,请慢慢看,我会在里面进行一些分析的
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
prepareRefresh();
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
prepareBeanFactory(beanFactory);
try {
postProcessBeanFactory(beanFactory);
invokeBeanFactoryPostProcessors(beanFactory);
registerBeanPostProcessors(beanFactory);
initMessageSource();
initApplicationEventMulticaster();
onRefresh();
registerListeners();
finishBeanFactoryInitialization(beanFactory);
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
destroyBeans();
cancelRefresh(ex);
throw ex;
}
finally {
resetCommonCaches();
}
}
}
简单总结一下就是刷新Spring应用的上下文,确保所有的Bean和相关组件都被正确初始化和配置。
BeanFactory
我们顺着getBean去看源码
BeanFacoty是用来加载Bean的重要接口,那么我们这里的实现类在哪呢?
让我们接着debug进去看看
为什么来到这里了呢?让我们看看AbstractApplicationContext
的类关系
原来如此,我们最终还是要在AbstractApplicationContext
里面去实现getBean的。
接着我们来看看这个BeanFactory是如何出现的,关注到上面的refresh源码中的这一行
以下是源码:
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
this.refreshBeanFactory();
return this.getBeanFactory();
}
看来重点在refreshBeanFactory()
protected final void refreshBeanFactory() throws BeansException {
if (this.hasBeanFactory()) {
this.destroyBeans();
this.closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = this.createBeanFactory();
beanFactory.setSerializationId(this.getId());
beanFactory.setApplicationStartup(this.getApplicationStartup());
this.customizeBeanFactory(beanFactory);
this.loadBeanDefinitions(beanFactory);
this.beanFactory = beanFactory;
} catch (IOException var2) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var2);
}
}
protected DefaultListableBeanFactory createBeanFactory() {
return new DefaultListableBeanFactory(this.getInternalParentBeanFactory());
}
而这个DefaultListableBeanFactory实际上也是BeanFactory的另一个实现
我们顺着loadBeanDefinitions去看,发现在我们的xml加载例子中,我们进入了这个类
让我研究研究这个源码
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
this.initBeanDefinitionReader(beanDefinitionReader);
this.loadBeanDefinitions(beanDefinitionReader);
}
对这个XmlBeanDefinitionReader,我们可以看见他是这样的继承关系
从右边可以看见,还有着Groovy和Properties两种实现,不过我们不在意他们的实现,接着我们关注到this.loadBeanDefinitions(beanDefinitionReader);
这一行,我们debug进去
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = this.getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
String[] configLocations = this.getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}
接着让我们进入loadBeanDefinitons这个方法中
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
int count = 0;
String[] var3 = locations;
int var4 = locations.length;
for(int var5 = 0; var5 < var4; ++var5) {
String location = var3[var5];
count += this.loadBeanDefinitions(location);
}
return count;
}
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return this.loadBeanDefinitions(location, (Set)null);
}
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = this.getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException("Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
} else {
int count;
if (resourceLoader instanceof ResourcePatternResolver) {
ResourcePatternResolver resourcePatternResolver = (ResourcePatternResolver)resourceLoader;
try {
Resource[] resources = resourcePatternResolver.getResources(location);
count = this.loadBeanDefinitions(resources);
if (actualResources != null) {
Collections.addAll(actualResources, resources);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
}
return count;
} catch (IOException var7) {
throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern [" + location + "]", var7);
}
} else {
Resource resource = resourceLoader.getResource(location);
count = this.loadBeanDefinitions((Resource)resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
}
return count;
}
}
}
我们接着追踪loadBeanDefinitions,最终在XmlBeanDefinitionReader中找到答案:
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return this.loadBeanDefinitions(new EncodedResource(resource));
}
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (this.logger.isTraceEnabled()) {
this.logger.trace("Loading XML bean definitions from " + encodedResource);
}
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
catch (IOException ex) {
}
}
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
Document doc = doLoadDocument(inputSource, resource);
return registerBeanDefinitions(doc, resource);
}
catch (BeanDefinitionStoreException ex) {
}
}
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
如果接着往下走就会发现,将输入流中的xml标签解析然后注册BeanDefiniton工厂中
至此我们成功把xml解析成功了,接下来我们回到这里
让我们关注这个函数
我们关注到这个函数,然后我们关注getBean
我们第一次进来这里会式null,所以会走下面的逻辑,等后续我们实例代码中使用getBean的时候这里就不为空了
然后下面的else的内容就是使用BeanFactory去创建一个Bean,代码很长,有兴趣可以自己看,我们要关注的是这一段之后的内容
{
Object sharedInstance = getSingleton(beanName);
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
if (newSingleton) {
addSingleton(beanName, singletonObject);
}
}
在这之后经过一系列的Bean的初始化,然后放入缓存中,就算是大功告成了,最后返回示例代码中的getBean即可从单例缓存中拿到bean !!!
总结
Spring加载Bean的过程为:
- 创建Spring容器
- 读取配置(或者注解),解析内容,封装到BeanDefinition的实现类中(通过registerBeanDefiniton方法来注册到ConcurrentHashMap中,然后将beanName放到list中方便取出)
- 实例化Bean放到Spring容器中
至于一些实例化之类的主要就是BeanPostProcessor了,可以从我的手写rpc项目的文章中找到
Bean的生命周期
- 创建Bean的实例:Bean容器会先找到Bean的定义,然后通过Java反射API来创建Bean的实例
- Bean属性赋值/填充:为Bean设置相关属性和依赖,例如填入@Autowired等注解注入的对象,setter方法和构造函数
- Bean初始化:
- 销毁Bean:把Bean的销毁方法记录下来,将爱需要销毁Bean或者销毁容器时,调用这些方法去释放Bean所持有的资源
- 如果 Bean 实现了
DisposableBean
接口,执行destroy()
方法。 - 如果 Bean 在配置文件中的定义包含
destroy-method
属性,执行指定的 Bean 销毁方法。或者,也可以直接通过@PreDestroy
注解标记 Bean 销毁之前执行的方法。
实例化和初始化的区别:
初始化会调用PostConstruct来调用Bean的初始化方法
- 如果 Bean 实现了
循环依赖
循环依赖的详细介绍可以去我的另一篇文章中找到,这里就带大家找到三级缓存的实际位置
解决流程如下:
SpringBoot
SpringBoot实际上就是通过自定义注解去标记,然后启动的时候去扫描这些注解来创建Bean,从而替代Spring的xml配置方式,所以这里先略过,后续会单独出一片文章深入探讨SpringBoot,大体介绍一下流程:
- 启动类: 启动类上使用 @SpringBootApplication 注解,包含了 @ComponentScan 和 @EnableAutoConfiguration。
- 自动配置: 通过 @EnableAutoConfiguration 注解加载自动配置类,这些类通常使用 @Conditional 注解来控制 Bean 的创建。
- 组件扫描: 扫描启动类所在包及其子包中的组件(如 @Component, @Service, @Repository, @Controller 等)。
- 条件注解: 根据条件注解的配置,有选择地注册 Bean