Spring - 1( 相关了解 + IOC 容器 + DI 依赖注入 + )

发布时间 2023-09-16 19:12:32作者: 朱呀朱~

Spring - 1

了解

  • spring官网
  • Spring 家族三巨头:
    • Spring Framework:框架,spring 所有技术都是依赖此框架
    • Spring Boot:在简化开发的基础上加快速度,主提速
    • Spring Cloud:分布式开发相关技术

Spring Framework 系统架构

  • Spring Framework 是 Spring 家族中其他框架的底层基础
  • Spring4.0 根据 JDK 的版本升级对个别 API 进行了调整
  • Spring5.0 已经全面支持 JDK8

系统架构图

image-20230818152434238

  • 核心层

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

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

    • Aspects:AOP是思想,Aspects 是对 AOP 思想的具体实现

  • 数据层

    • Data Access:数据访问,Spring 全家桶中有对数据访问的具体实现技术

    • Data Integration:数据集成,Spring 支持整合其他的数据层解决方案,比如 Mybatis

    • Transactions:事务,Spring 中事务管理是 Spring AOP 的一个具体实现,也是后期学习的重点内容

  • Web层

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

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

一、核心容器

相关概念

  • IOC、DI 思想
  • IOC 容器
  • Bean

存在问题

  • 一开始都是 xxxServiceImpl implements xxxService { 接口=new Impl实现类 }xxxDaoImpl implements xxxDao
    • 想更换使用的 Dao 层方法,就要重新 new ( 重新编译、测试、部署、发布 ...... ),这样耦合度偏高
    • 想降低耦合度去掉等于号 ( = ) 后面的内容即可,但会空指针异常

解决引出 IOC

  • 使用对象时,在程序中不主动使用 new 产生对象,转换为由外部提供对象 ———— 即:IOC ( Inversion of Control ) 控制反转

  • IOC 控制反转:

    • 使用对象时,由主动 new 产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转
  • Spring 和 IOC 之间的关系

    • Spring 技术对 IOC 思想进行了实现
    • Spring 提供了一个容器,称为 IOC 容器 ( 架构图中的核心容器 ),用来充当 IOC 思想中的 " 外部 "
    • IOC 思想中的外部 ( 别人 ) 指的就是 Spring 的 IOC 容器
  • IOC 容器的作用以及内部存放是

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

仍存在问题并引出 DI

  • 但 IOC 容器中创建好 service 和 dao 对象后,service 仍不能运行,因为要依赖对应的 dao 对象
    • 所有就需要把 dao 对象交给 service,也就是说要绑定 service 和 dao 对象之间的关系
    • 这个时候就引出了 ———— DI ( Dependency Injection ) 依赖注入
  • DI ( Dependency Injection ) 依赖注入
    • 在容器中建立 bean 与 bean 之间的依赖关系的整个过程,称为依赖注入
      • 业务层要用数据层的类对象,以前是自己 new 的
      • 现在自己不 new 了,靠 别人 ( 外部其实指的就是 IOC 容器 ) 来注入进来
      • 这种思想就是依赖注入

完成目标:充分解耦

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

最终结果

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

IOC 入门案例

分析

  • Spring 是使用容器来管理 bean 对象的

    • 主要管理项目中所使用到的类对象,比如 Service 和 Dao
  • 如何将被管理的对象告知 IOC 容器

    • 使用配置文件的方式
  • 被管理的对象交给 IOC 容器,那如何获取到 IOC 容器

    • Spring 框架提供相应的接口
  • IOC 容器得到后,如何从容器中获取 bean

    • 调用 Spring 框架提供对应接口中的方法
  • 使用 Spring 导入哪些坐标

    • 用别人的东西,就需要在 pom.xml 添加对应的依赖

实现

  1. 创建 Maven 的 java 项目

  2. pom.xml 添加 Spring 的依赖 jar 包

    • 要先加入依赖才能创建 Spring 的配置文件

      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-context</artifactId>
          <version>5.0.7.RELEASE</version>
          <!--  版本自选  -->
      </dependency>
      
  3. 创建 BookService,BookServiceImpl,BookDao 和 BookDaoImpl 四个类 ( 此时 dao 和 service 还暂时是 new 的老方法 )

    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. resources 下添加 spring 配置文件,并完成 bean 的配置

    image-20230818160650518

    • 自定义命名:applicationContext.xml

      <bean id="bookDao" class="com.qut.dao.impl.BookDaoImpl"/>
      <bean id="bookService" class="com.qut.service.impl.BookServiceImpl"/>
      
    • 注意:bean 定义时 id 属性在同一个上下文 ( 配置文件 ) 中不能重复

  5. 使用 Spring 提供的接口完成 IOC 容器的创建,新建 App 类的 main 方法中:

    public class App {
        public static void main(String[] args) {
            //获取IOC容器
    		ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); 
        }
    }
    
  6. 从容器中获取对象进行方法调用

    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();
        }
    }
    
    • 运行结果:

      book service save ...
      book dao save ...
      

