您现在的位置是:首页 >技术交流 >Spring概述网站首页技术交流

Spring概述

静看º一季花开花落 2024-06-17 10:22:18
简介Spring概述

介绍

​ Spring是为了简化JavaEE开发。 Spring是一个提供了更完善开发环境的一个框架,可以为POJO面向对象提供企业级的服务。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情,是个分层架构。

Spring主要由CoreContainer、Data Access/Integration、Web模块、AOP、Aspects、Instrumentation和Messaging、Test模块构成。

特性:

1)非侵入式

2)控制反转

3)依赖注入

4)面向切面编程

5)容器

6)组件化

7)一站式
在这里插入图片描述

Spring 组件

Core Container(Spring的核心容器)

Spring的核心容器是其他模块建立的基础,由 Beans 模块、Core 核心模块、Context 上下文模块和 SpEL 表达式语言模块组成,没有这些核心容器,也不可能有 AOP、Web 等上层的功能。

Beans 模块:提供了框架的基础部分,包括控制反转和依赖注入

Core 核心模块:封装了 Spring 框架的底层部分,包括资源访问、类型转换及一些常用工具类

Context 上下文模块集成 Beans 模块功能并添加资源绑定、数据验证、国际化、Java EE 支持、容器生命周期、事件传播等。ApplicationContext 接口是上下文模块的焦点。

SpEL 模块提供了强大的表达式语言支持,支持访问和修改属性值,方法调用,支持访问及修改数组、容器和索引器,命名变量,支持算数和逻辑运算,支持从 Spring 容器获取 Bean,它也支持列表投影、选择和一般的列表聚合等。


Data Access/Integration(数据访问/集成)

数据访问/集成层包括 JDBC、ORM、OXM、JMS 和 Transactions 模块

JDBC 模块:提供了一个 JBDC 的样例模板,使用这些模板能消除传统冗长的 JDBC 编码还有必须的事务控制,而且能享受到 Spring 管理事务的好处。

ORM 模块:提供与流行的“对象-关系”映射框架无缝集成的 API,包括 JPA、JDO、Hibernate 和 MyBatis 等。而且还可以使用 Spring 事务管理,无需额外控制事务。

OXM 模块:提供了一个支持 Object /XML 映射的抽象层实现,如 JAXB、Castor、XMLBeans、JiBX 和 XStream。将 Java 对象映射成 XML 数据,或者将XML 数据映射成 Java 对象。

JMS 模块:指 Java 消息服务,提供一套 “消息生产者、消息消费者”模板用于更加简单的使用 JMS,JMS 用于用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。

Transactions 事务模块:支持编程和声明式事务管理。


Web模块

Spring 的 Web 层包括 Web、Servlet、WebSocket 和 Webflux 组件

Web 模块:提供了基本的 Web 开发集成特性,例如多文件上传功能、使用的 Servlet 监听器的 IOC 容器初始化以及 Web 应用上下文。

Servlet 模块:提供了一个 Spring MVC Web 框架实现。Spring MVC 框架提供了基于注解的请求资源注入、更简单的数据绑定、数据验证等及一套非常易用的 JSP 标签,完全无缝与 Spring 其他技术协作。

WebSocket 模块:提供了简单的接口,用户只要实现响应的接口就可以快速的搭建 WebSocket Server,从而实现双向通讯

Webflux 模块: Spring WebFlux 是 Spring Framework 5.x中引入的新的响应式web框架。与Spring MVC不同,它不需要Servlet API,是完全异步且非阻塞的,并且通过Reactor项目实现了Reactive Streams规范。Spring WebFlux 用于创建基于事件循环执行模型的完全异步且非阻塞的应用程序。

Portlet 模块:提供了在 Portlet 环境中使用 MVC 实现,类似 Web-Servlet 模块的功能。 在Spring 5.x中已经移除。


Aop、Aspects、Instrumentation和Messaginging

在 Core Container 之上是 AOP、Aspects 等模块

AOP 模块提供了面向切面编程实现,提供比如日志记录、权限控制、性能统计等通用功能和业务逻辑分离的技术,并且能动态的把这些功能添加到需要的代码中,这样各司其职,降低业务逻辑和通用功能的耦合。

Aspects 模块提供与 AspectJ 的集成,是一个功能强大且成熟的面向切面编程(AOP)框架

