# Spring

最近回过神来,觉得是时候开始对学习已经的Spring框架,进行源码级别的解读,其一是加强自己对知识点的把握,其二是为了预防自己后面又遗忘/(ㄒoㄒ)/~~。

本文将从最基本的概念出发,辅助常见常考的面试题,来阐述Spring框架的思想以及应用点。

文章内容仅是我个人的小总结,资历尚浅,如有误还请指正。

# IOC 的认识

IOC:也即控制反转,DI即依赖注入,控制反转IOC和依赖注入DI其实就是同个概念的两个不同角度的解释。 控制反转可以理解为获取依赖对象的控制反转过来。

通过IOC容器实现的,由IOC容器负责创建和获取依赖对象,对象只是被动地接受依赖对象,从而实现更好的解耦。

IOC 的初始化过程如下:

spring-1.jpg

实现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装配顺序
    • 如果同时指定了nametype,则从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容器 在初始化容器时,并未实例化Bean

  • ApplicationContext 应用上下文 提供了国际化支持和框架事件体系 在初始化应用上下文时就实例化所有单实例的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容器中的一些资源->初始化阶段之前调用
    • BeanNameAwareBeanClassLoaderAwareBeanFactoryAware
    • EnvironmentAwareEmbeddedValueResolverAwareApplicationContextAware(ResourceLoaderAwareApplicationEventPublisherAwareMessageSourceAware)
  • 生命周期接口:
    • InitializingBean 对应生命周期的初始化阶段
    • DisposableBean 类似于InitializingBean,对应生命周期的销毁阶段
  • BeanPostProcessor的注册时机
    • 优先级:PriorityOrderedOrdered

# 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() {
    }
}
1
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-2.jpg

步骤如下:

  • Spring首先从一级缓存singletonObjects中获取。
  • 如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。
  • 如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取.
  • 如果从三级缓存中获取到就从singletonFactories中移除,并放入earlySingletonObjects中。其实也就是从三级缓存移动到了二级缓存。

该提前暴露的核心是:spring创造了一个 循环依赖的结束点标识

:从第三级缓存中获取实例,而第三级缓存里保存的并非真正的实例对象,而是ObjectFactory对象。说白了,两次从三级缓存中获取都是ObjectFactory对象,而通过它创建的实例对象每次可能都不一样的。而第三级缓存的目的是保证对实例对象进行增强。

AbstractAutowireCapableBeanFactory类的doCreateBean

通过定义了一个匿名内部类,通过getEarlyBeanReference方法获取代理对象,其实底层是通过AbstractAutoProxyCreator类的getEarlyBeanReference生成代理对象。

对于SCOPE_PROTOTYPE类型的类,其不会被提前初始化bean,所以程序能够正常启动。因为它没有用缓存,每次都会生成一个新对象。

构造器注入没能添加到三级缓存,也没有使用缓存,所以也无法解决循环依赖问题。

对于单例的代理对象setter注入时,在bean初始化完成之后,后面还有一步去检查:第二级缓存 和 原始对象 是否相等,因此无法解决。

解决方案:

生成代理对象产生的循环依赖

这类循环依赖问题解决方法很多,主要有:

  1. 使用@Lazy注解,延迟加载
  2. 使用@DependsOn注解,指定加载先后关系
  3. 修改文件名称,改变循环依赖类的加载顺序

使用@DependsOn产生的循环依赖

这类循环依赖问题要找到@DependsOn注解循环依赖的地方,迫使它不循环依赖就可以解决问题。

多例循环依赖

这类循环依赖问题可以通过把bean改成单例的解决。

构造器循环依赖

这类循环依赖问题可以通过使用@Lazy注解解决。

# MVC工作原理

SpringMVC 工作原理:

  1. 客户端(浏览器)发送请求,直接请求到 DispatcherServlet
  2. DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler
  3. 解析到对应的 Handler(也就是我们平常说的 Controller 控制器)后,开始由 HandlerAdapter 适配器处理。
  4. HandlerAdapter 会根据 Handler 来调用真正的处理器来处理请求,并处理相应的业务逻辑。
  5. 处理器处理完业务后,会返回一个 ModelAndView 对象,Model 是返回的数据对象,View 是个逻辑上的 View
  6. ViewResolver 会根据逻辑 View 查找实际的 View
  7. DispaterServlet 把返回的 Model 传给 View(视图渲染)。
  8. View 返回给请求者(浏览器)