【Spring从成神到升仙系列 二】2023年再不会 IOC 源码,就要被淘汰了
- 👏作者简介:大家好,我是爱敲代码的小黄,独角兽企业的Java开发工程师,CSDN博客专家,阿里云专家博主
- 📕系列专栏:Java设计模式、数据结构和算法、Kafka从入门到成神、Kafka从成神到升仙、Spring从成神到升仙系列
- 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
- 🍂博主正在努力完成2023计划中:以梦为马,扬帆起航,2023追梦人
- 📝联系方式:hls1793929520,和大家一起学习,一起进步👀

文章目录
- Spring IOC源码解析
- 一、引言
- 二、Spring启动配置
- 三、IOC 源码剖析
- 1、prepareRefresh
- 2、obtainFreshBeanFactory
- 2.1 refreshBeanFactory
- 3、prepareBeanFactory
- 4、postProcessBeanFactory
- 5、invokeBeanFactoryPostProcessors
- 6、registerBeanPostProcessors
- 7、initMessageSource
- 8、initApplicationEventMulticaster
- 9、onRefresh
- 10、registerListeners
- 11、finishBeanFactoryInitialization
- 11.1 创建实例
- 11.2 属性填充
- 11.3 初始化逻辑
- 12、finishRefresh
- 13、销毁
- 五、流程图
- 六、总结
- 七、附录
- 1、注册别名的原因
- 2、BeanFactoryPostProcessor 和 BeanDefinitionRegistryPostProcessor
- 3、BeanFactoryPostProcessor 和 BeanDefinitionRegistryPostProcessor
Spring IOC源码解析
一、引言
对于Java开发者而言,关于 Spring ,我们一般当做黑盒来进行使用,不需要去打开这个黑盒。
但随着目前程序员行业的发展,我们有必要打开这个黑盒,去探索其中的奥妙。
本期 Spring 源码解析系列文章,将带你领略 Spring 源码的奥秘
本期源码文章吸收了之前 Kafka 源码文章的错误,将不再一行一行的带大家分析源码,我们将一些不重要的部分当做黑盒处理,以便我们更快、更有效的阅读源码。
废话不多说,发车!
本文流程图可关注公众号:爱敲代码的小黄,回复:IOC 获取
贴心的小黄为大家准备的文件格式为 POS文件,方便大家直接导入 ProcessOn 修改使用
二、Spring启动配置
首先,我们要引入 Spring 的依赖,这里是用的 4.3.11.RELEASE 版本的,不同的版本源码较有差异,但整体业务逻辑不变
这里讲个小细节,如果你在面试,这里一定要提前给面试官说好你阅读的源码版本,有三方面好处:
第一:避免不同版本的Spring源码不一致导致和面试官的分歧问题
第二:让面试官感觉你小子是真的阅读过源码,就算你说的逻辑和面试官有分歧,面试官第一反应会是源码版本差异导致的
第三:装逼使用…
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>4.3.11.RELEASE</version>
</dependency>
创建接口 MessageService:
public interface MessageService {String getMessage();
}
实现类 MessageServiceImpl :
public class MessageServiceImpl implements MessageService {public String getMessage() {return "hello world";}
}
配置文件 application.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="messageService" class="cn.hls.spring.MessageServiceImpl"/>
</beans>
启动类 SpringStart:
public class SpringStart {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");System.out.println("context 启动成功");MessageService messageService = context.getBean(MessageService.class);// 输出: hello worldSystem.out.println(messageService.getMessage());}
}
最终输出结果:
context 启动成功
hello world
通过上述代码,我们可以看到,Spring 的 IOC 完全代替了我们之前的 new 的功能,将创建实例交由 Spring 来管理。
三、IOC 源码剖析
那 Spring 是如何管理的呢?源码背后又有什么小技巧呢?今天我们一起来看一下 IOC 源码的解析
为了阅读性,我们将以 xml 文件的配置来阅读源码
首先,我们从 ApplicationContext context = new ClassPathXmlApplicationContext("application.xml"); 这一行入手,看其到底执行了什么
我们 debug 点进去可以看到共分为了三部分:
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent){super(parent);setConfigLocations(configLocations);if (refresh) {refresh();}
}
简单来说,这三部分的作为分别是:
super(parent):调用父类的构造方法,创建PathMathingResourcePatternResolver解析配置文件setConfigLocations(configLocations):设置configLocations(配置文件路径)到当前应用程序中refresh():解析配置文件、完成bean的注册、实例化、初始化、代理等一系列的工作最终创建出一个Bean实例
我们一起来看一下 refresh 到底做了什么
1、prepareRefresh
**整体简介:**做容器刷新前的准备工作
- 设置容器的启动时间:
this.startupDate = System.currentTimeMillis(); - 设置活跃状态为
true:active.set(true) - 设置关闭状态为
false:closed.set(false) - 获取
Environment对象,并加载当前系统的属性值到Environment对象中 - 准备监听器和事件的集合对象,默认为空的集合:
earlyApplicationEvents = new LinkedHashSet<ApplicationEvent>();
这个方法不重要,也不必要去深入了解,知道做了容器刷新的准备工作即可。
2、obtainFreshBeanFactory
**整体简介:**创建容器,并且完成配置文件的加载
重要的来了,这个方法是比较重要的,一定要记住
首先该方法分为了以下几部分:
refreshBeanFactory:解析我们的application.xml文件并生成BeanDefinition注册至DefaultListableBeanFactory的beanDefinitionMap中getBeanFactory:获取工厂bean
2.1 refreshBeanFactory
protected final void refreshBeanFactory() throws BeansException {// 创建 BeanFactory,类型为 DefaultListableBeanFactoryDefaultListableBeanFactory beanFactory = createBeanFactory();beanFactory.setSerializationId(getId());customizeBeanFactory(beanFactory);loadBeanDefinitions(beanFactory);synchronized (this.beanFactoryMonitor) {this.beanFactory = beanFactory;}
}
上述我们必须记住这个工厂类:DefaultListableBeanFactory,甚至要做到背诵+默写的程度
其次,最重要的就属 loadBeanDefinitions(beanFactory) 方法了
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory){// 创建XmlBeanDefinitionReader,这个是我们xml文件的解析器XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);beanDefinitionReader.setEnvironment(this.getEnvironment());beanDefinitionReader.setResourceLoader(this);beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));initBeanDefinitionReader(beanDefinitionReader);// 加载BeanDefinitionsloadBeanDefinitions(beanDefinitionReader);
}
我们继续深入看其做了什么
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {// 拿到xml文件的地址Resource[] configResources = getConfigResources();if (configResources != null) {reader.loadBeanDefinitions(configResources);}String[] configLocations = getConfigLocations();if (configLocations != null) {reader.loadBeanDefinitions(configLocations);}
}
继续往下看,一直到 loadBeanDefinitions
public int loadBeanDefinitions(String location, Set<Resource> actualResources){ResourceLoader resourceLoader = getResourceLoader();if (resourceLoader instanceof ResourcePatternResolver) {try {Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);// 直接看这行,其余不重要int loadCount = loadBeanDefinitions(resources);if (actualResources != null) {for (Resource resource : resources) {actualResources.add(resource);}}return loadCount;}} else {Resource resource = resourceLoader.getResource(location);int loadCount = loadBeanDefinitions(resource);if (actualResources != null) {actualResources.add(resource);}return loadCount;}
}
XmlBeanDefinitionReader 第 388 行
// 将路径封装成一个DOC格式
Document doc = doLoadDocument(inputSource, resource);
// 继续注册
return registerBeanDefinitions(doc, resource);
XmlBeanDefinitionReader 第 505 行
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;
}public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {this.readerContext = readerContext;// 获取根节点Element root = doc.getDocumentElement();// 从根节点开始解析遍历doRegisterBeanDefinitions(root);
}protected void doRegisterBeanDefinitions(Element root) {BeanDefinitionParserDelegate parent = this.delegate;this.delegate = createDelegate(getReaderContext(), root, parent);if (this.delegate.isDefaultNamespace(root)) {String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);if (StringUtils.hasText(profileSpec)) {String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);}}preProcessXml(root);// 直接看这里,其余不重要parseBeanDefinitions(root, this.delegate);postProcessXml(root);this.delegate = parent;}
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {if (delegate.isDefaultNamespace(root)) {NodeList nl = root.getChildNodes();for (int i = 0; i < nl.getLength(); i++) {Node node = nl.item(i);if (node instanceof Element) {Element ele = (Element) node;if (delegate.isDefaultNamespace(ele)) {// 直接看这里parseDefaultElement(ele, delegate);}else {delegate.parseCustomElement(ele);}}}}else {delegate.parseCustomElement(root);}
}// 根据不同的配置走不同的分支,配置:import、alias、bean、beans
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {importBeanDefinitionResource(ele);}else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {processAliasRegistration(ele);}else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {processBeanDefinition(ele, delegate);}else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {// recursedoRegisterBeanDefinitions(ele);}}
我们这里只看 bean 的配置,其余的读者有兴趣可以自己去 debug 下
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {// 解析各种xml标签去生成对应的BeanDefinition,读者有兴趣可以自己看一下BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);if (bdHolder != null) {bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);try {// 重点:正式开始注册BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());}getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));}
}public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry){// 重点,开始注册String beanName = definitionHolder.getBeanName();registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());// 注册别名String[] aliases = definitionHolder.getAliases();if (aliases != null) {for (String alias : aliases) {registry.registerAlias(beanName, alias);}}}public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {// 看一下原来的beanDefinitionMap是不是已经有该 beanName 了// 如果已经存在,我们要排抛出异常(Spring不允许覆盖)BeanDefinition oldBeanDefinition = this.beanDefinitionMap
beanDefinitionMap.get(beanName);if (oldBeanDefinition != null) {if (!isAllowBeanDefinitionOverriding()) {throw new BeanDefinitionStoreException()}}if (oldBeanDefinition != null) {this.beanDefinitionMap.put(beanName, beanDefinition);}else {// 重点在这:将我们的beanName与beanDefinition放至beanDefinitionMap中this.beanDefinitionMap.put(beanName, beanDefinition);this.beanDefinitionNames.add(beanName);this.manualSingletonNames.remove(beanName);}}
到这里,基本就结束了,我们来回顾一下 refreshBeanFactory 的业务:
- 通过我们传递的
xml文件的路径,利用documentLoader将其封装成Document格式 - 创建
BeanDefinitionDocumentReader来正式解析xml文件并找到文件的root - 根据
root扫描遍历,对不同配置的标签(import、alias、bean、beans)走不同的逻辑判断 - 将当前的标签各属性进行组装成
beanDefinition,调用DefaultListableBeanFactory进行注册 - 根据
BeanName查询该beanDefinition是否被注册过,如果被注册过,则直接抛出异常(Spring不允许覆盖) - 如果没有注册过,则将
BeanName与beanDefinition注册至DefaultListableBeanFactory的beanDefinitionMap中 - 最后,如果该
beanDefinition含有别名,也要将别名进行注册,至于为什么注册别名,可见:附录1
这里可能有人会说,小黄小黄,按你之前解析
kafka的套路,肯定会分析beanDefinition的形成的,现在怎么偷懒不分析了,是不是看不懂~答:之前分享的
kafka系列的文章,大家都知道分享的很细,但是我们细细品味一下,我们读源码到底为了什么,以及如何去读、如何有效的读、如何快速的读,我相信每一个人心中都有一套读源码的方式。至于哪一种阅读方式更为合理,后面博主准备单独出一篇文章来讲解,或者你可以私信我,告知我你的读源码的方式,一起加油、一起学习。
3、prepareBeanFactory
整体简介: beanFactory 的准备工作,对各种属性进行填充
这个方法不重要,也不必要去深入了解,知道做了 beanFactory 的填充即可
不过,这里记住,beanFactory 的类一定要记清楚,是 DefaultListableBeanFactory ,不多说直接 背诵+默写
4、postProcessBeanFactory
整体简介: 默认没有实现,留给子类进行实现操作
5、invokeBeanFactoryPostProcessors
整体简介: 可以自由扩展,通过实现BeanFactoryPostProcessor 和 BeanDefinitionRegistryPostProcessor 接口,对 beanFactory 里面的 BeanDefinition 进行修改
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {// 找到当前 beanDefinitionMap 中`BeanFactoryPostProcessor` 和 `BeanDefinitionRegistryPostProcessor`接口的实现// 若这些实现有对应的order(顺序),则排序之后依次调用PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));}
}
温馨小提示:这里有的小伙伴可能对
BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor接口不熟悉,我们将此处的讲解放至:附录2
6、registerBeanPostProcessors
整体简介: 完成 spring 自带或者用户自定义的 BeanPostProcessor 的解析
protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {// 实例化并且注册所有的beanPostProcessorPostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);
}
这里的操作实际上和我们上面 invokeBeanFactoryPostProcessors 里面很像,都是对实现一些特定接口的类做加载,但需要注意的是:对于实现 BeanPostProcessor 接口的来说,我们不会在此立即调用,会在 Bean 初始化方法前后调用。
对了,提前剧透一下,我们响当当的 AOP 也是在这里实现的,后续我们也会讲的。
温馨小提示:这里有的小伙伴可能对
BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor接口不熟悉,我们将此处的讲解放至:附录3
7、initMessageSource
整体简介: Spring 中国际化的功能
8、initApplicationEventMulticaster
整体简介: 初始化事件广播器
9、onRefresh
整体简介: 在 spring 中默认没有任何实现,模板方法,但是在 springboot 中启动了 web 容器
10、registerListeners
整体简介: 注册监听器,为了方便接受广播的事件
11、finishBeanFactoryInitialization
整体简介:完成所有非懒加载的单例对象的实例化操作,从此方法开始进行对象的创建,包含了实例化,初始化,循环依赖,AOP等核心逻辑的处理过程,此步骤是最最核心且关键的点,要对其中的细节最够清楚
由于篇幅原因,博主会尽量挑选一些重要的地方进行分析。
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {// 实例化剩下的单例对象beanFactory.preInstantiateSingletons();
}public void preInstantiateSingletons(){// 拿到我们之前存储的所有beanDefinition的名字List<String> beanNames = new ArrayList<>(this.beanDefinitionNames); // 触发单例bean的初始化,遍历集合的对象for (String beanName : beanNames) {// 如果beanName对应的bean不是FactoryBean,只是普通的bean,通过beanName获取bean实例getBean(beanName);}
}public Object getBean(String name) throws BeansException {// 此方法是实际获取bean的方法,也是触发依赖注入的方法return doGetBean(name, null, null, false);
}protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly){// 这里需要一步转换,这里的原因我们附录1提到过,这里不再过多讨论String beanName = transformedBeanName(name);// 提前检查单例缓存中是否有手动注册的单例对象,剧透一下(和循环依赖有关联)Object sharedInstance = getSingleton(beanName);// 当对象都是单例的时候会尝试解决循环依赖的问题,但是原型模式下如果存在循环依赖的情况,那么直接抛出异常if (isPrototypeCurrentlyInCreation(beanName)) {throw new BeanCurrentlyInCreationException(beanName);}if (mbd.isSingleton()) {// 返回以beanName的(原始)单例对象,如果尚未注册,则使用singletonFactory创建并注册一个对象:sharedInstance = getSingleton(beanName, () -> {try {// 为给定的合并后BeanDefinition(和参数)创建一个bean实例// 这也是我们的核心方法return createBean(beanName, mbd, args);}});
}protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args){// 实际创建bean的调用Object beanInstance = doCreateBean(beanName, mbdToUse, args);
}protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args){// 根据执行bean使用对应的策略创建新的实例,如,工厂方法,构造函数主动注入、简单初始化BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);// 对bean的属性进行填充,将各个属性值注入,其中,可能存在依赖于其他bean的属性,则会递归初始化依赖的beanpopulateBean(beanName, mbd, instanceWrapper);// 执行初始化逻辑exposedObject = initializeBean(beanName, exposedObject, mbd);
}
到这里我们先停一停,我们总结一下创建实例的一些步骤:
-
拿到我们之前注册的
beanDefinitionNames,遍历整个beanDefinitionNames,每一个BeanName生成一个对象 -
我们需要进行名称转化,防止传入的是一个别名或其他的名称,利用转换后的别名去调用
-
查询我们的单例缓存中是否已经存在该实例,如果存在直接返回即可
-
如果不存在,则需要去根据该
beanDefinition去生成对应的实例 -
对于生成实例共有三个步骤:
- 创建实例
- 属性填充
- 初始化逻辑
- 实现
BeanPostProcessor的前置方法 - 对象的初始化方法
- 实现
BeanPostProcessor的后置方法
- 实现
我们对于每个步骤都进行分析:
11.1 创建实例
对于实例创建,Spring 中创建 bean 的方式大致可分为三种:
- 类名称 + 自定义
beanName - 工厂类名称+ 自定义工厂静态方法 + 自定义
beanName - 提前注册工厂
bean,使用工厂bean+ 工厂方法+自定义beanName
可能大家有点懵,怎么这么多创建的方法,这里其实我们不需要太过于关注,只需要关注 类名称 + 自定义 beanName 这种方法即可,其余两种基本很少用到
对于 类名称 + 自定义 beanName 我们一般有两种构造方法:
- 无参构造(常用)
- 有参构造(不常用)
为了便于理解,我们这里只介绍无参构造
BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) {// 根据当前的beanName拿到其 ClassClass<?> beanClass = resolveBeanClass(mbd, beanName);// 前面的有参都不存在,则进行无参构造return instantiateBean(beanName, mbd);
}protected BeanWrapper instantiateBean(String beanName, RootBeanDefinition mbd) {// 获取实例化策略并且进行实例化操作beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, this);
}public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {// 锁一下对象,线程安全synchronized (bd.constructorArgumentLock) {// 得到当前bean的ClassClass<?> clazz = bd.getBeanClass();// 通过class得到其默认的构造方法constructorToUse = clazz.getDeclaredConstructor();// return BeanUtils.instantiateClass(constructorToUse);}
}public static <T> T instantiateClass(Constructor<T> ctor, Object... args) {// 构造方法+入参// 如果当前是无参构造方法的话,则argsWithDefaultValues为空return ctor.newInstance(argsWithDefaultValues);
}
总结一下通过无参构造创建实例的步骤:
- 加锁,保证线程安全
- 得到当前
bean的Class,通过其Class得到默认的无参构造方法 - 通过反射直接创建即可
其实有参的构造方法也类似,只不过相较于无参构造,反射传入的 argsWithDefaultValues 的参数,这里的参数可以为 Bean 也可以为数值,所以这里也会出现循环依赖的问题。
11.2 属性填充
属性填充相对简单,流程我们大致过一下,属性注入类似:
<bean id="messageService" class="com.mashibing.hls.MessageServiceImpl"><property name="name" value="hls"/>
</bean>
其中的 property 标签就是我们的属性值。
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {// 得到当前 BeanDefinition 的属性值PropertyValues pvs = mbd.getPropertyValues();if (pvs != null) {// 注入属性applyPropertyValues(beanName, mbd, bw, pvs);}
}protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs){// 获取pvs的PropertyValue对象数组,并将其转换成列表List<PropertyValue> original = Arrays.asList(pvs.getPropertyValues());for (PropertyValue pv : original) {// 获取属性的名字String propertyName = pv.getName();// 获取未经类型转换的值Object originalValue = pv.getValue();// 这里需要进行一系列的转换// 因为我们的属性注入有可能注入的是一个BeanReference,需要重新去 BeanFactory 中获取实例// 转换后的放至 deepCopy// 按原样使用deepCopy构造一个新的MutablePropertyValues对象然后设置到bw中以对bw的属性值更新bw.setPropertyValues(new MutablePropertyValues(deepCopy));}
}public void setPropertyValues(PropertyValues pvs){setPropertyValues(pvs, false, false);
}public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid){// 后续主要通过反射对值进行设置,感兴趣的可以自己去看下源码实现setPropertyValue(pv);
}
11.3 初始化逻辑
我们这里先实现一个 BeanPostProcessor 接口,便于我们的观察:
public class MyTest implements BeanPostProcessor {public Object postProcessBeforeInitialization(Object bean, String beanName) {System.out.println("我前置增强");return bean;}public Object postProcessAfterInitialization(Object bean, String beanName) {System.out.println("我后置增强");return bean;}
}
直接看我们的源码:
// 执行初始化逻辑
exposedObject = initializeBean(beanName, exposedObject, mbd);protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {// 将BeanPostProcessors应用到给定的现有Bean实例,调用它们的postProcessBeforeInitialization初始化方法。// 返回的Bean实例可能是原始Bean包装器wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);//调用初始化方法,先调用bean的InitializingBean接口方法,后调用bean的自定义初始化方法invokeInitMethods(beanName, wrappedBean, mbd);// 将BeanPostProcessors应用到给定的现有Bean实例,调用它们的postProcessAfterInitialization方法。// 返回的Bean实例可能是原始Bean包装器wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);//返回包装后的Beanreturn wrappedBean;
}// 执行所有的BeanPostProcessors接口下的类
// 如果我们自己实现的类对 Bean 进行了包装,比如AOP,则使用我们实现类里面返回的
public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName){Object result = existingBean;//遍历 该工厂创建的bean的BeanPostProcessors列表for (BeanPostProcessor processor : getBeanPostProcessors()) {// 默认实现按原样返回给定的 BeanObject current = processor.postProcessBeforeInitialization(result, beanName);// 如果 current为nullif (current == null) {//直接返回result,中断其后续的BeanPostProcessor处理return result;}//让result引用processor的返回结果,使其经过所有BeanPostProcess对象的后置处理的层层包装result = current;}//返回经过所有BeanPostProcess对象的后置处理的层层包装后的resultreturn result;
}protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd){// 如果mbd不为null&&bean不是NullBean类if (mbd != null && bean.getClass() != NullBean.class) {// 获取mbd指定的初始化方法名String initMethodName = mbd.getInitMethodName();// 在bean上调用指定的自定义init方法invokeCustomInitMethod(beanName, bean, mbd);// 具体调用,反射执行// Method methodToInvoke = ClassUtils.getInterfaceMethodIfPossible(initMethod);// methodToInvoke.invoke(bean);}
}// 执行所有的BeanPostProcessors接口下的类
// 如果我们自己实现的类对 Bean 进行了包装,比如AOP,则使用我们实现类里面返回的
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName){//初始化结果对象为result,默认引用existingBeanObject result = existingBean;//遍历该工厂创建的bean的BeanPostProcessors列表for (BeanPostProcessor processor : getBeanPostProcessors()) {//回调BeanPostProcessor#postProcessAfterInitialization来对现有的bean实例进行包装Object current = processor.postProcessAfterInitialization(result, beanName);// 如果current为nullif (current == null) {//直接返回result,中断其后续的BeanPostProcessor处理return result;}//让result引用processor的返回结果,使其经过所有BeanPostProcess对象的后置处理的层层包装result = current;}//返回经过所有BeanPostProcess对象的后置处理的层层包装后的resultreturn result;}
其实初始化的逻辑也很简单,就是调用我们的 BeanPostProcess 实现扩展点的应用
然后初始化 init 方法即可
12、finishRefresh
整体简介: 完成整个容器的启动,所有的对象都准备完成,可以进行后续业务流程的操作,清除上下文缓存,初始化生命周期处理器,发送刷新完成事件
13、销毁
前面我们已经创建成功了对象,当我们使用完成之后,肯定要进行销毁,那么 Spring 是如何做的销毁对象的管理呢
执行 context.close(); 方法:
public void close() {synchronized (this.startupShutdownMonitor) {doClose();}
}protected void doClose() {// 清空 DefaultListableBeanFactory 里面的缓存destroyBeans();// 直接将beanFactory置为nullcloseBeanFactory();
}protected void destroyBeans() {// 清空在包含的Bean名称之间映射:bean名称-Bean包含的Bean名称集this.containedBeanMap.clear();// 清空在相关的Bean名称之间映射:bean名称-一组相关的Bean名称this.dependentBeanMap.clear();// 清空在相关的Bean名称之j键映射:bean名称bean依赖项的Bean名称集this.dependenciesForBeanMap.clear();// 清除此注册表中所有缓存的单例实例clearSingletonCache();
}protected void clearSingletonCache() {// 加锁,使用单例对象的高速缓存:beam名称-bean实例作为锁synchronized (this.singletonObjects) {// 清空单例对象的高速缓存:beam名称-bean实例this.singletonObjects.clear();// 清空单例工厂的缓存:bean名称-ObjectFactorythis.singletonFactories.clear();// 清空早期单例对象的高速缓存:bean名称-bean实例this.earlySingletonObjects.clear();// 清空已注册的单例集,按照注册顺序包含bean名称this.registeredSingletons.clear();// 设置当前是否在destroySingletons中的标志为falsethis.singletonsCurrentlyInDestruction = false;}
}private void clearByTypeCache() {this.allBeanNamesByType.clear();this.singletonBeanNamesByType.clear();
}
// 直接将beanFactory置空
protected final void closeBeanFactory() {DefaultListableBeanFactory beanFactory = this.beanFactory;if (beanFactory != null) {beanFactory.setSerializationId(null);this.beanFactory = null;}
}
这基本就是销毁的整个流程。
这里还有一个小知识点,就是我们可以自定义我们的销毁方法,比如如下:
<bean id="messageService" class="cn.hls.spring.MessageServiceImpl" init-method="init" destroy-method="destroy"/>public class MessageServiceImpl implements MessageService {public String getMessage() {return "hello world";}public void init(){System.out.println("我是类的初始化");}public void destroy(){System.out.println("我是类的销毁");}
}
在执行 context.close(); 方法时,会调用该 Bean 的销毁方法,至于怎么调用的。
这里交给读者了(真不是我懒
五、流程图

六、总结
又是一篇大工程的文章结束了
记得校招时候,当时对 Spring 懵懂无知,转眼间也被迫看了源码
有些小伙伴可能疑惑:哎,博主,你这不对呀,你这循环依赖也没讲、三级缓存也没讲,你是不是漏的有点多。
因为咱们这篇文章主要针对的是 Spring IOC 的源码,对于三级缓存、循环依赖来说,主要解决 AOP 代理对象的问题,这个我们后面单独出一篇来描述,不要着急,小黄不会不讲的。
当然,本篇只介绍了 XML 配置,如果你对注解的配置感兴趣的话,也可以去看一下 AnnotationConfigApplicationContext 的流程,区别不大,一个是解析的 xml,一个是解析的注解
但通过这篇文章,我相信,99% 的人应该都可以理解了 Spring IOC 的来龙去脉
那么如何证明你真的理解了 Spring IOC 呢,我这里出个经典的题目,大家可以想一下:Bean 的生命周期
如果你能看到这,那博主必须要给你一个大大的鼓励,谢谢你的支持!
喜欢的可以点个关注,后续会更新 Spring 源码系列文章
我是爱敲代码的小黄,独角兽企业的Java开发工程师,CSDN博客专家,Java领域新星创作者,喜欢后端架构和中间件源码。
我们下期再见。
七、附录
1、注册别名的原因
当我们使用 GetBean(beanName) 时,Spring会默认其是别名,并进行循环获取
protected <T> T doGetBean(String name){final String beanName = transformedBeanName(name);
}
protected String transformedBeanName(String name) {return canonicalName(BeanFactoryUtils.transformedBeanName(name));
}
public String canonicalName(String name) {String canonicalName = name;String resolvedName;do {resolvedName = (String)this.aliasMap.get(canonicalName);if (resolvedName != null) {canonicalName = resolvedName;}} while(resolvedName != null);return canonicalName;
}
2、BeanFactoryPostProcessor 和 BeanDefinitionRegistryPostProcessor
这两个类的作用主要对我们 BeanFactory 的 BeanDefinitions 进行修改,举个例子:
public class MyTest implements BeanFactoryPostProcessor {@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {// 拿到我们 id = messageService 的 PropertyValueMutablePropertyValues messageService = beanFactory.getBeanDefinition("messageService").getPropertyValues();List<PropertyValue> propertyValueList = messageService.getPropertyValueList();// 遍历输出,当然也可以进行修改for (PropertyValue propertyValue : propertyValueList) {System.out.println("BeanFactoryPostProcessor : name = " + propertyValue.getName() + " value = " + propertyValue.getValue());}}
}
我们看一下启动的效果:
BeanFactoryPostProcessor : name = name value = TypedStringValue: value [hls], target type [null]
3、BeanFactoryPostProcessor 和 BeanDefinitionRegistryPostProcessor
这两个类主要是扩展进行使用,比如我们的 AOP 或者其他的扩展点,举个例子:
public class MyTest implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {System.out.println("我会在类初始化前调用");return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {System.out.println("我会在类初始化后调用");return bean;}
}
我们看一下启动的效果:
我会在类初始化前调用
我是类的初始化
我会在类初始化后调用
相关文章:
【Spring从成神到升仙系列 二】2023年再不会 IOC 源码,就要被淘汰了
👏作者简介:大家好,我是爱敲代码的小黄,独角兽企业的Java开发工程师,CSDN博客专家,阿里云专家博主📕系列专栏:Java设计模式、数据结构和算法、Kafka从入门到成神、Kafka从成神到升仙…...
菜鸟的进阶--手写一个小型dubbo框架
1.rpc调用流程2.组件1.Redis注册中心3.编解码/序列化在本例的Netty通信中,由于每次调用rpc服务都要发送同一个类对象invoker,所以可以使用Protobuf。但是在接受方法调用结果的时候就不行了,因为我们无法提前确定对方方法返回结果的类型&#…...
js逆向爬取某音乐网站某歌手的歌曲
js逆向爬取某音乐网站某歌手的歌曲一、分析网站1、案例介绍2、寻找列表页Ajax入口(1)页面展示图。(2)寻找部分歌曲信息Ajax的token。(3)寻找歌曲链接(4)获取歌曲名称和id信息3、寻找…...
为什么软件测试面试了几个月都没有offer,从HR角度分析
首先,我觉得你在软件测试面试的过程中,逻辑比较混乱的最大一个原因是,说明你没有形成一个一个整体的体系。 导致你说的时候很多东西都杂乱无章。 我个人认为软件测试,其实开始首先进行的是一些需求的分析工作,之后呢…...
DC-7 靶场学习
文章目录信息搜集账号密码获取修改密码反弹shell得到flag信息搜集 首先获取目标ip。 arp-scan -l nmap -sP 192.168.28.0/24得到目标ip为: 192.168.28.139先访问页面。 翻译一下。 欢迎来到 DC-7DC-7引入了一些“新”概念,但我会让你弄清楚它们是什么…...
深入理解JavaScript的事件冒泡与事件捕获
前言JavaScript中提供了很多操作DOM的API。事件冒泡和事件捕获是指浏览器中处理DOM元素上事件的两种不同方式。事件冒泡和事件捕获都是JavaScript事件模型中的一部分,可以用来处理事件。对于这个问题,在实际开发中,并不是非常重要,…...
格密码学习笔记(六):格中模运算
文章目录格中取模运算CVP和格的陪集致谢格中取模运算 定义(格的基本区域) P⊂Rn:{Px∣x∈L}\mathcal{P} \subset \mathbb{R}^n : \{ \mathcal{P} \bm{x} | \bm{x} \in \mathcal{L} \}P⊂Rn:{Px∣x∈L}是Rn\mathbb{R}^nRn的一种划分。 用P\mathcal{P}P对…...
【C++】非常重要的——多态
凡是面向对象的语言,都有三大特性,继承,封装和多态,但并不是只有这三个特性,是因为者三个特性是最重要的特性,那今天我们一起来看多态! 目录 1.多态的概念 1.1虚函数 1.2虚函数的重写 1.3虚…...
发票账单很多?python助你批量完成数据提取
每天面对成堆的发票,无论是税务发票还是承兑单据,抑或是其他各类公司数据要从照片、PDF等不同格式的内容中提取,我们都有必要进行快速办公的能力提升。因此,我们的目标要求就十分明显了,首先要从图片中获取数据&#x…...
[闪存2.1] NAND FLASH特性串烧 | 不了解闪存特性,你能用好闪存产品吗?
前言 为了利用好闪存, 发挥闪存的优势, 以达到更好的性能和使用寿命, 那自然要求了解闪存特性。 闪存作为一种相对较新的存储介质, 有很多特别的特性。 一.闪存的特性 凡是采用Flash Memory的存储设备,可以统称为闪存存储。我们经常谈的固态硬盘(SSD),可以由volatile/…...
面试官问我按钮级别权限怎么控制,我说v-if,面试官说再见
最近的面试中有一个面试官问我按钮级别的权限怎么控制,我说直接v-if啊,他说不够好,我说我们项目中按钮级别的权限控制情况不多,所以v-if就够了,他说不够通用,最后他对我的评价是做过很多东西,但…...
阿里云服务器使用教程:CentOS 7安装nginx详细步骤
目录 1、下载nginx压缩包 2、配置nginx安装所需环境 3、解压nginx压缩包 4、编译安装nginx 5、nginx启动...
Android JNI浅析、Java和Native通信对象的传值和回调
简单了解一下jni JNI是一个本地编程接口,它允许运行在Java虚拟机的Java代码与用其他语言(如C,C和汇编)编写的库交互。 jni函数签名 首先看一下java类型对应的jni类型: Java类型符号BooleanZByteBCharCShortSIntILongJFloatFDo…...
linux目录/usr/lib/systemd/system目录详解
文章目录前言一. systemd介绍二. service 脚本详解2.1 [Unit] 区块2.2 [Service] 区块2.3 [Install] 区块总结前言 init的进化经历了这么几个阶段: CentOS 5: SysV init,串行 CentOS 6:Upstart,并行,借鉴ubuntu CentOS 7:Syste…...
408考研计算机之计算机组成与设计——知识点及其做题经验篇目4:CPU的功能和基本结构
随着考研的慢慢复习,我们逐渐进入了计算机组成与设计的第五章中央处理器。它原名为CPU。姓C,名PU,字中央处理器,号计组难点,乃计算机之中心与核心部件,小编称之曰能算能控,赐名曰九天宏教普济生…...
2022-12-10青少年软件编程(C语言)等级考试试卷(五级)解析
2022-12-10青少年软件编程(C语言)等级考试试卷(五级)解析T1、漫漫回国路 2020年5月,国际航班机票难求。一位在美国华盛顿的中国留学生,因为一些原因必须在本周内回到北京。现在已知各个机场之间的航班情况,求问他回不回得来(不考虑转机次数和机票价格)。 时间限制:10…...
刷题专练之链表(一)
文章目录前言一、 移除链表元素1.题目介绍2.思路3.代码二、反转链表1.题目介绍2.思路3.代码三、链表的中间结点1.题目介绍2.思路3.代码四、链表的中间结点1.题目介绍2.思路3.代码前言 以下是链表经常考的面试题,我在这里进行归纳和讲解,采取的是循序渐进…...
elasticsearch高级查询api
yml配置 #es配置 spring:elasticsearch:rest:uris: 192.168.16.188:9200添加依赖 <dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-client</artifactId> </dependency>使用编程的形式…...
力扣-股票的资本损益
大家好,我是空空star,本篇带大家了解一道简单的力扣sql练习题。 文章目录前言一、题目:1393. 股票的资本损益二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.正确示范③提交SQL运行结果4.正确示范④提交SQL运行结果5.其他…...
蓝桥杯刷题冲刺 | 倒计时26天
作者:指针不指南吗 专栏:蓝桥杯倒计时冲刺 🐾马上就要蓝桥杯了,最后的这几天尤为重要,不可懈怠哦🐾 文章目录1.路径2.特别数的和3.MP3储存4.求和1.路径 题目 链接: 路径 - 蓝桥云课 (lanqiao.cn…...
大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...
在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能
下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能,包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...
Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...
Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...
中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...
Caliper 负载(Workload)详细解析
Caliper 负载(Workload)详细解析 负载(Workload)是 Caliper 性能测试的核心部分,它定义了测试期间要执行的具体合约调用行为和交易模式。下面我将全面深入地讲解负载的各个方面。 一、负载模块基本结构 一个典型的负载模块(如 workload.js)包含以下基本结构: use strict;/…...
深入浅出Diffusion模型:从原理到实践的全方位教程
I. 引言:生成式AI的黎明 – Diffusion模型是什么? 近年来,生成式人工智能(Generative AI)领域取得了爆炸性的进展,模型能够根据简单的文本提示创作出逼真的图像、连贯的文本,乃至更多令人惊叹的…...
深度学习之模型压缩三驾马车:模型剪枝、模型量化、知识蒸馏
一、引言 在深度学习中,我们训练出的神经网络往往非常庞大(比如像 ResNet、YOLOv8、Vision Transformer),虽然精度很高,但“太重”了,运行起来很慢,占用内存大,不适合部署到手机、摄…...
Spring Security 认证流程——补充
一、认证流程概述 Spring Security 的认证流程基于 过滤器链(Filter Chain),核心组件包括 UsernamePasswordAuthenticationFilter、AuthenticationManager、UserDetailsService 等。整个流程可分为以下步骤: 用户提交登录请求拦…...
华为OD最新机试真题-数组组成的最小数字-OD统一考试(B卷)
题目描述 给定一个整型数组,请从该数组中选择3个元素 组成最小数字并输出 (如果数组长度小于3,则选择数组中所有元素来组成最小数字)。 输入描述 行用半角逗号分割的字符串记录的整型数组,0<数组长度<= 100,0<整数的取值范围<= 10000。 输出描述 由3个元素组成…...
