参考资料
Spring 教程

说在前面

什么样的架构,我们认为是一个优秀的架构?

判断准则:可维护性好,可扩展性好,性能。

什么叫可扩展性好?

答:在不断添加新的代码的同时,可以不修改原有代码,即符合开闭原则。

如何让程序的可维护性好,可扩展性好呢?

业界有一个公认的标准:高内聚,低耦合。

高内聚:就是尽量将代码写在与之功能描述一致的模块中。如User表的操作写在UserDAO里面,不要写在非UserDAO的类里面。

低耦合:就是尽量减少类与类之间的直接关系。(重点)

直接关系:Controller层依赖Service层,在Controller层直接new Service层的类的对象。

Service层依赖Dao层,在Service层直接new Dao层的对象。

Spring框架就是通过IoC/DI(控制反转/依赖注入)实现程序的解耦。从而提高程序的维护性和扩展性。

Spring概述

Spring是什么

Spring是一个JavaEE轻量级的一站式开发框架。

JavaEE: 就是用于开发企业级(B/S)应用的技术。

轻量级:使用最少代码启动框架,然后根据需求选择需要使用的模块。

重量级:早期的EJB,开发一个HelloWorld程序都需要引入EJB的全部模块。

一站式:提供了表示层,服务层,持久层的所有支持。

Spring框架出现的背景

世界上第一套由Java官方Sun公司推出的企业级开发框架EJB瞬间风靡全球,被各大公司所应用。

Spring之父,Rod Jonhson是一个音乐博士,在Sun公司的大力推广下,也成为EJB框架的使用者。

在深入研究完EJB框架(由Sun主导开发的一个JavaEE开发框架),无法接受这么一个框架被吹成世界第一,具体查看他吐槽EJB的书《Expert one on one J2EE design and development》。

其中突出被他吐槽最厉害的一个点就EJB的重量级,就是只要使用EJB里面任何一个组件。都要导入EJB所有的jar包。于是他就提供了一个解决方案:轻量级的一站式企业级开发框架。

那么什么是轻量级呢?

就是除内核模块,其他模块由开发者自由选择使用,同时支持整合其他框架。也可以称为可插拔式开发框架,像插头和插座一样,插上就用,不用就拔下来。这就是Spring框架核心理念。

那么什么是一站式呢?

就是Spring框架提供涵盖了JavaEE开发的表示层,服务层,持久层的所有组件功能。也就是说,原则上,学完一套Spring框架,不用其他框架就可以完成网站一条流程的开发。

如图:

控制反转和依赖注入 - 图1

Spring框架的作用

根据以上章节的描述。Spring是一个JavaEE轻量级一站式开发框架。它提供的功能涵盖了JavaEE程序中的表示层,服务层,持久层功能组件。这意味着,单单Spring框架就可以满足整个JavaEE程序的开发。

但Spring框架,更加强调的是它的轻量级(模块的可插拔)。也就是说,除了内核模块,其他功能模块如果你想使用可以不用,并且Spring框架能够整合任何第三方的框架。在现实开发中,Spring主要用于整合其他框架

总结

  1. Spring是一个一站式的企业级(JavaEE)开发框架,意味着,仅仅使用一个Spring框架就可以满足JavaEE开发的表示层,服务层,持久层的开发。
  2. Spring强调的理念是:轻量级。意味着Spring提供的功能模块,除了内核模块以外,其他功能模块开发人员可以选择性使用。
  3. Spring框架在现实开发中,主要的功能是整合第三方框架。

Spring框架包

Spring官方网站

https://spring.io/

框架包的下载

Spring官方提供的Maven方式的项目下载。

https://start.spring.io/

但是基于简单入门的原则,我们要通过导入包的方式来学习。需要下载框架的zip包

路径为:http://repo.springsource.org/libs-release-local/org/springframework/spring/

目录说明

—根目录

控制反转和依赖注入 - 图2

—类库规则

控制反转和依赖注入 - 图3

—包说明

控制反转和依赖注入 - 图4

入门示例(IDEA)

Spring之所以可以实现模块的可插拔是支持依赖注入,所谓的依赖注入就是不用new就可以创建对象。

需求:使用Spring框架不用new创建一个对象。

配置流程图

控制反转和依赖注入 - 图5

  1. 创建一个普通类。
  2. 创建一个Spring配置文件,用于描述类与类之间的关系。
  3. 创建ApplicationContext容器对象,根据Spring配置文件的描述,创建对象并放在Spring容器里面。
  4. 使用ApplicationContext容器对象的getBean方法,获取Spring容器里面的对象。