Instrumentation 模块:提供了类工具的支持和类加载器的实现,可以在特定的应用服务器中使用。

messaging 模块:Spring 4.0 以后新增了消息(Spring-messaging)模块,该模块提供了对消息传递体系结构和协议的支持。

jcl 模块: Spring 5.x中新增了日志框架集成的模块。


Test

​ Test 模块:Spring 支持 Junit 和 TestNG 测试框架,而且还额外提供了一些基于 Spring 的测试功能,比如在测试 Web 框架时,模拟 Http 请求的功能。包含Mock Objects, TestContext Framework, Spring MVC Test, WebTestClient。

源码解析-容器的基本实现

前言

​ Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情,是个分层架构。Spring创建bean都需要通过读取解析校验配置文件,然后注册创建成Bean。Spring是一个Bean容器主要作用是替我们管理bean对象(简单的Java类对象的生命周期)。不管框架如何强大,还是需要来告诉其一些必要信息的(比如要管理的bean对象的类相关信息、是否开启组件扫描等),这些我们称之为对Spring框架的配置,目前主流的配置方式是通过使用配置文件或注解。

​ Spring中最核心的两个类:DefaultListableBeanFactory、XmlBeanDifinitionReader。DefaultListableBeanFactory是整个bean加载的核心部分,是Spring注册及加载bean的默认实现**。XmlBeanDefinitionReader**主要使用reader属性对资源文件进行读取和注册。

核心类DefaultListableBeanFactory(注册/加载)

​ XML配置文件读取是Spring中重要的功能,大部分Spring大部分功能都是以配置作为切入点XmlBeanFactory继承自DefaultListableBeanFactory,而对于DefaultListableBeanFactory不同的地方其实是在XmlBeanFactory中使用了自定义的XML读取器XmlBeanDefinitionReader,主要用于从XML文档中读取BeanDefinition,实现了个性化的BeanDefinitionReader读取,DefaultListableBeanFactory继承了AbstractAutowireCapableBeanFactory并实现了ConfigurableListableBeanFactory以及BeanDefinitionRegistry接口。

核心类XmlBeanDifinitionReader(自定义的XML读取器)

​ 自定义的XML读取器XmlBeanDefinitionReader,主要用于从XML文档中读取BeanDefinition,实现了个性化的BeanDefinitionReader读取。

Xml文件读取大致步骤:

1、通过继承自AbstractBeanDefinitionReader中的方法,来使用ResourceLoader将资源文件路径转换为对应的Resource文件。

2、通过DocumentLoaderResource文件进行转换,将Resource文件转换为Document文件。

3、通过实现接口BeanDefinitionDocumentReaderDefaultBeanDefinitionDocumentReader类对Document进行解析,并使用BeanDefinitionParserDelegateDocument对象的Element进行解析。

XmlBeanDefinitionReader下接口类说明:

● ResourceLoader:定义资源加载器,主要应用于根据给定的资源文件地址返回对应的 Resource;

● BeanDefinitionReader:主要定义资源文件的读取并转换为BeanDefinition的各个功能;

● EnvironmentCapable:定义获取 Environment 方法;

● DocumentLoader:定义从资源文件加载到转换为 Document 的功能;

● AbstractBeanDefinitionReader:对 EnvironmentCapable,BeanDefinitionReader 类定义的功能进行实现;

● BeanDefinitionDocumentReader:定义读取Document并注册 BeanDefinition 的功能;

● BeanDefinitionParserDelegate:定义解析Element的各种方法;

配置文件读取

Spring的配置文件读取是通过ClasaPathResource进行封装的,如:new ClassPathResource(“bean.xml”)。在java中,将不同来源的资源的读取逻辑抽象成URL,通过注册不同的handler来处理。一般handler的类型使用不同的前缀,URL没有默认定义相对的path路径,也没有提供相关方法对资源进行检查,顾Spring对其内部需要使用到的资源做了属于自己的抽象结构,用Resource接口来封装底层资源。

Resource接口继承InputStreamSource(封装了任何能返回InputStream的类)。

Resource接口抽象了所有Spring内部使用到的底层资源,首先它定义了3个能判断当前资源状态的方法:存在性(exists)、可读性(isReadable)、是否处于打开状态(isOpen)。有了Resource接口便可以对所有资源进行统一处理。ClassPathResource中的实现是通过class或classLoader提供的底层方法进行调用。以此完成对配置文件资源的封装。

