Spring#
虽然编码多年,我们再来重新认识Spring这个老朋友:
- spring有一大堆项目,核心的spring framework,以及发展而来的springboot、springcloud,工具类的spring-data、spring-security等
- spring framework通常的误区大家说他有IOC和AOP,然而并不是。
- 他的核心至少有: IoC Container, Events, Resources, i18n, Validation, Data Binding, Type Conversion, SpEL, AOP.
- 常用附加的还有:Transactions、JDBC、Spring MVC, WebSocket、JMS
- 以及新兴的:Spring WebFlux
BeanDefinition#
用来描述我们的Bean的信息,Bean不是普通的对象。不像Java使用一个XXX.getClass()就能描述这个对象(包括接口、字段、方法等)。Bean的信息更多(是否Singleton、是否是Spring特有的、DependsOn、Lazy)
IOC#
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)
- 我们使用DI完成IOC看起来编码似乎复杂度增加,有啥优势呢:
- 我们推荐面向抽象编程,这样可以更灵活。而面向抽象编程会产生类的依赖(具体使用哪个实现的问题)
- 基于抽象编程除了更灵活外,接触耦合可以给我们带来更强大的增强和拓展(如AOP增强)。最经典的基于代理的事务管理就可以实现。
- spring给我们提供一种机制来管理我们的依赖关系,就是IOC容器。
一些细节#
- xml和annotation是可以混用的。如果在
XMLApplicationContext中默认是没有开启注解的,此时想要使用annotation-based还是需要一个xml,在里面配置<context:component-scan base-package="com.xxx">。这一行会开启注解+自动扫描两个功能。 JavaConfig使用AnnotationConfigApplicationContext就不需要再次开启,默认是开启的。
xml细节#
-
可以
schema-based的XML方式管理 ,annotaion-based注解方式管理,java-based使用javabean管理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
<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"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--spring的p、c、contecxt等标签的xsd在jar包里,所以不用写远程xsd地址也会自动提示--/>
<!-- https://blog.csdn.net/fox_bert/article/details/80793030 -->
<!-- 这一行就可以开启注解自动扫描,此时就是注解+xml混用 -->
<context:component-scan base-package="com.sam.bean"/>
<bean id="school" class="com.sam.bean.School">
<property name="schoolName" value="xxx小学"></property>
<property name="student" ref="zhangsan"></property>
</bean>
<!--可以使用p属性完成属性注入-->
<bean id="zhangsan" class="com.sam.bean.Student" p:age="5">
<property name="name" value="张三"></property>
<!-- <property name="age" value="5"></property>-->
<property name="dad" ref="laozhang"></property>
</bean>
<bean id="laozhang" class="com.sam.bean.Person">
<property name="name" value="老张"/>
<property name="company" ref="school"/>
<property name="job" value="teacher"/>
</bean>
<!-- 构造器的循环依赖,无法解决 -->
<!--构造器也可以用c属性实现-->
<!-- <bean id="school" class="com.sam.bean.School" c:schoolName="xxx小学" c:student-ref="zhangsan"/>
-->
<!-- <bean id="school" class="com.sam.bean.School">-->
<!-- <constructor-arg name="schoolName" value="xxx小学"></constructor-arg>-->
<!-- <constructor-arg name="student" ref="zhangsan"></constructor-arg>-->
<!-- </bean>-->
<!-- <bean id="zhangsan" class="com.sam.bean.Student">-->
<!-- <constructor-arg name="age" value="5"></constructor-arg>-->
<!-- <constructor-arg name="name" value="张三"></constructor-arg>-->
<!-- <constructor-arg name="dad" ref="laozhang"></constructor-arg>-->
<!-- </bean>-->
<!-- <bean id="laozhang" class="com.sam.bean.Person">-->
<!-- <constructor-arg name="company" ref="school"></constructor-arg>-->
<!-- <constructor-arg name="name" value="老张"></constructor-arg>-->
<!-- <constructor-arg name="job" value="teacher"></constructor-arg>-->
<!-- </bean>-->
</beans>
注解细节#
@Autowired默认使用byType进行配置,重复的话去byName,多个实现的Class驼峰和属性name不一样就不知道哪个会报错。此时可以用@Primary指定优先一个。或者用@Qualifier ("xxxImpl")去配合使用@Resource默认使用byName进行配置。- 当一个单例对象引用另一个多例对象的时候,直接
@Autowaired或者@Resource会有问题,被引用的多例对象只会创建一次,变成一个单例对象。此时解决方案:- 给单例对象实现
ApplicationContextAware注入context,每次去context里面getBean就会拿到多例对象,但是侵入性太高。 - 用
Lookup Method Injection方法注入:@Lookup注解放到一个抽象方法上去获取bean,那么spring会直接每次给你一个新的(必然是代理模式实现的)
- 给单例对象实现
1 | // 一个单例对象 |
其他细节#
profile当前生效的配置环境,可以自己起名字,默认是default-profile。可以在@Configuration(或者在单独一个Class,但是很少用)的类名上写上@Profile("test"),那么就只有测试环境test的时候才会生效。profile怎么获取到的呢?他是放在environment里面的,可以从applicatoincontext.xml加载,或者java里面设置context.getEnvirionment().setActiveprofiles("test"),但是java设置之后要使用context.resresh()才能生效。
启动过程#