配置步骤说明

  1. 导入包。
  2. 创建一个普通类。
  3. 创建一个Spring主配置文件(去官方文档上拷贝约束)。
  4. 编写一个测试类,使用ApplicationContext的子类对象根据配置文件创建Spring容器对象ApplicationContext。并且在容器里面获得创建的对象。

配置步骤

3.3.1. 第一步:搭建环境

1.创建一个Java项目

控制反转和依赖注入 - 图6

2.导包,String的基础支撑包和依赖的日志包复制到lib文件下,并且加入项目中。

—-导入Spring基础支撑包。

控制反转和依赖注入 - 图7

—导入Spring依赖的日志包

控制反转和依赖注入 - 图8

3.3.2. 第二步:创建配置文件

  1. 在项目的src下面创建配置文件applicationContext.xml,配置文件模块查看位置:spring框架/docs/spring-framework-reference/html/beans.html。
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans.xsd">
  6. </beans>

3.3.3. 第三步:创建对象到容器里面

  1. 创建一个类
  1. package org.cjw.service;
  2. public class HelloWorldService {
  3. public void say() {
  4. System.out.println("--你好世界!--");
  5. }
  6. }
  1. applicationContext.xml配置文件加入配置
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans.xsd">
  6. <!--
  7. <bean name="" class="" />标签:用于声明一个类,在启动Spring框架的时候根据该配置的类创建对象到spring容器里面去
  8. name属性:bean的唯一标识
  9. class属性:bean的全限定名,用于反射创建对象到spring容器里面去
  10. -->
  11. <bean id="helloWorldService" class="org.cjw.service.HelloWorldService" />
  12. </beans>
  1. 测试使用getBean获得容器中的对象
  1. package org.cjw.service.test;
  2. import org.cjw.service.HelloWorldService;
  3. import org.junit.Test;
  4. import org.springframework.context.support.ClassPathXmlApplicationContext;
  5. public class HelloWorldServiceTest {
  6. @Test
  7. public void testSay() {
  8. // 创建一个ApplicationContext对象,根据xml配置创建对象到spring容器里面去
  9. // 直接读取src目录下的配置文件的子类是ClassPathXmlApplicationContext
  10. ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  11. // 通过getBean方法获取spring容器里面的对象
  12. HelloWorldService helloWorldService = context.getBean(HelloWorldService.class);
  13. // 对象调用方法
  14. helloWorldService.say();
  15. }
  16. }
  1. 测试结果
  1. --你好世界!--

通过代码可以得出:Spring框架果然不用new就可以创建对象。

Spring容器的两个实现

控制反转和依赖注入 - 图9

ClassPathXmlApplicationContext:通过classpath路径(相对路径)直接获得加载的xml文件(推荐使用)

FileSystemXmlApplicationContext:通过文件路径(绝对路径)来获得加载的xml文件。

  1. @Test
  2. public void testSay() {
  3. // 创建一个ApplicationContext对象,根据xml配置创建对象到spring容器里面去
  4. // 直接读取src目录下的配置文件的子类是ClassPathXmlApplicationContext
  5. // ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  6. FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("C:\\Users\\JackMi\\IdeaProjects\\spring-demo-starat\\src\\applicationContext.xml");
  7. // 通过getBean方法获取spring容器里面的对象
  8. HelloWorldService helloWorldService = context.getBean(HelloWorldService.class);
  9. // 对象调用方法
  10. helloWorldService.say();
  11. }

ApplicationContext类图结构图

Spring框架容器对象的继承体系

控制反转和依赖注入 - 图10

通过结构图可以看到,Spring使用了工厂设计模式,Spring容器顶级接口是BeanFactory,ApplicationContext是它的子接口。在开发中,使用ApplicationContext即可。

Spring的控制反转和依赖注入(重点-spring核心之一)

IoC:Inversion of Control(控制反转):

读作“反转控制”,更好理解,不是什么技术,而是一种设计思想,好比于MVC。就是将原本在程序中手动创建对象的控制权,交由Spring框架来管理。

正控:若开发者需要使用某个对象,那么就必须通过new关键字来创建该对象。

反控:开发者只管从Spring容器中获取需要使用的对象,无需关心对象的创建过程,这也就是把对象的创建控制权反转给了Spring框架。(Don’t call me ,I’ll call you)

