# 为什么要学 Spring

  • Spring 技术是 JavaEE 开发必备技能,企业开发技术选型命中率 > 90%
  • 专业角度
    • 简化开发,减低企业级开发的复杂性

 <img data-src="https://gitee.com/Doukaixin/note_image/raw/master/https://gitee.com/doukaixin/note_image/image-20221106130410622.png" alt="image-20221106130410622" style="zoom:50%;float:left;"                                                            />

# 学什么

  • 简化开发
    • IoC
    • AOP
      • 事务处理
  • 框架整合
    • MyBatis
    • MyBatis-plus
    • Struts
    • Struts2
    • Hibernate
    • .....

Spring Framework(弹簧框架) 系统架构

image-20221106134923750

image-20221106135603744

# 核心概念

  • <font color = blue size = 5> 代码书写现状 </font>
    • 耦合度偏高

image-20221106140023807

业务层的实现突然想换另一个接口实现类,此时如果直接更换那么就要动源码,重新测试,重新部署,成本很高

image-20221106140202518

如果不写耦合度就低了,但是这么写肯定是会发生错误的不合理

  • <font color = blue size = 5> 解决方案 </font>

    • 使用对象时,在程序中不要主动使用 new 产生对象,转换为由外部提供对象
  • 这种思想就是 <font color = red>IoC</font>

    • <font color = red>IoC (Inversion of Control) 控制反转 </font>
    • 对象的创建控制权由 <font color = red><u> 程序 </u></font> 转移 到 <font color = red><u> 外部 </u></font> , 这种思想成为 < font color = red size = 4><u> 控制反转 </u></font>

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

  • Spring 技术对 IoC 思想进行了实现
    • Spring 提供了一个容器,称为 <font color = red>IoC 容器 </font>, 用来充当 IoC 思想中的 < font color = red>"IoC"</font>

也就是由主动 new 对象变成了由 IoC 提供对象

  • IoC 容器负责对象的创建,初始化等一系列工作,被创建或管理的对象在 IoC 容器中统称为 <font color = red>Bean</font>

image-20221106142132042

  • <font color = red>DI (Dependency Injection) 依赖注入 </font>
    • 在容器中建立 bean 与 bean 之间的依赖关系的整个过程,称为依赖注入

<font size = 5> 目标 :</font> <font color = red size = 4> 充分解耦 </font>

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

<font color = blue size = 4> 最终效果 </font>

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

# <font color = grenn> 小结:</font>

  1. IoC / DI 思想

<u>IoC : 用对象的时候不要自己 new, 外部提供 </u>

<u>DI : 将有依赖关系的 Bean (对象) 进行绑定 </u>

  1. IoC 容器

<u>Spring 在进行实现时是 IoC 容器进行提供 </u>

  1. Bean

<u>IoC 管理的对象称为 Bean</u>

# IoC 入门案例 (XML 版)

思路分析:

  1. 管理什么?(Service 与 Dao)

  2. 如何将被管理的对象告知 IoC 容器?(配置)

  3. 被管理的对象交给 IoC 容器,如何获取到 IoC 容器?(接口)

  4. IoC 容器得到后,如何从容器中获取 bean?

  5. 使用 Spring 导入哪些坐标?(pom.xml)

# 定义 Spring 管理的类 (接口)

  • 导入 pom.xml

Spring context 是什么

org.springframework.context.ApplicationContext 接口表示 Spring IoC 容器,负责实例化、配置和组装 bean。容器通过读取配置元数据获取关于要实例化、配置和组装哪些对象的指令。 配置元数据用 XML、Java 注释或 Java 代码表示。它允许您表达组成应用程序的对象以及这些对象之间丰富的相互依赖关系。

Spring 提供了 ApplicationContext 接口的几个实现。在独立应用程序中,通常创建 ClassPathXmlApplicationContext 或 FileSystemXmlApplicationContext 的实例。

在大多数应用程序场景中,不需要显式用户代码来实例化 Spring IoC 容器实例。例如,在 web 应用程序场景中,在应用程序的 web. XML 文件中使用简单的 8 行 web 描述符 XML 就足够了。

<dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>
  • BookDao
public interface BookDao {
    void save();
}
  • 实现类
public class BookDaoImpl implements BookDao {
    public void save(){
        System.out.println("bookDao save runing...");
    }
}
  • BookService
public interface BookService {
    void save();
}
  • 实现类
public class BookServiceImpl implements BookService {
    private BookDao bookdao = new BookDaoImpl();
    public void save(){
        System.out.println("book service save ...");
        bookdao.save();
    }
}
  • 创建 Spring 配置文件,配置对应类作为 Spring 管理的 bean