BeanDefinitionRegistryPostProcessor是BeanFactoryPostProcessor的子接口
注解启动的ConfigurationClassPostProcessor就是典型实现。
spring解决循环依赖方案#
首先有两种循环依赖(一般都是说单例的问题),constructor的循环依赖问题无法解决。setter的循环依赖可以解决。
注册到DefaultSingletonBeanRegistry的对象有三级缓存,getSingleton获取的时候可以提前暴露暂时还没setter完的对象。

- 这个
DefaultSingletonBeanRegistry是默认Bean工厂DefaultListableBeanFactory的父类。所以beanFactory也是注册中心:
1 | public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry { |
SpringFramework的拓展点#
官网关于拓展点的链接:
先说结论:一般来说开发者无需自己去实现一个ApplicationContext进行拓展(虽然在诸如refresh方法中预留了拓展类),而是选用实现spring的插件式接口进行拓展。
通过 BeanPostProcesso定制化Bean#
如果要更改实际的bean实例(即从配置元数据创建的对象),则需要使用BeanPostProcessor进行拓展。可以插手Bean创建的过程,可以是多个BPP链式执行,可以减轻BF的负担。
如AOP就是Bean实例化的期间通过BPP织入切面逻辑到代理对象的。
-
what:
BeanPostProcessor接口定义回调方法,可以实现这些回调方法来提供您自己的(或覆盖容器的缺省)实例化逻辑、依赖项解析逻辑等。 如果希望在Spring容器完成实例化、配置和初始化bean之后实现一些自定义逻辑,可以插入一个或多个自定义BeanPostProcessor实现。 -
后处理器可以对bean实例执行任何操作,如回调接口、包装Bean。比如 一些Spring AOP基础结构类被实现为bean后处理器,以提供代理包装逻辑。
-
**when:**当类被注册为
BeanPostProcessor时,对于容器创建的每个bean实例,后处理器都会在容器初始化方法(如InitializingBean.afterPropertiesSet()或 任何声明的init方法),并在任何bean初始化回调之后被调用。 -
how: 自动检测。
ApplicationContext自动检测实现BeanPostProcessor接口的所有bean。ApplicationContext将这些bean注册BFPP,以便以后在bean创建时可以调用它们。 -
**other:**可以配置多个
BeanPostProcessor实例,通过设置order属性并实现Ordered接口来控制这些BeanPostProcessor实例的执行顺序。
1 | package scripting; |
通过拓展BeanFactoryPostProcessor自定义配置信息#
-
what:
BeanFactoryPostProcessor对Bean配置**元数据(METADATA)**进行操作。 也就是说,Spring IoC容器允许BeanFactoryPostProcessor读取配置元数据,并有可能在容器实例化除BeanFactoryPostProcessor实例以外的任何bean之前更改它。(官网原文,说的很严谨)。同样可以使用Orderd进行排序。 -
wheh:
BeanFactoryPostProcessor在ApplicationContext中声明后会自动执行,以便将更改应用于定义容器的配置元数据。 Spring包含许多预定义的BFPP,例如PropertyOverrideConfigurer和PropertySourcesPlaceholderConfigurer。 我们也可以使用自定义BeanFactoryPostProcessor,例如注册自定义属性编辑器 -
**how:**自动检测,
ApplicationContext自动检测实现BeanFactoryPostProcessor接口的Bean。它在适当的时候将这些bean用作BFPP。
BeanFactory和FactoryBean#
FactoryBean是一个
1 | /** |
beanFactory.getBeanNamesForType(Car.class)===carFactoryBean
beanFactory.getBeanNamesForType(CarFactoryBean.class)===&carFactoryBean
自定义初始化器initializer拓展#
首先实现ApplicationContextInitializer接口,会有一个initialize(context)方法需要重写。会在resresh()之前进行调用,如:ContextIdApplicationContextInitializer给context设置ID。下面自己实现了一个毫无意义的初始化器:
1 | public class MyApplicationContextInitializer implements ApplicationContextInitializer <ConfigurableApplicationContext>{ |
注册到容器有两种方式:
-
系统级:
-
建立
META-INF/spring.factories,然后注册,如: -
# 增加一个自定义初始化器,在new SpringApplication的时候load创建,refresh之前会被调用 org.springframework.context.ApplicationContextInitializer=com.sam.bootdemo.extend.MyApplicationContextInitializer1
2
3
4
5
6
7
8
9
10
11
12
这种方式将会在系统启动的时候使用`getSpringFactoriesInstances(ApplicationContextInitializer.class)`方式加载到,然后注册到context中去统一执行。
- 委派级:
- 不用创建spring.factories。直接注册到context和系统级的initlitializeri并列,而是委派给另一个`DelegatingApplicationContextInitializer`调用。如:
- 在spring的yaml或者propertiers中添加如下的属性(多个逗号分割):
- ```properties
# 将会被`DelegatingApplicationContextInitializer`解析并反射创建后以一个成员变量list存在。当DelegatingApplicationContextInitializer执行初始化方法的时候,会把这些自定义的初始化器进行排序、然后逐个调用。
context.initializer.classes=com.sam.bootdemo.extend.MyApplicationContextInitializer
-
自定义监听器listener拓展#
- 实现
ApplicationListener接口,有一个onApplicationEvent(ApplicationEvent event)方法需要重写。
1 | public class MyApplicationListener implements ApplicationListener<ApplicationEvent> { |
- 有一个顶级派发监听器接口叫做
SpringApplicationRunListener,他里面有一个实现:EventPublishingRunListener - 系统级的
EventPublishingRunListener监听器有多拨器SimpleApplicationEventMulticaster- 多拨器里面有我们的所有
ApplicationListener列表,可以根据supportsEvent方法判断哪些listener支持这个事件,并在doInvokeListener中调用
- 多拨器里面有我们的所有
- 所有的系统级监听器里面有一个很特殊的
DelegatingApplicationListener,和上面的初始化器很像,也是可以委派分发事件。
流程:
- 容器发生对应的业务场景时调用
ApplicationRunListener#starting/environmentPrepared/contextPrepared/contextLoaded/started/running/failed - ApplicationRunListener回去调用多拨器向listener分发事件,如
ApplicationStartingEvent、ApplicationEnvironmentPreparedEvent、ApplicationContextInitializedEvent、ApplicationPreparedEvent、ApplicationStartedEvent、ApplicationReadyEvent等等 - 多拨器分发到一个特殊的DelegatingApplicationListener时候,它又去加载和环境中的"context.listener.classes",然后反射创建、排序后向他们转发RunListener发来的事件。
注册到容器:
同上面的初始化器也是两种方式
META-INF/spring.factories里面注册