视频资料:https://www.bilibili.com/video/BV1Fi4y1S7ix?p=5&spm_id_from=pageDriver&vd_source=1656d4f8efefd2dc9cca344e8978b19e

1.Spring框架的主要内容:

简化开发: Spring框架中提供了两个大的核心技术,分别是:

  • ==IOC==
  • ==AOP==

1.1系统架构图

image-20220811154554527

(1)核心层

  • Core Container:核心容器,这个模块是Spring最核心的模块,其他的都需要依赖该模块

(2)AOP层

  • AOP:面向切面编程,它依赖核心层容器,目的是==在不改变原有代码的前提下对其进行功能增强==
  • Aspects:AOP是思想,Aspects是对AOP思想的具体实现

(3)数据层

  • Data Access:数据访问,Spring全家桶中有对数据访问的具体实现技术
  • Data Integration:数据集成,Spring支持整合其他的数据层解决方案,比如Mybatis
  • Transactions:事务,Spring中事务管理是Spring AOP的一个具体实现,也是后期学习的重点内容

(4)Web层

  • 这一层的内容将在SpringMVC框架具体学习

(5)Test层

  • Spring主要整合了Junit来完成单元测试和集成测试

1.2学习路线

  • ==Spring的IOC/DI==
  • ==Spring的AOP==
  • ==AOP的具体应用,事务管理==
  • ==IOC/DI的具体应用,整合Mybatis==

image-20220811154648225

2.Spring核心概念

在Spring核心概念这部分内容中主要包含**IOC/DIIOC容器Bean**

2.1先来说说为什么要用这些,我们之前项目中的痛点

image-20220811155422099

(1)业务层需要调用数据层的方法,就需要在业务层new数据层的对象

(2)如果数据层的实现类发生变化,那么业务层的代码也需要跟着改变,发生变更后,都需要进行编译打包和重部署

(3)所以,现在代码在编写的过程中存在的问题是:**==耦合度偏高==**

针对这个问题,该如何解决呢?

image-20220811160902185

我们就想,如果能把框中的内容给去掉,不就可以降低依赖了么,但是又会引入新的问题,去掉以后程序能运行么?

答案肯定是不行,因为bookDao没有赋值为Null,强行运行就会出空指针异常。

所以现在的问题就是,业务层不想new对象,运行的时候又需要这个对象,该咋办呢?

针对这个问题,Spring就提出了一个解决方案:

  • 使用对象时,在程序中不要主动使用new产生对象,转换为由==外部==提供对象

这种实现思就是Spring的一个核心概念

2.IOC、IOC容器、Bean、DI

2.1IOC(Inversion of Control)控制反转

使用对象时,由主动new产生对象转换为由==外部==提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转。

1.外部:

  • 业务层u要用数据层的类对象,以前是自己new
  • 现在自己不new了,交给别人[外部]来创建对象
  • 别人[外部]就反转控制了数据层对象的创建权
  • 这种思想就是控制反转
  • 别人[外部]指定是什么呢?继续往下学

2.Spring和IOC之间的关系是什么呢?

  • Spring技术对IOC思想进行了实现
  • Spring提供了一个容器,称为==IOC容器==,用来充当IOC思想中的”外部”
  • IOC思想中的别人[外部]指的就是Spring的IOC容器

3.IOC容器的作用以及内部存放的是什么?(什么是bean)

  • IOC容器负责对象的创建、初始化等一系列工作,其中包含了数据层和业务层的类对象
  • 被创建或被管理的对象在IOC容器中统称为==Bean==
  • IOC容器中放的就是一个个的Bean对象

4.当IOC容器中创建好service和dao对象后,程序能正确执行么?

  • 不行,因为service运行需要依赖dao对象
  • IOC容器中虽然有service和dao对象
  • 但是service对象和dao对象没有任何关系
  • 需要把dao对象交给service,也就是说要绑定service和dao对象之间的关系

像这种在容器中建立对象与对象之间的绑定关系就要用到DI:

2.DI(Dependency Injection)依赖注入