<?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">
<!--    1. 导入 spring 的坐标 spring-context, 对应版本是 5.2.10.RELEASE-->
<!--    2. 配置 bean-->
<!--    bean 标签表示配置 bean-->
<!--    id 属性表示给 bean 起名称 -->
<!--    class 属性表示给 bean 定义类型 -->
    <bean id="bookDao" class="com.dkx.spring.dao.impl.BookDaoImpl"/>
    <bean id="bookService" class="com.dkx.spring.service.impl.BookServiceImpl"/>
</beans>

<font style="color:red"> 注意事项 </font> bean 定义时 id 属性在同一个上下文中不能重复

测试代码:

test

public class SpringTest {
    private BookService service = new BookServiceImpl();
    @Test
    public void test(){
        service.save();
    }
}
----------------------Result----------------------
bookService save runing...
bookDao save runing...

# DI 入门案例思路分析

  1. 基于 IoC 管理 bean

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

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

操作步骤

  1. 删除使用 new 的形式创建对象的代码:以下代码
public class BookServiceImpl implements BookService(){
    private BookDao bookDao = new BookDaoImpl();
    public void save(){
        bookDao.save();
    }
}
  1. 提供依赖对象对应的 setter 方法:以上删除后的代码
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 d){
        this.bookdao = d;
    }
}

思考:这个 setter 方法是谁调的?

  • 谁要往里面传对象呢?容器,所以说这个 setter 方法是容器在执行的
  1. 配置 service 与 dao 之间的关系
<?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">
<!--    1. 导入 spring 的坐标 spring-context, 对应版本是 6.0.5-->
<!--    2. 配置 bean-->
<!--    bean 标签表示配置 bean-->
<!--    id 属性表示给 bean 起名称 -->
<!--    class 属性表示给 bean 定义类型 -->
    <bean id="bookDao" class="com.dkx.spring.dao.impl.BookDaoImpl"/>
    <bean id="bookService" class="com.dkx.spring.service.impl.BookServiceImpl">
<!--        配置 server 与 dao 的关系   ref:bean id 中的名称 -->
<!--        property 标签表示配置当前 bean 的属性 -->
<!--        name 属性表示配置哪一个具体的属性 -->
<!--        ref 属性表示参照哪一个 bean-->
        <property name="bookDao" ref="bookDao"/>
    </bean>
</beans>
  • 如果不使用这种方式而调用 dao 的代码则会报错

test 测试代码

public class SpringTest {
    // 不能使用传统的方式实例化对象然后去调用了
    //private BookService service = new BookServiceImpl();
    @Test
    public void test(){
        //service.save();
        // 获取 IoC 容器
        ApplicationContext a = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 获取 Bean
        BookService service = (BookService) a.getBean("bookService");
        // 调用方法
        service.save();
    }
}
-----------------------------Result-----------------------------
bookService save runing...
bookDao save runing...

<font style="color:red"> 注意:将实例化 BookDao 的代码后面的 new 的行为删除后就不要使用传统的测试类中实例化 BookService 来调用方法了,这样的话会因为不去读取配置文件走的还是传统方式去执行了没有被实例化的 BookDao 就会发生空指针异常,需要使用 ApplicationContext 读取配置文件获取 IoC 容器并 DI 将 BookDao 依赖注入就不会报错了 </font>

# bean 配置

# bean 基础配置

类别描述
名称bean
类型标签
所属beans 标签
功能定义 Spring 核心容器管理的对象
格式< beans><br> < bean/><br> < bean> < bean/><br>< beans/>
属性列表id:bean 的 id, 使用容器可以通过 id 值获取对应的 bean, 在一个容器中 id 值唯一 <br>class:bean 的类型,即配置的 bean 的全路径类名
范例< bean id="bookDao" class="com.dkx.spring.dao.impl.BookDaoImpl"/><br>< bean id="bookService" class="com.dkx.spring.service.impl.BookServiceImpl"/>

<font style="color:red"> 注意事项 </font>

获取 bean 无论是通过 id 还是 name 获取,如果无法获取到,将抛出异常 <font style="color:red">NoSuchBeanDefinitionException</font>

# bean 别名配置

  • name: 用于控制一个 bean 可以有多个名称和 id 的功能基本是等同的
<!--    1. 导入 spring 的坐标 spring-context, 对应版本是 6.0.5-->
<!--    2. 配置 bean-->
<!--    bean 标签表示配置 bean-->
<!--    id 属性表示给 bean 其名称 -->
<!--class 属性表示给 bean 定义类型 name:ref 引用的名称 -->
    <bean id="bookDao" name="dao" class="com.dkx.spring.dao.impl.BookDaoImpl"/>
<!--                       name: 这里面以空格分隔的名称在获取 bean 对象都可以使用 -->
    <bean id="bookService" name="service abc def" class="com.dkx.spring.service.impl.BookServiceImpl">