DI 入门案例

分析

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

    • DI 的入门案例要依赖于前面 IOC 的入门案例
  • Service 中使用 new 形式创建的 Dao 对象

    • 需要删除掉 new,最终要使用 IOC 容器中的 bean 对象
  • Service 中需要的 Dao 对象如何进入到 Service中

    • 在 Service 中提供方法,让 Spring 的 IOC 容器可以通过该方法传入 bean 对象
  • Service 与 Dao 间的关系的描述

    • 使用配置文件

实现

  1. 删除业务层中使用 new 的方式创建的 dao 对象

    • ServiceImpl 中只写前部分:

      private BookDao bookDao;
      
  2. 在业务层提供 BookDao 的 setter 方法

    • ServiceImpl 中:

      private BookDao bookDao;
      // ......
      
      //提供上面bookDao的set获取方法(就像属性的setter方法一样的获取方式)
      //set + 下面xml中name所指bookDao的首字母大写
      public void setBookDao(BookDao bookDao) {
          this.bookDao = bookDao;
      }
      public void yyy() {
          //......
          bookDao.yyy();
          //......
      }
      
  3. 在配置文件中添加依赖注入的配置

    • applicationContext.xml 中:

      <bean id="bookDao" class="com.qut.dao.impl.BookDaoImpl"/>
      
      <bean id="bookService" class="com.qut.service.impl.BookServiceImpl">
          <!--配置server与dao的关系-->
          <!--property标签表示配置当前bean的属性
                          name属性表示配置哪一个具体的属性
                          ref属性表示参照哪一个bean  -->
          <property name="bookDao" ref="bookDao"/>
      </bean>
      
      • name="bookDao":中的 bookDao 是让 Spring 的 IOC 容器在获取到名称后,将首字母大写后前面加 set 找对应的 setBookDao() 方法进行对象注入 ( 即第二步 public void setBookDao 中后面的 BookDao )
      • ref="bookDao":中 bookDao 是让 Spring 能在 IOC 容器中找到 id 为 bookDao 的 Bean 对象给 bookService 进行注入
      • ctrl 左键点击可见
  4. 运行程序调用方法

    • 运行结果同上

      book service save ...
      book dao save ...
      

IOC 相关内容

bean 配置

id、class 基础配置

  • id:bean 的 id,使用容器可以通过 id 值获取对应的 bean,在一个容器中 id 值唯一
  • class:bean 的类型,即配置的 bean 的全路径类名
    • 注意:class 属性不能写接口,如 BookDao 的类全名,因为接口是没办法创建对象的

name 别名配置

  • 多个人的 id 起名习惯不同,所以就出现了别名,即一个 bean 有多个名字
  • 配置文件 xml 中的 bean 标签除了 id 外还可以取别名:
    • 标签加一个 name 属性指定别名
    • 别名可以有多个,用逗号、分号或空格隔开
    • 不仅在 main 方法中调用可以别名,service 的 bean 标签的 ref 指向也可以是别名 ( 建议还是用 id )
  • 获取 bean 无论是通过 id 还是 name 获取,如果无法获取到,将抛出异常 NoSuchBeanDefinitionException

scope 作用范围

  • bean 标签的 scope 属性有两个值:singleton、prototype

    • 默认是单例 singleton
    • prototype 是非单例
  • 更改 main 方法中的调用方法为

    • ( 打印对象地址 )

      System.out.println(bookDao1);
      System.out.println(bookDao2);
      
    • 单例的话两个打印的地址相同,非单例的话两个地址不同