​ 当通过Resource相关类完成了对配置文件进行封装,接下来由XmlBeanDefinitionReader完成对配置文件的读取工作。

XmlBeanDefinitionReader完成工作大致分为以下三步:

1、获取对XML文件的验证模式

2、加载XML文件,并得到对应的Document.

3、根据返回的Doucument注册Bean信息

获取XML的验证模式

XML文件的验证模式有两种:DTD、XSD(XML Schema)

​ DTD即文档类型定义,是一种XML约束模式语言,是XML文件的验证机制。是一种保证XML文档格式正确的有效方法,可以通过比较XML文档和DTD文件来查看文档是否符合规范,元素和标签的使用是否正确。一个DTD文档包含:元素的定义规则、元素间关系的定义规则、元素可使用的属性、可使用的实体或符号规则。要使用DTD验证模式需要在XML文件的头部声明。

XML Schema语言就是XSD。 XML Schema描述了XML文档的结构。可以用一个指定的XML Schema来验证某个XML文档,以检查该XML文档是否符合其要求。也可以通过XML Schema指定一个XML文档所允许的结构和内容。XML Schema本身也是一个XML文档,符合XML语法结构,可以用通用的XML解析器解析它。

使用XML Schema文档对XML实例进行校验,要声明名称空间和指定该名称空间所对应的XML Schema文档存储位置。通过schemaLocation属性来指定名称空间所对应的XML Schema文档的存储地址(1、名称空间URL;2、该名称空间所标识的XML Schema文件地址或URL地址)。

​ 另外验证模式通过XmlBeanDefinitionReader中的setValidationMode方法进行设定。而Spring用来检测验证模式的方法实际上就是判断是否包含DOCTYPE,如果包含就是DTD,否则就是XSD

解析配置文件

​ XML文件经过验证模式,交由DocumentLoader进行解析成对应的**Document。**而解析的过程中存在这么一环节:(EntityResolver)根据声明去寻找对应的DTD定义,以便对文档进行验证认证。也可以通过setEntityResolver设置DTD定义。EntityResolver它用来接收两个参数publicId和systemId,xsd格式文件通常publicId为null。而对于不同的验证模式采用不同的解析器进行解析,并把文件转换成Document文件,用于提取及注册bean。

解析注册bean

Document文件通过BeanDefinitionDocumentReader进行内部逻辑处理,并提取root用于作为参数继续完成BeanDefinition的注册。

注意:

​ 在注册bean的最开始就是对profile attribute属性进行解析,该profile属性主要用于环境切换。

小结

Resouce对配置文件资源进行封装 -》获取对XML文件的验证模式(DTD/XSD) -》加载XML文件得到对应的Document -》根据文档定义进行验证文件并采用对应的解析器解析配置文件以及根据profile属性设置不同的环境来进行装配数据 -》注册bean

源码解析-标签解析

前言

在Spring的XML配置中有两大类bean声明,一个是默认的,一个是自定义。两种格式的读取及解析天差万别,自定义的需要用户实现一些接口及配置。其中对于根节点、子节点如果是默认命名空间则采用parseDefaultElement方法进行解析,否则采用delegate.parseCustomElement对自定义命名空间进行解析。而判断命名空间采用node.getNamespaceURI()来获取,并与Spring中固定的命名空间进行对比,一致是默认,否则是自定义。

格式如下:

1、默认

2、自定义 <tx:annotation-driven />

默认标签的解析

默认标签的解析是在parseDefaultElement函数中进行,函数中的功能分别对4中标签(import、alias、bean、beans)做了不同的解析。其中bean标签的解析最为复杂也最为重要。默认的beanName初始为bean的id,若name存在为name,如果不存在beanName,那么根据Spring中提供的命名规则为当前bean生成对应的beanName.

解析步骤

1、解析属性首先要创建用于承载属性的实例,也就是创建GenericBeanDefinition类型的实例。(createBeanDefinition)

2、对bean信息的各种属性解析。(parseBeanDefinitionAttributes)属性有scope、singleton、abstract、lazy-init、autowire、dependency-check、depends-on、autowire-candidate、primary、init-method、destory-method、factory-method、factory-bean属性。

3、解析子元素meta。是一个额外的说明,当需要使用meta信息,可通过BeanDefinition的getAttribute(key)方法获取。

