【死磕 Spring】----- IOC 之 加载 Bean

简介:

先看一段熟悉的代码:

 
  1. ClassPathResource resource = new ClassPathResource("bean.xml");

  2. DefaultListableBeanFactory factory = new DefaultListableBeanFactory();

  3. XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);

  4. reader.loadBeanDefinitions(resource);

这段代码是 Spring 中编程式使用 IOC 容器,通过这四段简单的代码,我们可以初步判断 IOC 容器的使用过程。

●  获取资源
●  获取 BeanFactory
●  根据新建的 BeanFactory 创建一个BeanDefinitionReader对象,该Reader 对象为资源的解析器
●  装载资源

整个过程就分为三个步骤:资源定位、装载、注册,如下:

414b491b1268bbb96c2ad7fa92cc5ea3f93af372
● 资源定位 。我们一般用外部资源来描述 Bean 对象,所以在初始化 IOC 容器的第一步就是需要定位这个外部资源。在上一篇博客(【死磕 Spring】----- IOC 之 Spring 统一资源加载策略)已经详细说明了资源加载的过程。
● 装载 。装载就是 BeanDefinition 的载入。BeanDefinitionReader 读取、解析 Resource 资源,也就是将用户定义的 Bean 表示成 IOC 容器的内部数据结构:BeanDefinition。在 IOC 容器内部维护着一个 BeanDefinition Map 的数据结构,在配置文件中每一个 <bean> 都对应着一个BeanDefinition对象。
● 注册 。向IOC容器注册在第二步解析好的 BeanDefinition,这个过程是通过 BeanDefinitionRegistery 接口来实现的。在 IOC 容器内部其实是将第二个过程解析得到的 BeanDefinition 注入到一个 HashMap 容器中,IOC 容器就是通过这个 HashMap 来维护这些 BeanDefinition 的。在这里需要注意的一点是这个过程并没有完成依赖注入,依赖注册是发生在应用第一次调用 getBean() 向容器索要 Bean 时。当然我们可以通过设置预处理,即对某个 Bean 设置 lazyinit 属性,那么这个 Bean 的依赖注入就会在容器初始化的时候完成。

资源定位在前面已经分析了,下面我们直接分析加载,上面提过 reader.loadBeanDefinitions(resource) 才是加载资源的真正实现,所以我们直接从该方法入手。

 
  1. public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {

  2. return loadBeanDefinitions(new EncodedResource(resource));

  3. }

从指定的 xml 文件加载 Bean Definition,这里会先对 Resource 资源封装成 EncodedResource。这里为什么需要将 Resource 封装成 EncodedResource呢?主要是为了对 Resource 进行编码,保证内容读取的正确性。封装成 EncodedResource 后,调用 loadBeanDefinitions(),这个方法才是真正的逻辑实现。如下:

 
  1. public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {

  2. Assert.notNull(encodedResource, "EncodedResource must not be null");

  3. if (logger.isInfoEnabled()) {

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

  5. }

  6. // 获取已经加载过的资源

  7. Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();

  8. if (currentResources == null) {

  9. currentResources = new HashSet<>(4);

  10. this.resourcesCurrentlyBeingLoaded.set(currentResources);

  11. }

  12. // 将当前资源加入记录中

  13. if (!currentResources.add(encodedResource)) {

  14. throw new BeanDefinitionStoreException(

  15. "Detected cyclic loading of " + encodedResource + " - check your import definitions!");

  16. }

  17. try {

  18. // 从 EncodedResource 获取封装的 Resource 并从 Resource 中获取其中的 InputStream

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

  20. try {

  21. InputSource inputSource = new InputSource(inputStream);

  22. // 设置编码

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

  24. inputSource.setEncoding(encodedResource.getEncoding());

  25. }

  26. // 核心逻辑部分

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

  28. }

  29. finally {

  30. inputStream.close();

  31. }

  32. }

  33. catch (IOException ex) {

  34. throw new BeanDefinitionStoreException(

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

  36. }

  37. finally {

  38. // 从缓存中剔除该资源

  39. currentResources.remove(encodedResource);

  40. if (currentResources.isEmpty()) {

  41. this.resourcesCurrentlyBeingLoaded.remove();

  42. }

  43. }

  44. }