思考
  • 为什么 bean 默认为单例
    • bean 为单例的意思是在 Spring 的 IOC 容器中只会有该类的一个对象
    • bean 对象只有一个就避免了对象的频繁创建与销毁,达到了 bean 对象的复用,性能高
  • bean 在容器中是单例的,会不会产生线程安全问题
    • 如果对象是有状态对象,即该对象有成员变量可以用来存储数据的,
      • 因为所有请求线程共用一个 bean 对象,所以会存在线程安全问题。
    • 如果对象是无状态对象,即该对象没有成员变量没有进行数据存储的,
      • 因方法中的局部变量在方法调用完成后会被销毁,所以不会存在线程安全问题。
  • 哪些 bean 对象适合交给容器进行管理 ( 造一次就行,可以反复使用 )
    • 表现层对象
    • 业务层对象
    • 数据层对象
    • 工具对象
  • 哪些 bean 对象不适合交给容器进行管理
    • 封装实例的域对象 ( 有状态的 )
      • 因为会引发线程安全问题,所以不适合。

bean 实例化

构造方法实例化

  • bean 本质上就是对象,对象在 new 的时候会使用构造方法完成,那创建 bean 也是使用构造方法完成的
    • 就是前面讲的解耦的 IOC + DI 的方法
  • 私有的构造方法也可以创建出对象 ( 体现了 Spring 底层用的是反射 )
  • 调用的是无参的构造方法,有参会报错

静态工厂实例化 ( 了解 )

  • bean:

    <bean id="orderDao" class="com.qut.factory.OrderDaoFactory" factory-method="getOrderDao"/>
    
    • class:工厂类的类全名
    • factory-mehod:具体工厂类中创建对象的方法名 ( 静态的 )
  • 在工厂的静态方法中,除了 new 对象还可以做其他的一些业务操作,而这些操作又必不可少

    • 如:

      public class OrderDaoFactory {
          public static OrderDao getOrderDao(){
              System.out.println("factory setup....");//一些必要的业务操作
              return new OrderDaoImpl();
          }
      }
      
    • 之前 new 对象的方式就无法添加其他的业务内容

  • 这种方式一般是用来兼容早期的一些老系统,了解为主

实例工厂实例化 - 旧 ( 了解 )

  • 实例工厂与 FactoryBean

  • Bean:

    <bean id="userFactory" class="com.qut.factory.UserDaoFactory"/>
    
    <bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>
    
    • factory-bean 就是指这个工厂的实例在哪 —— 上面的工厂 bean 的 id
    • factory-method 指这个工厂用哪个方法 new 的对象
  • main 中和构造方法实例化的相同

  • 配置的过程还是比较复杂,了解即可

    • 工厂 bean 纯是为了配合使用
    • 所以 Spring 为了简化这种配置方式就提供了一种叫 FactoryBean 的方式来简化开发

实例化工厂实例化 - 简化改良

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

    • 其中的 FactoryBean 的泛型就是你想造什么对象就写什么对象

      public class UserDaoFactoryBean implements FactoryBean<UserDao> {
          //代替原始实例工厂中创建对象的方法(即方法名指定了统一叫getObject)
          public UserDao getObject() throws Exception {
              return new UserDaoImpl();
          }
          //返回所创建类的Class对象
          public Class<?> getObjectType() {
              return UserDao.class;
          }
      }
      
  • 在 Spring 的配置文件中进行配置

    <bean id="userDao" class="com.qut.factory.UserDaoFactoryBean"/>
    
  • AppForInstanceUser运行类不用做任何修改,直接运行 ( 正常运行不报错 )

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

方法拓展
  • 查看源码会发现,FactoryBean 接口其实会有三个方法,分别是:
T getObject() throws Exception;

Class<?> getObjectType();

default boolean isSingleton() {
		return true;
}
  • 方法一:getObject(),被重写后,在方法中进行对象的创建并返回

  • 方法二:getObjectType(),被重写后,主要返回的是被创建类的 Class 对象

  • 方法三:没有被重写,因为它已经给了默认值,从方法名中可以看出其作用是设置对象是否为单例,默认 true 单例

    • 想要改为非单例只需要将 isSingleton() 方法进行重写,修改 true 返回为 false 即可
    • 一般都是单例,所以一般都不写此方法

bean 的生命周期

相关了解

  • 什么是生命周期
    • 从创建到消亡的完整过程,例如人从出生到死亡的整个过程就是一个生命周期
  • bean 生命周期是什么
    • bean 对象从创建到销毁的整体过程
  • bean 生命周期控制是什么
    • 在 bean 创建以后,到销毁之前做一些事情
    • 即下述的两种 init 与 destroy 等

