【Spring】Spring ioc源码学习以及关于阅读源码方式的一些小笔记
创始人
2025-05-31 00:41:11
0次

Spring ioc源码学习以及关于阅读源码方式的一些小笔记

学习源码的方式

最好的方法是带着问题去学习。
如果拿到的是一个新的框架源码,首先应当去搞懂如何使用,这个框架能做什么事。接下来可以采取两种方式:
第一种:

  1. 第一步了解它的核心概念有哪些,确定你要学习的目标
  2. 由整体到局部的分析,先拆分目标,这个框架为了达到这个效果做了哪些步骤
  3. 找到每个步骤对应的入口,先理清楚主干流程,再详细看具体细节
  4. 理清楚之后针对其可扩展的部分,自己编写一些代码去验证是否能实现(多折腾)

第二种:

  1. 第一步了解它的核心概念有哪些,确定你要学习的目标
  2. 自己动手去实现
  3. 自己实现完之后再对比框架源码,思考为什么源码此处要这么写

由于本人自认为水平欠佳,在阅读过程中采取的是第一种方式。

做了些什么

ioc容器最核心的功能就是帮助用户管理bean,把用户自己创建的模式改成由容器创建的模式,所以至少需要两个步骤:创建bean和获取bean。

实现的步骤

主要有如下几个步骤:

  1. 用户配置bean定义
  2. 容器读取用户配置的bean定义
  3. 容器创建bean定义
  4. 用户从容器中读取bean

首先找到加载的入口,此处以xml配置方式为例,注解方式后续补充:
xml配置方式的入口为:ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("lalala.xml");

把握整体

ApplicationContext是Spring中的高级容器,它内嵌了一个BeanFactory, ClassPathXmlApplicationContext也是ApplicaitonContext的实现类。下面是ApplicationContext的类图。

在这里插入图片描述

这里采用了设计模式中的装饰模式(有疑问,算装饰模式吗):

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver {@NullableString getId();String getApplicationName();String getDisplayName();long getStartupDate();@NullableApplicationContext getParent();AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;
}

可以看到ApplicationContext 继承的功能有:

  • EnvironmentCapable: 获取环境相关变量
  • ListableBeanFactory: 提供BeanFactory容器
  • HierarchicalBeanFactory: 提供父子容器相关的内容
  • MessageSource: 提供国际化的内容
  • ApplicationEventPublisher: 提供事件发布机制
  • ResourcePatternResolver : 提供资源解析器

具体实现

第一次读源码的时候首先要找准一个你熟悉的过程,debug打断点,获取到它的调用栈并截图保存。 需要关注的重点是入参在这些方法中是如何变化的,从而知道每一个类是干什么用的。

接下来再将步骤拆分的细一点:

  1. 读取用户配置的xml文件
  2. 将xml树解析为beanDefinition
  3. 创建beanFactory
  4. 将beanDefinition注册到beanFactory

首先我们观察到启动时候的入参只有一个xml文件的地址:

/*** xml文件配置方式*/private static void testClassPathXmlApplicationContext() {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("lalala.xml");for (String beanName : context.getBeanDefinitionNames()) {System.out.println(beanName);}System.out.println(context.getBean(Bean2.class).getBean1());}

那第一步就要先将这个xml文件配置的内容解析成beanDefinition。上面提到过ResourcePatternResolver 是用来解析资源的,点开ClassPathXmlApplicationContext的类图,找到上层哪个类持有这个解析器->AbstractApplicationContext。
在这里插入图片描述
然后定位到对应的方法,再一步一步往下跟踪,能够得到如下的调用栈:

在这里插入图片描述
下面拿出一些个人认为重要的步骤进行说明:

  1. 在org.springframework.context.support.AbstractRefreshableApplicationContext#refreshBeanFactory()方法中新建了一个BeanFactory.
    它的默认类型是DefaultListableBeanFactory。
    在这里插入图片描述
  2. 在org.springframework.context.support.AbstractXmlApplicationContext#loadBeanDefinitions(DefaultListableBeanFactory beanFactory) 中新建了一个XmlBeanDefinitionReader,容器在此处将xml文件的解析过程委托给了这个XmlBeanDefinitionReader进行下一步的解析:

在这里插入图片描述
3. 在org.springframework.beans.factory.support.AbstractBeanDefinitionReader 中读取xml的内容并将其转换为Resource 对象。对于多个配置文件是进行循环读取。
在这里插入图片描述

  1. 在org.springframework.beans.factory.xml.XmlBeanDefinitionReader中有一处转换是将Resource 转化为EncodedResource