<!--        配置 server 与 dao 的关系   ref:bean id 中的名称 -->
<!--        property 标签表示配置当前 bean 的属性 -->
<!--        name 属性表示配置哪一个具体的属性 -->
<!--        ref 属性表示参照哪一个 bean 	如果引用的 bean 没有 name 则需要执行 id 的名称 -->
        <property name="bookDao" ref="dao"/>
    </bean>
  • 与如下原理还是相同的

image-20230416104137776

  • setter 方法去掉前面的 set 后的首字母小写

# bean 作用范围

类型描述
名称scope
类型属性
所属bean 标签
功能定义 bean 的作用范围,可选范围如下:<br>* singleton: 单例 (默认)<br>* prototype: 非单例
范围< bean id="bookDao" class="com.dkx.spring.dao.impl.BookDaoImpl" scope="prototype"/>
  • 为什么 bean 默认为单例?

  • 优势

    • 较少 Jvm 垃圾回收

    • 可以快速获取到 Bean

  • 劣势

    • 做不到线程安全
  • 适合交给容器进行管理的 bean

    • 表现层对象
    • 业务层对象
    • 数据层对象
    • 工具对象
  • 不适合交给容器进行管理的 bean

    • 封装实体的域对象

是否为单例模式测试:

  • 配置
<!--    方式一:使用构造方法实例化 bean-->
    <bean id="bookDao" class="com.dkx.spring.dao.impl.BookDaoImpl"/>
  • 测试代码
public void test3(){
//        创建 Spring 工厂对象
        ApplicationContext c = new ClassPathXmlApplicationContext("applicationContext.xml");
//        通过 xml 获取 bean 实例
        BookDao dao = (BookDao) c.getBean("bookDao");
//        通过 xml 获取 bean 实例
        BookDao dao1 = (BookDao) c.getBean("bookDao");
//        输出两个对象的地址值查看是否相同,如果相同则为单例的
        System.out.println(dao);
        System.out.println(dao1);
        dao.save();
    }

Run 结果

image_2023-02-23-10-43-30

  • 配置
<!--    方式一:使用构造方法实例化 bean-->
    <bean id="bookDao" class="com.dkx.spring.dao.impl.BookDaoImpl" scope="prototype"/>

同样的测试代码直接看 Run 结果

image_2023-02-23-10-45-23

# bean 实例化

bean 是如何创建的实例化 bean 的三种方式

  • bean 本质上就是对象,创建 bean 使用无参数的构造方法完成

# 实例化 bean 的第一种方式 ---- 构造方法 (常用)

  • 提供可访问的构造方法
public class BookDaoImpl implements BookDao {
    public BookDaoImpl(){
        System.out.println("BookDaoImpl rning ...");
    }
    public void save(){
        System.out.println("book dao save ...");
    }
}
  • 无论是 <font style="color:red">public</font> 还是 < font style="color:red">private </font> 都可以访问到

  • Spring 创建对象时调的是无参的构造方法你给它一个有参的它就返回给你一个报错

  • 配置

<!--class 属性表示给 bean 定义类型 name:ref 引用的名称 -->
    <bean id="bookDao" name="dao" class="com.dkx.spring.dao.impl.BookDaoImpl"/>

无参构造方法如果不存在,将抛出异常 <font style="color:red">BeanCreationExceptioin</font>

test 测试代码

public class SpringTest {
    // 不能使用传统的方式实例化对象然后去调用了
    //private BookService service = new BookServiceImpl();
    @Test
    public void test(){
        //service.save();
        // 获取 IoC 容器
        ApplicationContext a = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 获取 Bean
        BookDao service = (BookDao) a.getBean("bookDao");
        // 调用方法
        service.save();
    }
}
----------------------------Result----------------------------
BookDao Constructor runing...
bookDao save runing...
  • <font style="color:red"> 由此可知,Spring 实例化是通过无参构造来实例化的 </font>

# 实例化 bean 的第二种方式 ---- 静态工厂 (了解)

  • 静态工厂
public class OrderDaoFactory{
    public static BookDao getB(){
        return new BookDaoImpl();
    }
}
  • 配置
  • <font style="color:red"> 注意:这里不需要配置 BookDao 的 bean 了,因为静态工厂已经实例化一个 BookDao 了,如果再配置 BookDao 的 bean 就会通过无参构造又实例一个你会发现 BookDao 的无参构造被执行了 2 次 </font>
<!--    方式二:使用静态工厂实例化 bean-->
<bean id="orderDao" class="com.dkx.spring.factory.OrderDaoFactory" factory-method="getB"/><!--factory-method 配置方法名需要全名不能省略 get 了 -->

test 测试代码

  • 使用传统方式
