不定期补充、修正、更新;欢迎大家讨论和指正
Java学习_Spring_AOP

目录

  • spring
    • 什么是spring?
    • Spring框架的设计目标,设计理念,和核心是什么
    • Spring的优缺点是什么?
    • Spring有哪些应用场景
    • Spring价值
    • Spring由哪些模块组成?
  • IoC
    • DI
  • xml配置
    • set方法注入
    • 构造器注入
    • C/P命名空间注入
    • 注入复杂类型
    • 自动装配
  • Java显示配置
  • 注解实现自动装配
    • 自动装配的歧义性
  • 混合配置
  • bean的作用域
    • 配置连接池
  • bean的生命周期
  • BeanFactory
  • 源码分析
    • prepareRefresh
    • obtainFreshBeanFactory(⭐)
    • prepareBeanFactory
    • 后置处理器
    • onRefresh
    • finishBeanFactoryInitialization(⭐)

spring

什么是spring?

Spring是一个轻量级Java开发框架,最早由Rod Johnson创建,目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题。它是一个分层的JavaSE/JavaEE full-stack(一站式)轻量级开源框架,为开发Java应用程序提供全面的基础架构支持。Spring负责基础架构,因此Java开发者可以专注于应用程序的开发。

Spring最根本的使命是解决企业级应用开发的复杂性,即简化企业级Java开发。

Spring可以做很多事情,它为企业级开发提供给了丰富的功能,但是这些功能的底层都依赖于它的两个核心特性,也就是依赖注入(dependency injection,DI)和面向切面编程(aspect-oriented programming,AOP)。

为了降低Java开发的复杂性,Spring采取了以下4种关键策略

  • 基于POJO的轻量级和最小侵入性编程;
  • 通过依赖注入和面向接口实现松耦合;
  • 基于切面和惯例进行声明式编程;
  • 通过切面和模板减少样板式代码。

Spring框架的设计目标,设计理念,和核心是什么

Spring设计目标:Spring为开发者提供一个一站式轻量级应用开发平台;

Spring设计理念:在JavaEE开发中,支持POJO和JavaBean开发方式,使应用面向接口开发,充分支持OOP(面向对象)设计方法;Spring通过IoC容器实现对象耦合关系的管理,并实现依赖反转,将对象之间的依赖关系交给IoC容器,实现解耦;

Spring框架的核心:IoC容器AOP模块。通过IoC容器管理POJO对象以及他们之间的耦合关系;通过AOP以动态非侵入的方式增强服务。

IoC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。

Spring的优缺点是什么?

优点

  • 方便解耦,简化开发
  • Spring就是一个大工厂,可以将所有对象的创建和依赖关系的维护,交给Spring管理。
  • AOP编程的支持
  • Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能。
  • 声明式事务的支持
  • 只需要通过配置就可以完成对事务的管理,而无需手动编程。
  • 方便程序的测试
  • Spring对Junit4支持,可以通过注解方便的测试Spring程序。
  • 方便集成各种优秀框架
  • Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架的直接支持(如:Struts、Hibernate、MyBatis等)。
  • 降低JavaEE API的使用难度
  • Spring对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低。

缺点

  • Spring明明一个很轻量级的框架,却给人感觉大而全
  • Spring依赖反射机制,反射影响性能
  • 使用门槛升高,入门Spring需要较长时间

Spring有哪些应用场景

应用场景:JavaEE企业应用开发,包括SSH、SSM等

Spring价值

Spring是非侵入式的框架,目标是使应用程序代码对框架依赖最小化;
Spring提供一个一致的编程模型,使应用直接使用POJO开发,与运行环境隔离开来;
Spring推动应用设计风格向面向对象和面向接口开发转变,提高了代码的重用性和可测试性;

Spring由哪些模块组成?

Spring 总共大约有 20 个模块, 由 1300 多个不同的文件构成。 而这些组件被分别整合在核心容器(Core Container) 、 AOP(Aspect Oriented Programming)和设备支持(Instrmentation) 、数据访问与集成(Data Access/Integeration) 、 Web、 消息(Messaging) 、 Test等 6 个模块中。 以下是 Spring 5 的模块结构图:

  • spring core:提供了框架的基本组成部分,包括控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)功能。
  • spring beans:提供了BeanFactory,是工厂模式的一个经典实现,Spring将管理对象称为Bean。
  • spring context:构建于 core 封装包基础上的 context 封装包,提供了一种框架式的对象访问方法。
  • spring jdbc:提供了一个JDBC的抽象层,消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析, 用于简化JDBC。
  • spring aop:提供了面向切面的编程实现,让你可以自定义拦截器、切点等。
  • spring Web:提供了针对 Web 开发的集成特性,例如文件上传,利用 servlet listeners 进行 ioc 容器初始化和针对 Web 的 - ApplicationContext。
  • spring test:主要为测试提供支持的,支持使用JUnit或TestNG对Spring组件进行单元测试和集成测试。
  • spring transactions:事务管理

以上均摘自Spring面试题(2020最新版)

