Spring IoC源码学习:obtainFreshBeanFactory 详解

@Override

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {

// Create a new XmlBeanDefinitionReader for the given BeanFactory.

// 1.为指定BeanFactory创建XmlBeanDefinitionReader

XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

// Configure the bean definition reader with this context’s

// resource loading environment.

// 2.使用此上下文的资源加载环境配置 XmlBeanDefinitionReader

beanDefinitionReader.setEnvironment(getEnvironment());

// resourceLoader赋值为XmlWebApplicationContext

beanDefinitionReader.setResourceLoader(this);

beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

// Allow a subclass to provide custom initialization of the reader,

// then proceed with actually loading the bean definitions.

initBeanDefinitionReader(beanDefinitionReader);

// 3.加载 bean 定义

loadBeanDefinitions(beanDefinitionReader);

}

3.加载 bean 定义,见代码块3详解

代码块3:loadBeanDefinitions


protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {

// 1.获取配置文件路径

String[] configLocations = getConfigLocations();

if (configLocations != null) {

for (String configLocation : configLocations) {

// 2.根据配置文件路径加载 bean 定义

reader.loadBeanDefinitions(configLocation);

}

}

}

// AbstractRefreshableWebApplicationContext.java

@Override

public String[] getConfigLocations() {

return super.getConfigLocations();

}

// AbstractRefreshableConfigApplicationContext.java

protected String[] getConfigLocations() {

return (this.configLocations != null ? this.configLocations : getDefaultConfigLocations());

}

// XmlWebApplicationContext.java

@Override

protected String[] getDefaultConfigLocations() {

if (getNamespace() != null) {

return new String[]{DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};

} else {

return new String[]{DEFAULT_CONFIG_LOCATION};

}

}

1.获取配置文件路径:如果 configLocations 属性不为空,则返回 configLocations 的值;否则,调用 getDefaultConfigLocations() 方法。在 Spring IoC:ApplicationContext 刷新前的配置 文中介绍过 configLocations 属性,该属性会被赋值为我们在web.xml中配置的 contextConfigLocation 的参数值,例如下图即为:classpath*:config/spring/appcontext-*.xml 。

如果 web.xml 中没有配置 contextConfigLocation 参数,则会拿到 Spring 默认的配置路径:/WEB-INF/applicationContext.xml。

2.根据配置文件路径加载 bean 定义,见代码块4详解

代码块4:loadBeanDefinitions


@Override

public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {

return loadBeanDefinitions(location, null);

}

public int loadBeanDefinitions(String location, Set actualResources) throws BeanDefinitionStoreException {

// 1.获取 resourceLoader,这边为 XmlWebApplicationContext

ResourceLoader resourceLoader = getResourceLoader();

if (resourceLoader == null) {

throw new BeanDefinitionStoreException(

“Cannot import bean definitions from location [” + location + “]: no ResourceLoader available”);

}

// 2.判断 resourceLoader 是否为 ResourcePatternResolver 的实例

if (resourceLoader instanceof ResourcePatternResolver) {

// Resource pattern matching available.

try {

// 2.1 根据路径拿到该路径下所有符合的配置文件,并封装成Resource

Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);

// 2.2 根据Resource,加载Bean的定义

int loadCount = loadBeanDefinitions(resources);

if (actualResources != null) {

for (Resource resource : resources) {

actualResources.add(resource);

}

}

if (logger.isDebugEnabled()) {

logger.debug(“Loaded " + loadCount + " bean definitions from location pattern [” + location + “]”);

}

return loadCount;

} catch (IOException ex) {

throw new BeanDefinitionStoreException(

“Could not resolve bean definition resource pattern [” + location + “]”, ex);

}

} else {

// Can only load single resources by absolute URL.

// 3.只能通过绝对URL加载单个资源

Resource resource = resourceLoader.getResource(location);

// 3.1 根据Resource,加载Bean的定义

int loadCount = loadBeanDefinitions(resource);

if (actualResources != null) {

actualResources.add(resource);

}

if (logger.isDebugEnabled()) {

logger.debug(“Loaded " + loadCount + " bean definitions from location [” + location + “]”);

}

return loadCount;

}

}

1.获取 resourceLoader:这个 resourceLoader 值为 XmlWebApplicationContext。在上文代码块2中会将 resourceLoader 属性值赋为 XmlWebApplicationContext。

2.判断 resourceLoader 是否为 ResourcePatternResolver 的实例:这边 resourceLoader  为 XmlWebApplicationContext,而 XmlWebApplicationContext 的继承关系如下图,可以看到是有实现 ResourcePatternResolver 接口的,因此这边判断结果为 true。

2.1 根据路径拿到该路径下所有符合的配置文件,并封装成 Resource。如果我们配置的路径为:classpath*:config/spring/appcontext-*.xml,并且项目配置如下图,则该方法会拿到2个配置文件:appcontext-bean.xml 和 appcontext-core.xml。

