官方文档:https://docs.spring.io/spring/docs/current/spring-framework-reference/index.html

一、IOC核心理论回顾


知识点:

  1. Ioc理念概要
  2. 实体Bean的创建
  3. Bean的基本特性
  4. 依赖注入
    1. set方法注入
    2. 构造方法注入
    3. 自动注入(byName、byType)
    4. 依赖检测

      1、Ioc理论概要

      在JAVA的世界中,一个对象A怎么才能调用对象B?通常有以下几种方法。
类别 描述 时间点
外部传入 构造方法传入 创建引用对象时
属性设置传入 设置对象状态时
运行时作为参数传入 调用时
内部创建 属性中直接创建 创建引用对象时
初始化方法创建 创建引用对象时
运行时动态创建 调用时

上表可以看到, 引用一个对象可以在不同地点(其它引用者)、不同时间由不同的方法完成。如果B只是一个非常简单的对象 如直接new B(),怎样都不会觉得复杂,比如你从来不会觉得创建一个String 是一个件复杂的事情。但如果B 是一个有着复杂依赖的Service对象,这时在不同时机引用B将会变得很复杂。
Spring - 图1

无时无刻都要维护B的复杂依赖关系,试想B对象如果项目中有上百过,系统复杂度将会成陪数增加。 IOC容器 的出现正是为解决这一问题,其可以将对象的构建方式统一,并且自动维护对象的依赖关系,从而降低系统的实现成本。前提是需要提前对目标对象基于XML进行声明。

2、实体Bean的构建

  1. 基于Class构建
  2. 构造方法构建
  3. 静态工厂方法创建
  4. FactoryBean创建

2.1、基于ClassName构建

  1. <bean class="com.test.spring.HelloSpring"></bean>

这是最常规的方法,其原理是在spring底层会基于class 属性 通过反射进行构建。

2.2、构造方法构建

  1. <bean class="com.test.spring.HelloSpring">
  2. <constructor-arg name="name" type="java.lang.String" value="test"/>
  3. <constructor-arg index="1" type="java.lang.String" value="sex" />
  4. </bean>

如果需要基于参数进行构建,就采用构造方法构建,其对应属性如下: name:构造方法参数变量名称 type:参数类型 index:参数索引,从0开始 value:参数值,spring 会自动转换成参数实际类型值 ref:引用容串的其它对象

2.3、静态工厂方法创建

  1. <bean class="com.test.spring.HelloSpring" factory-method="build">
  2. <constructor-arg name="type" type="java.lang.String" value="B"/>
  3. </bean>

如果你正在对一个对象进行A/B测试 ,就可以采用静态工厂方法的方式创建,其于策略创建不同的对像或填充不同的属性。 该模式下必须创建一个静态工厂方法,并且方法返回该实例,spring 会调用该静态方法创建对象。

  1. public static HelloSpring build(String type) {
  2. if (type.equals("A")) {
  3. return new HelloSpring("test", "man");
  4. } else if (type.equals("B")) {
  5. return new HelloSpring("test", "woman");
  6. } else {
  7. throw new IllegalArgumentException("type must A or B");
  8. }
  9. }

2.4、FactoryBean创建

  1. <bean class="com.test.spring.TestFactoryBean" id="helloSpring123"></bean>

指定一个Bean工厂来创建对象,对象构建初始化 完全交给该工厂来实现。配置Bean时指定该工厂类的类名。

  1. public class TestFactoryBean implements FactoryBean {
  2. @Override
  3. public Object getObject() throws Exception {
  4. return new HelloSpring();
  5. }
  6. @Override
  7. public Class<?> getObjectType() {
  8. return HelloSpring.class;
  9. }
  10. @Override
  11. public boolean isSingleton() {
  12. return false;
  13. }
  14. }

3、bean的基本特性

  • 作用范围
  • 生命周期
  • 装载机制

a、作用范围 很多时候Bean对象是无状态的 ,而有些又是有状态的 无状态的对象我们采用单例即可,而有状态则必须是多例的模式,通过scope 即可创建 scope=”prototype” scope=”singleton”

  1. <bean class="com.test.spring.HelloSpring" scope="prototype">
  2. </bean>