DI:Dependency Injection(依赖注入)

从字面上分析:

IoC:指将对象的创建权,反转给了Spring容器;

DI:指Spring创建对象的过程中,将对象依赖的属性(简单值、对象、集合)通过配置信息设置给该对象。

IoC和DI其实是同一个概念的不同角度描述,DI相对IoC而言,明确描述了“被注入对象依赖IoC容器配置依赖对象”。

Container:容器,在生活中容器就是一种盛放东西的器皿,从程序设计角度看作是装对象的对象,因为存在对对象的存入、取出等操作,所以容器还要管理对象的生命周期。

控制反转和依赖注入 - 图11

IoC(控制反转)的概述

Spring号称是一个可以实现模块可插拔的JavaEE开发框架。那么它是如何实现程序的可插拔的呢?

实现程序可插拔的核心理念就是,控制反转(Inversion of Control,英文缩写为IoC)

所谓的控制反转,就是将代码的调用权从调用方转移给被调用方(服务提供方)。

如图所示:

  1. 强耦合调用方式

将A类调用B类对象修改调用C类对象,修改的是调用方的代码,所以我们认为代码的调用权在调用方。

控制反转和依赖注入 - 图12

  1. 基于IoC(控制反转)的调用方式

将上图的需求,修改为Ioc方式。就是将代码的控制权从调用方修改为被调用方,意味着,代码的调用权转移给被调用方(我们也称为服务方),不用修改调用方的代码。

Ioc方式下只需修改配置文件就实现对象的切换。

如下图:将A类调用B类对象修改为C类对象,修改的是被调用方的配置文件的代码,所以代码的调用权转移到了被调用方。通过控制反转,我们可以实现增加模块或者移除模块统一由配置文件关联,增加或者移除模块,配置XML配置文件即可。

我们将代码的调用权(控制权)调用方转移给被调用方(服务提供方)的设计模式称为控制反转(IoC)。

控制反转和依赖注入 - 图13

根据上图可以得出,实现一个IoC的框架,必须要解决两个问题:

  1. 被调用方(服务方),在程序启动时就要创建好对象,放在一个容器里面。
  2. 调用方使用一个接口或类的引用(不用使用new),在程序运行过程中调用方就可以获取对象。

我们将这种不用new,而是根据接口或者类的引用就可以从容器里获得创建的对象的方式称为依赖注入DI。

所以,控制反转(Ioc),就是依赖注入加上面向接口的编程思想的实现。

在这里,我们首先抓住一个重点:Spring之所以可以实现可插拔程序,是实现了不用new,使用类或接口就可以获得对象!

Spring的个人思考

Spring框架本质上作为一个容器的角色存在,我们程序中对象的创建,以及对象间的依赖关系都交由Spring来完成,当我们需要使用对象时,只需向spring容器获取即可,实现了模块间、对象间的解耦,所有对象信息都配置在applicationContext.xml配置文件中,当Spring框架启动时,创建spring容器,读取applicationContext.xml配置文件对象对象到spring容器中去。程序后续使用对象时都从spring容器中获取(getBean方法)。

项目目录结构

控制反转和依赖注入 - 图14

示例代码

4.4.1. CustomerService接口代码

  1. package org.cjw.service;
  2. public interface CustomerService {
  3. /**
  4. * 保存方法
  5. */
  6. void save();
  7. }

4.4.2. CustomerServiceImpl子类

  1. package org.cjw.service.impl;
  2. import org.cjw.service.CustomerService;
  3. public class CustomerServiceImpl implements CustomerService {
  4. @Override
  5. public void save() {
  6. System.out.println("-保存客户-CustomerServiceImpl");
  7. }
  8. }

4.4.3. CustomerServiceImpl2子类

  1. package org.cjw.service.impl;
  2. import org.cjw.service.CustomerService;
  3. public class CustomerServiceImpl2 implements CustomerService {
  4. @Override
  5. public void save() {
  6. System.out.println("-保存客户-CustomerServiceImpl2");
  7. }
  8. }

4.4.4. CustomerClient类(调用方)

  1. package org.cjw.controller;
  2. import org.cjw.service.CustomerService;
  3. public class CustomerController {
  4. // 1.声明一个父接口的引用
  5. private CustomerService customerService;
  6. // 2.使用set方法注入对象,我们将通过方法注入对象的方式称为依赖注入
  7. public void setCustomerService(CustomerService customerService) {
  8. this.customerService = customerService;
  9. }
  10. public void save() {
  11. // 调用服务方的方法
  12. customerService.save();
  13. }
  14. }