public class SpringTest {
    // 实例化静态工厂通过 getB 方法获取 BookDao 对象的返回值
    private BookDao orderDao = new OrderDaoFactory().getB();
    @Test
    public void test(){
        // 通过 BookDao 实例化对象名调用 save 方法
        orderDao.save();
    }
-------------------------Result-------------------------
BookDao Constructor runing...
bookDao save runing...
  • 使用 Spring 的形式来运行
public class SpringTest {
    // 实例化静态工厂通过 getB 方法获取 BookDao 对象的返回值
//    private BookDao orderDao = new OrderDaoFactory().getB();
    @Test
    public void test(){
        // 通过 BookDao 实例化对象名调用 save 方法
//        orderDao.save();
        // 获取 IoC 容器
        ApplicationContext a = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 获取 Bean, 通过 orderDao 静态工厂返回的实例是一个 BookDao 的实例对象
        BookDao service = (BookDao) a.getBean("orderDao");
        // 调用方法
        service.save();
    }
}
-------------------------Result-------------------------
BookDao Constructor runing...
bookDao save runing...

# 实例化 bean 的第三种方式 ---- 实例工厂 (了解)

  • 实例工厂
public class UserDaoFactory {
    public UserDao getUserDaoFactory(){
        return new UserDaoImpl();
    }
}
  • 配置
<!--    方式三:使用实例工厂实例化 bean-->
<bean id="orderDaoFactory" class="com.dkx.factory.OrderDaoFactory"/>
<!--Spring 获取 bookDao 的 bean 并调用工厂方法 getB 来进行实例化 -->
<bean id="bookDao" factory-method="getB" factory-bean="orderDaoFactory"/>

test 测试代码

public class SpringTest {
    // 实例化静态工厂通过 getB 方法获取 BookDao 对象的返回值
//    private BookDao orderDao = new OrderDaoFactory().getB();
    @Test
    public void test(){
        // 通过 BookDao 实例化对象名调用 save 方法
//        orderDao.save();
       // 上面的方式不通过 Spring 传统方式所以还是可以用的
        // 获取 IoC 容器
        ApplicationContext a = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 获取 Bean, 通过 bookDao 实例对象
        BookDao service = (BookDao) a.getBean("bookDao");
        // 调用方法
        service.save();
    }
}
-------------------------Result-------------------------
BookDao Constructor runing...
bookDao save runing...

第三种方式问题引出

image-20230416113044896

  • 上面的 bean 显得多余

  • 方法名不固定

# 对第三种方式进行改良:FactoryBean (实用)

  • 创建一个实例化工厂类并实现 FactoryBean <泛型为 UserDao (接口)> 工厂接口
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
//    代替原始实例工厂对象中创建对象的方法
    @Override
    public UserDao getObject() throws Exception {
        return new UserDaoImpl();
    }
//  得到 bean 类型的我们需要的类型为 UserDao (接口)
    @Override
    public Class<?> getObjectType() {
        return UserDao.class;
    }
}
  • 配置
<!--    方式三的变种方式四:使用 FactoryBean 实例化 Bean-->
    <bean id="userDao" class="com.dkx.spring.factory.UserDaoFactoryBean"/>
  • 测试代码
public void test2(){
//        获取 IoC 容器对象
        ApplicationContext c = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao dao = (UserDao) c.getBean("userDao");
        dao.getUserDao();
    }

# 查看通过工厂实例化对象是否为单例

  • 测试代码
public void test3(){
//        创建 Spring 工厂对象
        ApplicationContext c = new ClassPathXmlApplicationContext("applicationContext.xml");
//        通过 xml 获取 bean 实例
        UserDao dao = (UserDao) c.getBean("userDao");
//        通过 xml 获取 bean 实例
        UserDao dao1 = (UserDao) c.getBean("userDao");
//        输出两个对象的地址值查看是否相同,如果相同则为单例的
        System.out.println(dao);
        System.out.println(dao1);
    }

Run 结果

image_2023-02-23-10-56-18

结论:是单例的

# 改为非单例

  1. 在工厂类中将实现方法加上

image_2023-02-23-10-58-54

image_2023-02-23-11-00-50

// 设置是否为单例模式
    @Override
    public boolean isSingleton() {
        return true;
    }

该重写方法默认返回为 false 将其返回为 true

Run 结果

image_2023-02-23-11-03-02

现在还是单例模式只需要将 true 改为 false 就不为单例模式了

# bean 生命周期

  • 生命周期:从创建到消亡的完整过程

  • bean 生命周期:bean 从创建到销毁的整体过程

  • bean 生命周期控制:在 bean 创建后到销毁前做一些事情

  • 在 BookDaoImpl 中定义方法

    • init
    • destroy