2.1什么是依赖注入呢?(关系)

  • 在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入
    • 业务层要用数据层的类对象,以前是自己new
    • 现在自己不new了,靠别人[外部其实指的就是IOC容器]来给注入进来
    • 这种思想就是依赖注入

image-20220811162451015

2.IOC容器中哪些bean之间要建立依赖关系呢?

  • 这个需要程序员根据业务需求提前建立好关系,如业务层需要依赖数据层,service就要和dao建立依赖关系

2.2核心概念:解耦

目标:充分解耦

  • 使用IoC容器管理bean(IoC)
  • 在IoC容器内将有依赖关系的bean进行关系绑定(DI)

最终效果:

  • 使用对象时不仅可以直接IoC容器中获取,并且获取到的bean已经绑定了所有的依赖关系。

2.3核心概念小结

1.什么IOC/DI思想?

  • IOC:控制反转,控制反转的是对象的创建权
  • DI:依赖注入,绑定对象与对象之间的依赖关系

2.什么是IOC容器?

Spring创建了一个容器用来存放所创建的对象,这个容器就叫IOC容器

3.什么是Bean?

容器中所存放的一个个对象就叫Bean或Bean对象

3.入门案例(了解步骤即可)

1.Spring是使用容器来管理bean对象的,那么管什么? (Service和Dao)

2.如何将被管理的对象告知IOC容器?(使用配置文件)

3.被管理的对象交给IOC容器,要想从容器中获取对象,就先得思考如何获取到IOC容器?(Spring框架提供相应的接口)

4.IOC容器得到后,如何从容器中获取bean?(调用Spring框架提供对应接口中的方法)

5.使用Spring导入哪些坐标?(需要在pom.xml添加对应的依赖)

image-20220811173946684

步骤1:创建Maven项目

1629734010072

步骤2:添加Spring的依赖jar包

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>

步骤3:添加案例中需要的类

创建BookService,BookServiceImpl,BookDao和BookDaoImpl四个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public interface BookDao {
public void save();
}
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
public interface BookService {
public void save();
}
public class BookServiceImpl implements BookService {
private BookDao bookDao = new BookDaoImpl();
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}

步骤4:添加spring配置文件

resources下添加spring配置文件applicationContext.xml,并完成bean的配置

image-20220811180028847

步骤5:在配置文件中完成bean的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<!--bean标签标示配置bean
id属性标示给bean起名字
class属性表示给bean定义类型
-->
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"/>

</beans>

==注意事项:bean定义时id属性在同一个上下文中(配置文件)不能重复==

步骤6:获取IOC容器

使用Spring提供的接口完成IOC容器的创建,创建App类,编写main方法

1
2
3
4
5
6
public class App {
public static void main(String[] args) {
//获取IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
}
}

步骤7:从容器中获取对象进行方法调用

1
2
3
4
5
6
7
8
9
10
public class App {
public static void main(String[] args) {
//获取IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// BookDao bookDao = (BookDao) ctx.getBean("bookDao");
// bookDao.save();
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}

步骤8:运行程序

测试结果为:

image-20210729184337603

Spring的IOC入门案例已经完成,但是在BookServiceImpl的类中依然存在BookDaoImpl对象的new操作,它们之间的耦合度还是比较高,这块该如何解决,就需要用到下面的DI:依赖注入

4.DI入门案例(上个案例基础)

4.1思路分析

(1)要想实现依赖注入,必须要基于IOC管理Bean

  • DI的入门案例要依赖于前面IOC的入门案例

2.Service中使用new形式创建的Dao对象是否保留?

  • 需要删除掉,最终要使用IOC容器中的bean对象

3.Service中需要的Dao对象如何进入到Service中?

  • 在Service中提供方法,让Spring的IOC容器可以通过该方法传入bean对象

4.Service与Dao间的关系如何描述?