4.4.5. 配置文件applicationContext.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans.xsd">
  6. <!--
  7. <bean>标签:用于声明一个类,在启动Spring框架的时候根据配置信息创建对象到容器里面去
  8. -->
  9. <!--
  10. <bean id="customerServiceImpl" class="org.cjw.service.impl.CustomerServiceImpl" />
  11. 可以根据需求,将CustomerServiceImpl修改为CustomerServiceImpl2
  12. -->
  13. <bean id="customerServiceImpl" class="org.cjw.service.impl.CustomerServiceImpl2" />
  14. <bean id="customerController" class="org.cjw.controller.CustomerController">
  15. <!--
  16. <property name="" ref="" />标签对应set方法关联对象customerService
  17. name属性:关联对应的set方法,关联规则:xxx对应setXxx();
  18. 如:customerService对应setCustomerService()
  19. ref属性:指向容器中的对象,即set方法的参数
  20. -->
  21. <property name="customerService" ref="customerServiceImpl"/>
  22. </bean>
  23. </beans>

4.4.6. 测试代码

  1. package org.cjw.controller.test;
  2. import org.cjw.controller.CustomerController;
  3. import org.junit.Test;
  4. import org.springframework.context.support.ClassPathXmlApplicationContext;
  5. public class CustomerControllerTest {
  6. @Test
  7. public void testSave() {
  8. ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  9. CustomerController customerController = context.getBean("customerController", CustomerController.class);
  10. customerController.save();
  11. }
  12. }

4.4.7. 测试结果

控制反转和依赖注入 - 图15

Spring依赖注入 DI

DI:Dependency Injection(依赖注入)

从字面上分析:

  • IoC:指将对象的创建权,反转给了Spring容器;
  • DI :指Spring创建对象的过程中,将对象依赖属性(简单值,集合,对象)通过配置设值给该对象。

IoC和DI其实是同一个概念的不同角度描述,DI相对IoC而言,明确描述了“被注入对象依赖IoC容器配置依赖对象”。

所谓的依赖注入,就是属性不创建对象,通过配置文件的配置将Spring容器里面的对象注入给对应的属性。

依赖注入有四种方式

setter注入(属性注入)

setter注入,(也可以称之为属性注入)

使用setter注入:

1,使用bean元素的子元素设置;

①简单类型值,直接使用value赋值;

②引用类型,使用ref赋值;

③集合类型,直接使用对应的集合类型元素即可。

2,spring通过属性的setter方法注入值;

3,在配置文件中配置的值都是string,spring可以自动的完成类型转换

  1. package org.cjw.pojo;
  2. public class Employee {
  3. private String name;
  4. private Integer age;
  5. private Department dept;
  6. // get、set、toString、无参构造、有参构造方法
  7. }
  8. package org.cjw.pojo;
  9. public class Department {
  10. private Integer id;
  11. private String name;
  12. // get、set方法
  13. }

—配置文件

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans.xsd">
  6. <!-- 员工 -->
  7. <bean id="employee" class="org.cjw.pojo.Employee">
  8. <!-- setter方法注入,属性注入
  9. <property name="" value/ref="">
  10. name:属性名称
  11. value:基本类型+String+包装类型值注入
  12. ref:除了value以外的引用类型(对象)注入
  13. 注意:value和ref只能二选一
  14. -->
  15. <property name="name" value="cjw" />
  16. <property name="age" value="18" />
  17. <property name="dept" ref="dept" />
  18. </bean>
  19. <!-- 部门 -->
  20. <bean id="dept" class="org.cjw.pojo.Department">
  21. <property name="id" value="1" />
  22. <property name="name" value="研发部" />
  23. </bean>
  24. </beans>