public class BookDaoImpl implements BookDao {
    public void save(){
        System.out.println("BookDao rning...");
    }
//    表示 bean 初始化对应的操作
    public void init(){
        System.out.println("init rning...");
    }
//    表示 bean 销毁前对应的操作
    public void destroy(){
        System.out.println("destory rning...");
    }
}
  • 测试代码
public void test2(){
        ApplicationContext c = new ClassPathXmlApplicationContext("applicationContext.xml");
        BookDao dao = (BookDao) c.getBean("bookDao");
        dao.save();
    }

Run 结果

image_2023-02-23-13-49-44

我们明明在配置文件中 加了 destroy 且没有输出为什么呢?

  • 原因:java 虚拟机执行加载了 ApplicationContext 创建工厂然后获取 Bean 实例,然后执行了 init 初始化方法和 save 最后 ApplicationContext 还没有关闭虚拟机就停止了所以执行不到 destroy 方法了

解决方案:将工厂关闭但是,ApplicationContext 中并没有提供共 close 这样的方法但是在 ClassPathXmlApplicationContext 中开始有 close 方法我们需要使用改接口来将其关闭

image_2023-02-23-13-54-44

  • 测试类代码
public void test2(){
        ClassPathXmlApplicationContext c = new ClassPathXmlApplicationContext("applicationContext.xml");
        BookDao dao = (BookDao) c.getBean("bookDao");
        dao.save();
        c.close();
    }

Run 结果

image_2023-02-23-13-58-59

  • close 关闭比较暴力,它不会等待 Spring 工厂的情况如何直接就给关闭了,我们可以使用如下方式关闭 Spring 工厂让 Spring 工厂工作完毕后自行关闭

  • registerShutdownHook

public void test2(){
        ClassPathXmlApplicationContext c = new ClassPathXmlApplicationContext("applicationContext.xml");
        BookDao dao = (BookDao) c.getBean("bookDao");
//        注册关闭钩子,等待 Spring 工厂工作完后关闭
        c.registerShutdownHook();
        dao.save();
//        close 比较暴力,会直接将 Spring 工厂关闭掉
//        c.close();
    }

Run 结果

image_2023-02-23-14-15-00

# 使用接口形式控制生命周期

  • BookServiceImpl
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
    private BookDao bookDao;
    public BookDao getBookDao(){
        return new BookDaoImpl();
    }
    public void setBookDao(BookDao d){
        this.bookDao = d;
    }
    public void save(){
        System.out.println("BookService rning ...");
        bookDao.save();
    }
    @Override
    public void destroy() throws Exception {
        System.out.println("service destroy rnings...");
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("service afterProperties rning...");
    }
}

测试代码

public void test2(){
        ClassPathXmlApplicationContext c = new ClassPathXmlApplicationContext("applicationContext.xml");
        BookDao dao = (BookDao) c.getBean("bookDao");
//        注册关闭钩子,等待 Spring 工厂工作完后关闭
        c.registerShutdownHook();
        dao.save();
//        close 比较暴力,会直接将 Spring 工厂关闭掉
//        c.close();
    }

执行时为 BookDao 因为 xml 配置所以 Service 也会被执行

意思是,如果 xml 中定义了 service 的 bean 那么该类会被注册也会打印出它的生命周期方法

Run 结果

image_2023-02-23-14-33-00

# BookService 构造器优先于 afterPropertiesSet 方法

  • BookServiceImpl 代码
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
    private BookDao bookDao;
    public BookDao getBookDao(){
        return new BookDaoImpl();
    }
    public void setBookDao(BookDao d){
        System.out.println("BookService method:setBookDao rnings ...");
        this.bookDao = d;
    }
    public void save(){
        System.out.println("BookService rning ...");
        bookDao.save();
    }
    @Override
    public void destroy() throws Exception {
        System.out.println("service destroy rnings...");
    }
//    afterPropertiesSet:在属性设置之后
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("service afterProperties rning...");
    }
}
  • applicationContext.xml
<bean id="bookDao" class="com.dkx.spring.dao.impl.BookDaoImpl" init-method="init" destroy-method="destroy"/>
    <bean id="bookService" class="com.dkx.spring.service.impl.BookServiceImpl">
        <property name="bookDao" ref="bookDao"/>
    </bean>

如果不这么写不会走无参构造方法

  • 测试代码:
public void test2(){
        ClassPathXmlApplicationContext c = new ClassPathXmlApplicationContext("applicationContext.xml");
        BookDao dao = (BookDao) c.getBean("bookDao");
//        注册关闭钩子,等待 Spring 工厂工作完后关闭
        c.registerShutdownHook();
        dao.save();
//        close 比较暴力,会直接将 Spring 工厂关闭掉
//        c.close();
    }