  • 使用配置文件

image-20220811180506719

步骤1: 去除代码中的new

在BookServiceImpl类中,删除业务层中使用new的方式创建的dao对象

1
2
3
4
5
6
7
8
9
public class BookServiceImpl implements BookService {
//删除业务层中使用new的方式创建的dao对象
private BookDao bookDao;

public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}

步骤2:为属性提供setter方法

在BookServiceImpl类中,为BookDao提供setter方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class BookServiceImpl implements BookService {
//删除业务层中使用new的方式创建的dao对象
private BookDao bookDao;

public void save() {
System.out.println("book service save ...");
bookDao.save();
}
//提供对应的set方法
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}

步骤3:修改配置完成注入

在配置文件中添加依赖注入的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--bean标签标示配置bean
id属性标示给bean起名字
class属性表示给bean定义类型
-->
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>

<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<!--配置server与dao的关系-->
<!--property标签表示配置当前bean的属性
name属性表示配置哪一个具体的属性
ref属性表示参照哪一个bean
-->
<property name="bookDao" ref="bookDao"/>
</bean>

</beans>

==注意:配置中的两个bookDao的含义是不一样的==

  • name=”bookDao”中bookDao的作用是让Spring的IOC容器在获取到名称后,将首字母大写,前面加set找对应的setBookDao()方法进行对象注入
  • ref=”bookDao”中bookDao的作用是让Spring能在IOC容器中找到id为bookDao的Bean对象给bookService进行注入
  • 综上所述,对应关系如下:

1629736314989

步骤4:运行程序

运行,测试结果为:

image-20220811213203146

5.IOC相关内容

5.1 bean基础配置(bean的作用范围配置)

对于bean的配置中,主要会讲解bean基础配置,bean的别名配置,bean的作用范围配置==(重点)==,这三部分内容:

5.1.1 bean基础配置(id与class)

对于bean的基础配置,在前面的案例中已经使用过:

1
<bean id="" class=""/>

其中,bean标签的功能、使用方式以及id和class属性的作用,我们通过一张图来描述下

image-20220811213613210

这其中需要大家重点掌握的是:==bean标签的id和class属性的使用==。

思考:

  • class属性能不能写接口如BookDao的类全名呢?

答案肯定是不行,因为接口是没办法创建对象的。

5.1.2 bean作用范围scope配置

关于bean的作用范围是bean属性配置的一个==重点==内容。

看到这个作用范围,我们就得思考bean的作用范围是来控制bean哪块内容的?

我们先来看下bean作用范围的配置属性:

image-20220811214427112

验证IOC容器中对象是否为单例

验证思路

​ 同一个bean获取两次,将对象打印到控制台,看打印出的地址值是否一致。

具体实现

  • 创建一个AppForScope的类,在其main方法中来验证

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class AppForScope {
    public static void main(String[] args) {
    ApplicationContext ctx = new
    ClassPathXmlApplicationContext("applicationContext.xml");

    BookDao bookDao1 = (BookDao) ctx.getBean("bookDao");
    BookDao bookDao2 = (BookDao) ctx.getBean("bookDao");
    System.out.println(bookDao1);
    System.out.println(bookDao2);
    }
    }
  • 打印,观察控制台的打印结果

    1629772538893

  • 结论:默认情况下,Spring创建的bean对象都是单例的

image-20220811214525664

哪些bean对象不适合交给容器进行管理?

  • 封装实例的域对象,因为会引发线程安全问题,所以不适合。

获取到结论后,问题就来了,那如果我想创建出来非单例的bean对象,该如何实现呢?

bean的scope属性可以控制bean的创建是否为单例:(重要)

  • singleton默认为单例
  • prototype为非单例

image-20220811214655754

1
scope="singleton"/>

5.2 bean实例化(重要 )

bean本质上就是对象,创建bean使用构造方法完成。

实例化bean的三种方式————构造方法(常用)

image-20220811215825898

静态工厂实例化(了解)

具体实现步骤为:

(1)在spring的配置文件application.properties中添加以下内容:

1
<bean id="orderDao" class="com.itheima.factory.OrderDaoFactory" factory-method="getOrderDao"/>

class:工厂类的类全名

factory-mehod:具体工厂类中创建对象的方法名

对应关系如下图:

image-20220811221553973

(2)在AppForInstanceOrder运行类,使用从IOC容器中获取bean的方法进行运行测试

1
2
3
4
5
6
7
public class AppForInstanceOrder {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");
orderDao.save();
}
}

