Spring
什么是Spring框架
Spring是一种轻量级框架,旨在提高开发人员的开发效率和系统的可维护性。 我们一般说的Spring框架就是SPring Framework,他是很多模块的集合,使用这些模块可以很方便的协助我们进行开发。这些模块是核心容器、数据访问/集成、web、aop、工具、消息和测试模块。比如Core Container中的Core组件是Spring所有组件的核心,Beans组件和Context组件是实现IOC和DI的基础,AOP组件用来实现面向切面编程。 Spring官网列出来的Spring的6个特征:
- 核心技术:依赖注入,aop,事件,资源,i18n,验证,数据绑定,类型转换,SpEL
- 测试:模拟对象,TestContext框架,SPringMVC测试,WebTestClient
- 数据访问:事务,DAO支持,JDBC,ORM,编组XML
- Web支持:Spring MVC和SPring WebFlux Web框架
- 集成:远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存
- 语言:Kotlin,Groovy,动态语言
Spring中的单例bean的线程安全问题了解吗?
大部分我们并没有在系统中使用多线程,所以很少有人会关注这个问题。单例bean存在线程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。
有两种常见解决方案: 1、在bean对象中尽量避免定义可变的成员变量。 2、在类中定义一个ThreadLocal成员变量,将需要的可变的成员变量保存在ThreadLocal中。
Spring中的bean生命周期?
Bean的完整生命周期经历了各种方法调用,这些方法可以划分为以下几类:
- Bean自身的方法:这个包括了Bean本身调用的方法和通过配置文件中的init-method和destroy-method指定的方法
- Bean级生命周期接口方法:这个包括了BeanNameAware、BeanFactoryAware、ApplicationContext;当然也包括InitializingBean和DiposableBean这些接口的方法
- 容器级生命周期接口方法:这个包括了InstantiationAwareBeanPostProcessor和BeanPostProcessor这两个接口实现,一般称它们的实现类为“后处理器”
- 工厂后处理器接口方法:这个包括了AspectJWeavingEnabler,ConfigurationClassPostProcessor,CustomAutowireConfigurer等等非常有用的工厂后处理接口的方法。工厂后处理器也是容器级的。在应用上下文装配配置文件之后立即调用。
如何将Bean从XML配置中解析后放到IoC容器中得?
初始化的入口
对于xml配置的Spring应用,在main()方法中实例化ClasspathXmlApplication即可创建一个IoC容器。我们可以从这个构造方法开始,探究一下IoC容器的初始化过程。
1
2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
设置资源解析器和环境
调用父类容器AbstractApplicationContext的构造方法(super(parent)方法)为容器设置好Bean资源加载器
1
2
3
4
5
6
7
通过AbstractApplicationContext默认构造器初始化容器id,name,状态以及资源解析器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
通过AbstractApplicationContext的setParent(parent)方法将父容器的Enviroment合并到当前容器
1
2
3
4
5
6
7
8
9
设置配置路径
在设置容器的资源加载器之后,接下来FileSystemXmlApplicationContent执行setConfigLocations方法通过调用其父类AbstractRefreshableConfigApplicationContext的方法进行对Bean定义资源文件的定位。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
初始化的主体流程
Spring IoC容器对Bean定义资源的加载是从refresh()函数开始的,refresh()是一个模板方法,refresh()方法的作用是:在创建IoC容器前,如果已经有容器存在,则需要把已有的容器销毁和关闭,以保证在refresh()之后使用的是新建立起来的IoC容器。refresh的作用类似于对IoC容器的重启,在新建立好的容器中对容器进行初始化,对Bean定义资源进行载入。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
这里的设计上是一个非常典型的资源类加载处理型的思路
- 模板方法设计模式,模板方法中使用典型的钩子方法
- 将具体的初始化加载方法插入到钩子方法之间
- 将初始化的阶段封装,用来记录当前初始化到什么阶段;常见的设计是xxxPhase/xxxStage
- 资源加载初始化有失败等处理,必然是try/catch/finally
初始化BeanFactory之obtainFreshBeanFactory
AbstractApplicationContext的obtainFreshBeanFactory()方法调用子类容器的refreshBeanFactory()方法,启动容器载入Bean定义资源文件的过程,代码如下
1
2
3
4
5
AbstractApplicationCOntext类中只抽象定义了refreshBeanFactory()方法,容器真正调用的是子类的AbstractRefreshableApplicationContext实现的refreshBeanFactory()方法留在创建IoC容器之前,如果已经有容器存在,则需要把已有的容器销毁和关闭,以保证在refresh之后使用的是新建立起来的IoC容器。方法源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
初始化BeanFactory之loadBeanDefinitions
AbstractRefreshableApplicationContext中只定义了抽象的loadBeanDefinitions方法,容器真正调用的是其子类的AbstractXmlApplicationContext对该方法的实现,AbstractXmlApplicationContext的主要源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Xml Bean读取器(XmBeanDefinitionReader)调用其父类AbstractBeanDefinitionReader的reader.loadBeanDefinitions方法读取bean定义资源
由于我们使用ClassPathXmlApplicationCOntext作为例子分析,因此getConfigResources的返回值为null,因此程序执行reader,loadBeanDefinitions(configLocations)分支。
AbstractBeanDefinitionReader读取Bean定义资源
AbstractBeanDifinitionReader的loadBeanDefinitions方法源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
从对AbstractBeanDefinitionReader的loadBeanDefinitions方法源码分析可以看出该方法做了以下两件事:
1、首先,调用资源加载器的获取资源方法resourceLoader.getResource(location),获取到要加载的资源。 2、其次,真正执行加载功能的是其子类XmlBeanDefinitionReader的loadBeanDefinitions方法
XmlBeanDefinitionReader加载Bean定义资源
继续看子类XmlBeanDefinitionReader的loadBeanDefinitions(Resource…)方法看到代表bean文件的资源定义以后的载入过程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
通过源码分析,载入Bean定义资源文件的最后一步是将Bean定义资源转换为Document对象,该过程由documentLoader实现
DocumentLoader将Bean定义资源转换为Document对象
DocumentLoader将Bean定义资源转换为Document对象的源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
该解析过程调用JavaEE标准的JAXP标准进行处理。
至此Spring IoC容器根据定位的Bean定义资源文件,将其加载读入并转换成为Document对象过程完成。
接下来我们继续分析Spring IoC容器将载入的Bean定义资源文件转换为Document对象之后,是如何将其解析为Spring IoC管理的Bean对象,并将其注册到容器中的。
XmlBeanDefinitionReader解析载入的Bean定义资源文件
XmlBeanDefinitionReader类中的doLoadBeanDefinitions方法是从特定的XML文件中实际载入Bean定义资源的方法,该方法在载入Bean定义资源之后将其转换为Document对象,接下来调用registerBeanDefinitions启动Spring IoC容器对Bean定义的解析过程,registerBeanDefinition方法源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Bean定义资源的载入解析分为以下两个过程:
1、首先,通过调用XML解析器将Bean定义资源文件转换得到Document对象,但是这些Document对象并没有按照Spring的Bean规则进行解析。这一部是载入的过程 2、其次,在完成通用的XML解析之后,按照Spring的Bean规则对Document对象进行解析。 按照Spring的Bean规则对Document对象解析的过程是在接口BeanDifinitionDocumentReader的实现类DefaultBeanDefinitionDocumentReader中实现的。
DefaultBeanDefinitionDocumentReader对bean定义的Document对象解析
BeanDefinitionDocumentReader接口通过registerBeanDefinitions方法调用其实现类DefaultBeanDefinitionDocumentReader对Document对象进行解析,解析代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
BeanDefinitionParserDelegate 解析Bean定义资源文件生成BeanDefinition
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
解析Bean生成BeanDefinitionHolder的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
解析过后的BeanDefinition在IoC容器中注册
Document对象的解析后得到封装BeanDefinition的BeanDifinitionHold对象,然后调用BeanDefinitionReaderUtils的registerBeanDefinition方法向IoC容器注册解析的Bean,BeanDefinitionReaderUtils的注册的源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
当调用BeanDefinitionReaderUtils向IoC容器注册解析的BeanDefinition时,真正完成注册功能的是DefaultListableBeanFactory.
DefaultListableBeanFactory 向IoC容器注册解析后的BeanDefinition
IoC容器本质上就是一个beanDefinitionMap,注册即将BeanDefinition put 到map中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
至此,Bean定义资源文件中配置的Bean被解析过后,已经注册到IoC容器中,被容器管理起来,真正完成了IoC容器初始化所做的全部工作。现在IoC容器中已经建立了整个Bean的配置信息,这些BeanDefinition信息已经可以使用,并且可以被检索,IoC容器的作用就是对这些注册的Bean定义信息进行处理和维护。这些的注册的Bean定义信息是IoC容器控制反转的基础,正是有了这些注册的数据,容器才可以进行依赖注入。
总结
现在通过上面的代码,总结一下IoC容器初始化的基本步骤:
- 初始化的入口在容器实现中的refresh()调用来完成 对bean定义载入IoC容器使用的方法是loadBeanDefinition,其中大致过程如下:
1、通过ResourceLoader来完成资源文件位置的定位,DefaultResourceLoader是默认的实现,同时上下文本身就给出了ResourceLoader的实现,可以从类路径,文件系统,URL等方式来定义为资源位置。如果是XmlBeanFactory作为IOC容器,那么需要为它指定bean定义的资源,也就是说bean定义文件时通过抽象成Resource来被IOC容器处理的。 2、通过BeanDefinitionReader来完成定义信息的解析和Bean信息的注册,往往使用的是XmlBeanDefinitionReader来解析bean的xml定义文件-实际的处理过程是委托给BeanDefinitionParserDelegate来完成的,从而得到bean的定义信息,这些信息在Spring中使用BeanDefinition对象来表示-这个名字可以让我们想到loadBeanDefinition,RegisterBeanDefinition这些相关方法-他们都是为处理BeanDefinitin服务的。 3、容器解析得到BeanDefinition以后,需要把它在IOC容器中注册,这由IOC实现BeanDefinitionRegister接口来实现。注册过程就是在IOC容器内部维护一个HashMap来保存得到的BeanDefinition的过程。这个HashMap是IoC容器持有bean信息的场所,以后对bean的操作都是围绕这个HashMap来实现的。
- 然后我们就可以通过BeanFactory和ApplicationCOntext来享受到SPring IOC的服务了,在使用IoC容器的时候,我们注意到除了少量粘合代码,绝大多数以正确IoC风格编写的应用程序代码完全不用关心如何到达工厂,因为容器将把这些对象与容器管理的其他对象钩在一起。基本的策略是把工厂放到已知的地方,最好是放在对预期使用的上下文有意义的地方,以及代码将实际需要访问工厂的地方。Spring本身提供了对声明式载入Web应用程序用法的应用程序上下文,并将其存储在ServletContext中的框架实现。
Spring中getBean的主体思路
BeanFactory实现getBean方法在AbstractBeanFactory中,这个方法重载都是调用doGetBean方法进行实现的:
1
2
3
4
5
6
7
8
9
10
11
12
13
接下来看下doGetBean方法(这个方法很长,我们主要看它的整体思路和设计要点):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
上述代码主要看中文注释即可:
- 解析bean的真正name,如果bean是工厂类,name前缀会加&,需要去掉
- 无参单例先从缓存中获取
- 如果bean实例还在创建中,则直接抛出异常
- 如果bean definition存在于的bean工厂中,委派给父Bean工厂获取
- 标记这个beanName的实例正在创建
- 确保它的依赖也被初始化 真正创建:
-
单例时
-
原型时
-
根据bean的scope创建
重点: SPring如何解决循环依赖问题
首先我们需要声明,Spring只是解决了单例模式下属性依赖的循环问题;Spring为了解决单例的循环依赖问题,使用了三级缓存。
Spring单例模式下的属性依赖
先来看下这三级缓存
1
2
3
4
5
6
7
8
- 第一级缓存(singletonObjects): 单例对象缓存池,已经实例化并且属性赋值,这里的对象是成熟对象
- 第二层缓存(earlySingletonObjects): 单例对象缓存池,已经实例化但尚未属性赋值,这里的对象是半成品对象
- 第三层缓存(singletonFactories): 单例工厂的缓存 如下是获取单例中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
补充一些方法和参数
- isSingletonCurrentlyIncreation(): 判断当前单例bean是否正在建立中,也就是没有初始化完成(好比A的构造器依赖了B对象因此得先去建立B对象,或则在A得populateBean过程当中依赖了B对象,得先去建立B对象,这时得A就是处于建立中的状态。)
- allowEarlyRefrence: 是否容许从singletonFactories中经过getObject拿到对象 分析getSinflwton()整个过程,Spring首先从一级缓存singletonObjects中获取。若是获取不到,而且对象正在建立中,就再从二级缓存earlySingletonObjects中获取。若是仍是获取不到且容许singletonFactories经过getObject()获取,就从三级缓存singlwtonFactory.getObject()(三级缓存)获取,若是获取到了则从三级缓存移动到了二级缓存。
从上面三级缓存的分析,咱们能够知道,Spring解决循环依赖的诀窍就在于singletonFactories这个三级cache。这个cache的类型是ObjectFactory,定义以下:
1
2
3
在bean建立过程当中,有两处比较重要的匿名内部类实现了该接口。一处是Spring利用其建立bean的时候,另外一处就是:
1
2
3
4
此处就是解决循环依赖的关键,这段代码发生在createBeanInstance以后,也就是说单例对象此时已经被建立出来的。这个对象已经被生产出来了,虽然还不完美(尚未进行初始化的第二步和第三步),可是已经能被人认出来了(根据对象引用能定位到堆中的对象),因此Spring此时将这个对象提早曝光出来让你们认识,让你们使用。
Spring为何不能解决非单例属性之外的循环依赖?
Spring为什么不能解决构造器的循环依赖
构造器注入形成的循环依赖: 也就是BeanB需要在beanA的构造函数中完成初始化,beanA也需要2在beanB的构造函数中完成初始化,这种情况的结果就是两个bean都不能完成初始化,循环依赖难以解决。
Spring解决循环依赖主要是依赖三级缓存,但是得在调用构造方法之前还未将其放入三级缓存之中,因此后续得依赖调用构造方法的时候并不能从三级缓存中获取到依赖的Bean,因此不能解决。
Spring为什么不能解决prototype作用域循环依赖
这种循环依赖同样无法解决,因为Spring不会缓存prototype作用域的bean,而spring中循环依赖的解决正是通过缓存来实现的。
Spring为什么不能解决多例的循环依赖?
多实例Bean是每次调用一次getBean都会执行一次构造方法并且给属性赋值,根本没有三级缓存,因此不能解决循环依赖。
那么其他循环依赖如何解决?
在实际开发中,类似的依赖是如何解决?
- 生成代理对象产生的循环依赖 这类循环依赖解决方法有很多,主要有:
1、使用@Lazy注解,延迟加载
2、使用@DependsOn注解,指定加载先后关系
3、修改文件名称,改变循环依赖类的加载顺序
- 使用@DependsOn产生的循环依赖 这类循环依赖问题要找到@DependsOn注解循环依赖的地方,迫使它不循环依赖就可以解决问题。
- 多例循环依赖 这类循环依赖问题可以通过把bean改成单例的解决
- 构造器循环依赖 这类循环依赖问题可以通过使用@Lazy注解解决。
重点: Spring中Bean的生命周期
Spring只帮我们管理单例模式Bean的完整的生命周期,对于prototype的bean,SPring在创建好交给使用者之后则不会再管理后续的生命周期。
Spring容器可以管理singleton作用域Bean的生命周期,在此作用域下,SPring能够精确地知道该Bean何时被创建,何时初始化完成,以及何时被销毁。
而对于prototype作用域的Bean,Spring只负责创建,当容器创建了Bean的实例后,Bean的实例就交给客户端代码管理,Spring容器就不再跟踪其生命周期。每次客户端请求prototype作用域的Bean时,SPring容器都会创建一个新的实例,并且不会管那些被配置成prototype作用域的Bean的生命周期。
了解SPring生命周期的意义就在于,可以利用Bean在其存货期间的指定时刻完成一些相关操作。这种时刻可能有很多,但一般情况下,会在Bean被初始化后和被销毁前执行一些相关操作。
Spring Bean生命周期流程
在SPring中,Bean的生命周期是一个很复杂的执行过程,我们可以利用Spring提供的方法定制Bean的创建过程。
- 如果BeanFactoryPostProcessor和Bean关联,则调用postProcessBeanFactory方法.(即首先尝试从Bean工厂中获取Bean)
- 如果InstantiationAwareBeanPostProcessor和Bean关联,则调用postProcessBeforeInstantiation方法
- 根据配置情况调用Bean构造方法实例化Bean。
- 利用依赖注入完成Bean中所有属性值的配置注入。
- 如果InstantiationAwareBeanPostProcessor和Bean关联,则调用postProcessAfterInstantition方法和postProcessProperties 调用xxxAware接口(上图只是给了几个例子)
1、如果bean实现了BeanNameAware接口,则Spring调用Bean的setBeanName()方法传入当前Bean的id值。
2、如果Bean实现了BeanClassLoaderAware接口,则Spring调用setBeanClassLoader()方法传入classLoader的引用。
3、如果Bean实现了BeanFactoryAware接口,则Spring调用setBeanFactory()方法传入当前工厂实例的引用。
第二类Aware接口
1、如果Bean实现了EnvironmentAware接口,则Spring调用setEnviroment()方法传入当前Enviroment实例的引用。
2、如果Bean实现了EmbeddedValueResolverAware接口,则Spring调用setEmbeddedValueResolver()方法传入当前StringValueResolver实例的引用。
3、如果Bean实现了ApplicationContextAware接口,则Spring调用setApplicationContext()方法传入当前ApplicationContext实例的引用。
-
如果BeanPostProcessor和Bean关联,则SPring将调用该接口的预初始化方法postProcessBeforeInitialzation()对Bean进行加工操作,此处非常重要,SPring的AOP就是利用它实现的
-
如果Bean实现了InitializingBean接口,则SPring将调用afterPropertiesSet()方法。(或者有执行@PostConstruct注解的方法)
-
如果在配置文件中通过init-method属性指定了初始化方法,则调用该初始化方法
-
如果BeanPostProcessor和Bean关联,则Spring将调用该接口的初始化方法postProcesasAfterInitialization()。此时,Bean已经可以被应用系统使用了。
-
如果在bean中指定了该Bean的作用范围为scope=“singleton”,则将该Bean放入Spring IoC的缓存池中,将触发Spring对该Bean的生命周期管理;如果在bean中指定了该Bean的作用范围为scope=“prototype”,则将该Bean交给调用者,调用者管理该Bean的生命周期,Spring不再管理该Bean.
-
如果Bean实现了DisposableBean接口,则Spring会调用destory()方法将SPring中的Bean销毁;
-
如果在配置文件中通过destory-method属性指定了Bean的销毁方法,则Spring将调用该方法对Bean进行销毁。 Bean的完整生命周期经历了各种方法调用,这些方法可以划分为以下几类:
-
Bean自身的方法: 这个包括了Bean本身调用的方法和通过配置文件中bean的init-method和destroy-method指定的方法
-
Bean级生命周期接口方法: 这个包括了BeanNameAware、BeanFactoryAware、ApplicationContextAware;当然也包括InitializingBean和DiposableBean这些接口的方法(可以被@PostConstruct和@PreDestroy注解替代)
-
容器级生命周期接口方法: 这个包括了AspectJWeavingEnable,ConfigurationClassPostProcessor,CustomAutowireConfigurer等的非常有用的工厂后处理接口的方法,工厂后处理器也是容器级的。在应用上下文装配文件之后立即调用。