Run 结果

image_2023-02-23-14-46-55

这里定义了两个 bean 的 BookService 其第二个也会加载一次 BookService 的实现类所以声明周期输出了两次

  • 初始化容器

    1. 创建对象 (内存分配)
    2. 执行构造方法
    3. 执行属性注入 (set 操作)
    4. <font style="color:red"> 执行 bean 初始化方法 </font>
  • 使用 bean

    1. 执行业务操作
  • 关闭 / 销毁容器

    1. <font style="color:red"> 执行 bean 销毁方法 </font>

bean 销毁时机

  • 容器关闭前触发 bean 的销毁

  • 关闭容器方式:

    • 手工关闭容器

ConfigurableApplicationContext 接口 close () 操作

  • 注册关闭钩子,在虚拟机推出前先关闭容器再退出虚拟机

ConfigurableApplicationContext 接口 regiterShutdownHook () 操作

# 依赖注入

  • 思考:向一个类中传递数据的方式有几种?

    • 普通方法 (Set 方法)
    • 构造方法
  • 思考:依赖注入描述了在容器中建立了 bean 与 bean 之间依赖关系的过程,如果 bean 运行需要的是数字或字符串呢?

    • 引用类型
    • 简单类型 (基本数据类型与 String)
  • 依赖注入方式

    • setter 注入
      • 简单类型
      • <font style="color:red"> 引用类型 </font>
    • 构造器注入
      • 简单类型
      • 引用类型

# setter 注入 ---- 引用类型

  • 在 bean 中定义引用类型属性并提供可访问的 <font style="color:red">set</font > 方法
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
    private BookDao bookDao;
    private UserDao userDao;
    public BookDao getBookDao(){
        return new BookDaoImpl();
    }
    public void setUserDao(UserDao u){
        this.userDao = u;
    }
    public void setBookDao(BookDao d){
        System.out.println("BookService method:setBookDao rnings ...");
        this.bookDao = d;
    }
    public void save(){
        System.out.println("BookService rning ...");
        bookDao.save();
        userDao.save();
    }
    @Override
    public void destroy() throws Exception {
        System.out.println("service destroy rnings...");
    }
//    afterPropertiesSet:在属性设置之后
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("service afterProperties rning...");
    }
}
  • 配置中使用 <font style="color:red">property</font > 标签 < font style="color:red">ref</font > 属性注入引用类型对象
<bean id="userDao" class="com.dkx.spring.dao.impl.UserDaoImpl"/>
    <bean id="bookService" class="com.dkx.spring.service.impl.BookServiceImpl">
        <property name="bookDao" ref="bookDao"/>
        <property name="userDao" ref="userDao"/>
    </bean>

image-20230318133539732

# setter 注入 ---- 简单类型

  • 在 bean 中定义引用类型属性并提供可访问的 <font style="color:red">set</font > 方法
public class BookDaoImpl implements BookDao {
    private int connectionNum;
    private String databaseName;
    public void setConnectionNum(int connectionNum) {
        this.connectionNum = connectionNum;
    }
    public void setDatabaseName(String databaseName) {
        this.databaseName = databaseName;
    }
    public void save(){
        System.out.println("BookDao rning..."+connectionNum+","+databaseName);
    }
//    表示 bean 初始化对应的操作
    public void init(){
        System.out.println("init rning...");
    }
//    表示 bean 销毁前对应的操作
    public void destroy(){
        System.out.println("destory rning...");
    }
}
  • 配置中使用 <font style="color:red">property</font > 标签 < font style="color:red">name</font > 使用 set 方法去除 set 的 < font style="color:green"> 方法名称 </font>, 标签 < font style="color:red">value</font > 属性注入简单类型数据
<bean id="bookDao" class="com.dkx.spring.dao.impl.BookDaoImpl">
        <property name="databaseName" value="mysql"/>
        <property name="connectionNum" value="10"/>
    </bean>

image-20230318134651496

# 构造器注入 ---- 简单类型

在 bean 中定义 <font style="color:red"> 有参构造器 </font>

  • BookDaoImpl 实现类
public class BookDaoImpl implements BookDao {
    private int connectionNum;
    private String databaseName;
    public BookDaoImpl(String d,int c){
        this.connectionNum = c;
        this.databaseName = d;
    }
    public void save(){
        System.out.println("bookDaoImpl rning..."+connectionNum+","+databaseName);
    }
}

# 构造器注入 ---- 引用类型

在 bean 中定义 <font style="color:red"> 有参构造器 </font>

  • BookServiceImpl 实现类
public class BookServiceImpl implements BookService {
    private BookDao bookDao;
    private UserDao userDao;
    public BookServiceImpl(BookDao d,UserDao u){
        this.bookDao = d;
        this.userDao = u;
    }
    public void save(){
        System.out.println("bookService rning...");
        userDao.save();
        bookDao.save();
    }
}