(3)运行后,可以查看到结果

看到这,可能有人会问了,你这种方式在工厂类中不也是直接new对象的,和我自己直接new没什么太大的区别,而且静态工厂的方式反而更复杂,这种方式的意义是什么?

主要的原因是:

  • 在工厂的静态方法中,我们除了new对象还可以做其他的一些业务操作,这些操作必不可少,如:
1
2
3
4
5
6
public class OrderDaoFactory {
public static OrderDao getOrderDao(){
System.out.println("factory setup....");//模拟必要的业务操作
return new OrderDaoImpl();
}
}

3.实例工厂(了解)与FactoryBean(知道)

实例工厂实例化

具体实现步骤为:

(1)在spring的配置文件中添加以下内容:

1
2
<bean id="userFactory" class="com.itheima.factory.UserDaoFactory"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>

实例化工厂运行的顺序是:

  • 创建实例化工厂对象,对应的是第一行配置

  • 调用对象中的方法来创建bean,对应的是第二行配置

    • factory-bean:工厂的实例对象

    • factory-method:工厂对象中的具体创建对象的方法名,对应关系如下:

      image-20210729200203249

factory-mehod:具体工厂类中创建对象的方法名

(2)在AppForInstanceUser运行类,使用从IOC容器中获取bean的方法进行运行测试

1
2
3
4
5
6
7
8
public class AppForInstanceUser {
public static void main(String[] args) {
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) ctx.getBean("userDao");
userDao.save();
}
}

(3)运行后,可以查看到结果

实例工厂实例化的方式就已经介绍完了,配置的过程还是比较复杂,所以Spring为了简化这种配置方式就提供了一种叫FactoryBean的方式来简化开发。

FactoryBean的使用

具体的使用步骤为:

(1)创建一个UserDaoFactoryBean的类,实现FactoryBean接口,重写接口的方法

1
2
3
4
5
6
7
8
9
10
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
//代替原始实例工厂中创建对象的方法
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
//返回所创建类的Class对象
public Class<?> getObjectType() {
return UserDao.class;
}
}

(2)在Spring的配置文件中进行配置

1
<bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean"/>

(3)AppForInstanceUser运行类不用做任何修改,直接运行

1629788769436

这种方式在Spring去整合其他框架的时候会被用到,所以这种方式需要大家理解掌握。

总结

(1)bean是如何创建的呢?

1
构造方法

(2)Spring的IOC实例化对象的三种方式分别是:

  • 构造方法(常用)
  • 静态工厂(了解)
  • 实例工厂(了解)
    • FactoryBean(实用)

这些方式中,重点掌握构造方法FactoryBean即可。

需要注意的一点是,构造方法在类中默认会提供,但是如果重写了构造方法,默认的就会消失,在使用的过程中需要注意,如果需要重写构造方法,最好把默认的构造方法也重写下。

5.3 bean的生命周期

  • 首先理解下什么是生命周期?
    • 从创建到消亡的完整过程,例如人从出生到死亡的整个过程就是一个生命周期。
  • bean生命周期是什么?
    • bean对象从创建到销毁的整体过程。
  • bean生命周期控制是什么?
    • 在bean创建后到销毁前做一些事情。

5.3.1 生命周期设置

接下来,在上面这个环境中来为BookDao添加生命周期的控制方法,具体的控制有两个阶段:

  • bean创建之后,想要添加内容,比如用来初始化需要用到资源
  • bean销毁之前,想要添加内容,比如用来释放用到的资源
步骤1:添加初始化和销毁方法

针对这两个阶段,我们在BooDaoImpl类中分别添加两个方法,==方法名任意==