4、解析子元素lookup-method属性。获取器注入。

5、解析replace-method属性。方法替换,可动态替换返回实体bean,还可以替换原有方法的逻辑。

6、解析子元素(构造函数)construstor-arg.

7、解析子元素property

8、解析qualifier子元素。qualifier元素的获取,更多是注解的形式。

注册解析的BeanDefinition

BeanDefinition的注册分为两部分:beanName、别名。注册完成之后通知监听器已经注册完成。

beanName注册的步骤:

1、对AbstractBeanDefinition的校验。对methodOverrides属性。

2、对beanName已经注册的情况处理。如果设置不允许覆盖,则抛出异常。

3、加入map缓存。

4、清除解析之前留下的对应beanName的缓存。

通过别名的注册步骤:

1、alias与beanName相同情况处理。相同不需要处理,删除掉原有alias。

2、alias覆盖处理。若是aliasName已经存在并指向另外一个beanName就需要用户设置处理。

3、alias循环检查。出现就抛出异常

4、注册alias。

小结

Spring通过BeanDefinition将配置文件中的bean标签配置信息转换成容器的内部表示,并将BeanDefinition注册到BeanDefinitionRegistry中。spring容器的BeanDefinitionRegistry就像是Spring配置信息的内存数据库,主要是以map的形式保存,后续操作直接从BeanDefinitionRegistry中读取配置信息


自定义标签的解析

​ 前言提到Spring根据命名空间来区分是默认标签与自定义标签。在很多情况下,我们需要为系统提供可配置化的支持,通过Spring提供的可扩展Schema的支持(XSD)来实现想要的配置。常见的自定义标签为事务标签(tx:annotation-driven)

扩展Spring自定义标签配置大致步骤:

1、创建一个需要扩展的组件。

2、定义一个XSD文件用于描述组件内容。

3、创建一个文件,实现BeanDefinitionParser接口,用于解析XSD文件中的定义和组件定义。

4、创建一个Handler文件,扩展自NamespaceHandlerSupport,目的是将组件注册到Spring容器。

5、编写Spring.handlers和Spring.schemas文件。

注意:

1、在Spring.handlers中需要配置命名空间与命名空间处理器的映射关系。

2、当得到自定义命名空间处理后会马上执行命名空间初始化(进行自定义bean的注册)。

3、getHandlerMappings主要功能是读取配置文件并将配置文件信息缓存到map中。(过程中有保证同步synchronized)

4、需要先注册bean才能调用父类方法handler.parse的解析方法(自定义解析类)。另外自定义解析函数parseInternal并不是直接调用自定义解析函数doParse,而是先进行一系列的数据准备,包括对beanClass、scope、lazyInit等属性准备。

小结

Spring加载的流程中遇到自定义标签,根据bean获取对应的命名空间,再由命名空间去查找Spring.handlers和Spring.schemas去找对应的handler和XSD,进而找到对应的handler和解析元素的Psrser,从而完成整个自定义元素的解析。简单来说Spring将自定义标签解析工作委托给用户实现。

源码解析-Bean的加载

Bean加载过程

bean加载过程大致步骤如下:

**1、转换对应beanName。**传入的参数bean可能是别名,也可能是FactoryBean,需要进行一系列的解析。别名的取最终的beanName;FactoryBean则是去掉修饰符&。

2、尝试从缓存或者实例工厂中加载单例。单例在Spring同一容器中只会被创建一次。Spring创建bean的原则:不等bean创建完成就会将创建bean的ObjectFactory提前曝光加入到缓存中,一旦下一个bean创建时候需要依赖上一个bean则直接使用ObjectFactory

**3、bean的实例化。**会存在诸如BeanFactory并不是直接返回实例本身,需要进行实例化后返回。我们需要的是BeanFactory中定义的factory-method方法中的值。

**4、原型模式的依赖检查。**只有单例模式下才会尝试解决循环依赖,不进行检查会导致循环依赖。

**5、检查parentBeanFactory。**如果是BeanFactory,缓存中没有数据会转到父类工厂上去加载,然后递归调用getBean方法。

**6、将存储XML配置文件的GenericBeanDefinition转换为RootBeanDefinition。**从配置文件读取到的信息是存在在GenericBeanDefinition,而后续操作都是针对RootBeanDefinition,故做个转换。