首先通过 resourcesCurrentlyBeingLoaded.get() 来获取已经加载过的资源,然后将 encodedResource 加入其中,如果 resourcesCurrentlyBeingLoaded 中已经存在该资源,则抛出 BeanDefinitionStoreException 异常。完成后从 encodedResource 获取封装的 Resource 资源并从 Resource 中获取相应的 InputStream ,最后将 InputStream 封装为 InputSource 调用 doLoadBeanDefinitions()。方法 doLoadBeanDefinitions() 为从 xml 文件中加载 Bean Definition 的真正逻辑,如下:

 
  1. protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)

  2. throws BeanDefinitionStoreException {

  3. try {

  4. // 获取 Document 实例

  5. Document doc = doLoadDocument(inputSource, resource);

  6. // 根据 Document 实例****注册 Bean信息

  7. return registerBeanDefinitions(doc, resource);

  8. }

  9. catch (BeanDefinitionStoreException ex) {

  10. throw ex;

  11. }

  12. catch (SAXParseException ex) {

  13. throw new XmlBeanDefinitionStoreException(resource.getDescription(),

  14. "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);

  15. }

  16. catch (SAXException ex) {

  17. throw new XmlBeanDefinitionStoreException(resource.getDescription(),

  18. "XML document from " + resource + " is invalid", ex);

  19. }

  20. catch (ParserConfigurationException ex) {

  21. throw new BeanDefinitionStoreException(resource.getDescription(),

  22. "Parser configuration exception parsing XML from " + resource, ex);

  23. }

  24. catch (IOException ex) {

  25. throw new BeanDefinitionStoreException(resource.getDescription(),

  26. "IOException parsing XML document from " + resource, ex);

  27. }

  28. catch (Throwable ex) {

  29. throw new BeanDefinitionStoreException(resource.getDescription(),

  30. "Unexpected exception parsing XML document from " + resource, ex);

  31. }

  32. }

核心部分就是 try 块的两行代码。

●  调用 doLoadDocument() 方法,根据 xml 文件获取 Document 实例。
●  根据获取的 Document 实例注册 Bean 信息。

其实在 doLoadDocument()方法内部还获取了 xml 文件的验证模式。如下:

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

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

  3. getValidationModeForResource(resource), isNamespaceAware());

  4. }

调用 getValidationModeForResource() 获取指定资源(xml)的验证模式。所以 doLoadBeanDefinitions()主要就是做了三件事情。

●  调用 getValidationModeForResource() 获取 xml 文件的验证模式
●  调用 loadDocument() 根据 xml 文件获取相应的 Document 实例。
●  调用 registerBeanDefinitions() 注册 Bean 实例。

原文发布时间为:2018-09-7
本文作者:chenssy
本文来自云栖社区合作伙伴“ Java技术驿站”,了解相关信息可以关注“ Java技术驿站”。
相关文章
|
2天前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
6天前
|
XML Java 数据格式
Spring容器Bean之XML配置方式
通过对以上内容的掌握,开发人员可以灵活地使用Spring的XML配置方式来管理应用程序的Bean,提高代码的模块化和可维护性。
33 6
|
8天前
|
XML Java 数据格式
🌱 深入Spring的心脏:Bean配置的艺术与实践 🌟
本文深入探讨了Spring框架中Bean配置的奥秘,从基本概念到XML配置文件的使用,再到静态工厂方式实例化Bean的详细步骤,通过实际代码示例帮助读者更好地理解和应用Spring的Bean配置。希望对你的Spring开发之旅有所助益。
52 3
|
22天前
|
安全 Java 开发者
Spring容器中的bean是线程安全的吗?
Spring容器中的bean默认为单例模式,多线程环境下若操作共享成员变量,易引发线程安全问题。Spring未对单例bean做线程安全处理,需开发者自行解决。通常,Spring bean(如Controller、Service、Dao)无状态变化,故多为线程安全。若涉及线程安全问题,可通过编码或设置bean作用域为prototype解决。
32 1
|
1天前
|
存储 Java Spring
【Spring】获取Bean对象需要哪些注解
@Conntroller,@Service,@Repository,@Component,@Configuration,关于Bean对象的五个常用注解
|
1天前
|
存储 Java 应用服务中间件
【Spring】IoC和DI,控制反转,Bean对象的获取方式
IoC,DI,控制反转容器,Bean的基本常识,类注解@Controller,获取Bean对象的常用三种方式
|
2月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
244 2
|
2天前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
9天前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
53 14
|
1月前
|
缓存 IDE Java
SpringBoot入门(7)- 配置热部署devtools工具
SpringBoot入门(7)- 配置热部署devtools工具
49 1
SpringBoot入门(7)- 配置热部署devtools工具