1
2
3
4
5
6
7
8
9
10
11
12
13
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
//表示bean初始化对应的操作
public void init(){
System.out.println("init...");
}
//表示bean销毁前对应的操作
public void destory(){
System.out.println("destory...");
}
}
步骤2:配置生命周期

在配置文件添加配置,如下:

1
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>
步骤3:运行程序

运行AppForLifeCycle打印结果为:

image-20220812172403469

从结果中可以看出,init方法执行了,但是destroy方法却未执行,这是为什么呢?

  • Spring的IOC容器是运行在JVM中
  • 运行main方法后,JVM启动,Spring加载配置文件生成IOC容器,从容器获取bean对象,然后调方法执行
  • main方法执行完后,JVM退出,这个时候IOC容器中的bean还没有来得及销毁就已经结束了
  • 所以没有调用对应的destroy方法

4.3.3 close关闭容器

  • ApplicationContext中没有close方法

  • 需要将ApplicationContext更换成ClassPathXmlApplicationContext

    1
    2
    ClassPathXmlApplicationContext ctx = new 
    ClassPathXmlApplicationContext("applicationContext.xml");
  • 调用ctx的close()方法

    1
    ctx.close();
  • 运行程序,就能执行destroy方法的内容

    1629792857608

4.3.4 注册钩子关闭容器

  • 在容器未关闭之前,提前设置好回调函数,让JVM在退出之前回调此函数来关闭容器

  • 调用ctx的registerShutdownHook()方法

    1
    ctx.registerShutdownHook();

    **注意:**registerShutdownHook在ApplicationContext中也没有

  • 运行后,查询打印结果

    1629792857608

两种方式介绍完后,close和registerShutdownHook选哪个?

相同点:这两种都能用来关闭容器

不同点:close()是在调用的时候关闭,registerShutdownHook()是在JVM退出前调用关闭。

分析上面的实现过程,会发现添加初始化和销毁方法,即需要编码也需要配置,实现起来步骤比较多也比较乱。

Spring提供了两个接口来完成生命周期的控制,好处是可以不用再进行配置init-methoddestroy-method

接下来在BookServiceImpl完成这两个接口的使用:

修改BookServiceImpl类,添加两个接口InitializingBeanDisposableBean并实现接口中的两个方法afterPropertiesSetdestroy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
public void destroy() throws Exception {
System.out.println("service destroy");
}
public void afterPropertiesSet() throws Exception {
System.out.println("service init");
}
}

重新运行AppForLifeCycle类,

1629794527419

那第二种方式的实现,我们也介绍完了。

小细节

  • 对于InitializingBean接口中的afterPropertiesSet方法,翻译过来为属性设置之后

  • 对于BookServiceImpl来说,bookDao是它的一个属性

  • setBookDao方法是Spring的IOC容器为其注入属性的方法

  • 思考:afterPropertiesSet和setBookDao谁先执行?

    • 从方法名分析,猜想应该是setBookDao方法先执行

    • 验证思路,在setBookDao方法中添加一句话

      1
      2
      3
      4
      5
      public void setBookDao(BookDao bookDao) {
      System.out.println("set .....");
      this.bookDao = bookDao;
      }

    • 重新运行AppForLifeCycle,打印结果如下:

      1629794928636

      验证的结果和我们猜想的结果是一致的,所以初始化方法会在类中属性设置之后执行。

4.3.5 bean生命周期小结

(1)关于Spring中对bean生命周期控制提供了两种方式:

  • 在配置文件中的bean标签中添加init-methoddestroy-method属性
  • 类实现InitializingBeanDisposableBean接口,这种方式了解下即可。

(2)对于bean的生命周期控制在bean的整个生命周期中所处的位置如下:

  • 初始化容器
    • 1.创建对象(内存分配)
    • 2.执行构造方法
    • 3.执行属性注入(set操作)
    • 4.执行bean初始化方法
  • 使用bean
    • 执行业务操作
  • 关闭/销毁容器
    • 执行bean销毁方法

(3)关闭容器的两种方式:

  • ConfigurableApplicationContext是ApplicationContext的子类
    • close()方法
    • registerShutdownHook()方法