**7、寻找依赖。**bean的初始化中很可能会用某些属性,而某些属性很可能是动态配置的,且配置成依赖其他的bean,那么就需要先加载依赖的这个bean。

**8、针对不同的scope进行bean的创建。**在Spring中存在着不同的scope,其中默认singleton,Spring会根据不同的配置进行不同的初始化策略。

9、类型转换。bean已经实例完了,但是需要进行检查需要的类型是否符合bean的实际类型,如果不符合就转换为requiredType所指的类型。

​ 一般情况下,Spring通过反射机制利用bean的class属性指定实现类来实例化bean。在某些情况下,实例化bean过程比较复杂,按照传统的方式,则需要在中提供大量的配置信息,配置灵活性有限。Spring提供了FactoryBean的工厂类接口,可以通过实现该接口定制实例化bean的逻辑

单例在Spring的同一容器内只会被创建一次,后续再次获取bean直接从单例缓存中获取。

步骤大概是以下几步;

1、检查缓存中是否存在实例。

2、如果不存在实例,则锁定全局变量进行处理。处理过程中如果此bean正在加载则不处理。处理过程为:

​ 先从singletonObjects里面获取实例,获取不到再从earlySingletonObjects里面获取。如果还是获取不到,就从SingletonFactories里面获取beanName对应的ObjectFactory,再调用ObjectFactorygetObject来创建bean,并放到earlySingletonObjects里面去,并且从SingletonFactories里面删除掉这个ObjectFactory,而对于后续的所有内存操作只是为了循环依赖检查时候使用,也就是在allowEarlyReference为true的情况下使用。

**singletonObjects:用于保存beanName和创建bean实例直接的关系。**bean name->bean instance

**SingletonFactories:用于保存beanName和创建bean的工厂之间的关系.**bean name->ObjectFactory

earlySingletonObjects:也是保存beanName和创建bean实例直接的关系,但是不同的是当一个单例bean被放到这里面之后,那么当bean还在创建的时候,就可以通过getBean获取到,其目的是用来检测循环引用。

registeredSingletons:用来保存当前所有已注册的bean.

从bean的实例中获取对象

从bean的实例中获取对象(getBean)方法中有一个重要的方法getObjectForBeanInstance用于检测bean的正确性。 Spring获取bean的规则:尽可能保证所有bean初始化后都调用注册的BeanPostProcessor的postProcessAfterInitialization方法进行处理。

单例通常都是从缓冲中获取,如果缓存中不存在已经加载的单例bean,就需要从头开始bean的加载过程,而Spring中使用getSingLeton的重载方法实现bean的加载过程。

大致加载过程:

1、检查缓存是否已经加载过。

2、若没有加载,则记录beanName的正在加载状态。

3、加载单例前记录加载状态。

4、通过调用参数传入的ObjectFactory的个体Object方法实例化bean。(获取单例bean)。

5、加载单例后的处理方法调用。

6、将结果记录到缓存并删除加载bean过程中所记录的各种辅助状态。

7、返回处理结果。

创建Bean

大致过程

准备创建bean(createBean)完成的具体步骤以及功能:

1、根据设置的class属性或者根据className来解析Class。

2、对override属性进行标记及验证。其目的因为在Spring配置中存在lookup-method和replace-method,被统一存放在BeanDefinition中的methodOverrides属性里面。

3、应用初始化钱的后处理器,解析指定bean是否存在初始化前的短路操作。如果经过前置处理后返回的结果不为空,就直接返回结果。AOP功能就是基于这儿判断。

​ 实例化前的后处理器主要是将AbsractBeanDefintion转换为BeanWrapper前的处理,给修改BeanDefinition的一个机会,经过这个方法,bean有可能是个代理bean。

​ 实例化后的处理器主要是为了保证将注册的后处理器的方法应用到该bean,因为如果返回的bean不为空,便不会再次经理普通bean的创建过程。

4、创建bean。

循环依赖问题

循环依赖就是循环引用,就是两个或两个以上相互之间的持有对方。循环调用是无法解决的,除非有终结条件,否则就是死循环,最终导致内存溢出错误。Spring容器的循环依赖有两种构造器循环依赖和setter循环依赖。

构造器循环依赖

