# 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
作用于实例化阶段的前后postProcessBeforeInstantion
postProcessAfterInstatantion
BeanPostProcessor
作用于初始化阶段的前后postProcessBeforeInitialization
postProcessAfterIntitalization
- Aware类型的接口:获得Spring容器中的一些资源->初始化阶段之前调用
BeanNameAware
、BeanClassLoaderAware
、BeanFactoryAware
EnvironmentAware
、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
返回给请求者(浏览器)