2.2 根据 Resource,加载 bean 定义,见代码块5详解

代码块5:loadBeanDefinitions


@Override

public int loadBeanDefinitions(Resource… resources) throws BeanDefinitionStoreException {

Assert.notNull(resources, “Resource array must not be null”);

int counter = 0;

// 1.遍历所有的Resource

for (Resource resource : resources) {

// 2.根据Resource加载bean的定义,XmlBeanDefinitionReader实现

counter += loadBeanDefinitions(resource);

}

return counter;

}

2.根据 Resource 加载 bean 定义,由 XmlBeanDefinitionReader 实现,见代码块6详解

方法块6:loadBeanDefinitions


@Override

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {

// 加载 bean 定义

return loadBeanDefinitions(new EncodedResource(resource));

}

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {

Assert.notNull(encodedResource, “EncodedResource must not be null”);

if (logger.isInfoEnabled()) {

logger.info("Loading XML bean definitions from " + encodedResource.getResource());

}

// 1.当前正在加载的EncodedResource

Set currentResources = this.resourcesCurrentlyBeingLoaded.get();

if (currentResources == null) {

currentResources = new HashSet(4);

this.resourcesCurrentlyBeingLoaded.set(currentResources);

}

// 2.将当前encodedResource添加到currentResources

if (!currentResources.add(encodedResource)) {

// 如果添加失败,代表当前的encodedResource已经存在,则表示出现了循环加载

throw new BeanDefinitionStoreException(

“Detected cyclic loading of " + encodedResource + " - check your import definitions!”);

}

try {

// 3.拿到Resource的inputStream

InputStream inputStream = encodedResource.getResource().getInputStream();

try {

// 4.将inputStream封装成org.xml.sax.InputSource

InputSource inputSource = new InputSource(inputStream);

if (encodedResource.getEncoding() != null) {

inputSource.setEncoding(encodedResource.getEncoding());

}

// 5.加载 bean 定义(方法以do开头,真正处理的方法)

return doLoadBeanDefinitions(inputSource, encodedResource.getResource());

} finally {

inputStream.close();

}

} catch (IOException ex) {

throw new BeanDefinitionStoreException(

"IOException parsing XML document from " + encodedResource.getResource(), ex);

} finally {

currentResources.remove(encodedResource);

if (currentResources.isEmpty()) {

this.resourcesCurrentlyBeingLoaded.remove();

}

}

}

5.加载 bean 定义,方法以 do 开头,真正处理的方法,见代码块7详解

代码块7:doLoadBeanDefinitions


protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)

throws BeanDefinitionStoreException {

try {

// 1.根据inputSource和resource加载XML文件,并封装成Document

Document doc = doLoadDocument(inputSource, resource);

// 2.根据返回的Document注册Bean信息(对配置文件的解析,核心逻辑)

return registerBeanDefinitions(doc, resource);

} 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);

}

}

1.根据 inputSource 和 resource 加载 XML文件,并封装成 Document,见代码块8详解

2.根据返回的 Document 注册 bean 信息,见代码块9详解

代码块8:doLoadDocument


protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {

// 1.getValidationModeForResource(resource): 获取XML配置文件的验证模式

// 2.documentLoader.loadDocument: 加载XML文件,并得到对应的 Document

return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,

getValidationModeForResource(resource), isNamespaceAware());

}

protected int getValidationModeForResource(Resource resource) {

int validationModeToUse = getValidationMode();

// 1.1 如果手动指定了XML文件的验证模式则使用指定的验证模式

if (validationModeToUse != VALIDATION_AUTO) {

return validationModeToUse;

}

// 1.2 如果未指定则使用自动检测

int detectedMode = detectValidationMode(resource);

// 1.3 如果检测出的验证模式不为 VALIDATION_AUTO, 则返回检测出来的验证模式

if (detectedMode != VALIDATION_AUTO) {

return detectedMode;

}

// Hmm, we didn’t get a clear indication… Let’s assume XSD,

// since apparently no DTD declaration has been found up until

// detection stopped (before finding the document’s root tag).

// 1.4 如果最终没找到验证模式,则使用 XSD

return VALIDATION_XSD;

}