通过构造器注入构成的循环依赖是无法解决的,只能抛出BeanCurrentlyInCreationException异常。循环依赖产生的原因是Spring容器将每一个正在创建的bean标识符放在“当前创建bean池”中,创建完毕将从池中移除。如果创建过程中发现自己已经在池中,则抛出BeanCurrentlyInCreationException异常。

setter循环依赖

通过setter注入方式构成的循环依赖。循环依赖产生的原因是通过Spring容器提前暴露刚完成构造器注入但未完成其他步骤(如setter注入)的bean来完成而且只能解决单例作用域的bean循环依赖。通过提前暴露一个单例工厂方法,从而使其他bean能引用到该bean。

prototype范围的依赖处理

对于prototype作用域bean,Spring容器无法完成依赖注入,因为Spring容器不进行缓存prototype作用域的bean,因此无法提前暴露一个创建中的bean.

依赖注入方式

依赖注入方式有三种:属性注入、setter方法注入、构造方法注入

**1、属性注入。**使用 @Autowired 注解注入。另外也有 @Resource 以及 @Inject 等注解。

**2、setter方法注入。**set 方法注入太过于臃肿,实际上很少使用。

**3、构造方法注入。**如果类只有一个构造方法,那么 @Autowired 注解可以省略;如果类中有多个构造方法,那么需要添加上 @Autowired 来明确指定到底使用哪个构造方法。

题外话

官方推荐通过构造方法注入

​ 在 Spring3.0 时代,官方还是提倡 set 方法注入的。不过从 Spring4.x 开始,官方就不推荐这种注入方式了,转而推荐构造器注入。通过构造方法注入的方式,能够保证注入的组件不可变,并且能够确保需要的依赖不为空。此外,构造方法注入的依赖总是能够在返回客户端(组件)代码的时候保证完全初始化的状态

不推荐属性注入?

​ 属性注入其实有一个显而易见的缺点,那就是对于 IOC 容器以外的环境除了使用反射来提供它需要的依赖之外,无法复用该实现类因为该类没有提供该属性的 set 方法或者相应的构造方法来完成该属性的初始化。换言之,要是使用属性注入,那么你这个类就只能在 IOC 容器中使用,要是想自己 new 一下这个类的对象,那么相关的依赖无法完成注入。

三级缓存

1、单例池 singletonObjects 一级缓存,用于保存实例化、注入、初始化完成的bean实例

2、提前曝光对象 earlySingletonObjects 二级缓存,用于保存实例化完成的bean实例

3、提前曝光对象工厂 singletonFactories 三级缓存,用于保存bean创建工厂,以便于后面扩展有机会创建代理对象。

出现循环依赖如何解决?

1、生成代理对象产生的循环依赖

1)使用@Lazy注解,延迟加载

2)使用@DependsOn注解,指定加载先后关系

3)修改文件名称,改变循环依赖类的加载顺序

2、使用@DependsOn产生的循环依赖

​ 这类循环依赖问题要找到@DependsOn注解循环依赖的地方,迫使它不循环依赖就可以解决问题。

3、多例循环依赖

​ 这类循环依赖问题可以通过把bean改成单例的解决。

4、构造器循环依赖

​ 这类循环依赖问题可以通过使用@Lazy注解解决。

创建bean

​ 当经历过resolveBeforeInstantiation方法后,程序有两个选择,如果创建了代理或者说重写了InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation方法并在方法postProcessBeforeInstantiation中改变了bean,则直接返回就可以了,否则需要进行常规bean的创建。而常规bean的创建就是在doCreateBean中完成的。

常规bean的创建大致步骤:

(1)如果是单例则需要首先清除缓存。

**(2)实例化bean,将BeanDefinition转换为BeanWrapper。**转换的大致功能:

​ 1)如果存在工厂方法则使用工厂方法进行初始化

​ 2)一个类有多个构造函数,每个构造函数都有不同的参数,所以需要根据参数锁定构造函数并进行初始化

​ 3)如果既不存在工厂方法也不存在带有参数的构造函数,则使用默认的构造函数进行bean的实例化

(3)MergedBeanDefinitionPostProcessor的应用。bean合并后的处理,Autowired注解正是通过此方法实现诸如类型的预解析。

**(4)依赖处理。**只处理单例的循环处理,处理流程是通过放入缓存中的ObjectFactory来创建实例,来解决循环依赖的。

**(5)属性填充。**将所有属性填充至bean的实例中,其中可能存在依赖其他bean属性,则会递归初始依赖bean。