实例操作

  • 在起初 IOC + DI 环境中来为 BookDao 添加生命周期的控制方法,具体的控制有两个阶段:

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

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

      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("destroy...");
          }
      }
      
  2. 配置生命周期

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

      <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" init-method="init" destroy-method="destroy"/>
      
  3. 运行结果

    init...
    book dao save ...
    
  4. init 方法执行了,但是 destroy 方法未执行

    • 因为 Spring 的 IOC 容器是运行在 JVM 中
      • 运行 main 方法后,JVM 启动,Spring 加载配置文件生成 IOC 容器,从容器获取 bean 对象,然后调方法执行
      • main 方法执行完后,JVM 退出,这个时候 IOC 容器中的 bean 还没有来得及销毁就已经结束了,所以没有调用对应的 destroy 方法
销毁操作方法一:close()
  • 使用 close() 在退出虚拟机之前关闭此容器

  • 但 ApplicationContext 接口中不具有 close() 方法,但是 ClassPathXmlApplicationContext 具有,所以想要关闭就将语句改为 ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

    • 然后就可以调用 ctx.close();
    • 此方法必须放在 main 最后的地方
  • 运行结果:

    init...
    book dao save ...
    destroy...
    
销毁操作方法二:注册钩子关闭容器
  • 在容器未关闭之前,提前设置好回调函数,让 JVM 在退出之前回调此函数来关闭容器

  • 调用 ctx 的 registerShutdownHook() 方法,仍是 ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

    • registerShutdownHook 在 ApplicationContext 中也没有
    • 此关闭钩子在任何时间都可以,不一定要放在 main 最后的位置
  • 运行结果相同

比较

  • 上两种方法其实都不怎么会用到
  • 相同点:这两种都能用来关闭容器
  • 不同点:close() 是在调用的时候关闭,registerShutdownHook() 是在 JVM 退出前调用关闭

Spring 提供接口控制生命周期

  • Spring 提供了两个接口来完成生命周期的控制,好处是可以不用在 xml 配置里的 bean 标签写 init-methoddestroy-method

  • 直接在 BookServiceImpl 完成这两个接口的使用

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

    public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
        private BookDao bookDao;
        public void setBookDao(BookDao bookDao) {
            System.out.println("属性设置...");
            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");
        } // afterPropertiesSet:属性设置之后
    }
    
  • 运行结果

    init...
    属性设置...
    service init
    book dao save ...
    service destroy
    destroy...
    
  • 方法了解即可

生命周期小结

  • 对于 bean 的生命周期控制在 bean 的整个生命周期中所处的位置如下:
  • 初始化容器
    • 创建对象 ( 内存分配 )
    • 执行构造方法
    • 执行属性注入 ( set 操作,即上述的属性设置部分 )
    • 执行 bean 初始化方法
  • 使用 bean
    • 执行业务操作
  • 关闭 / 销毁容器
    • 执行 bean 销毁方法

DI 依赖注入相关内容

分析

  • 向一个类中传递数据的方式有几种
    • 普通方法 ( set 方法 )
    • 构造方法
  • 依赖注入描述了在容器中建立 bean 与 bean 之间的依赖关系的过程,如果 bean 运行需要的是数字或字符串呢
    • 引用类型
    • 简单类型 ( 基本数据类型与 String )
  • 基于上面这些知识点,Spring 提供了两种注入方式:

    • setter 注入

      • 简单类型
      • 引用类型 ( 先前已讲的 xxServiceImpl 内 setxxDao 方法,bean 里用 ref 引用 )
    • 构造器注入

      • 简单类型
      • 引用类型

setter 注入

引用类型

  • 本文前面 DI 入门案例中的实现所讲:

    1. 在 BookServiceImpl 中声明 userDao 属性

    2. 为 userDao 属性提供 setter 方法 ( service 方法里直接 dao属性.方法 使用即可 )

    3. 在配置文件中使用 property 标签注入

  • 在一个 xxServiceImpl 中可以有多个 dao、多个对应 dao 的 setter 方法

    • 对应的,xml 里就得有多个 bean,service 的 <property ..../> 也得写多个