protected int detectValidationMode(Resource resource) {

// 1.2.1 校验resource是否为open stream

if (resource.isOpen()) {

throw new BeanDefinitionStoreException(

“Passed-in Resource [” + resource + "] contains an open stream: " +

"cannot determine validation mode automatically. Either pass in a Resource " +

"that is able to create fresh streams, or explicitly specify the validationMode " +

“on your XmlBeanDefinitionReader instance.”);

}

InputStream inputStream;

try {

// 1.2.2 校验resource是否可以打开InputStream

inputStream = resource.getInputStream();

} catch (IOException ex) {

throw new BeanDefinitionStoreException(

“Unable to determine validation mode for [” + resource + "]: cannot open InputStream. " +

"Did you attempt to load directly from a SAX InputSource without specifying the " +

“validationMode on your XmlBeanDefinitionReader instance?”, ex);

}

try {

// 1.2.3 根据inputStream检测验证模式

return this.validationModeDetector.detectValidationMode(inputStream);

} catch (IOException ex) {

throw new BeanDefinitionStoreException(“Unable to determine validation mode for [” +

resource + “]: an error occurred whilst reading from the InputStream.”, ex);

}

}

public int detectValidationMode(InputStream inputStream) throws IOException {

// Peek into the file to look for DOCTYPE.

BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

try {

boolean isDtdValidated = false;

String content;

// 1.2.3.1 按行遍历xml配置文件,获取xml文件的验证模式

while ((content = reader.readLine()) != null) {

content = consumeCommentTokens(content);

// 如果读取的行是空或者注释则略过

if (this.inComment || !StringUtils.hasText(content)) {

continue;

}

// 内容包含"DOCTYPE"则为DTD,否则为XSD

if (hasDoctype(content)) {

isDtdValidated = true;

break;

}

// 如果content带有 ‘<’ 开始符号,则结束遍历。因为验证模式一定会在开始符号之前,所以到此可以认为没有验证模式

if (hasOpeningTag(content)) {

// End of meaningful data…

break;

}

}

// 1.2.3.2 根据遍历结果返回验证模式是 DTD 还是 XSD

return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);

} catch (CharConversionException ex) {

// Choked on some character encoding…

// Leave the decision up to the caller.

return VALIDATION_AUTO;

} finally {

reader.close();

}

}

// DefaultDocumentLoader.java

@Override

public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,

ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

// 2.1 创建DocumentBuilderFactory

DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);

if (logger.isDebugEnabled()) {

logger.debug(“Using JAXP provider [” + factory.getClass().getName() + “]”);

}

// 2.2 通过DocumentBuilderFactory创建DocumentBuilder

DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);

// 2.3 使用DocumentBuilder解析inputSource返回Document对象

return builder.parse(inputSource);

}

1.获取 XML 配置文件的验证模式。XML 文件的验证模式是用来保证 XML 文件的正确性,常见的验证模式有两种:DTD 和 XSD,以下简单展示下这两种验证模式的配置。

DTD 验证模式(已停止更新)

要使用 DTD 验证模式的时候需要在 XML 文件的头部声明,以下是在 Spring 中使用 DTD 声明方式的代码:

XSD 验证模式

从 Spring的 源码中可以看到,dtd 验证模式已经停止更新,因此目前使用的验证模式基本上是 XSD。

代码块9:registerBeanDefinitions


public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {

// 1.使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader

BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();

// 2.记录统计前BeanDefinition的加载个数

int countBefore = getRegistry().getBeanDefinitionCount();

// 3.createReaderContext:根据resource创建一个XmlReaderContext

// 4.registerBeanDefinitions:加载及注册Bean定义

documentReader.registerBeanDefinitions(doc, createReaderContext(resource));

// 5.返回本次加载的BeanDefinition个数

return getRegistry().getBeanDefinitionCount() - countBefore;

}

3.根据 resource 创建一个 XmlReaderContext,见代码块10详解

4.加载及注册 bean 定义,由 DefaultBeanDefinitionDocumentReader 实现,见代码块11详解

代码块10:createReaderContext


public XmlReaderContext createReaderContext(Resource resource) {

return new XmlReaderContext(resource, this.problemReporter, this.eventListener,

this.sourceExtractor, this, getNamespaceHandlerResolver());

}

public NamespaceHandlerResolver getNamespaceHandlerResolver() {

if (this.namespaceHandlerResolver == null) {

this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();

}

return this.namespaceHandlerResolver;

}

protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {

return new DefaultNamespaceHandlerResolver(getResourceLoader().getClassLoader());

}

// DefaultNamespaceHandlerResolver.java

public DefaultNamespaceHandlerResolver(ClassLoader classLoader) {

this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION);

}

public DefaultNamespaceHandlerResolver(ClassLoader classLoader, String handlerMappingsLocation) {

Assert.notNull(handlerMappingsLocation, “Handler mappings location must not be null”);

this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());

this.handlerMappingsLocation = handlerMappingsLocation;

}

这边会根据 resource 构建一个 XmlReaderContext,用于存放解析时会用到的一些上下文信息。

其中 namespaceHandlerResolver 会创建默认的 DefaultNamespaceHandlerResolver,DefaultNamespaceHandlerResolver的handlerMappingsLocation 属性会使用默认的值 “META-INF/spring.handlers”,并且这边有个重要的属性 handlerMappings,handlerMappings 用于存放命名空间和该命名空间handler类的映射,如下图:

