# Spring
最近回过神来,觉得是时候开始对学习已经的Spring框架,进行源码级别的解读,其一是加强自己对知识点的把握,其二是为了预防自己后面又遗忘/(ㄒoㄒ)/~~。
本文将从最基本的概念出发,辅助常见常考的面试题,来阐述Spring框架的思想以及应用点。
文章内容仅是我个人的小总结,资历尚浅,如有误还请指正。
# IOC 的认识
IOC:也即控制反转,DI即依赖注入,控制反转IOC和依赖注入DI其实就是同个概念的两个不同角度的解释。 控制反转可以理解为获取依赖对象的控制反转过来。
通过IOC容器实现的,由IOC容器负责创建和获取依赖对象,对象只是被动地接受依赖对象,从而实现更好的解耦。
IOC 的初始化过程如下:

实现IOC主要有三种方法:
- XML配置方式
- 注解方式
- 自动装配方式
- 组件扫描(
component scanning):Spring会自动发现应用上下文中所创建的bean - 自动装配(
autowiring):Spring自动满足bean之间的依赖
- 组件扫描(
装配注解主要有:@Autowired、@Qualifier、@Resource,它们的特点是:
- @
Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入; - @
Autowired默认是按照类型装配注入的,如果想按照名称来转配注入,则需要结合@Qualifier一起使用; - @
Resource注解是又J2EE提供,而@Autowired是由spring提供,故减少系统对spring的依赖建议使用@Resource的方式;如果Maven项目是1.5的JRE则需换成更高版本的。 - @
Resource和@Autowired都可以书写注解在字段或者该字段的setter方法之上 - @
Autowired可以对成员变量、方法以及构造函数进行注释,而 @Qualifier的注解对象是成员变量、方法入参、构造函数入参。 - @
Qualifier(“XXX”) 中的 XX是 Bean 的名称,所以 @Autowired和 @Qualifier结合使用时,自动注入的策略就从byType转变成byName了。 - @
Autowired注释进行自动注入时,Spring 容器中匹配的候选 Bean 数目必须有且仅有一个,通过属性required可以设置非必要。 - @
Resource装配顺序- 如果同时指定了
name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常 - 如果指定了
name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常 - 如果指定了
type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常 - 如果既没有指定name,又没有指定type,则自动按照
byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配
- 如果同时指定了
# AOP的理解
含义:
- 面向切面编程,基本单位为Aspect(切面): 它既包含了横切逻辑的定义, 也包括了连接点的定义,
- 即将切面所定义的横切逻辑织入到切面所指定的连接点中
- point cut 规定了哪些 join point 可以执行哪些 advice
- advice 是在 join point 上执行的
- aspect: aspect 是 point cut 与 advice 的组合
- 一个类被 AOP 织入 advice, 就会产生一个结果类AOP proxy( JDK 动态代理对象或 CGLIB 代理对象)
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。
- JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是
InvocationHandler接口和Proxy类。 - 如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类。
注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
# Bean获取方式
主要与如下两个对象有关:
BeanFactory管理不同类型的Java对象 IOC容器 在初始化容器时,并未实例化BeanApplicationContext应用上下文 提供了国际化支持和框架事件体系 在初始化应用上下文时就实例化所有单实例的Bean使用
BeanFactory直接获取(不推荐)在初始化时保存
ApplicationContext对象继承自抽象类
ApplicationObjectSupport继承自抽象类
WebApplicationObjectSupport使用Spring提供的工具类
WebApplicationContextUtils实现
ApplicationContextAware接口使用
ContextLoader提供的getCurrentWebApplicationContext方法
# Bean生命周期
Bean的生命周期:
实例化 -> 属性赋值 -> 初始化 -> 销毁
构造方法和setter方法的注入
主要逻辑doCreate()方法
createBeanInstance()-> 实例化 、populateBean()-> 属性赋值、initializeBean()-> 初始化InstantiationAwareBeanPostProcessor作用于实例化阶段的前后postProcessBeforeInstantionpostProcessAfterInstatantion
BeanPostProcessor作用于初始化阶段的前后postProcessBeforeInitializationpostProcessAfterIntitalization
- Aware类型的接口:获得Spring容器中的一些资源->初始化阶段之前调用
BeanNameAware、BeanClassLoaderAware、BeanFactoryAwareEnvironmentAware、EmbeddedValueResolverAware、ApplicationContextAware(ResourceLoaderAware、ApplicationEventPublisherAware、MessageSourceAware)
- 生命周期接口:
InitializingBean对应生命周期的初始化阶段DisposableBean类似于InitializingBean,对应生命周期的销毁阶段
BeanPostProcessor的注册时机- 优先级:
PriorityOrdered、Ordered
- 优先级:
# Bean的循环依赖
循环依赖:说白是一个或多个对象实例之间存在直接或间接的依赖关系,这种依赖关系构成了构成一个环形调用。
//TestService1.class
@Service
public class TestService1 {
@Autowired
private TestService2 testService2;
public void test1() {
}
}
//TestService2.class
@Service
public class TestService2 {
@Autowired
private TestService1 testService1;
public void test2() {
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
常见的循环依赖场景如下:
单例的setter注入
这是一个经典的循环依赖,但是它能正常运行,得益于spring的内部机制,让我们根本无法感知它有问题,因为spring默默帮我们解决了。
Spring为了解决单例的循环依赖问题,使用了三级缓存。
spring内部有三级缓存:
singletonObjects一级缓存,用于保存实例化、注入、初始化完成的bean实例earlySingletonObjects二级缓存,用于保存实例化完成的bean实例singletonFactories三级缓存,用于保存bean创建工厂,以便于后面扩展有机会创建代理对象。
下面用一张图告诉你,spring是如何解决循环依赖的:

步骤如下:
- Spring首先从一级缓存
singletonObjects中获取。 - 如果获取不到,并且对象正在创建中,就再从二级缓存
earlySingletonObjects中获取。 - 如果还是获取不到且允许
singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取. - 如果从三级缓存中获取到就从
singletonFactories中移除,并放入earlySingletonObjects中。其实也就是从三级缓存移动到了二级缓存。
该提前暴露的核心是:spring创造了一个 循环依赖的结束点标识。
注:从第三级缓存中获取实例,而第三级缓存里保存的并非真正的实例对象,而是ObjectFactory对象。说白了,两次从三级缓存中获取都是ObjectFactory对象,而通过它创建的实例对象每次可能都不一样的。而第三级缓存的目的是保证对实例对象进行增强。
在AbstractAutowireCapableBeanFactory类的doCreateBean中
通过定义了一个匿名内部类,通过getEarlyBeanReference方法获取代理对象,其实底层是通过AbstractAutoProxyCreator类的getEarlyBeanReference生成代理对象。
对于SCOPE_PROTOTYPE类型的类,其不会被提前初始化bean,所以程序能够正常启动。因为它没有用缓存,每次都会生成一个新对象。
构造器注入没能添加到三级缓存,也没有使用缓存,所以也无法解决循环依赖问题。
对于单例的代理对象setter注入时,在bean初始化完成之后,后面还有一步去检查:第二级缓存 和 原始对象 是否相等,因此无法解决。
解决方案:
生成代理对象产生的循环依赖
这类循环依赖问题解决方法很多,主要有:
- 使用
@Lazy注解,延迟加载 - 使用
@DependsOn注解,指定加载先后关系 - 修改文件名称,改变循环依赖类的加载顺序
使用@DependsOn产生的循环依赖
这类循环依赖问题要找到@DependsOn注解循环依赖的地方,迫使它不循环依赖就可以解决问题。
多例循环依赖
这类循环依赖问题可以通过把bean改成单例的解决。
构造器循环依赖
这类循环依赖问题可以通过使用@Lazy注解解决。
# MVC工作原理
SpringMVC 工作原理:
- 客户端(浏览器)发送请求,直接请求到
DispatcherServlet。 DispatcherServlet根据请求信息调用HandlerMapping,解析请求对应的Handler。- 解析到对应的
Handler(也就是我们平常说的Controller控制器)后,开始由HandlerAdapter适配器处理。 HandlerAdapter会根据Handler来调用真正的处理器来处理请求,并处理相应的业务逻辑。- 处理器处理完业务后,会返回一个
ModelAndView对象,Model 是返回的数据对象,View是个逻辑上的View。 ViewResolver会根据逻辑View查找实际的View。DispaterServlet把返回的Model传给 View(视图渲染)。- 把
View返回给请求者(浏览器)