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;}
}
我们看一下启动的效果:
我会在类初始化前调用
我是类的初始化
我会在类初始化后调用
相关文章:

2023年再不会 IOC 源码,就要被淘汰了
👏作者简介:大家好,我是爱敲代码的小黄,独角兽企业的Java开发工程师,CSDN博客专家,阿里云专家博主📕系列专栏:Java设计模式、数据结构和算法、Kafka从入门到成神、Kafka从成神到升仙…...

MQ面试题
1、为什么使用消息队列? 其实就是问问你消息队列都有哪些使用场景,然后你项目里具体是什么场景,说说你在这个场景里用消息队列是什么? 面试官问你这个问题,期望的一个回答是说,你们公司有个什么业务场景&…...

pnpm 基本详细使用(安装、卸载、使用)
一、简介 官网地址、GitHub地址、官方安装文档、官方卸载文档。 pnpm 全称 performant npm,意思为 高性能的 npm。pnpm 由 npm/yarn 衍生而来,解决了 npm/yarn 内部潜在的 bug,极大的优化了性能,扩展了使用场景。被誉为 最先进的…...

Kafka生产者的粘性分区算法
分区算法分类 kafka在生产者投递消息时,会根据是否有key采取不用策略来获取分区。 存在key时会根据key计算一个hash值,然后采用hash%分区数的方式获取对应的分区。 而不存在key时采用随机算法选取分区,然后将所有的消息封装到这个batch上直…...

java基础篇
1.基础篇注释注释是在程序指定位置添加的说明性信息注释不参与程序运行,仅起到说明作用单行注释 格式:// 注释信息多行注释 格式:/* 注释信息 */关键字关键字:就是被Java语言赋予了特定含义的单词java中共有53个关键字1.全部有小写…...

Java与Winform进行AES加解密数据传输的工具类与对应关系和示例
场景 AndroidJava中使用Aes对称加密的工具类与使用: AndroidJava中使用Aes对称加密的工具类与使用_霸道流氓气质的博客-CSDN博客 上面讲的Java与安卓进行数据传输时使用AES加解密的示例工具类。 如果Java需要与其他第三方平台比如Winform程序进行数据传递时也需…...
OpenAI模型的API调用与使用-测试(2)
OpenAI模型的API调用与使用-测试(2)1. 参考Quick start搭建一个demo1.1 安装openai包1.2 demo测试11.3 demo测试2参考资料1. 参考Quick start搭建一个demo 1.1 安装openai包 注意关掉科学上网工具,下载openai包 pip install openai安装好后…...

【LeetCode】剑指 Offer 22. 链表中倒数第k个节点 p136 -- Java Version
题目链接:https://leetcode.cn/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/ 1. 题目介绍(22. 链表中倒数第k个节点) 输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数&…...
经典卷积模型回顾7-轻量化模型MobileNet实现图像分类(matlab)
MobileNet是一种轻量级卷积神经网络,适用于较小的设备和低功耗环境。在MATLAB中,可以使用Deep Learning Toolbox进行MobileNet的图像分类训练。 使用预先训练好的MobileNet模型对自定义数据集进行微调训练: matlab % 导入数据集 imds im…...

程序员压力大?用 PyQt 做一个美*女GIF设置桌面,每天都有好心情
嗨害大家好鸭!我是小熊猫~ 要说程序员工作的最大压力不是来自于工作本身, 而是来自于需要不断学习才能更好地完成工作, 因为程序员工作中面对的编程语言是在不断更新的, 同时还要学习熟悉其他语言来提升竞争力… 好了,…...
Shell命令——sed命令
以下内容整理于《linux命令行与shell脚本编程大全【第三版】》一书。 一、简介sed编辑器 1、sed编辑器的本质 sed是stream editor的缩写,中文意思是“流编辑器”。 sed编辑器是一个命令行编辑器,也就是可以在命令行上完成数据的处理(替换、…...
C语言练习 | 初学者经典练习汇(2)
目录 1、编写一个程序从1到100中,所有出现9的个数 2、分数求和 3、10个整形数字中选出最大值 4、打印9*9的乘法口诀 5、字符串逆序 6、计算一个数的每位之和(递归实现) 7、递归实现n的K次方 8、写个冒泡排序,把一个整形数组变成升序。 9、二进制…...

git分支
分支什么是分支在版本控制过程中,同时推进多个任务,为每个任务,我们就可以创建每个任务的单独分支。使用分支意味着程序员可以把自己的工作从开发主线上分离开来,开发自己分支的时候,不会影响主线分支的运行。对于初学…...
Java每天15道面试题 | redisII
1、什么是 Redis?简述它的优缺点? Redis 本质上是一个 Key-Value 类型的内存数据库,很像 memcached,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据 flush 到硬盘上进行保存。因为是纯内存操作&a…...

浏览器渲染原理
阶段 - Parse 1、解析HTML,浏览器将从服务器获取到的HTML文件之后,会产生一个渲染任务,交给消息队列(EventLoop/MessageLoop)。 2、在事件循环机制的作用下,会将渲染任务交给主线程 3、主线程在获取到渲染…...
华为OD机试题 - 查找单入口空闲区域(JavaScript)| 含思路
华为OD机试题 最近更新的博客使用说明本篇题解:查找单入口空闲区域题目输入输出示例一输入输出说明示例二输入输出说明示例三输入输出说明示例四输入输出说明Code解题思路华为OD其它语言版本<...

制造型企业想要做好数字化改造,要注意以下几点!
很多企业在“工业4.0、智能制造、互联网”等概念满天飞的环境下迷失了方向,不知该如何下手,盲目跟风,看别人投自动化,自己也跟着投,看别人上信息化,自己也跟着上。 其实,智能制造也好ÿ…...
【蓝桥杯集训·每日一题】AcWing 1488. 最短距离
文章目录一、题目1、原题链接2、题目描述二、解题报告1、思路分析2、时间复杂度3、代码详解三、知识风暴Dijkstra算法一、题目 1、原题链接 1488. 最短距离 2、题目描述 有 N 个村庄,编号 1 到 N。 村庄之间有 M 条无向道路,第 i 条道路连接村庄 ai 和村…...

比亚迪:全球最大电动汽车制造商的坎坷成长之路
来源:猛兽财经 作者:猛兽财经 特斯拉(TSLA)首席执行官埃隆马斯克表示,特斯拉最接近的竞争对手可能是一家中国电动汽车公司。猛兽财经认为,沃伦•巴菲特支持的比亚迪(0211)可能是马斯…...

Java开发 - Quartz初体验
前言 在上一篇博客中,我们对单点登录有了初步了解,这也让我们独立做系统有了最基础的保障。但在业务开发中,总是会出现一些定期处理的任务,我们首先想到的是Timer,但由于其调度功能单一,我们实际并不会用它…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...

K8S认证|CKS题库+答案| 11. AppArmor
目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...

学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...

企业如何增强终端安全?
在数字化转型加速的今天,企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机,到工厂里的物联网设备、智能传感器,这些终端构成了企业与外部世界连接的 “神经末梢”。然而,随着远程办公的常态化和设备接入的爆炸式…...

springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
动态 Web 开发技术入门篇
一、HTTP 协议核心 1.1 HTTP 基础 协议全称 :HyperText Transfer Protocol(超文本传输协议) 默认端口 :HTTP 使用 80 端口,HTTPS 使用 443 端口。 请求方法 : GET :用于获取资源,…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

android RelativeLayout布局
<?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"android:gravity&…...

Axure 下拉框联动
实现选省、选完省之后选对应省份下的市区...