**(6)循环依赖检查。**检测已经加载的bean是否已经出现了依赖循环,并判断抛出异常。是通过判断是否存在依赖,获取所有依赖bean的名称,递归检测依赖bean是否已经创建,如果没有创建完,也就是说存在循环依赖。

**(7)注册DisposableBean。**如果配置了destory-method,需要注册以便于在销毁时候调用。

(8)完成创建并返回

初始化bean

bean配置中有个init-method属性,这个属性的作用就是在bean实例化前调用init-method指定方法来根据用户业务进行相应的实例化

Spring中程序已经执行过bean的实例化,并且进行了属性的填充,而就在这时将会调用用户设定的初始化方法。虽说主要目的是进行客户设定的初始化方法的调用,但是除此之外还有些其他必要的工作。

​ 其他工作例如:

(1)激活Aware,用于注入Aware相关一些实例。

​ Spring中提供一些Aware相关接口,比如BeanFactoryAware、ApplicationContextAware、ResourceLoaderAware、ServletContextAware等,实现这些Aware接口的bean在被初始之后,可以取得一些相应的资源,例如实现BeanFactoryAware的bean在初始后,Spring容器将会注入BeanFactory的实例,而实现ApplicationContextAware的bean,在bean被初始后,将会被注入ApplicationContext的实例等。

(2)处理器的应用

BeanPostProcessor是Spring有提供给用户权限去更改、扩展Spring在bean初始化之前调用客户自定义初始化方法之前以及调用自定义初始化方法后分别会调用BeanPostProcessor的postProcessBeforeInitialization和postProcessAfterInitialization方法,使用户可以根据自己的业务需求进行响应的处理。

(3)激活自定义的init方法

客户定制的初始化方法除了我们熟知的使用配置init-method外,还有使自定义的bean实现InitializingBean接口,并在afterPropertiesSet中实现自己的初始化业务逻辑。init-methodafterPropertiesSet都是在初始化bean时执行,执行顺序是afterPropertiesSet先执行,而init-method后执行。

​ 在invokeInitMethods方法中就实现了这两个步骤的初始化方法调用。属性初始化后处理、自定义初始化方法。

注册DisposableBean

Spring中不但提供了对初始化方法的扩展入口,同样也提供了销毁方法的扩展入口,对于销毁方法的扩展,除了我们熟知的配置属性destroy-method方法外,用户还可以注册后处理器DestructionAwareBeanPostProcessor来统一处理bean的销毁方法。

小结

​ Spring在单例模式下的setter方法依赖注入引起的循环依赖问题,主要是通过二级缓存和三级缓存来解决的,其中三级缓存是主要功臣。解决的核心原理就是:在对象实例化之后,依赖注入之前,Spring提前暴露的Bean实例的引用在第三级缓存中进行存储。

拓展:为什么要使用二级缓存和三级缓存一起来解决循环依赖呢?二级缓存能单独解决循环依赖吗?

如果没有 AOP 代理,二级缓存可以解决问题,但是有 AOP 代理的情况下,只用二级缓存就意味着所有 Bean 在实例化后就要完成 AOP 代理,这样违背了 Spring 设计的原则,Spring 在设计之初就是通过 AnnotationAwareAspectJAutoProxyCreator 这个后置处理器来在 Bean 生命周期的最后一步来完成 AOP 代理,而不是在实例化后就立马进行 AOP 代理

依赖注入案例