handlerMappings 的值默认位于“META-INF/spring.handlers” 文件下,一般在我们定义自定义注解时需要用到。

代码块11:registerBeanDefinitions


@Override

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {

this.readerContext = readerContext;

logger.debug(“Loading bean definitions”);

// 1.拿到文档的子节点,对于Spring的配置文件来说,理论上应该都是

Element root = doc.getDocumentElement();

// 2.通过拿到的节点,注册 Bean 定义

doRegisterBeanDefinitions(root);

}

2.通过拿到的节点,注册 bean 定义,见代码块12详解

代码块12:doRegisterBeanDefinitions


protected void doRegisterBeanDefinitions(Element root) {

// Any nested elements will cause recursion in this method. In

// order to propagate and preserve default-* attributes correctly,

// keep track of the current (parent) delegate, which may be null. Create

// the new (child) delegate with a reference to the parent for fallback purposes,

// then ultimately reset this.delegate back to its original (parent) reference.

// this behavior emulates a stack of delegates without actually necessitating one.

BeanDefinitionParserDelegate parent = this.delegate;

// 构建BeanDefinitionParserDelegate

this.delegate = createDelegate(getReaderContext(), root, parent);

// 1.校验root节点的命名空间是否为默认的命名空间(默认命名空间http://www.springframework.org/schema/beans)

if (this.delegate.isDefaultNamespace(root)) {

// 2.处理profile属性

String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);

if (StringUtils.hasText(profileSpec)) {

String[] specifiedProfiles = StringUtils.tokenizeToStringArray(

profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);

// 校验当前节点的 profile 是否符合当前环境定义的, 如果不是则直接跳过, 不解析该节点下的内容

if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {

if (logger.isInfoEnabled()) {

logger.info(“Skipped XML bean definition file due to specified profiles [” + profileSpec +

"] not matching: " + getReaderContext().getResource());

}

return;

}

}

}

// 3.解析前处理, 留给子类实现

preProcessXml(root);
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

总目录展示

该笔记共八个节点(由浅入深),分为三大模块。

高性能。 秒杀涉及大量的并发读和并发写,因此支持高并发访问这点非常关键。该笔记将从设计数据的动静分离方案、热点的发现与隔离、请求的削峰与分层过滤、服务端的极致优化这4个方面重点介绍。

一致性。 秒杀中商品减库存的实现方式同样关键。可想而知,有限数量的商品在同一时刻被很多倍的请求同时来减库存,减库存又分为“拍下减库存”“付款减库存”以及预扣等几种,在大并发更新的过程中都要保证数据的准确性,其难度可想而知。因此,将用一个节点来专门讲解如何设计秒杀减库存方案。

高可用。 虽然介绍了很多极致的优化思路,但现实中总难免出现一些我们考虑不到的情况,所以要保证系统的高可用和正确性,还要设计一个PlanB来兜底,以便在最坏情况发生时仍然能够从容应对。笔记的最后,将带你思考可以从哪些环节来设计兜底方案。


篇幅有限,无法一个模块一个模块详细的展示(这些要点都收集在了这份《高并发秒杀顶级教程》里),麻烦各位转发一下(可以帮助更多的人看到哟!)

由于内容太多,这里只截取部分的内容。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!**

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

总目录展示

该笔记共八个节点(由浅入深),分为三大模块。

高性能。 秒杀涉及大量的并发读和并发写,因此支持高并发访问这点非常关键。该笔记将从设计数据的动静分离方案、热点的发现与隔离、请求的削峰与分层过滤、服务端的极致优化这4个方面重点介绍。

一致性。 秒杀中商品减库存的实现方式同样关键。可想而知,有限数量的商品在同一时刻被很多倍的请求同时来减库存,减库存又分为“拍下减库存”“付款减库存”以及预扣等几种,在大并发更新的过程中都要保证数据的准确性,其难度可想而知。因此,将用一个节点来专门讲解如何设计秒杀减库存方案。

高可用。 虽然介绍了很多极致的优化思路,但现实中总难免出现一些我们考虑不到的情况,所以要保证系统的高可用和正确性,还要设计一个PlanB来兜底,以便在最坏情况发生时仍然能够从容应对。笔记的最后,将带你思考可以从哪些环节来设计兜底方案。


篇幅有限,无法一个模块一个模块详细的展示(这些要点都收集在了这份《高并发秒杀顶级教程》里),麻烦各位转发一下(可以帮助更多的人看到哟!)

[外链图片转存中…(img-eX8C2e62-1713301649326)]

[外链图片转存中…(img-TaFNeX6e-1713301649326)]

由于内容太多,这里只截取部分的内容。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 17
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值