如果一个Bean设置成 prototype 我们可以 通过BeanFactoryAware 获取 BeanFactory 对象即可每次获取的都是新对像。

b、生命周期 Bean对象的创建、初始化、销毁即是Bean的生命周期。通过 init-method、destroy-method 属性可以分别指定期构建方法与初始方法。

  1. <bean class="com.test.spring.HelloSpring" init-method="init" destroy-method="destroy"></bean>

如果觉得麻烦,可以让Bean去实现 InitializingBean.afterPropertiesSet()、DisposableBean.destroy()方法。分别对应 初始和销毁方法

c、加载机制 指示Bean在何时进行加载。设置lazy-init 即可,其值如下: true: 懒加载,即延迟加载 false:非懒加载,容器启动时即创建对象 default:默认,采用default-lazy-init 中指定值,如果default-lazy-init 没指定就是false

  1. <beans xmlns="http://www.springframework.org/schema/beans"
  2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
  4. default-lazy-init="true">

什么时候使用懒加载? 懒加载会容器启动的更快,而非懒加载可以容器启动时更快的发现程序当中的错误 ,选择哪一个就看追求的是启动速度,还是希望更早的发现错误,一般我们会选 择后者。

4、依赖注入

试想IOC中如果没有依赖注入,那这个框架就只能帮助我们构建一些简单的Bean,而之前所说的复杂Bean的构建问题将无法解决,spring这个框架不可能会像现在这样成功。 spring 中 ioc 如何依赖注入呢。有以下几种方式:

  1. set方法注入
  2. 构造方法注入
  3. 自动注入(byName、byType)
  4. 方法注入(lookup-method)

4.1、set方法注入

  1. <bean class="com.test.spring.HelloSpring">
  2. <property name="fine" ref="fineSpring"/>
  3. </bean>

4.2、构造方法注入

  1. <bean class="com.test.spring.HelloSpring">
  2. <constructor-arg name="fine">
  3. <bean class="com.test.spring.FineSpring"/>
  4. </constructor-arg>
  5. </bean>

4.3、自动注入(byName\byType\constructor)

  1. <bean id="helloSpringAutowireConstructor" class="com.test.spring.HelloSpring" autowire="byName">
  2. </bean>

byName:基于变量名与bean 名称相同作为依据插入 byType:基于变量类别与bean 名称作 constructor:基于IOC中bean 与构造方法进行匹配(语义模糊,不推荐)

4.4、依赖方法注入(lookup-method)

当一个单例的Bean,依赖于一个多例的Bean,用常规方法只会被注入一次,如果每次都想要获取一个全新实例就可以采用lookup-method 方法来实现。

  1. #编写一个抽像类
  2. public abstract class MethodInject {
  3. public void handlerRequest() {
  4. // 通过对该抽像方法的调用获取最新实例
  5. getFine();
  6. }
  7. # 编写一个抽像方法
  8. public abstract FineSpring getFine();
  9. }
  1. // 设定抽像方法实现
  2. <bean id="MethodInject" class="com.test.spring.MethodInject">
  3. <lookup-method name="getFine" bean="fine"></lookup-method>
  4. </bean>

该操作的原理是基于动态代理技术,重新生成一个继承至目标类,然后重写抽像方法到达注入目的。 前面说所单例Bean依赖多例Bean这种情况也可以通过实现 ApplicationContextAware 、BeanFactoryAware 接口来获取BeanFactory 实例,从而可以直接调用getBean方法获取新实例,推荐使用该方法,相比lookup-method语义逻辑更清楚一些。

5、循环依赖解决方法

循环依赖的几种关系
1、自己依赖自己
2、两个对象互相依赖
3、多个对象形成间接循环依赖

image.png

spring内部有三级缓存:

  • singletonObjects 一级缓存,用于保存实例化、注入、初始化完成的bean实例
  • earlySingletonObjects 二级缓存,用于保存实例化完成的bean实例
  • singletonFactories 三级缓存,用于保存bean创建工厂,以便于后面扩展有机会创建代理对象。