在配置文件中使用 <font style="color:red">constructor-arg</font> 标签 < font style="color:red">name</font> 使用构造器中的参数名,<font style="color:red">value</font> 赋值

  • applicationContext.xml 配置文件
<bean id="bookDao" class="com.dkx.spring.dao.impl.BookDaoImpl">
        <constructor-arg name="d" value="mysql"/>
        <constructor-arg name="c" value="10"/>
    </bean>
    <bean id="userDao" class="com.dkx.spring.dao.impl.UserDaoImpl"/>
    <bean id="bookService" class="com.dkx.spring.service.impl.BookServiceImpl">
<!--        这里的 name 指的是 BookServiceImpl 构造器中的形参的名称 -->
        <constructor-arg name="d" ref="bookDao"/>
        <constructor-arg name="u" ref="userDao"/>
    </bean>
  • 测试代码
public void test(){
        ApplicationContext c = new ClassPathXmlApplicationContext("applicationContext.xml");
        BookService service = (BookService) c.getBean("bookService");
        service.save();
    }

Run 结果

image_2023-02-23-16-56-00

  • 配置文件中 constructor-arg 中的 name 使用的是构造器的形参这样耦合度就太高了有以下方式

# 构造器注入 ---- 参数适配 (了解)

# 使用 type 来指定数据类型代替 name 的指定形参

<!--    解决形参名称问题,与形参名不耦合了 -->
    <bean id="bookDao" class="com.dkx.spring.dao.impl.BookDaoImpl">
        <constructor-arg type="java.lang.String" value="mysql"/>
        <constructor-arg type="int" value="10"/>
    </bean>

但是这样如果有相同类型的成员变量呢,又不好办了,看下面解决方案

# 使用 index 来通过索引指定数据类型代替 name 的指定形参和 type 指定数据类型

<!--    解决参数类型重复问题,使用位置来解决参数匹配 -->
    <bean id="bookDao" class="com.dkx.spring.dao.impl.BookDaoImpl">
        <constructor-arg index="0" value="mysql"/>
        <constructor-arg index="1" value="10"/>
    </bean>

# 依赖注入方式选择

  1. 强制依赖使用构造器进行,使用 setter 注入有概率不进行注入导致 null 对象出现

  2. 可选依赖使用 setter 注入进行,灵活性强

  3. Spring 框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨

  4. 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用 setter 注入完成可选依赖的注入

  5. 实际开发过程中还要根据实际情况分析,如果受控对象没有提供 setter 方法就必须使用构造器注入

  6. <font> 自己开发的模块推荐使用 setter 注入 </font>

# 依赖自动装配

  • IoC 容器根据 bean 所依赖的资源在容器中自动查找并注入到 bean 中的过程称为自动装配

  • 自动装配方式

    • <font style="color:red"> 按类型 (常用)</font>
    • 按名称
    • 按 set 方法
    • 不启用自动装配
  • applicationContext.xml

<bean id="userDao1" class="com.dkx.spring.dao.impl.UserDaoImpl"/>
<!--                        autowire: 自动装配
                            - 参数:byType
                                - 按类型装配
                            - 按类型去匹配时必须是唯一的如果有重复的则报错
-->
<!--    <bean id="userService" autowire="byType" class="com.dkx.spring.service.impl.UserServiceImpl"/>-->
<!--                        autowire: 自动装配
                            - 参数:byName
                                - 按名称装配
                            - 跟 userDao,bean 标签中 id 进行匹配,如果有重复的其中一个名称与实现类匹配正确则使用如果没有则报错
-->
<bean id="userService" autowire="byName" class="com.dkx.spring.service.impl.UserServiceImpl"/>

# 依赖自动装配特征

  • 自动装配用于引用类型依赖注入,不能对简单类型进行操作

  • 使用按类型装配时 (byType) 必须保障容器中相同类型的 bean 唯一,推荐使用

  • 使用按名称装配时 (byName) 必须保障容器中具有指定名称的 bean, 因变量名与配置耦合,不推荐使用

  • 自动装配优先级低于 setter 注入与构造器注入,同时出现时自动装配配置失效

# 集合注入

  • BookDaoImpl 类