构造器注入

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans.xsd">
  6. <!--
  7. <constructor-arg name/index="" value/ref=""/>
  8. 默认情况下,constructor-arg的顺序就是构造器参数的顺序
  9. name: 在构造器中按照构造器的参数名字设置值
  10. index:在构造器中的参数索引(从0开始),index有默认值,就是当前constructor-arg出现的序号
  11. value/ref:在构造器中的实参值,value用于简单值、ref用于对象(引用值)
  12. 注意:要么name和value/ref搭配(常用,直观明了)、要么index和value/ref搭配
  13. ====================
  14. 使用哪种注入方式比较好(setter?构造器?)?
  15. 1,如果一个类必须依赖另一个类才能正常运行,用构造器;
  16. 2,但是构造器的参数如果过多,构造器很难看;
  17. 3,更多的还是使用setter注入;
  18. 4,可以使用@Required标签来要求一个属性必须注入
  19. [1] 将@Required注解放置在需要必须注入的属性的setter方法上
  20. [2] 在Spring容器中注册RequiredAnnotationBeanPostProcessor类,用于检测@Required注解下的方法是否以被执行过。
  21. <bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor"/>
  22. @Required注解只针对setter注入方式的DI,用于确认某些属性是否已被初始化,而构造器注入则是根据提供的有参构造来进行的,
  23. 换句话说,有参构造的形式决定了构造器注入的形式。
  24. -->
  25. <bean id="employee" class="org.cjw.pojo.Employee">
  26. <constructor-arg name="name" value="cjw"/>
  27. <constructor-arg name="age" value="18" />
  28. <constructor-arg name="dept" ref="dept" />
  29. </bean>
  30. <bean id="dept" class="org.cjw.pojo.Department"/>
  31. </beans>

p命名空间注入

使用p命名空间注入,需要先在约束上面引入 p标签

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:p="http://www.springframework.org/schema/p"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. http://www.springframework.org/schema/beans/spring-beans.xsd">
  7. <!--
  8. 属性类型是简单类型:p:属性名=属性值
  9. 属性类型是引用类型:p:属性名-ref=对象引用
  10. -->
  11. <bean id="employee" class="org.cjw.pojo.Employee" p:name="cjw" p:age="18" p:dept-ref="dept"/>
  12. <bean id="dept" class="org.cjw.pojo.Department"/>
  13. </beans>

集合类型值注入

在处理的数据中,

有标量类型(基础数据类型以及包装类+String)— value属性

也有引用类型 —ref属性

还要很多数据JDK内置的数据结构:

  1. 键值对 Map、Properties
  2. 数组
  3. 集合Set、List

对于集合类型的属性使用集合类型值注入。

  1. package org.cjw.pojo;
  2. import java.util.*;
  3. public class CollectionBean {
  4. private Set<String> set;
  5. private List<String> list;
  6. private String[] array;
  7. private Map<String, String> map;
  8. private Properties prop; //读取本地 xxx.properties文件(本质就是一个Map集合)
  9. // get、set、toString方法
  10. }

— 配置文件

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans.xsd">
  6. <bean id="collectionBean" class="org.cjw.pojo.CollectionBean">
  7. <property name="set">
  8. <set>
  9. <value>set1</value>
  10. <value>set2</value>
  11. <value>set3</value>
  12. </set>
  13. </property>
  14. <property name="list">
  15. <list>
  16. <value>list1</value>
  17. <value>list2</value>
  18. <value>list3</value>
  19. </list>
  20. </property>
  21. <property name="array">
  22. <array>
  23. <value>array1</value>
  24. <value>array2</value>
  25. <value>array3</value>
  26. </array>
  27. </property>
  28. <property name="map">
  29. <map>
  30. <entry key="key1" value="value1" />
  31. <entry key="key2" value="value2" />
  32. <entry key="key3" value="value3" />
  33. </map>
  34. </property>
  35. <property name="prop">
  36. <props>
  37. <prop key="prop1">proValue1</prop>
  38. <prop key="prop2">proValue2</prop>
  39. <prop key="prop3">proValue3</prop>
  40. </props>
  41. </property>
  42. </bean>
  43. </beans>

Spring 循环依赖

什么是循环依赖?

多个 bean 之间相互依赖,形成一个闭环。比如:A 依赖于 B,B 依赖于 C ,C 依赖于 A。

构造方法注入和 setter 方法注入异同?

1、其实循环依赖很好理解,Spring 容器初始化会去创建 bean 。你 bean 中注入了属性,比如 A 这个类中你注入了 B 这个类,那么要创建 A 这个 bean ,就必须要 B 这个 bean 存在,然后你又在 B 这个类中注入了 A 这个类,同样,要创建 B 这个类,首先需要 A 这个 bean 存在,最后的结果就是异常。

2、根据 Spring 官网的描述,循环依赖对构造方法注入不友好,对 setter 方法注入比较友好。

3、我们 AB 循环依赖问题只要 A 的注入方式是 setter 且 singleton ,就不会有循环依赖问题