假设现在需要获取对象 A 的实例,并且对象 A 依赖对象 B,需要先获取对象 B 的实例。

  1. 首先,从一级缓存(singletonObjects)中查找对象 A,如果找到了,则直接返回该实例。如果没有找到,则继续下一步。

  2. 接着,从二级缓存(earlySingletonObjects)中查找对象 A 的提前暴露的实例(可能还没有初始化完成)。如果找到了,则将其放到一级缓存(singletonObjects)中并返回该实例。如果没有找到,则继续下一步。

  3. 然后,从三级缓存(singletonFactories)中获取对象 A 的 ObjectFactory。如果没有找到,则创建该对象的 ObjectFactory 对象,并将其放到三级缓存(singletonFactories)中。然后,调用 ObjectFactory 的 getObject() 方法来创建对象 A 的实例,并将其放到二级缓存(earlySingletonObjects)中。

  4. 在创建对象 A 的过程中,发现对象 A 依赖对象 B。因此,需要先获取对象 B 的实例。根据上面的流程,从一级、二级和三级缓存中查找对象 B 的实例,都没有找到。

  5. 所以,需要先从三级缓存(singletonFactories)中获取对象 B 的 ObjectFactory。如果没有找到,则创建该对象的 ObjectFactory 对象,并将其放到三级缓存(singletonFactories)中。然后,调用 ObjectFactory 的 getObject() 方法来创建对象 B 的实例,并将其放到二级缓存(earlySingletonObjects)中。

  6. 在创建对象 B 的过程中,发现对象 B 依赖对象 A。因此,需要从二级缓存(earlySingletonObjects)中获取对象 A 的实例。如果找到了,则将其注入到对象 B 中。否则,根据上面的流程,从一级和三级缓存中查找对象 A 的实例。

  7. 所以,再次从三级缓存(singletonFactories)中获取对象 A 的 ObjectFactory。如果没有找到,则创建该对象的 ObjectFactory 对象,并将其放到三级缓存(singletonFactories)中。然后,调用 ObjectFactory 的 getObject() 方法来创建对象 A 的实例,并将其放到二级缓存(earlySingletonObjects)中。最后将其注入到对象 B 中。

  8. 现在,对象 B 的实例已经创建完成,并且对象 A 的实例也已经创建完成并注入到了对象 B 中。接下来,将对象 B 放到一级缓存(singletonObjects)中并返回该实例。

  9. 最后,在获取对象 A 的实例时,根据上述流程,现在可以从一级缓存(singletonObjects)中获取到对象 A 的实例了,然后将其注入到对象 B 中,并将其放到一级缓存(singletonObjects)中并返回该实例

总结

JavaBean注册流程

Spring使用基本JavaBean所完成事情的流程:

1)读取配置文件(Resource)

2)校验配置文件XML(校验模式DTD/XSD)

3)解析配置文件(默认/自定义bean标签;都有对应的解析器))

4)资源转换(Document)

5)bean加载

​ 5-1)转换beanName

​ 5-2)加载单例(缓存中获取单例bean)

​ 5-3)bean实例化(从bean的实例中获取对象,执行实例化策略)

​ 5-4)原型模式的依赖检查(循环依赖问题)

​ 5-5)数据装配

​ 5-6)针对不同的scope进行bean的创建

​ 5-7)类型转换(bean已经实例完了,但是需要检查是否符合Bean的实际类型)

6)根据profile属性设置不同的环境来进行装配数据

7)注册bean(主要是以map的形式保存,不但要实现注册功能,还要实现对后处理器的激活操作。)

8)注册环节会进行执行后置处理器的相关信息检查/容器扩展(通过后置处理器进行扩展,例如:AOP)

依赖注入

​ Spring依赖注入有3种方式:属性注入、setter方法注入、构造方法注入.

**1、属性注入。**使用 @Autowired 注解注入。另外也有 @Resource 以及 @Inject 等注解。

**2、setter方法注入。**set 方法注入太过于臃肿,实际上很少使用。

**3、构造方法注入。**如果类只有一个构造方法,那么 @Autowired 注解可以省略;如果类中有多个构造方法,那么需要添加上 @Autowired 来明确指定到底使用哪个构造方法。

循环依赖

​ 循环依赖就是循环引用,就是两个或两个以上相互之间的持有对方.Spring容器的循环依赖有两种构造器循环依赖和setter循环依赖。

1、构造器循环依赖。不能解决,只能抛出异常

2、setter循环依赖。产生的原因是通过Spring容器提前暴露刚完成构造器注入但未完成其他步骤(如setter注入)的bean来完成**,而且只能解决单例作用域的bean循环依赖**。

3、prototype范围的依赖处理。对于prototype作用域bean,Spring容器无法完成依赖注入,因为Spring容器不进行缓存prototype作用域的bean,因此无法提前暴露一个创建中的bean

Spring内部有三级缓存:

1、单例池 singletonObjects 一级缓存用于保存实例化、注入、初始化完成的bean实例

2、提前曝光对象 earlySingletonObjects 二级缓存.用于保存实例化完成的bean实例

3、提前曝光对象工厂 singletonFactories 三级缓存.用于保存bean创建工厂,以便于后面扩展有机会创建代理对象。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。