image.png

5.1 多例的setter注入

这种注入方法偶然会有,特别是在多线程的场景下,具体代码如下:

  1. @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
  2. @Service
  3. public class TestService1 {
  4. @Autowired
  5. private TestService2 testService2;
  6. public void test1() {
  7. }
  8. }
  9. @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
  10. @Service
  11. public class TestService2 {
  12. @Autowired
  13. private TestService1 testService1;
  14. public void test2() {
  15. }
  16. }

很多人说这种情况spring容器启动会报错,其实是不对的,我非常负责任的告诉你程序能够正常启动。
为什么呢?
其实在AbstractApplicationContext类的refresh方法中告诉了我们答案,它会调用finishBeanFactoryInitialization方法,该方法的作用是为了spring容器启动的时候提前初始化一些bean。该方法的内部又调用了preInstantiateSingletons方法

image.png
标红的地方明显能够看出:非抽象、单例 并且非懒加载的类才能被提前初始bean。
而多例即SCOPE_PROTOTYPE类型的类,非单例,不会被提前初始化bean,所以程序能够正常启动。
如何让他提前初始化bean呢?
只需要再定义一个单例的类,在它里面注入TestService1

  1. @Service
  2. public class TestService3 {
  3. @Autowired
  4. private TestService1 testService1;
  5. }

重新启动程序,执行结果:
Requested bean is currently in creation: Is there an unresolvable circular reference?
果然出现了循环依赖。
注意:这种循环依赖问题是无法解决的,因为它没有用缓存,每次都会生成一个新对象。

5.2 构造器注入

这种注入方式现在其实用的已经非常少了,但是我们还是有必要了解一下,看看如下代码:

  1. @Service
  2. public class TestService1 {
  3. public TestService1(TestService2 testService2) {
  4. }
  5. }
  6. @Service
  7. public class TestService2 {
  8. public TestService2(TestService1 testService1) {
  9. }
  10. }

运行结果:
Requested bean is currently in creation: Is there an unresolvable circular reference?
出现了循环依赖,为什么呢?
image.png
从图中的流程看出构造器注入没能添加到三级缓存,也没有使用缓存,所以也无法解决循环依赖问题。

5.3 单例的代理对象setter注入

这种注入方式其实也比较常用,比如平时使用:@Async注解的场景,会通过AOP自动生成代理对象。
我那位同事的问题也是这种情况。

  1. @Service
  2. public class TestService1 {
  3. @Autowired
  4. private TestService2 testService2;
  5. @Async
  6. public void test1() {
  7. }
  8. }
  9. @Service
  10. public class TestService2 {
  11. @Autowired
  12. private TestService1 testService1;
  13. public void test2() {
  14. }
  15. }

从前面得知程序启动会报错,出现了循环依赖:

  1. org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'testService1': Bean with name 'testService1' has been injected into other beans [testService2] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.

为什么会循环依赖呢?
答案就在下面这张图中:

image.png

说白了,bean初始化完成之后,后面还有一步去检查:第二级缓存 和 原始对象 是否相等。由于它对前面流程来说无关紧要,所以前面的流程图中省略了,但是在这里是关键点,我们重点说说:

image.png

那位同事的问题正好是走到这段代码,发现第二级缓存 和 原始对象不相等,所以抛出了循环依赖的异常。
如果这时候把TestService1改个名字,改成:TestService6,其他的都不变。

  1. @Service
  2. public class TestService6 {
  3. @Autowired
  4. private TestService2 testService2;
  5. @Async
  6. public void test1() {
  7. }
  8. }

再重新启动一下程序,神奇般的好了。
what? 这又是为什么?
这就要从spring的bean加载顺序说起了,默认情况下,spring是按照文件完整路径递归查找的,按路径+文件名排序,排在前面的先加载。所以TestService1比TestService2先加载,而改了文件名称之后,TestService2比TestService6先加载。
为什么TestService2比TestService6先加载就没问题呢?
答案在下面这张图中:

image.png

这种情况testService6中其实第二级缓存是空的,不需要跟原始对象判断,所以不会抛出循环依赖。

5.4 DependsOn循环依赖

还有一种有些特殊的场景,比如我们需要在实例化Bean A之前,先实例化Bean B,这个时候就可以使用@DependsOn注解。

  1. @DependsOn(value = "testService2")
  2. @Service
  3. public class TestService1 {
  4. @Autowired
  5. private TestService2 testService2;
  6. public void test1() {
  7. }
  8. }
  9. @DependsOn(value = "testService1")
  10. @Service
  11. public class TestService2 {
  12. @Autowired
  13. private TestService1 testService1;
  14. public void test2() {
  15. }
  16. }

程序启动之后,执行结果:
Circular depends-on relationship between ‘testService2’ and ‘testService1’
这个例子中本来如果TestService1和TestService2都没有加@DependsOn注解是没问题的,反而加了这个注解会出现循环依赖问题。
这又是为什么?
答案在AbstractBeanFactory类的doGetBean方法的这段代码中:

image.png
它会检查dependsOn的实例有没有循环依赖,如果有循环依赖则抛异常。

6、出现循环依赖如何解决?

项目中如果出现循环依赖问题,说明是spring默认无法解决的循环依赖,要看项目的打印日志,属于哪种循环依赖。目前包含下面几种情况:
image.png

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

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

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

    使用@DependsOn产生的循环依赖

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

    多例循环依赖

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

    构造器循环依赖

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

    二、IOC 设计原理与实现

    知识点:
    1、源码学习的目标
    2、Bean的构建过程
    3、BeanFactory与ApplicationContext区别

1、源码学习目标:

不要为了读书而读书,同样不要为了阅读源码而读源码。没有目的一头扎进源码的黑森林当中很快就迷路了。到时就不是我们读源码了,而是源码‘毒’我们。毕竟一个框架是由专业团队,历经N次版本迭代的产物,我们不能指望像读一本书的方式去阅读它。 所以必须在读源码之前找到目标。是什么呢? 大家会想,读源码的目标不就是为了学习吗?这种目标太过抽像,目标无法验证。通常我们会设定两类型目标:一种是对源码进行改造,比如添加修改某些功能,在实现这种目标的过程当中自然就会慢慢熟悉了解该项目。但然这个难度较大,耗费的成本也大。另一个做法是 自己提出一些问题,阅读源码就是为这些问题寻找答案。以下就是我们要一起在源码中寻找答案的问题:

  1. Bean工厂是如何生产Bean的?
  2. Bean的依赖关系是由谁解来决的?
  3. Bean工厂和应用上文的区别?

    2、Bean的构建过程

    spring.xml 文件中保存了我们对Bean的描述配置,BeanFactory 会读取这些配置然后生成对应的Bean。这是我们对ioc 原理的一般理解。但在深入一些我们会有更多的问题?

  4. 配置信息最后是JAVA中哪个对象承载的?

  5. 这些承载对象是谁读取XML文件并装载的?
  6. 这些承载对象又是保存在哪里?

BeanDefinition (Bean定义) ioc 实现中 我们在xml 中描述的Bean信息最后 都将保存至BeanDefinition (定义)对象中,其中xml bean 与BeanDefinition 是一对一的关系
Spring - 图11
由此可见,xml bean中设置的属性最后都会体现在BeanDefinition中。如:

xml-bean BeanDefinition
class beanClassName
scope scope
lazy-init lazyInit
constructor-arg ConstructorArgument
property MutablePropertyValues
factory-method FactoryMethodName
destory-method AbstractBeanDefinition.destoryMethodName
init-method AbstractBeanDefinition.initMethodName
autowire AbstractBeanDefinition.autowireMode
id
name

演示查看 BeanDefinition 属性结构
Spring - 图12