小结论:

  • Spring 容器中,默认的单例(singleton)场景是支持循环依赖的,不会报错
  • Spring 容器中,原型(prototype)的场景是不支持循环依赖的,会报错。

Spring 是怎么解决循环依赖的?

Spring 内部通过 3 级缓存来解决循环依赖。

第一级缓存:singletonObjects ,存放已经经历完整生命周期的 Bean 对象

第二级缓存:earlySingletonObjects ,存放早期暴露出来的 Bean 对象,Bean 的生命周期未结束(属性还未填充完成),说人话就是 bean 已经创建了,但是属性还没有初始化。类似于房子买好了,但是家具还没有搬进来。

第三级缓存:singletonFactories 存放可以生成 Bean 的工厂。

只有单例的 bean 会通过三级缓存提前暴露来解决循环依赖问题,而非单例的 bean ,每次从容器中获取的都是一个新的对象,都会重新创建,所以非单例的 bean 是没有缓存的,不会将其放到三级缓存中。

控制反转和依赖注入 - 图16

实例化和初始化的概念:

详细参考Spring Bean 生命周期

实例化:内存中申请一块内存空间(类似于买好房子,但是家具什么的还没有搬进去)

初始化属性填充:完成属性的各种赋值(类似于装修,家具什么的搬进去)

A/B 两个对象在三级缓存中的迁移说明:

1、A 创建过程需要 B ,于是 A 将自己放到三级缓存中,去实例化 B

2、B 实例化的时候发现需要A,于是 B 先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A,然后把三级缓存里面的A放到二级缓存里面,并删除三级缓存里面的A

3、B顺利初始化完毕,将自己放到一级缓存里面(此时 B 中的 A 依然是创建中状态),然后回来接着创建A,此时B已经创建结束,直接冲一级缓存中拿到B,然后完成创建,并将 A 自己放到一级缓存里面。

Spring 解决循环依赖:

Spring 创建 bean 主要分为两个步骤,创建原始 bean 对象,接着去填充对象属性和初始化。

每次创建 bean 之前,我们都会从缓存中查一下有没有该 bean,因为是单例,只能有 1 个。

当我们创建 beanA 的原始对象后,并把它放到三级缓存中,接下来就该填充对象的属性了,这时候发现依赖了 beanB,接着就去创建 beanB,同样的流程,创建完 beanB ,填充属性时又发现它依赖了 beanA,又是同样的流程。

不同的是:

这时候可以在三级缓存中查到刚放进去的原始对象 beanA,所以不需要继续创建,用它注入 beanB,完成 beanB 的创建。

既然 beanB 创建好了,所以 beanA 就可以完成填充属性的步骤了,接着执行剩下的逻辑,闭环完成。

注:

Spring 解决循环依赖依靠的是 Bean 的 “中间态” 这个概念,而这个中间态指的是已经实例化但还没有初始化的状态 (半成品)。实例化的过程又是通过构造器创建的,如果 A 还没创建好出来怎么可能提前曝光,所以构造器的循环依赖无法解决。

小结

  • 优秀的项目架构的特点

(1) 高内聚
① 项目分层开发(每层处理各自的任务,职责分明)
(2) 低耦合
① 对象与对象之间有不直接产生依赖(不直接new对象)

  • 低耦合的解决方案

(1) 开发者自己底层使用反射进行封装相关代码
(2) 使用优秀的框架 Spring

  • 什么是Spring?

(1) 轻量级一站式框架
(2) 轻量级
① Spring有20个模块,只需要四个模块即可启动Spring框架,其他模块按需引入即可
(3) 一站式
① Web开发的三层架构 ,Web层、Service层,Dao层,全部使用Spring框架完成

  • Spring如何实现解耦

(1) IOC :控制反转
① 将对象创建权交给Spring管理
② 负责管理对象生命周期(有效期)
③ 执行对象的初始化方法,销毁方法
④ IOC创建对象实例有四种方式

  1. 直接使用无参数构造函数-推荐
    • <bean id = "", class ="">
  2. 使用静态工厂创建bean
  3. 使用实例工厂创建bean
  4. 使用变种实例工厂,工厂类实现 FactoryBean接口

(2) DI :依赖注入(将对象的属性通过Spring赋值)
Setter方法(属性)注入
构造器注入
P命名空间注入
集合类型值类型,引用类型和支持各种集合数据类型的注入

  • Spring如何解决循环依赖