public class BookDaoImpl implements BookDao {
    private int[] array;
    private List<String> list;
    private Set<String> set;
    private Map<String,String> map;
    private Properties properties;
    public int[] getArray() {
        return array;
    }
    public void setArray(int[] array) {
        this.array = array;
    }
    public List<String> getList() {
        return list;
    }
    public void setList(List<String> list) {
        this.list = list;
    }
    public Set<String> getSet() {
        return set;
    }
    public void setSet(Set<String> set) {
        this.set = set;
    }
    public Map<String, String> getMap() {
        return map;
    }
    public void setMap(Map<String, String> map) {
        this.map = map;
    }
    public Properties getProperties() {
        return properties;
    }
    public void setProperties(Properties properties) {
        this.properties = properties;
    }
    public void save(){
        System.out.println("BookDaoImpl save rning...");
        System.out.println("遍历数组:"+ Arrays.toString(array));
        System.out.println("遍历List:"+list);
        System.out.println("遍历Set:"+set);
        System.out.println("遍历Map:"+map);
        System.out.println("遍历Properties:"+properties);
    }
}

# 数组

<property name="array">
<!--            表名要注入一个 array 数组 -->
            <array>
<!--                设置值 -->
                <value>100</value>
                <value>200</value>
                <value>300</value>
            </array>
        </property>

# List

<property name="list">
            <list>
                <value>list刘桑</value>
                <value>list张三</value>
                <value>list李四</value>
            </list>
        </property>

# Set

<property name="set">
            <set>
                <value>set刘桑</value>
                <value>set张三</value>
                <value>set李四</value>
            </set>
        </property>

# Map

<property name="map">
            <map>
                <entry key="刘桑" value="故乡的樱花又开啦"/>
                <entry key="张三" value="法外狂徒"/>
                <entry key="李四" value="无名之辈"/>
            </map>
        </property>

# Properties

<property name="properties">
            <props>
                <prop key="a">aaa</prop>
                <prop key="b">bbb</prop>
                <prop key="c">ccc</prop>
            </props>
        </property>
  • applicationContext.xml 整体配置
<?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 id="bookDao" class="com.dkx.spring.dao.impl.BookDaoImpl">
        <property name="array">
<!--            表名要注入一个 array 数组 -->
            <array>
<!--                设置值 -->
                <value>100</value>
                <value>200</value>
                <value>300</value>
            </array>
        </property>
        <property name="list">
            <list>
                <value>list刘桑</value>
                <value>list张三</value>
                <value>list李四</value>
            </list>
        </property>
        <property name="set">
            <set>
                <value>set刘桑</value>
                <value>set张三</value>
                <value>set李四</value>
            </set>
        </property>
        <property name="map">
            <map>
                <entry key="刘桑" value="故乡的樱花又开啦"/>
                <entry key="张三" value="法外狂徒"/>
                <entry key="李四" value="无名之辈"/>
            </map>
        </property>
        <property name="properties">
            <props>
                <prop key="a">aaa</prop>
                <prop key="b">bbb</prop>
                <prop key="c">ccc</prop>
            </props>
        </property>
    </bean>
</beans>

# 容器

# 创建容器

  • 方式一:类路径加载配置文件
ApplicationContext c = new ClassPathXmlApplicationContext("applicationContext.xml");
  • 方式二:文件路径加载配置文件
ApplicationContext c = new FileSystemXmlApplicationContext("E:\\apache-maven-3.6.3\\maven-work-space\\space-idea\\maven-spring-Demo06\\src\\main\\resources\\applicationContext.xml");

加载多个配置文件

  • 不管是方式一,还是方式二都可以使用加载多个配置文件
ApplicationContext c = new ClassPathXmlApplicationContext("bean1.xml","bean2.xml");

# 获取 bean

  • 方式一:使用 bean 名称获取
BookDao dao = (BookDao) c.getBean("bookDao");
  • 方式二:使用 bean 名称获取并指定类型
BookDao dao = c.getBean("bookDao",BookDao.class);
  • 方式三:使用 bean 类型获取
BookDao dao = c.getBean(BookDao.class);

# 容器类层次结构

  • 容器类层次结构图

image_2023-02-24-16-19-21

# BeanFactory 初始化

  • 类路径加载配置文件
public void test6(){
        Resource resource = new ClassPathResource("applicationContext.xml");
        BeanFactory bean = new XmlBeanFactory(resource);
        BookDao dao = bean.getBean("bookDao",BookDao.class);
        dao.save();
    }
  • BeanFactory 创建完毕后,所有的 bean 均为延迟加载

# 容器相关

  • BeanFactory 是 IoC 容器的顶层接口,初始化 BeanFactory 对象时,加载的 bean 延迟加载

  • ApplicationContext 接口是 Spring 容器的核心接口,初始化时 bean 立即加载

  • ApplicationContext 接口提供基础的 bean 操作相关方法,通过其它接口扩展其功能

  • ApplicationContext 接口常用初始化类

    • ClassPathXmlApplicationContext
    • FileSystemXmlApplicationContext

# bean 相关

image_2023-02-24-17-05-28

# 依赖注入相关

image_2023-02-24-17-51-08