简单数据类型

  • 类似上述引用类型方式:

    1. 在 BookDaoImpl 类中声明对应的简单数据类型的属性

    2. 为这些属性提供对应的 setter 方法

    3. 在 applicationContext.xml 中配置

  • 声明属性并提供 setter 方法

    public class BookDaoImpl implements BookDao {
    
        private String databaseName;
        private int connectionNum;
    
        public void setConnectionNum(int connectionNum) {
            this.connectionNum = connectionNum;
        }
    
        public void setDatabaseName(String databaseName) {
            this.databaseName = databaseName;
        }
    
        public void save() {
            System.out.println("book dao save ..."+databaseName+","+connectionNum);
        }
    }
    
  • 配置文件中进行注入配置 ( 仍是 property 标签,但 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">
    
        <bean id="bookDao" class="com.qut.dao.impl.BookDaoImpl">
            <property name="databaseName" value="mysql"/>
            <property name="connectionNum" value="10"/>
        </bean>
        <bean id="userDao" class="com.qut.dao.impl.UserDaoImpl"/>
        
        <bean id="bookService" class="com.qut.service.impl.BookServiceImpl">
            <property name="bookDao" ref="bookDao"/>
            <property name="userDao" ref="userDao"/>
        </bean>
    </beans>
    
    • value:后面跟的是简单数据类型,对于参数类型,Spring 在注入的时候会自动转换,但是不能写成 <property name="connectionNum" value="abc"/>
    • 这样的话,spring 在将 abc 转换成 int 类型的时候就会报错
      • connectionNum 在 service 声明为了 int 类型
  • 运行结果

    book dao save ...mysql,10
    

构造器注入

多个引用数据类型

  • 提供多个属性的构造函数

    public class BookServiceImpl implements BookService{
        private BookDao bookDao;
        private UserDao userDao;
    
        public BookServiceImpl(BookDao bookDao,UserDao userDao) {
            this.bookDao = bookDao;
            this.userDao = userDao;
        }
    
        public void save() {
            System.out.println("book service save ...");
            bookDao.save();
            userDao.save();
            // 都调用
        }
    }
    
    • 原为 setBookDao() 方法的方式
  • 配置文件中配置多参数注入

    <?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.itheima.dao.impl.BookDaoImpl"/>
        <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
        
        <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
            <constructor-arg name="bookDao" ref="bookDao"/>
            <constructor-arg name="userDao" ref="userDao"/>
        </bean>
    </beans>
    
    • 上述两个 <contructor-arg> 的配置顺序可以任意
    • 此处的 name 是指形参的名称,即 public BookServiceImpl(BookDao bookDao) 这个形参列表里最后面的 bookDao,而不是初始化属性的命名
  • 运行程序

    book service save ...
    book dao save ...
    user dao save ...
    

简单数据类型

标准书写 — 耦合高
  • 添加多个简单属性并提供构造方法

    public class BookDaoImpl implements BookDao {
        private String databaseName;
        private int connectionNum;
    
        public BookDaoImpl(String databaseName, int connectionNum) {
            this.databaseName = databaseName;
            this.connectionNum = connectionNum;
        }
    
        public void save() {
            System.out.println("book dao save ..."+databaseName+","+connectionNum);
        }
    }
    
  • 配置完成多个属性构造器注入

    <?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.itheima.dao.impl.BookDaoImpl">
            <constructor-arg name="databaseName" value="mysql"/>
            <constructor-arg name="connectionNum" value="666"/>
        </bean>
        
        
        <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
        <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
            <constructor-arg name="bookDao" ref="bookDao"/>
            <constructor-arg name="userDao" ref="userDao"/>
        </bean>
    </beans>
    
    • 同上 <contructor-arg> 的配置顺序可以任意
    • 引用类型是 ref,基本数据类型是 value
  • 运行程序

    book service save ...
    book dao save ... mysql,666
    user dao save ...
    
  • 但是这种方法在 BookDaoImpl 构造器中形参列表中的形参若是做了更改,在 xml 配置文件中 constructor-argname 属性值也要更改,配置文件和代码部分的耦合度较高,

特殊书写 — 了解为主
  • 解决方法一:删除name属性,添加type属性,按照类型注入

    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
        <constructor-arg type="int" value="10"/>
        <constructor-arg type="java.lang.String" value="mysql"/>
    </bean>
    
    • 但是如果构造方法参数中有类型相同的参数,这种方式就不太好实现了
  • 解决方法二:删除 type 属性,添加 index 属性,按照索引下标注入,下标从 0 开始

    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
        <constructor-arg index="1" value="100"/>
        <constructor-arg index="0" value="mysql"/>
    </bean>
    
    • 但是如果构造方法参数顺序发生变化后,这种方式又带来了耦合问题

上述两种依赖注入方式选择

  • 强制依赖使用构造器进行,使用 setter 注入有概率不进行注入导致 null 对象出现
    • 强制依赖指对象在创建的过程中必须要注入指定的参数,因为不存在的话连对象都造不出来,即强制型
  • 可选依赖使用 setter 注入进行,灵活性强
    • 可选依赖指对象在创建过程中注入的参数可有可无
  • Spring 框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨
    • 框架的编写要求严谨
  • 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用 setter 注入完成可选依赖的注入
    • 可以,但一般不这样写
  • 实际开发过程中还要根据实际情况分析,如果受控对象没有提供 setter 方法就必须使用构造器注入
    • 自己管理的代码部分不一定是自己写的
  • 自己开发的模块推荐使用 setter 注入

自动装配

  • IOC 容器根据 bean 所依赖的资源在容器中自动查找并注入到 bean 中的过程称为自动装配
  • 自动装配方式
    • 按类型 ( 常用 )
    • 按名称
    • 按构造方法
    • 不启用自动装配

实例演示

  • BookServiceImpl 的 setBookDao() 方法做保留

    public class BookServiceImpl implements BookService{
        private BookDao bookDao;
    
        public void setBookDao(BookDao bookDao) {
            this.bookDao = bookDao;
        }
    
        public void save() {
            System.out.println("book service save ...");
            bookDao.save();
        }
    }
    
  • 仅更改 xml 配置文件:

    • <property> 标签删除
    • <bean> 标签中添加 autowire 属性
    <?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">
    
        <!-- 按类型的话这里可以不写id(但是按名称的话必须要写) -->
        <bean class="com.itheima.dao.impl.BookDaoImpl"/>
        
        <!--autowire属性:开启自动装配,通常使用按类型装配-->
        <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" autowire="byType"/>
    
    </beans>
    
    • 因为 BookServiceImpl 中要 BookDao 类型的属性,所以 xml 中要有满足此 BookDao 接口的 bean ( 因为是按类型装配 )
      • 但若是有两个不同 id 但都是 BookDao 接口就会报错 ( 不唯一类型会报错 )
      • 但只有一个接口却没 id 也可以,不会报错
    • 但不用 byType 而用 byName 就可解决,即让 bean 标签的 id 与 BookServiceImpl 的 setBookDao 方法的去掉 set 首字母小写的 bookDao 对应即可

特征

  • 自动装配用于引用类型依赖注入,不能对简单类型进行操作
  • 使用按类型装配时 ( byType ) 必须保障容器中相同类型的 bean 唯一,推荐使用
  • 使用按名称装配时 ( byName ) 必须保障容器中具有指定名称的 bean,因变量名与配置耦合,不推荐使用
  • 自动装配优先级低于 setter 注入与构造器注入,同时出现时自动装配配置失效

集合注入

  • 常见的集合类型

    • 数组
    • List
    • Set
    • Map
    • Properties
  • 项目中添加添加 BookDao、BookDaoImpl 类

    public interface BookDao {
        public void save();
    }
    
    // ——————————————————————————————————
    
    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 void save() {
            System.out.println("book dao save ...");
    
            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);
        }
    	//setter....方法省略不展示
        
    }
    
  • 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.itheima.dao.impl.BookDaoImpl">
            <!-- property注入所需类型数据 -->
        </bean>
    </beans>
    
  • 运行类,main 里加载 Spring 的 IOC 容器并获取 bean 对象

    public class AppForDICollection {
        public static void main( String[] args ) {
            ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
            BookDao bookDao = (BookDao) ctx.getBean("bookDao");
            bookDao.save();
        }
    }
    