@Overridepublic int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {return loadBeanDefinitions(new EncodedResource(resource));}
  1. 在org.springframework.beans.factory.xml.XmlBeanDefinitionReader 中开始进行注册beanDefinition的行为
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)throws BeanDefinitionStoreException {try {Document doc = doLoadDocument(inputSource, resource);int count = registerBeanDefinitions(doc, resource);if (logger.isDebugEnabled()) {logger.debug("Loaded " + count + " bean definitions from " + resource);}return count;}catch (BeanDefinitionStoreException ex) {throw ex;}catch (SAXParseException ex) {throw new XmlBeanDefinitionStoreException(resource.getDescription(),"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);}catch (SAXException ex) {throw new XmlBeanDefinitionStoreException(resource.getDescription(),"XML document from " + resource + " is invalid", ex);}catch (ParserConfigurationException ex) {throw new BeanDefinitionStoreException(resource.getDescription(),"Parser configuration exception parsing XML from " + resource, ex);}catch (IOException ex) {throw new BeanDefinitionStoreException(resource.getDescription(),"IOException parsing XML document from " + resource, ex);}catch (Throwable ex) {throw new BeanDefinitionStoreException(resource.getDescription(),"Unexpected exception parsing XML document from " + resource, ex);}}

注册BeanDefinition的行为实际是被委托给了BeanDefinitionDocumentReader 去完成。

  1. 在org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader 中进行实际的BeanDefinition 注册过程。
protected void doRegisterBeanDefinitions(Element root) {BeanDefinitionParserDelegate parent = this.delegate;// 处理嵌套的标签this.delegate = createDelegate(getReaderContext(), root, parent);// 判断是否是默认命名空间  http://www.springframework.org/schema/beansif (this.delegate.isDefaultNamespace(root)) {// 判断运行的环境 profile属性String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);if (StringUtils.hasText(profileSpec)) {String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {if (logger.isDebugEnabled()) {logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +"] not matching: " + getReaderContext().getResource());}return;}}}// xml的前置处理preProcessXml(root);// 处理BeanDefinitionparseBeanDefinitions(root, this.delegate);// xml的后置处理postProcessXml(root);this.delegate = parent;}

进入parseBeanDefinitions方法:

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)) {// 处理默认元素 import alias bean beansparseDefaultElement(ele, delegate);}else {delegate.parseCustomElement(ele);}}}}else {delegate.parseCustomElement(root);}}

下面来看看它怎么解析bean标签的:

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);if (bdHolder != null) {bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);try {BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());}catch (BeanDefinitionStoreException ex) {getReaderContext().error("Failed to register bean definition with name '" +bdHolder.getBeanName() + "'", ele, ex);}getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));}}
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {// 获取id属性的值String id = ele.getAttribute(ID_ATTRIBUTE);// 获取name属性的值String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);// 获取别名List aliases = new ArrayList<>();if (StringUtils.hasLength(nameAttr)) {String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);aliases.addAll(Arrays.asList(nameArr));}String beanName = id;if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {beanName = aliases.remove(0);if (logger.isTraceEnabled()) {logger.trace("No XML 'id' specified - using '" + beanName +"' as bean name and " + aliases + " as aliases");}}// 校验名称的唯一性if (containingBean == null) {checkNameUniqueness(beanName, aliases, ele);}AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);if (beanDefinition != null) {if (!StringUtils.hasText(beanName)) {try {if (containingBean != null) {beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);}else {beanName = this.readerContext.generateBeanName(beanDefinition);String beanClassName = beanDefinition.getBeanClassName();if (beanClassName != null &&beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {aliases.add(beanClassName);}}if (logger.isTraceEnabled()) {logger.trace("Neither XML 'id' nor 'name' specified - " +"using generated bean name [" + beanName + "]");}}catch (Exception ex) {error(ex.getMessage(), ele);return null;}}String[] aliasesArray = StringUtils.toStringArray(aliases);return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);}return null;}
@Nullablepublic AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, @Nullable BeanDefinition containingBean) {this.parseState.push(new BeanEntry(beanName));String className = null;// 获取class属性的值if (ele.hasAttribute(CLASS_ATTRIBUTE)) {className = ele.getAttribute(CLASS_ATTRIBUTE).trim();}String parent = null;// 获取parent属性的值if (ele.hasAttribute(PARENT_ATTRIBUTE)) {parent = ele.getAttribute(PARENT_ATTRIBUTE);}try {// 创建beanDefinition 默认的beanDefinition类型是GenericBeanDefinitionAbstractBeanDefinition bd = createBeanDefinition(className, parent);// 解析scope abstract lazy-init(默认为false) autowire depends-on autowire-candidate primary init-method destory-method factory-method factory-bean 属性 这里有部分属性会在populateDefaults中设置一遍parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));// 解析meta标签parseMetaElements(ele, bd);// 解析lookup-methodparseLookupOverrideSubElements(ele, bd.getMethodOverrides());// 解析replaced-methodparseReplacedMethodSubElements(ele, bd.getMethodOverrides());// 解析constructor-argparseConstructorArgElements(ele, bd);// 解析propertyparsePropertyElements(ele, bd);// 解析qualifierparseQualifierElements(ele, bd);bd.setResource(this.readerContext.getResource());bd.setSource(extractSource(ele));return bd;}catch (ClassNotFoundException ex) {error("Bean class [" + className + "] not found", ele, ex);}catch (NoClassDefFoundError err) {error("Class that bean class [" + className + "] depends on not found", ele, err);}catch (Throwable ex) {error("Unexpected failure during bean definition parsing", ele, ex);}finally {this.parseState.pop();}return null;}

在org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.populateDefaults方法中会先处理如下属性内容:

protected void populateDefaults(DocumentDefaultsDefinition defaults, @Nullable DocumentDefaultsDefinition parentDefaults, Element root) {String lazyInit = root.getAttribute(DEFAULT_LAZY_INIT_ATTRIBUTE);if (isDefaultValue(lazyInit)) {lazyInit = (parentDefaults != null ? parentDefaults.getLazyInit() : FALSE_VALUE);}defaults.setLazyInit(lazyInit);String merge = root.getAttribute(DEFAULT_MERGE_ATTRIBUTE);if (isDefaultValue(merge)) {merge = (parentDefaults != null ? parentDefaults.getMerge() : FALSE_VALUE);}defaults.setMerge(merge);String autowire = root.getAttribute(DEFAULT_AUTOWIRE_ATTRIBUTE);if (isDefaultValue(autowire)) {autowire = (parentDefaults != null ? parentDefaults.getAutowire() : AUTOWIRE_NO_VALUE);}defaults.setAutowire(autowire);if (root.hasAttribute(DEFAULT_AUTOWIRE_CANDIDATES_ATTRIBUTE)) {defaults.setAutowireCandidates(root.getAttribute(DEFAULT_AUTOWIRE_CANDIDATES_ATTRIBUTE));}else if (parentDefaults != null) {defaults.setAutowireCandidates(parentDefaults.getAutowireCandidates());}if (root.hasAttribute(DEFAULT_INIT_METHOD_ATTRIBUTE)) {defaults.setInitMethod(root.getAttribute(DEFAULT_INIT_METHOD_ATTRIBUTE));}else if (parentDefaults != null) {defaults.setInitMethod(parentDefaults.getInitMethod());}if (root.hasAttribute(DEFAULT_DESTROY_METHOD_ATTRIBUTE)) {defaults.setDestroyMethod(root.getAttribute(DEFAULT_DESTROY_METHOD_ATTRIBUTE));}else if (parentDefaults != null) {defaults.setDestroyMethod(parentDefaults.getDestroyMethod());}defaults.setSource(this.readerContext.extractSource(root));}

接下来回到: BeanDefinitionReaderUtils.registerBeanDefinition 方法,它内层是这样的:

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)throws BeanDefinitionStoreException {Assert.hasText(beanName, "Bean name must not be empty");Assert.notNull(beanDefinition, "BeanDefinition must not be null");if (beanDefinition instanceof AbstractBeanDefinition) {try {// 验证beanDefinition的完整性((AbstractBeanDefinition) beanDefinition).validate();}catch (BeanDefinitionValidationException ex) {throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,"Validation of bean definition failed", ex);}}BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);// 判断容器中是否已经存在当前beanDefinitionif (existingDefinition != null) {if (!isAllowBeanDefinitionOverriding()) {throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);}else if (existingDefinition.getRole() < beanDefinition.getRole()) {}else if (!beanDefinition.equals(existingDefinition)) {}else {}this.beanDefinitionMap.put(beanName, beanDefinition);}else {if (hasBeanCreationStarted()) {synchronized (this.beanDefinitionMap) {this.beanDefinitionMap.put(beanName, beanDefinition);List updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);updatedDefinitions.addAll(this.beanDefinitionNames);updatedDefinitions.add(beanName);this.beanDefinitionNames = updatedDefinitions;removeManualSingletonName(beanName);}}else {this.beanDefinitionMap.put(beanName, beanDefinition);this.beanDefinitionNames.add(beanName);removeManualSingletonName(beanName);}this.frozenBeanDefinitionNames = null;}if (existingDefinition != null || containsSingleton(beanName)) {resetBeanDefinition(beanName);}else if (isConfigurationFrozen()) {clearByTypeCache();}}

从此看出ioc容器中的BeanDefinition都存放在beanDefinitionMap中。
此时beanDefinition已经加载完成。

ps: 嵌套bean标签注入的用法:

如果某个Bean所依赖的Bean不想被Spring容器直接访问,可以使用嵌套Bean。元素用来定义嵌套Bean,嵌套Bean只对嵌套它的外部Bean有效,Spring容器无法直接访问嵌套Bean,因此定义嵌套Bean时无须指定id属性。




时序图如下:

在这里插入图片描述

遗留的问题

  1. EncodedResource 的作用?
  2. BeanDefinitionHolder 的作用?
  3. 解析自定义标签的时候,readContext里面的内容是从哪里来的?
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {String namespaceUri = getNamespaceURI(ele);if (namespaceUri == null) {return null;}// 这里NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);if (handler == null) {error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);return null;}return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));}

参考资料

1.小不点啊的spring源码阅读系列文
2.注入嵌套bean的用法

相关内容

热门资讯

在大吉订购APP订购交易亏损被...   近期,不少投资者反映在“大吉订购App”上投资白银、铂金等商品遭遇严重亏损。该平台宣称提供现货订...
铂银壹号APP商品订购交易亏损...   许多中小投资者误以为网上的“商品现货订购”是合规合法的理财渠道,殊不知这背后隐藏着巨大的合规风险...
兴鑫淘金APP贵金属订购交易亏...   白银、贵金属或其他商品类投资,除国家批准的正规期货交易场所及持牌期货公司外,市面上大量第三方交易...
铂银掘金APP无资质的违规平台...   铂银掘金等平台的投资者,往往是被“注册送现金”“充值返现券”“首单免亏”等广告话术吸引,进而下载...
八方淘金APP订购交易不合规,...   “八方淘金APP”“铂银掘金APP”“铂银壹号APP”等软件被指以“现货订购”为名,实际采用高杠...
在指尖订购APP订购商品亏损被...   指尖订购APP这类平台,表面是“订购交易”,实则是披着电商外衣的非法期货陷阱。绝大多数投资者被蒙...
在犀贝易购APP做商品订购交易...   市面上打着“现货投资”“贵金属交易”旗号的APP并不少,但真正合规、受监管的平台并不多。犀贝易购...
易达淘金APP虚假商品订购交易...   易达淘金APP存在严重的虚假宣传与违规交易行为。该平台打着“商品订购”的旗号,实则通过不合规的交...
大浪淘金APP白银订购亏损被骗...   大浪淘金APP最隐蔽的套路,在于其完全封闭的内盘交易系统。表面上,它宣称与国际贵金属市场接轨;实...
一品好购APP上订购白银出现亏...   一品好购APP原名“一品订购”,运营方为上海宝智未来信息技术有限公司。近期不少投资者反映在该平台...
九州订购APP贵金属订购投资骗...   九州订购APP正通过抖.音、快手、今日头条等平台大量投放投资广告,用“5元起投”“充值返现”“0...
坚锅淘金APP交易软件并不正规...   当前,市面上大量所谓的“现货商城订购”交易软件,早已偏离了正常的实物商品买卖,演变为高杠杆、高手...
斗金订购APP贵金属期货交易骗...   斗金订购APP的投资者往往被铺天盖地的广告宣传所诱导——注册即送现金、充值返现券等看似诱人的福利...
警惕!云启购APP违规经营,披...   "5元起投、充值返现、稳赚不赔"——这些诱人的广告语背后,可能是一个精心设计的金融陷阱。  近期...
量子淘金APP贵金属投资不合规...   "4元起投""充值返券""月入过万"——这些极具诱惑力的广告语在短视频平台、社交软件上铺天盖地。...
鑫汇智投APP贵金属商品订购骗...   鑫汇智投APP非法期货投资骗局,受害者在投资过程中根本不懂交易,也不去研究和验证这个平台是否正规...
矅光智策APP平台违规经营非法...   矅光智策APP对外宣称是“商城订购”“现货交易”,但查询其经营范围,并未显示具备开展现货期货交易...
黄金珠宝怎么选?内侧刻字都代表... 潮新闻客户端 记者 马焱 通讯员 任倩影 9月8日下午,“云游杭州 乐购金秋”——“浙城·浙放心”活...
原创 冰... 最近全球国债市场正在上演魔幻一幕: 美债、日债、欧债遭遇猛烈抛售,收益率冲到几十年新高,美国财政部都...
日韩股市开盘表现分化 上证报中国证券网讯 9月9日,日韩股市开盘表现分化,韩国综合指数开盘上涨0.26%,报6972.87...