Spring框架不仅大大提高开发效率和质量,而且发展到现在依靠着庞大和富有活力的生态圈几乎活跃在目前所有Java开发中,重要性不言而喻。而Spring框架的基础两大核心就是IoCAOP

IoC

控制反转(Inversion of Control,IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。
IoC可以认为是一种全新的设计模式,但是理论和时间成熟相对较晚,并没有包含在GoF23(23种设计模式)中。

我们知道在面向对象设计的软件系统中,它的底层都是由N个对象构成的,各个对象之间通过相互合作,最终实现系统地业务逻辑

如果我们打开机械式手表的后盖,就会看到与上面类似的情形,各个齿轮分别带动时针、分针和秒针顺时针旋转,从而在表盘上产生正确的时间。图1中描述的就是这样的一个齿轮组,它拥有多个独立的齿轮,这些齿轮相互啮合在一起,协同工作,共同完成某项任务。我们可以看到,在这样的齿轮组中,如果有一个齿轮出了问题,就可能会影响到整个齿轮组的正常运转。
齿轮组中齿轮之间的啮合关系,与软件系统中对象之间的耦合关系非常相似。对象之间的耦合关系是无法避免的,也是必要的,这是协同工作的基础。现在,伴随着工业级应用的规模越来越庞大,对象之间的依赖关系也越来越复杂,经常会出现对象之间的多重依赖性关系,因此,架构师和设计师对于系统的分析和设计,将面临更大的挑战。对象之间耦合度过高的系统,必然会出现牵一发而动全身的情形。

耦合关系不仅会出现在对象与对象之间,也会出现在软件系统的各模块之间,以及软件系统和硬件系统之间。如何降低系统之间、模块之间和对象之间的耦合度,是软件工程永远追求的目标之一。为了解决对象之间的耦合度过高的问题,软件专家Michael Mattson 1996年提出了IoC理论,用来实现对象之间的“解耦”,目前这个理论已经被成功地应用到实践当中。
IoC理论提出的观点大体是这样的:借助于“第三方”实现具有依赖关系的对象之间的解耦。如下图:

大家看到了吧,由于引进了中间位置的“第三方”,也就是IoC容器,使得A、B、C、D这4个对象没有了耦合关系,齿轮之间的传动全部依靠“第三方”了,全部对象的控制权全部上缴给“第三方”IoC容器,所以,IoC容器成了整个系统的关键核心,它起到了一种类似“粘合剂”的作用,把系统中的所有对象粘合在一起发挥作用,如果没有这个“粘合剂”,对象与对象之间会彼此失去联系,这就是有人把IoC容器比喻成“粘合剂”的由来。
我们再来做个试验:把上图中间的IoC容器拿掉,然后再来看看这套系统

我们现在看到的画面,就是我们要实现整个系统所需要完成的全部内容。这时候,A、B、C、D这4个对象之间已经没有了耦合关系,彼此毫无联系,这样的话,当你在实现A的时候,根本无须再去考虑B、C和D了,对象之间的依赖关系已经降低到了最低程度。所以,如果真能实现IoC容器,对于系统开发而言,这将是一件多么美好的事情,参与开发的每一成员只要实现自己的类就可以了,跟别人没有任何关系!
我们再来看看,控制反转(IoC)到底为什么要起这么个名字?我们来对比一下:
软件系统在没有引入IoC容器之前,如图1所示,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。
软件系统在引入IoC容器之后,这种情形就完全改变了,如图3所示,由于IoC容器的加入,对象A与对象B之间失去了直接联系,所以,当对象A运行到需要对象B的时候,IoC容器会主动创建一个对象B注入到对象A需要的地方。
通过前后的对比,我们不难看出来:对象A获得依赖对象B的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。
摘自浅谈IoC–说清楚IoC是什么

DI


依赖注入(Dependency Injection,DI)是IoC的实现方式,因为Spring中IoC的实现方式是DI,所以很多人/博客认为IoC和DI等价,但严格来说IoC是目的,DI是手段。个人也认同这种观点,IoC是一种思想,IoC容器是利用这种思想创建的容器,可以进行组件管理(将普通的类注册为bean)、组件解耦等功能,DI是IoC容器来进行组件之间协作关系的手段,所以简单地认为DI等价于IoC是不严谨地。

我们知道,set方法和构造器两种方式是我们从外部获取对象的主要方式,所以DI也是使用set方法和构造器实现注入。创建应用对象之间协作关系的行为称为装配,这也是依赖注入的本质

Spring提供了三种主要bean管理方法,使用XML配置文件、Java中显示配置、注解自动装配
(Spring管理的对象必须是符合JavaBean规范的对象,可能有人还不了解JavaBean是什么,可以看看下方博客)
java对象 POJO和JavaBean的区别

xml配置

xml管理bean是Spring刚出现时采用的方法,目前有两种取代xml的方法,而且比xml更加方便。虽然xml已经有些过时,但是作为学习或者有维护旧项目的需求时还是很有学习的必要。

这里使用Maven创建项目,自己导包麻烦。在pom.xml文件配置spring框架的依赖(应该都会Maven吧,不会可以去B站看看视频),根据下图导入Spring核心组件的依赖(Beans、Core、Context、Expression这些组件保证Spring最基础的功能,必须有;不过也可以只导入高级应用比如SpringMVC,通过依赖也会自动把核心组件加载上)
2020最新Maven教程-Maven基础篇之Maven实战入门-bilibili

<dependencies>
<!-- Spring 框架基本的核心工具类。Spring 其它组件要都要使用到这个包里的类,是其它组件的基本核心 --><dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>5.2.12.RELEASE</version></dependency>
<!-- 它包含访问配置文件、创建和管理bean 以及进行(IoC/DI)操作相关的所有类。 --><dependency><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId><version>5.2.12.RELEASE</version></dependency>
<!--  Spring的上下文即IoC容器,通过上下文可以获得容器中的Bean。 ApplicationContext接口是Context模块的关键。  Context模块构建于Core和Beans模块基础之上,提供了一种类似于JNDI注册器的框架式的对象访问方法。--> <dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.12.RELEASE</version></dependency><!-- Expression Language模块提供了一个强大的表达式语言用于在运行时查询和操纵对象。--><dependency><groupId>org.springframework</groupId><artifactId>spring-expression</artifactId><version>5.2.12.RELEASE</version></dependency>
</dependencies>

遵循Maven约定的目录结构创建各个文件,创建bean的配置文件,官方建议名为applicationContext.xml,也可以使用其他名字,这里使用beans.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" xmlns:p="http://www.springframework.org/schema/p"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
</beans>

简单的创建一个Person类进行试验,里面只有年龄和姓名,敌人三个属性以及基本的方法。

set方法注入

接下来就可以配置bean了,id名字可以任意,但是和html一样全局唯一,class是类的路径。
property是通过set方法注入属性的值,所以没有要注入的属性没有set方法会报错
name的值是类的属性名,value、ref是其值,value和ref在property标签内只能二选一,其区别是value的值是基本类型,ref是对象
一个bean标签就相当于一个bean对象

在beans.xml已经创建好bean对象,现在只要从应用上下文(即IoC容器)取出就行
Spring提供多种类型的应用上下文,使用Hierarchy查看有哪些应用上下文,因为我们在XML文件来配置bean,所以使用ClassPathXmlApplicationContext的应用上下文,FileSystemXmlApplicationContext和ClassPathXmlApplicationContext类似,区别是前者读取的路径是磁盘上文件的路径,后者是项目内文件的路径

如果你的配置文件名和官方建议的不一样,就在ClassPathXmlApplicationContext()传入你配置文件名

构造器注入

创建构造器,这里只有空参构造器和三个参数的构造器,< constructor-arg >的个数也必须为0或者3,否则会报错找不当相应的构造器。
而默认是使用空参构造器创建bean对象(其他框架也如此),所以这也是为啥bean对象必须有一个空参构造器的缘由。

< constructor-arg >标签可以用几种方法定位某个属性,name根据属性名,type根据属性的类型(较少使用,有同类型出现会有歧义), index根据形参的索引,

从容器中获取bean

C/P命名空间注入

c/p其实就是对应< constructor-arg >和< property >注入,只是格式简便了一些
c/p命名空间注入的特点是使用属性而不是子元素的形式配置Bean的属性,从而简化了配置代码。
使用前要在spring配置文件导入c/p命名空间约束

xmlns:p="http://www.springframework.org/schema/p"
xmlns:p="http://www.springframework.org/schema/c"

c/p注入方式的格式为下图所示:

注入复杂类型

上面的例子只是注入了普通值和对象,而例如集合的其他复制类型则需要其他的注入方式。下图是官网给出可以注入的数据类型

创建一个Student类用于演示

<bean id="address" class="com.jojo.person.Address" p:addr="碧桂园"></bean><bean id="Student" class="com.jojo.person.Student"><property name="name" value="李华"></property><property name="addr" ref="address"></property><property name="books"><array><value>三国演义</value><value>西游记</value><value>水浒传</value><value>红楼梦</value></array></property><property name="hobby"><list><value></value><value></value><value>rap</value></list></property><property name="games"><set><value>DOTA2</value><value>CSGO</value><value>赛博朋克2077</value></set></property><property name="card"><map><entry key="移动" value="1370000000"></entry></map></property><property name="girlfriend"><null></null></property><property name="info"><props><prop key="age">18</prop><prop key="id">10086</prop></props></property></bean>

自动装配

bean标签内有autowire可选功能,可以让容器来帮助自动装配对象间的依赖以此来简化一些操作
autowire中有byName、byType、no、default、constructor五个选项

byName选项,Spring会通过属性名自动寻找与属性名相同的bean,然后装配
将Person2的id改为enemy,尽管Person1的enemy属性没有显示配置,Spring也会通过byName寻找与属性名相同的bean然后装配

byType,通过类型自动装配,当全局中有相同类型时会报错,上面的例子中Person1和enemy都是Person类,所以使用不了byType.
addr属性被注释,但是Spring还是可以找到与addr类型相同的bean(即Id为address)然后装配,找不到则为null

剩下的三个选项看看就行

  • no(默认),不采用autowire机制.。这种情况,当我们需要使用依赖注入,只能用标签。
  • constructor类似于byType,但是是通过构造函数的参数类型来匹配。假设bean A有构造函数A(B b, C c),那么Spring会在容器中查找类型为B和C的bean通过构造函数A(B b, C c)注入到A中。
    与byType一样,如果存在多个bean类型为B或者C,则会抛出异常。但时与byType不同的是,如果在容器中找不到匹配的类的bean,将抛出异常,因为Spring无法调用构造函数实例化这个bean。
  • default,采用父级标签(即beans的default-autowire属性)的配置。

Java显示配置

JavaConfig是Spring的子项目,当进行显式配置时,JavaConfig比XML配置更加强大、类型安全并且对重构友好,因为它就是Java代码,同时,JavaConfig与其他Java代码又有所区别,JavaConfig是配置代码,这意味着它不应该包含任何业务逻辑,所以通常放在单独一个包中。

创建JavaConfig的关键在于为其添加@Configuration注解,注解表明这是一个配置类,该类应该包含在Spring应用上下文如何创建bean的细节。

配置类创建好后,我们就可以创建和配置bean了,使用Java显示配置创建bean的关键在于@Bean注解和编写符合某种规则的方法(方法必须是public的,方法必须有返回值(对应bean的类型)、方法名就是bean的id,如果想取别名,则以@Bean(name = “别名”)的形式修改)

当处于配置类下的方法被加上@Bean后,Spring就能够将其转换为bean

我们可以对比XML方式配置bean,配置类就相当于整个XML文件、一个方法对应一个bean、方法名对应标签内的id、返回值对应class、bean的属性也根据需求配置

  <bean id="person1" class="com.jojo.person.Person" ><property name="age" value="18"></property><property name="name" value="空调承太郎" ></property><property name="enemy" ref="Person2"></property></bean>

从容器中取出bean,因为这里不是用XML配置bean,而是用注解,所以用AnnotationConfigApplicationContext类型的容器


Spring注解配置和xml配置优缺点比较

注解实现自动装配

这一种DI方式也是官方最为推荐的,因为可以将显示配置降到最低,对源码的侵入性最低。
注解自动装配和xml的autowire选项作用相同,在属性或者set方法加上@Autowired就可以实现相同的效果
Spring从两个角度来实现自动装配:组件扫描(component scanning):自动发现应用上下文中所创建的bean、自动装配(autowiring):自动满足bean之间的依赖。两者组合能够将显示配置降到最低。

首先需要在beans.xml中导入约束
xmlns:context="http://www.springframework.org/schema/context"和使用< context:annotation-config/ >标签开启注解功能

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.2.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsd"><context:annotation-config/>
</beans>

这里使用《Spring实战》的例子,个人觉得容易理解。

创建CompactDisc(CD)接口

创建一个类,实现CD接口和重写方法,CD名为SgtPeppers,作者是披头士。
在类上加上@Component注解,表明该类会作为组件类,并告知Spring为这个类创建bean,而不用在xml显示创建bean,现在一个类就是一个bean了
bean的名字就是与类名相同,除了第一个字母为小写即sgtPeppers,如果想自定义bean的名字,只需要类似@Component(sgt)配置即可。
即等价于在XML中创建的< bean id=“sgtPeppers” class=“com.jojo.soundsystem.SgtPeppers”>< /bean >
JAVA依赖注入规范提供@Named注解,大多场景可以和@Component相互替换,不过原书的作者并不喜欢使用@Named
(注意到左上角像绿色咖啡豆的标志,表明此类已经被视为组件类,并且Spring为其注册了bean)

默认情况下,组件扫描默认不启动,Spring暂时不会扫描带有@Component注解的组件,所以要显示配置。
我们需要另外创建一个类用于管理这些组件,为该类加上@ComponentScan表示启动组件扫描,默认情况下Spring会根据扫描这个类所在包以及其包下所有子包所有组件。
如果想扫描其他包则在注解设置value属性,如@ComponentScan(basePackages = “soundsystem”)
如果是多个包就用数组的形式@ComponentScan(basePackages ={ “soundsystem”,“video”})

除此之外也可以单独使用beans.xml中启动组件扫描,只需要添加以下标签就行
< context:component-scan base-package=“com.jojo.soundsystem”/>

点击左上角类似咖啡豆似的东西,可以找到同包下注册为bean的类

在容器中取出bean测试下效果

一张CD已经实现了,接下来就是实现一个播放器来播放CD,同样的为播放器添加@Component,这样Spring也可以将其视为bean来管理

播放器需要CD,我们可以使用类似XML中用到的自动装配功能,这里是使用@Autowired注解,如下
@Autowired表示当Spring创建CDplay类型的bean时,会通过这个构造器来实例化,并自动传入一个可设置给CompactDisc类型的bean
如果没有匹配的bean,就会抛出异常,可以设置@Autowired(required = false)避免。
@Autowired注解可以作用在在属性,构造器,set方法上。
@Autowired是Spring特有的注解,同样JAVA依赖注入规范也提供像@Named替代@Component的@Inject

取出播放器的实例,使用play()方法就可以播放美妙的音乐了。

自动装配的歧义性

当使用@Autowired注解时,Spring会从组件中找到符合的bean进行自动装配
也就是说下图CDplayer的构造器使用@Autowired注解,希望得到一张CD,在上面我们也成功将SgtPeppers这张CD装配到CDplayer中。
问题所在是,如果组件中有多个符合的CD,那么自动装配选择的是哪个CD呢?这就会出现歧义,并抛出NoUniqueBeanDefinitionException异常。

第一种方法是标识首选的bean,如果是用注解自动装配或者是Java配置的方式,只需要首选的bean添加@Primary即可
如果是XML配置,则在首选的bean的< bean >标签内添加primary = “true”

另一种方法是@Qualifier(“你想注入的bean”)来指定一个确定的bean

混合配置

Spring实战——XML和JavaConfig的混合配置
spring在xml配置中引用JavaConfig

bean的作用域

在默认情况下,Spring应用上下文中所有bean都是作为以单例模式的形式创建的,也就是说不管给定的bean被注入多少次,都是同一实例。

结果如下

在多数情况下,单例的bean是很理想的方案,初始化和垃圾回收所带来的资源消耗很小,但有时候如果使用的类是易变的,他们会保持一些状态,因此重用是不安全的,因为对象会被污染。对此我们可以改变bean的作用域

Spring中定义了四种作用域:

  1. 单例(Singleton):在整个应用中,之创建bean的一个实例
  2. 原型(Protocol):每次注入或者通过Spring应用上下文获取时,都会创建一个bean实例
  3. 会话(Session):在Web应用中,为每个会话创建一个bean实例
  4. 请求(Rquest):在web应用中,为每个请求创建一个bean实例

想修改bean的作用域,在bean上添加@Scope注释


如果采用的是XML配置,则在< bean >标签内添加 scope = " "

配置连接池

bean的作用域默认是单实例,所以用Spring配置数据库连接池再适合不过了。(JavaWeb中频繁要操作数据库,操作数据库就要创建连接,普通连接的创建和销毁比较消耗资源和产生一些问题,于是诞生数据库连接池技术用于管理所有连接,连接池一般一个项目一个就足够)
以C3P0为例,导入其相关依赖

  <dependency><groupId>com.mchange</groupId><artifactId>c3p0</artifactId><version>0.9.5.2</version></dependency>

配置连接池的bean

从上下文中获取bean

成功获取连接

bean的生命周期

这里涉及到底层源码,所以看看就好

博客推荐:Spring Bean生命周期

BeanFactory

Spring 提供了两种 IoC 容器,分别为 BeanFactory 和 ApplicationContext,了解BeanFactory对后面源码分析是很重要的。前面一直使用IoC容器都是实现ApplicationContext接口的容器,而实际上BeanFactory才是最基础的IoC容器,提供了最简单和最基础的容器功能;ApplicationContext是BeanFactory的子接口,并在其基础上扩充了诸多功能,例如

  • 国际化(MessageSource)
  • 访问资源,如XML和文件,比如之前用XML管理bean
  • 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层
  • 消息发送、响应机制(ApplicationEventPublisher)
  • AOP(拦截器)

BeanFactory的实现关系如下图

两者装载bean的区别:
1.BeanFactory在启动的时候不会去实例化Bean,只有从容器中拿Bean的时候才会去实例化;即延迟加载,好处是应用启动的时候占用资源很少;对资源要求较高的应用,比较有优势;
2.ApplicationContext在启动的时候就把所有的Bean全部实例化了。它还可以为Bean配置lazy-init=true来让Bean延迟实例化;
即非延迟加载,好处是:

  • 所有的Bean在启动的时候都加载,系统运行的速度快;
  • 在启动的时候所有的Bean都加载了,我们就能在系统启动的时候,尽早的发现系统中的配置问题,
  • 建议web应用,在启动的时候就把所有的Bean都加载了。(把费时的操作放到系统启动中完成)

推荐博客:BeanFactory和ApplicationContext的区别

  • ApplicationContext 继承了 ListableBeanFactory,这个 Listable 的意思就是,通过这个接口,我们可以获取多个 Bean,大家看源码会发现,最顶层 BeanFactory 接口的方法都是获取单个 Bean 的。
  • ApplicationContext 继承了 HierarchicalBeanFactory,Hierarchical(等级制度的.adj) 单词本身已经能说明问题了,也就是说我们可以在应用中起多个 BeanFactory,然后可以将各个 BeanFactory 设置为父子关系。
  • AutowireCapableBeanFactory 这个名字中的 Autowire 大家都非常熟悉,它就是用来自动装配 Bean 用的,但是仔细看上图,
    ApplicationContext 并没有继承它,不过不用担心,不使用继承,不代表不可以使用组合,ApplicationContext 接口定义中的最后一个方法 就是getAutowireCapableBeanFactory() 。
  • ConfigurableListableBeanFactory 也是一个特殊的接口,特殊之处在于它继承了第二层所有的三个接口。

源码分析

参考文章:Spring IOC 容器源码分析

我们知道ApplicationContext只是一个接口,不同的容器实现方法会有差异,Spring从XML解析出bean和用注解解析出bean的方式肯定不同,不过IoC容器的创建和bean注册等流程大体上还是相同的,所以这里拿ClassPathXmlApplicationContext作为分析流程对其他类型的容器也是适用的。

以下图几行基础代码来调试分析IoC容器的创建到bean的注册,再到从容器中取出相关的bean(spring版本为5.2.12,版本不同可能会有差异)

可以看到ClassPathXmlApplicationContext类绝大部分都是构造器

无论哪个构造器,最终调用的都是有三个参数的构造器


找到相应的构造器,refresh默认为true,显而易见refresh()就是我们要研究的核心方法
这里简单说下为什么是 refresh(),而不是 init() 这种名字的方法。因为 ApplicationContext 建立起来以后,其实我们是可以通过调用 refresh() 这个方法重建的,refresh() 会将原来的 ApplicationContext 销毁,然后再重新执行一次初始化操作。


我们看看其他类型的容器,也是调用refresh()方法

至于为什么那么巧,那还不是因为都是AbstractApplicationContext的子类,而调用的refresh()方法也是其下的方法

refresh()方法源码和简单分析如下

@Overridepublic void refresh() throws BeansException, IllegalStateException {//加锁保证线程同步,否则多线程下一个线程refresh()还没结束,另一个线程又启动或者销毁线程就会乱套synchronized (this.startupShutdownMonitor) {// Prepare this context for refreshing.//准备工作,记录下容器的启动时间、标记“已启动”状态、处理配置文件中的占位符prepareRefresh();// Tell the subclass to refresh the internal bean factory.//关键,解析XML中配置的bean,注册到Bean工厂中,但并不是说bean被初始化,只是把诸如bean的名字,所属的类提取出来//保存在Bean工厂中的beanDefinitionMap属性中ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// Prepare the bean factory for use in this context.//准备Bean工厂,设置 BeanFactory 的类加载器,添加几个 BeanPostProcessor,手动注册几个特殊的 beanprepareBeanFactory(beanFactory);try {// Allows post-processing of the bean factory in context subclasses.// 这里是提供给子类的扩展点,到这里的时候,所有的 Bean 都加载、注册完成了,但是都还没有初始化// 具体的子类可以在这步的时候添加一些特殊的 BeanFactoryPostProcessor 的实现类或做点什么事postProcessBeanFactory(beanFactory);// Invoke factory processors registered as beans in the context.//调用BeanFactoryPostProcessor 各个实现类的 postProcessBeanFactory(factory) 方法invokeBeanFactoryPostProcessors(beanFactory);// Register bean processors that intercept bean creation.//注册bean后置处理器来拦截bean的创建,注意区别BeanPostProcessors和BeanFactoryPostProcessor//两者功能类似但应用场所不同registerBeanPostProcessors(beanFactory);// Initialize message source for this context.//初始化信息源,主要用于国际化功能,略过initMessageSource();// Initialize event multicaster for this context.//初始化事件广播器,略过initApplicationEventMulticaster();// Initialize other special beans in specific context subclasses.//典型的模板方法(钩子方法),具体的子类可以在这里初始化一些特殊的 Bean(在初始化 singleton beans 之前)//例如SpringMVC重写该方法以初始化九大组件,当Spring容器执行到这步就会将九大组件跟着初始化onRefresh();// Check for listener beans and register them.//注册和检查事件监听器,监听器需要实现 ApplicationListener接口,略过registerListeners();// Instantiate all remaining (non-lazy-init) singletons.//关键,初始化所有单例的bean(懒加载的除外)finishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.//最后一步,ApplicationContext 初始化完成,发布相应的事件finishRefresh();}catch (BeansException ex) {if (logger.isWarnEnabled()) {logger.warn("Exception encountered during context initialization - " +"cancelling refresh attempt: " + ex);}// Destroy already created singletons to avoid dangling resources.//销毁所有已经创建的单实例Bean,以免有些bean会一致占用资源destroyBeans();// Reset 'active' flag.//重新设置active标志cancelRefresh(ex);// Propagate exception to caller.throw ex;}finally {// Reset common introspection caches in Spring's core, since we// might not ever need metadata for singleton beans anymore...resetCommonCaches();}}}

我们的关注点将重点放在这两方法上,前者将解析bean的信息后注册到Bean工厂,后者将所有单实例的Bean初始化。其他方法会简单了解而不作深入分析。

prepareRefresh

顾名思义,就是做些准备工作,水平有限很多名词都不了解,大家先参考下面博客
prepareRefresh方法源码跟踪

obtainFreshBeanFactory(⭐)

经过调试完这一步,将会返回beanFactory对象,这个对象即bean工厂包含了我们在XML配置的所有bean甚至还包括了注解等其他方式配置的bean,也就是说这一步会获取全部不管什么形式配置的bean的信息。所以这方法的重要性不言而喻。

obtainFreshBeanFactory()只是为了获取ConfigurableListableBeanFactory类型的bean工厂,所以核心逻辑需要更深入寻找,显然这里只有refreshBeanFactory()是我们要探索的

然而这个方法是抽象方法,只能去其子类看如何重写了

注意到这里就分为两个分支了,一个分支是AbstractRefreshableApplicationContext,另一个分支是GenericApplicationContext,我们目前是使用XML配置bean的方式来分析源码,所以走的AbstractRefreshableApplicationContext分支,如果是用注解方式配置就走另一条分支,两条分支重写的refreshBeanFactory()方法都是不同的,在此分道扬镳,容器的差异性就开始显现出来了。

@Overrideprotected final void refreshBeanFactory() throws BeansException {//如果已经有Bean工厂存在,就销毁素所有Bean,然后关闭Bean工厂//注意,应用中 BeanFactory 本来就是可以多个的,这里可不是说应用全局是否有 BeanFactory,而是当前// ApplicationContext 是否有 BeanFactoryif (hasBeanFactory()) {destroyBeans();closeBeanFactory();}try {DefaultListableBeanFactory beanFactory = createBeanFactory();//创建DefaultListableBeanFactory类型的Bean工厂beanFactory.setSerializationId(getId());//Bean工厂的序列化,略过customizeBeanFactory(beanFactory);//设置了Bean工厂的两个配置属性:是否允许Bean覆盖、是否循环引用loadBeanDefinitions(beanFactory);//加载Bean到BeanFactory中,看名字这就是我们要找的!this.beanFactory = beanFactory;}catch (IOException ex) {throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);}}

顺便看下另一条分支的重写的refreshBeanFactory() 方法

至于为什么要特别地创建DefaultListableBeanFactory类型的Bean工厂,我们可以看看接口实现图,DefaultListableBeanFactory类不仅是ConfigurableListableBeanFactory接口唯一的实现类,而且还独占一条实现链。所以DefaultListableBeanFactory类基本是功能最完全的Bean工厂了,这也是为什么用它来实例化的原因。

Bean工厂的序列化可以略过,接下来就是customizeBeanFactory(beanFactory)方法,我们看它是做什么的。
这里只有两个方法,简单了解就行。
第一个方法设置是否允许BeanDefinition覆盖,就是在配置文件中定义 bean 时使用了相同的 id 或 name,默认情况下,allowBeanDefinitionOverriding 属性为 null,如果在同一配置文件中重复了,会抛错,但是如果不是同一配置文件中,会发生覆盖。
第二个方法设置是否允许Bean之间的循环依赖,循环引用也很好理解:A 依赖 B,而 B 依赖 A。或 A 依赖 B,B 依赖 C,而 C 依赖 A。默认情况下,Spring 允许循环依赖,当然如果你在 A 的构造方法中依赖 B,在 B 的构造方法中依赖 A 是不行的。

接下来就剩loadBeanDefinitions(beanFactory)方法了,看方法名毫无疑问这就是我们此行的目的——bean的信息是被解析的并且是如何注册到Bean工厂中的。又是个抽象方法,只能去子类找了。

其子类只有AbstractXmlApplicationContext

@Overrideprotected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {// Create a new XmlBeanDefinitionReader for the given BeanFactory.//创建XmlBeanDefinitionReader阅读器XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);// Configure the bean definition reader with this context's//配置阅读器的一些参数// resource loading environment.beanDefinitionReader.setEnvironment(this.getEnvironment());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.// 初始化 BeanDefinitionReader,其实这个是提供给子类覆写的,跟后面讲的onRefresh作用类似,钩子方法// 暂时没有类重写这个方法,略过initBeanDefinitionReader(beanDefinitionReader);loadBeanDefinitions(beanDefinitionReader);//用初始化好的阅读器来解析XML配置}

其实到loadBeanDefinitions(beanDefinitionReader)这一步才真正解析XML中的bean,但到这一步足够了,我们不需要了解到底是如何解析XML中的标签,再从中识别bean的信息放到Bean工厂中的。有兴趣了解的可以看看参考博客,大约在四分之一处,自己找吧
Spring IOC 容器源码分析

prepareBeanFactory

这一步设置 BeanFactory 的类加载器,添加几个 BeanPostProcessor,手动注册几个特殊的 bean

protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {// Tell the internal bean factory to use the context's class loader etc.//设置BeanFactory的类加载器,因为BeanFactory需要加载类,这里设置为加载当前 ApplicationContext 类的类加载器beanFactory.setBeanClassLoader(getClassLoader());beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));// Configure the bean factory with context callbacks.//添加Bean后置处理器,后置处理器后面会讲beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));//以下几行的作用相同,即如果某个 bean 依赖于以下几个接口的实现类,在自动装配的时候忽略它们//Spring 会通过其他方式来处理这些依赖。beanFactory.ignoreDependencyInterface(EnvironmentAware.class);beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);beanFactory.ignoreDependencyInterface(MessageSourceAware.class);beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);// BeanFactory interface not registered as resolvable type in a plain factory.// MessageSource registered (and found for autowiring) as a bean./*** 下面几行就是为特殊的几个 bean 赋值,如果有 bean 依赖了以下几个,会注入这边相应的值,* 之前我们说过,"当前 ApplicationContext 持有一个 BeanFactory",这里解释了第一行。* ApplicationContext 还继承了 ResourceLoader、ApplicationEventPublisher、MessageSource* 所以对于这几个依赖,可以赋值为 this,注意 this 是一个 ApplicationContext* 那这里怎么没看到为 MessageSource 赋值呢?那是因为 MessageSource 被注册成为了一个普通的 bean*/beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);beanFactory.registerResolvableDependency(ResourceLoader.class, this);beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);beanFactory.registerResolvableDependency(ApplicationContext.class, this);// Register early post-processor for detecting inner beans as ApplicationListeners.// 这个 BeanPostProcessor 也很简单,在 bean 实例化后,如果是 ApplicationListener 的子类,// 那么将其添加到 listener 列表中,可以理解成:注册 事件监听器beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));// Detect a LoadTimeWeaver and prepare for weaving, if found.//寻找名为loadTimeWeaver的bean,如果有就准备织入//这个bean是 AspectJ 的概念,指的是在运行期进行织入,这个和 Spring AOP 不一样,暂时略过if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));// Set a temporary ClassLoader for type matching.beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));}// Register default environment beans.//注册跟默认环境相关的bean,依次为environment、systemProperties、systemEnvironment这三个beanif (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());}if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());}if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());}}

后置处理器

这三个方法都涉及后置处理器的知识点,可以简单理解类似JavaWeb中过滤器的功能,可以在核心逻辑之外做一些功能扩展,稍微了解就好。



BeanFactoryPostProcessor和BeanPostProcessor,这两个接口,都是Spring初始化bean时对外暴露的扩展点。两个接口名称看起来很相似,但作用及使用场景却不同
BeanFactoryPostProcessor接口定义如下,实现该接口,可以在spring的bean创建之前,修改bean的定义属性。也就是说,Spring允许BeanFactoryPostProcessor在容器实例化任何其它bean之前读取配置元数据,并可以根据需要进行修改,例如可以把bean的scope从singleton改为prototype,也可以把property的值给修改掉。可以同时配置多个BeanFactoryPostProcessor,并通过设置’order’属性来控制各个BeanFactoryPostProcessor的执行次序。

BeanPostProcessor接口定义如下,可以在spring容器实例化bean之后,在执行bean的初始化方法前后,添加一些自己的处理逻辑。这里说的初始化方法,指的是下面两种:
1)bean实现了InitializingBean接口,对应的方法为afterPropertiesSet

2)在bean定义的时候,通过init-method设置的方法

注意:BeanPostProcessor是在spring容器加载了bean的定义文件并且实例化bean之后执行的。BeanPostProcessor的执行顺序是在BeanFactoryPostProcessor之后。

参考博客:
Spring的BeanFactoryPostProcessor和BeanPostProcessor
Spring 钩子之BeanFactoryPostProcessor和BeanPostProcessor的源码学习

onRefresh

典型的模板方法(钩子方法),(什么是钩子方法暂时也不太了解,就是设计模式的一种,我理解是扩展点,用于一些自定义功能,上面的后置处理器也是钩子,先占个坑)
具体的子类可以在这里初始化一些特殊的 Bean,例如SpringMVC源码中重写了该方法,只要Spring容器执行到onRefresh方法,就会将SpringMVC九大组件初始化,如下图

finishBeanFactoryInitialization(⭐)

  • 占坑

Java_Spring_IoC相关推荐

最新文章

  1. 是什么让数据科学家从优秀变得伟大?
  2. Repeater嵌套
  3. 自动生成Hibernate框架结构
  4. 【Linux】【通信】1.ping不通
  5. BASIC-3 字母图形
  6. SAP 电商云 Accelerator 和 Spartacus UI 的工作机制差异
  7. 弱,弱,最弱,利用专业参考来利用垃圾收集器
  8. Visual Studio 2017中的第一个Python项目
  9. 神经网络技巧篇之寻找最优参数的方法【续】
  10. 西瓜书+实战+吴恩达机器学习(三)机器学习基础(多分类、类别不平衡)
  11. 查看Ubuntu系统的版本
  12. word如何去掉标题前面的黑点
  13. c语言编程:实现数字的翻转
  14. clickhouse的傻瓜式安装和基础入门
  15. 《ucore lab1 exercise5》实验报告
  16. html下拉框设置默认值_html下拉框怎么设置默认值
  17. SQL视图View的总结和使用
  18. Vue.js的下载和调用
  19. Android获取软键盘输入内容
  20. 常见的网络状态检测及分析工具

热门文章

  1. 基于 GraphQL 平台化 BFF 构建及微服务治理
  2. 《脱颖而出——成功网店经营之道》一1.4 网店的特点
  3. 达梦数据库发展史(包含数据库安装和使用链接)
  4. 毕业季--Java基础面试题
  5. linux运行软件代码,Linux软件安装(二进制,源码包)
  6. CAReplicatorLayer 学习和实践
  7. 你不知道的redis三-Redis的持久化机制
  8. rslidar LOAM系列建图
  9. mit app中计算器制作程序_Step7中配合WinCC制作比例阀控制程序示例
  10. el-select 多选设置默认值后无法操作问题处理