注入数组类型数据

<property name="array">
    <array>
        <value>100</value>
        <value>200</value>
        <value>300</value>
    </array>
</property>

注入List类型数据

<property name="list">
    <list>
        <value>cast</value>
        <value>xxx</value>
        <value>asc</value>
        <value>wsad</value>
    </list>
</property>

注入Set类型数据

<property name="set">
    <set>
        <value>cast</value>
        <value>xxx</value>
        <value>asc</value>
        <value>asc</value>
    </set>
</property>
  • 重复的 asc 会只留一个

注入Map类型数据

<property name="map">
    <map>
        <entry key="country" value="china"/>
        <entry key="province" value="shandong"/>
        <entry key="city" value="zaozhuang"/>
    </map>
</property>

注入Properties类型数据

<property name="properties">
    <props>
        <prop key="country">china</prop>
        <prop key="province">shandong</prop>
        <prop key="city">zaozhuang</prop>
    </props>
</property>

说明

  • property 标签表示 setter 方式注入,构造方式注入 constructor-arg 标签内部也可以写 <array><list><set><map><props> 标签
  • List 的底层也是通过数组实现的,所以 <list><array> 标签是可以混用
  • 集合中要添加引用类型,只需要把 <value> 标签改成 <ref> 标签,这种方式用的比较少