BeanDefinitionRegistry(Bean注册器) 在上表中我们并没有看到 xml bean 中的 id 和name属性没有体现在定义中,原因是ID 其作为当前Bean的存储key注册到了BeanDefinitionRegistry 注册器中。name 作为别名key 注册到了 AliasRegistry 注册中心。其最后都是指向其对应的BeanDefinition。
演示查看 BeanDefinitionRegistry属性结构

Spring - 图13
BeanDefinitionReader(Bean定义读取) 至此我们学习了 BeanDefinition 中存储了Xml Bean信息,而BeanDefinitionRegister 基于ID和name 保存了Bean的定义。接下要学习的是从xml Bean到BeanDefinition 然后在注册至BeanDefinitionRegister 整个过程。

Spring - 图14

上图中可以看出Bean的定义是由BeanDefinitionReader 从xml 中读取配置并构建出 BeanDefinitionReader,然后在基于别名注册到BeanDefinitionRegister中。

  • 查看BeanDefinitionReader结构

Spring - 图15

  • int loadBeanDefinitions(Resource resource)
    • 基于资源装载Bean定义并注册至注册器
  • int loadBeanDefinitions(String location)
    • 基于资源路径装载Bean定义并注册至注册器
  • BeanDefinitionRegistry getRegistry()
    • 获取注册器
  • ResourceLoader getResourceLoader()
    • 获取资源装载器

基于示例演示BeanDefinitionReader装载过程

  1. //创建一个简单注册器
  2. BeanDefinitionRegistry register = new SimpleBeanDefinitionRegistry();
  3. //创建bean定义读取器
  4. BeanDefinitionReader reader = new XmlBeanDefinitionReader(register);
  5. // 创建资源读取器
  6. DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
  7. // 获取资源
  8. Resource xmlResource = resourceLoader.getResource("spring.xml");
  9. // 装载Bean的定义
  10. reader.loadBeanDefinitions(xmlResource);
  11. // 打印构建的Bean 名称
  12. System.out.println(Arrays.toString(register.getBeanDefinitionNames());

Beanfactory(bean 工厂) 有了Bean的定义就相当于有了产品的配方,接下来就是要把这个配方送到工厂进行生产了。在ioc当中Bean的构建是由BeanFactory 负责的。其结构如下:
Spring - 图16
方法说明:

  • getBean(String)
  • 基于ID或name 获取一个Bean
  • T getBean(Class requiredType)
  • 基于Bean的类别获取一个Bean(如果出现多个该类的实例,将会报错。但可以指定 primary=“true” 调整优先级来解决该错误 )
  • Object getBean(String name, Object… args)
  • 基于名称获取一个Bean,并覆盖默认的构造参数
  • boolean isTypeMatch(String name, Class<?> typeToMatch)
  • 指定Bean与指定Class 是否匹配

以上方法中重点要关注getBean,当用户调用getBean的时候就会触发 Bean的创建动作,其是如何创建的呢?

演示基本BeanFactory获取一个Bean

  1. java.lang.reflect.Constructor.newInstance(Unknown Source:-1)
  2. BeanUtils.instantiateClass()
  3. //基于实例化策略 实例化Bean
  4. SimpleInstantiationStrategy.instantiate()
  5. AbstractAutowireCapableBeanFactory.instantiateBean()
  6. // 执行Bean的实例化方法
  7. AbstractAutowireCapableBeanFactory.createBeanInstance()
  8. AbstractAutowireCapableBeanFactory.doCreateBean()
  9. // 执行Bean的创建
  10. AbstractAutowireCapableBeanFactory.createBean()
  11. // 缓存中没有,调用指定Bean工厂创建Bean
  12. AbstractBeanFactory$1.getObject()
  13. // 从单例注册中心获取Bean缓存
  14. DefaultSingletonBeanRegistry.getSingleton()
  15. AbstractBeanFactory.doGetBean()
  16. // 获取Bean
  17. AbstractBeanFactory.getBean()
  18. // 调用的客户类
  19. com.test.spring.BeanFactoryExample.main()

Bean创建时序图:

Spring - 图17

从调用过程可以总结出以下几点:

  1. 调用BeanFactory.getBean() 会触发Bean的实例化。
  2. DefaultSingletonBeanRegistry 中缓存了单例Bean
  3. Bean的创建与初始化是由AbstractAutowireCapableBeanFactory 完成的。

3、BeanFactory 与 ApplicationContext区别

BeanFactory 看上去可以去做IOC当中的大部分事情,为什么还要去定义一个ApplicationContext 呢? ApplicationContext 结构图
Spring - 图18

从图中可以看到 ApplicationContext 它由BeanFactory接口派生而来,因而提供了BeanFactory所有的功能。除此之外context包还提供了以下的功能:

  1. MessageSource, 提供国际化的消息访问
  2. 资源访问,如URL和文件
  3. 事件传播,实现了ApplicationListener接口的bean
  4. 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层

    三、Spring AOP

    AOP:面向切面,是一种编程思想,OOP的补充。将系统中非核心的业务提取出来,进行单独处理,进行功能增强,比如事务、日志和安全等。
    AOP有几个重要的概念:1)通知:描述了切面是什么以及何时使用,通知分前置通知、后置通知、返回通知、环绕通知、异常通知;2)连接点:每个能够应用切点的地方都能成为一个连接点,比如方法调用时、以及抛出异常时;3)切点:切点描述了哪些连接点是需要进行增强的;4)切面:通知和切点一起组成一个切面,描述了何时何地如何进行增强;5)引入:向现有类添加新的方法或属性,无需修改现有类;6)织入:是将切面应用到目标对象并创建代理对象的过程,运行期织入
    切面的应用是在类之间进行方法调用时才会生效,对于类内部的方法调用,是不会织入的。

    四、事物传播和隔离级别

    事物传播属性分七个:

  5. REQUIRE 需要事务,如果当前存在事务,就加入事务,如果不存在就新建

  6. REQUIRE_NEW 需要新的事务,如果当前存在事务,就将事务挂起并新建事务运行
  7. SUPPORT 支持事务,如果存在事务就加入,如果不存在就以非事务方式运行
  8. NOT_SUPPORT 不支持事务,如果当前存在事务就挂起当前事务,不存在事务正常运行
  9. MANDATORY 必须有事务,如果当前存在事务就加入,不存在就报错
  10. NEVER 永远不支持事务,如果当前存在事务就报错
  11. NESTED 嵌套事务,如果当前存在事务就在嵌套事务内运行,然后新建事务运行

事务隔离级别:

  1. read-uncommitted:未提交读,会出现脏读
  2. read-committed: 提交读,会出现不可重复读和幻读,oracle,sql server 默认级别
  3. repeatable-read: 可重复读,会出现幻读,mysql默认
  4. serializable: 序列化读,级别最高,效率最低
  5. defalut: 使用数据库默认级别

spring通过切面对需要进行事务管理的切点加入事务代码,就是创建事务状态对象,获取数据库连接,设置连接属性进行事务控制,利用ThreadLocal实现多种事务传播属性。

五、动态代理和静态代理

静态代理是在编译期就确定了代理对象的,而动态代理是在运行期动态生成的代理对象,而动态代理又分jdkDynamic和cglib两种,两个针对的类型不一样,JDKDynamic针对的是接口,而cglib针对的是类。
JDKDynamic是java原生的一个针对接口的一种代理,其实针对接口创建代理相对来说比较容易,继承Proxy,实现接口,然后每个方法都通过调用InvocationHandler.invoke方法来处理,在invoke方法里面实现的有对应的增强代码,这样就做到了在不改变原有代码的基础上对功能做了增强。
cglib是针对类来代理的,通过继承类来实现,首先实现一个MethodInterceptor.intercept方法,这是cglib的一个接口,
Enhancer enhancer = ``new Enhancer();
enhancer.setSuperclass(HelloConcrete.``class``);
enhancer.setCallback(``new MyMethodInterceptor());

HelloConcrete hello = (HelloConcrete)enhancer.create();

之后对类的非final方法都会转到MethodInterceptor.intercept,里面可以加入自己需要增加的功能,不能代理final类会报异常,对于final方法会跳过代理