Spring学习笔记 - 第一章 - IoC(控制反转)、IoC容器、Bean的实例化与生命周期、DI(依赖注入)

前置内容:

Spring 学习笔记全系列传送门:

1、学习概述

  • Spring的优点:
    • 简化开发
      • IOC
      • AOP
        • 事务处理
    • 框架整合
      • MyBatis
      • MyBatis-plus
      • Struts
      • Struts2
      • Hibernate
      • ......
  • 主要的学习内容
    • IOC
    • 整合Mybatis(IOC的具体应用)
    • AOP
    • 声明式事务(AOP的具体应用)
  • 学习重心
    • Spring的思想
    • Spring的基础操作
    • 案例练习

2、Spring相关概念

2.1 Spring概述

官网:https://spring.io

2.1.1 Spring能做的工作

  • Web
  • 微服务
  • 分布式
  • ......

2.1.2 重点学习的内容

  • Spring Framework:是Spring中最早最核心的技术,也是所有其他技术的基础。
  • SpringBoot:简化开发,而SpringBoot是来帮助Spring在简化的基础上能更快速进行开发。
  • SpringCloud:用来做分布式之微服务架构的相关开发。
  • SpringData、SpringSecurity 等目前也是流行的技术

2.1.3 Spring发展史

随着时间推移,版本不断更新维护,目前最新的是Spring5

  • Spring1.0是纯配置文件开发
  • Spring2.0为了简化开发引入了注解开发,此时是配置文件加注解的开发方式
  • Spring3.0已经可以进行纯注解开发,使开发效率大幅提升
  • Spring4.0根据JDK的版本升级对个别API进行了调整
  • Spring5.0已经全面支持JDK8,现在Spring最新的是5系列

2.2 Spring系统架构

2.2.1 系统架构及其图示

  • 说明

    • 核心层
      • 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来完成单元测试和集成测试
  • 图示 ( Spring Framework 4.x )

    Spring Framework 4.x 系统架构图示

2.2.2 课程学习路线

  1. 核心容器

    • 核心概念( IoC / DI )
    • 容器基本操作
  2. AOP

    • 核心概念
    • AOP基础操作
    • AOP使用开发
  3. 事务

    • 事务实用开发
  4. 整合

    • 整合数据层技术MyBatis
  5. 家族

    • SpringMVC
    • SpringBoot
    • SpringCloud

2.3 Spring核心概念

2.3.1 目前项目中的问题

  • 存在问题:按照原先的JavaWeb开发,耦合度偏高

    如:Dao层更改了内容,将使用新的函数进行数据处理,此时Service层也要修改,并且需要重新编译

  • 解决方案

    • 使用对象时,避免主动new创建对象,转为由外部提供对象

2.3.2 IOC、IOC容器、Bean、DI —— 充分解耦

  • IOC(Inversion of Control)控制反转:使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想(对象的创建控制权的转移)称为控制反转。

  • IoC容器:Spring提供了一个容器,称为IOC容器,用来充当IOC思想中的"外部"

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

  • DI(Dependency Injection)依赖注入:IoC容器为了解决Bean对象之间的依赖关系而自动建立bean对象绑定的过程

    如:Service需要依赖Dao,IoC容器会将两个bean对象进行绑定

3、入门案例

3.1 IOC入门案例

3.1.1 入门案例思路分析

3.1.2 入门案例代码实现

  1. 创建Maven项目

  2. 添加Spring依赖jar包

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
    
  3. 添加需要的类

    • Dao

      • 接口

        package priv.dandelion.dao;
        
        public interface BookDao {
            public void save();
        }
        
      • 实现类

        package priv.dandelion.dao.Impl;
        
        import priv.dandelion.dao.BookDao;
        
        public class BookDaoImpl implements BookDao {
            public void save() {
                System.out.println("book dao save ...");
            }
        }
        
    • Service

      • 接口

        package priv.dandelion.service;
        
        public interface BookService {
            public void save();
        }
        
      • 实现类

        package priv.dandelion.service.Impl;
        
        import priv.dandelion.dao.BookDao;
        import priv.dandelion.dao.Impl.BookDaoImpl;
        import priv.dandelion.service.BookService;
        
        public class BookServiceImpl implements BookService {
            private BookDao bookDao = new BookDaoImpl();
        
            public void save() {
                System.out.println("book service save ...");
                bookDao.save();
            }
        }
        
    • Main

      package priv.dandelion;
      
      import priv.dandelion.service.BookService;
      import priv.dandelion.service.Impl.BookServiceImpl;
      
      public class App {
          public static void main(String[] args) {
              BookService bookService = new BookServiceImpl();
              bookService.save();
              /**
               * 控制台输出:
               * book service save ...
               * book dao save ...
               */
          }
      }
      
  4. resources目录下添加Spring配置文件:applicationContext.xml,在配置文件中完成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">
    
        <!-- 配置Bean-->
        <!--
            id : 给Bean命名
            class : 给Bean定义类型
        -->
        <bean id="bookDao" class="priv.dandelion.dao.Impl.BookDaoImpl"/>
        <bean id="bookService" class="priv.dandelion.service.Impl.BookServiceImpl"/>
    
    </beans>
    
  5. 获取IOC容器,从容器中获取对象进行方法调用

    package priv.dandelion;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import priv.dandelion.service.BookService;
    
    public class App2 {
        public static void main(String[] args) {
            // 获取IOC容器
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            // 获取Bean
            BookService bookService = (BookService) context.getBean("bookService");
            bookService.save();
            /**
             * 控制台输出:
             * book service save ...
             * book dao save ...
             */
        }
    }
    

3.2 DI ( 依赖注入 )入门案例

IoC入门案例中,Service层仍然使用了new的方式创建对象,耦合度依然高

3.2.1 入门案例思路分析

3.2.2 入门案例代码实现

  • 修改被依赖类的成员属性中创建对象部分,并为其添加set方法

    package priv.dandelion.service.Impl;
    
    import priv.dandelion.dao.BookDao;
    import priv.dandelion.service.BookService;
    
    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;
        }
    }
    
  • 修改配置文件,配置其和被依赖类的关系

    <?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 : 给Bean命名
            class : 给Bean定义类型
        -->
        <bean id="bookDao" class="priv.dandelion.dao.Impl.BookDaoImpl"/>
        <bean id="bookService" class="priv.dandelion.service.Impl.BookServiceImpl">
            <!-- 配置Service 与Dao的关系-->
            <!--
                property标签 : 表示配置当前Bean的属性
                name属性 : 表示配置参照哪一个具体的属性名称,注意需要实现其set方法
                ref属性 : 表示参照(引用)哪一个Bean
            -->
            <property name="bookDao" ref="bookDao"/>
        </bean>
    
    </beans>
    

4、IOC相关内容

4.1 Bean基础配置

4.1.1 bean基础配置( id 与 class )

类别 描述
名称 bean
类型 标签
所属 beans标签
功能 定义 Spring 核心容器管理的对象
格式 <beans> <bean/> <bean></bean> <beans>
属性列表 id:bean 的 id ,使用容器可以通过 id 值获取对应的 bean ,在一个容器中id唯一
class:bean 的类型,即配置的 bean 的全路径名 (不能是接口,接口不能实例化)
示例 <bean id="bookDao" class="priv.dandelion.dao.Impl.BookDaoImpl"/>
<bean id="bookService" class="priv.dandelion.service.Impl.BookServiceImpl"></bean>

4.1.2 bean 的别名 name 属性

  1. 配置别名

    • 使用 bean 标签的 name 属性为 bean 设置别名

    • 一个 bean 可以有多个别名,使用逗号或空格分隔

    • bean 的 ref 属性可以参照(引用)对应 bean 的 name 属性值,不过建议还是引用 id,保持一致

      <!--
          id : 给Bean命名
          class : 给Bean定义类型
      -->
      <bean id="bookDao" name="dao" class="priv.dandelion.dao.Impl.BookDaoImpl"/>
      <bean id="bookService" name="service,service2 bookEbi" class="priv.dandelion.service.Impl.BookServiceImpl">
          <!-- 配置Service 与Dao的关系-->
          <!--
              property标签 : 表示配置当前Bean的属性
              name属性 : 表示配置参照哪一个具体的属性名称,注意需要实现其set方法
              ref属性 : 表示参照(引用)哪一个Bean
          -->
          <property name="bookDao" ref="dao"/>
      </bean>
      
  2. 根据名称从容器中获取bean对象

    与使用 id 从容器中获取 bean 对象方法相同

4.1.3 bean 的作用范围 scope 配置

  • 多次创建 Bean 对象默认为单例,使用 scope 属性可以配置其不为单例

    • 核心配置文件

      <!-- bookDao 配置了属性 scope="prototype" -->
      <bean id="bookDao" class="priv.dandelion.dao.Impl.BookDaoImpl" scope="prototype"/>
      
      <bean id="bookService" name="service,service2 bookEbi" class="priv.dandelion.service.Impl.BookServiceImpl">
          <property name="bookDao" ref="bookDao"/>
      </bean>
      
    • 执行

      ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
      // 单例
      BookService bookService1 = (BookService) context.getBean("service");
      BookService bookService2 = (BookService) context.getBean("service");
      System.out.println(bookService1);   // priv.dandelion.service.Impl.BookServiceImpl@25bbe1b6
      System.out.println(bookService2);   // priv.dandelion.service.Impl.BookServiceImpl@25bbe1b6
      // 非单例
      BookDao bookDao1 = (BookDao) context.getBean("bookDao");
      BookDao bookDao2 = (BookDao) context.getBean("bookDao");
      System.out.println(bookDao1);   // priv.dandelion.dao.Impl.BookDaoImpl@5702b3b1
      System.out.println(bookDao2);   // priv.dandelion.dao.Impl.BookDaoImpl@69ea3742
      
  • 整理

    类别 描述
    名称 scope
    类型 属性
    所属 bean 标签
    功能 定义 bean 的作用范围,默认为单例(默认单例,节约资源),可进行配置
    * singletion:单例
    * prototype:非单例
    示例 <bean id="bookDao" class="priv.dandelion.dao.Impl.BookDaoImpl" scope="prototype"/>
  • 拓展

    • 适合交给容器进行管理的 bean

      • Dao 层对象
      • Service 层对象
      • Controller 层对象
      • Utils 对象
    • 不适合交给容器管理的 bean

      • 封装实体的域对象,各自带有不同的数据

4.2 bean实例化

bean 的本质就是对象,创建 bean 使用无参构造方法完成(底层采用暴力反射)

bean的三种实例化方法:

  • 构造方法
  • 静态工厂
  • 实例工厂

4.2.1 环境准备

4.2.2 构造方法实例化

  1. 准备需要被创建的类

    • Dao

      public class BookDaoImpl implements BookDao {
      
          // 验证:bean的实例化本质上是调用了无参构造方法,手动实现并在控制台输出内容
          // 验证:底层使用暴力反射机制,当构造方法使用private时,也不会影响bean的实例化
          private BookDaoImpl() {
              System.out.println("book dao constructor is running ....");
          }
      
          public void save() {
              System.out.println("book dao save ...");
          }
      
      
      
  2. 将类配置到Spring容器

    <bean id="bookDao" class="priv.dandelion.dao.Impl.BookDaoImpl"/>
    
  3. 编写运行程序

    public class AppForInstanceBook {
        public static void main(String[] args) {
    
            ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
            BookDao bookDao = (BookDao) ctx.getBean("bookDao");
    
            bookDao.save();
            
            /**
             * 控制台输出:
             * book dao constructor is running ....
             * book dao save ...
             *
             * Process finished with exit code 0
             */
    
        }
    }
    

4.2.3 静态工厂实例化

4.2.3.1 工厂方式创建 bean

使用工厂方式创建对象,在一定程度上进行解耦,但不如将工厂交给Spring来管理(见4.2.3.2)

  • Dao

    public class OrderDaoImpl implements OrderDao {
    
        public void save() {
            System.out.println("order dao save ...");
        }
    }
    
  • Factory

    //静态工厂创建对象
    public class OrderDaoFactory {
        public static OrderDao getOrderDao(){
            System.out.println("factory setup....");
            return new OrderDaoImpl();
        }
    }
    
  • Main

    public class AppForInstanceOrder {
        public static void main(String[] args) {
            // 通过静态工厂创建对象
            OrderDao orderDao = OrderDaoFactory.getOrderDao();
            orderDao.save();
            /**
             * 控制台输出:
             * factory setup....
             * order dao save ...
             */
        }
    }
    
4.2.3.2 静态工厂实例化

将工厂交给Spring来管理,实例化 bean

  • 核心配置文件

    <!-- property标签 : 表示配置当前Bean的属性 -->
    <!-- class属性 : 所使用的工厂的全类名 -->
    <!-- factory-method属性 : 工厂中创建bean的方法名 -->
    <bean id="orderDao" class="priv.dandelion.factory.OrderDaoFactory" factory-method="getOrderDao"/>
    
  • Main

    public class AppForInstanceOrder {
        public static void main(String[] args) {
    
            ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
            OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");
    
            orderDao.save();
            /**
             * 控制台输出:
             * factory setup....
             * order dao save ...
             */
        }
    }
    

4.2.4 实例工厂(非静态)与 FactoryBean

4.2.4.1 环境准备
  • Dao

    public class UserDaoImpl implements UserDao {
    
        public void save() {
            System.out.println("user dao save ...");
        }
    }
    
  • Factory

    //实例工厂创建对象
    public class UserDaoFactory {
        public UserDao getUserDao(){
            return new UserDaoImpl();
        }
    }
    
4.2.4.2 实例工厂实例化
  • 核心配置文件

    <!-- 工厂中获取Dao的方法是非静态的,必须拿到工厂对象 -->
    <bean id="userFactory" class="priv.dandelion.factory.UserDaoFactory"/>
    
    <!-- 指定工厂,并使用工厂对应方法获取所需的bean对象 -->
    <!-- factory-bean:指定工厂bean -->
    <bean id="userDao" factory-bean="userFactory" factory-method="getUserDao"/>
    
  • Main

    public class AppForInstanceUser {
        public static void main(String[] args) {
    
            ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
            UserDao userDao = (UserDao) ctx.getBean("userDao");
            
            userDao.save();
            /**
             * 控制台输出:
             * user dao save ...
             */ 
        }
    }
    
4.2.4.3 FactoryBean 的使用(改良:实例工厂实例化)

4.2.4.2 实例工厂实例化中存在问题,需要进行改进:

  1. 工厂的bean对象只是为了配合使用,毫无意义
  2. 对于每个需要的bean,其中factory-method属性值不固定,每次都需要配置

使用FactoryBean对其进行改进

  • FactoryBean 工厂类

    // FactoryBean创建对象,需要实现FactoryBean<T>接口并指定泛型
    public class UserDaoFactoryBean implements FactoryBean<UserDao> {
    
        // 代替原始实例工厂中创建对象的方法
        public UserDao getObject() throws Exception {
            return new UserDaoImpl();
        }
    
        // 指定Object的类型,返回对应的类的字节码文件
        public Class<?> getObjectType() {
            return UserDao.class;
        }
        
        // 创建出的 bean 是否为单例,返回 true 为单例,不覆写该方法时默认为为单例
        public boolean isSingleton() {
            return true;
        }
    }
    
  • 核心配置文件

    <!-- <bean id="userFactory" class="priv.dandelion.factory.UserDaoFactory"/> -->
    <!-- <bean id="userDao" factory-bean="userFactory" factory-method="getUserDao"/> -->
    <bean id="userDao" class="priv.dandelion.factory.UserDaoFactoryBean"/>
    
  • Main

    与 4.2.4.2 一致,无需修改

4.3 bean的生命周期

bean 的生命周期有以下几个阶段:

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

4.3.1 环境准备

  • Dao

    public class BookDaoImpl implements BookDao {
        public void save() {
            System.out.println("book dao save ...");
        }
    }
    
  • 核心配置文件

    <bean id="bookDao" class="priv.dandelion.dao.impl.BookDaoImpl"/>
    
  • Main

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

4.3.2 生命周期设置

  1. 类中添加初始化和销毁方法

    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 destroy(){
            System.out.println("destroy...");
        }
    }
    
  2. 在核心配置文件中进行配置

    <!--init-method:设置bean初始化生命周期回调函数-->
    <!--destroy-method:设置bean销毁生命周期回调函数,仅适用于单例对象-->
    <bean id="bookDao" class="priv.dandelion.dao.impl.BookDaoImpl" init-method="init" destroy-method="destroy"/><bean id="bookDao" class="priv.dandelion.dao.impl.BookDaoImpl" init-method="init" destroy-method="destroy"/>
    
  3. 运行程序,控制台输出仅包含init

    如运行情况中所示,控制台没有输出destroy...,说明销毁方法未被执行,该问题将在 4.3.3 和 4.3.4 得到解决

    /**
     * init...
     * book dao save ...
     */
    

4.3.3 close关闭容器

  • 销毁方法未执行原因与解决方案分析

    • 原因:程序运行结束时,JVM虚拟机直接被关闭,销毁方法来不及执行
    • 解决方案:在关闭JVM虚拟机之前手动关闭容器,可以执行销毁方法
  • 实现

    存在问题:ApplicationContext中没有实现close()

    解决方案:其实现类ClassPathXmlApplicationContext对其进行了实现

    public class AppForLifeCycle {
        public static void main( String[] args ) {
            ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
            BookDao bookDao = (BookDao) ctx.getBean("bookDao");
            bookDao.save();
            //关闭容器
            ctx.close();
            /**
             * init...
             * book dao save ...
             * destroy...
             */
        }
    }
    

4.3.4 注册钩子关闭容器

  • 使用该方式的原因

    • close关闭容器相对而言比较暴力,如果在 close 之后还有未执行的语句,可能会发生问题
    • 注册钩子关闭容器语句可以写在任何位置,且仅在JVM虚拟机关闭前执行销毁
  • 注册钩子关闭容器的使用方法

    • Main中

      //注册关闭钩子函数,在虚拟机退出之前回调此函数,关闭容器
      ctx.registerShutdownHook();
      

4.3.5 生命周期管理的接口方式

Spring为生命周期控制提供了接口,在上述代码的基础上添加以下代码进行演示

  • 核心配置文件(不需要写 init-method 和 destroy-method 属性)

    <bean id="bookService" class="priv.dandelion.service.impl.BookServiceImpl">
        <property name="bookDao" ref="bookDao"/>
    </bean>
    
  • Service

    public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
        private BookDao bookDao;
    
        public void setBookDao(BookDao bookDao) {
            System.out.println("set .....");
            this.bookDao = bookDao;
        }
    
        public void save() {
            System.out.println("book service save ...");
            bookDao.save();
        }
    
        public void destroy() throws Exception {
            System.out.println("service destroy");
        }
    
        // 如方法名所述,该方法运行在set方法之后
        public void afterPropertiesSet() throws Exception {
            System.out.println("service init");
        }
    }
    
  • Main

    public class AppForLifeCycle {
        public static void main( String[] args ) {
            ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
            BookDao bookDao = (BookDao) ctx.getBean("bookDao");
            bookDao.save();
            //注册关闭钩子函数,在虚拟机退出之前回调此函数,关闭容器
            ctx.registerShutdownHook();
            /**
             * init...
             * set .....
             * service init
             * book dao save ...
             * service destroy
             * destroy...
             */
        }
    }
    

5、DI(依赖注入)相关内容

  • 向一个类中传递数据的方式
    • 普通方法(setter)
    • 构造方法(构造器)
  • 容器中的 bean 可能是什么类型的数据
    • 引用类型
    • 简单类型(基本数据类型和 String)

5.1 setter注入 (推荐,使用简单)

5.1.1 注入引用数据类型

  1. 类中给出成员变量并实现其setter

    public class BookServiceImpl implements BookService {
        private BookDao bookDao;
        private UserDao userDao;
        //setter注入需要提供要注入对象的set方法
        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
        }
        //setter注入需要提供要注入对象的set方法
        public void setBookDao(BookDao bookDao) {
            this.bookDao = bookDao;
        }
    
        public void save() {
            System.out.println("book service save ...");
            bookDao.save();
            userDao.save();
        }
    }
    
  2. 核心配置文件

    <!-- 被注入的需要配置,定义成bean -->
    <bean id="bookDao" class="priv.dandelion.dao.impl.BookDaoImpl"/>
    <bean id="userDao" class="priv.dandelion.dao.impl.UserDaoImpl"/>
    
    <!-- 注入引用类型 -->
    <bean id="bookService" class="priv.dandelion.service.impl.BookServiceImpl">
        <!--property标签:设置注入属性-->
        <!--name属性:设置注入的属性名,实际是set方法对应的名称-->
        <!--ref属性:设置注入引用类型bean的id或name-->
        <property name="bookDao" ref="bookDao"/>
        <property name="userDao" ref="userDao"/>
    </bean>
    

5.1.2 注入简单数据类型

  1. 在类中定义成员变量并实现其 setter

    public class BookDaoImpl implements BookDao {
    
        private String databaseName;
        private int connectionNum;
        //setter注入需要提供要注入对象的set方法
        public void setConnectionNum(int connectionNum) {
            this.connectionNum = connectionNum;
        }
        //setter注入需要提供要注入对象的set方法
        public void setDatabaseName(String databaseName) {
            this.databaseName = databaseName;
        }
    
        public void save() {
            System.out.println("book dao save ..."+databaseName+","+connectionNum);
        }
    }
    
  2. 核心配置文件

    <!-- 注入简单类型 -->
    <bean id="bookDao" class="priv.dandelion.dao.impl.BookDaoImpl">
        <!--property标签:设置注入属性-->
        <!--name属性:设置注入的属性名,实际是set方法对应的名称-->
        <!--value属性:设置注入简单类型数据值,Spring内部会自动进行类型转换-->
        <property name="connectionNum" value="100"/>
        <property name="databaseName" value="mysql"/>
    </bean>
    

5.2 构造器注入

5.2.1 构造器注入引用数据类型

  1. 类中给出成员变量并使用有参构造对需要注入的成员进行初始化操作

    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();
        }
    }
    
  2. 核心配置文件

    <bean id="bookDao" class="priv.dandelion.dao.impl.BookDaoImpl"/>
    <bean id="userDao" class="priv.dandelion.dao.impl.UserDaoImpl"/>
    
    <bean id="bookService" class="priv.dandelion.service.impl.BookServiceImpl">
        <!-- 注意此处的 ref 属性值为:构造方法的形参名 -->
        <constructor-arg name="userDao" ref="userDao"/>
        <constructor-arg name="bookDao" ref="bookDao"/>
    </bean>
    

5.2.2 构造器注入简单数据类型

  1. 类中给出成员变量并使用有参构造对需要注入的成员进行初始化操作

    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);
        }
    }
    
  2. 核心配置文件

    <bean id="bookDao" class="priv.dandelion.dao.impl.BookDaoImpl">
        <!-- 根据构造方法参数名称注入 -->
        <constructor-arg name="connectionNum" value="10"/>
        <constructor-arg name="databaseName" value="mysql"/>
    </bean>
    

5.2.3 【补充】不同场景构造器注入简单数据类型的其他写法 —— 用于解耦

<!-- 1. 标准书写 -->
<bean id="bookDao" class="priv.dandelion.dao.impl.BookDaoImpl">
    <constructor-arg name="connectionNum" value="10"/>
    <constructor-arg name="databaseName" value="mysql"/>
</bean>

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

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

5.3 自动配置

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

  • 自动装配的方式:

    • 按类型(常用)

    • 按名称(耦合度高)

    • 按构造器(不推荐)

    • 不启用自动装配

  • 依赖自动装配特征

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

    • 注意事项

      1. 被注入的 bean 所在的类必须实现 setter
      2. 被注入的 bean(按照全类名,即 class 属性)有且仅有一个
      3. 按类型注入时,被注入的 bean 可以没有 id
    • 配置

      <bean id="bookDao" class="priv.dandelion.dao.impl.BookDaoImpl"/>
      <bean id="bookService" class="priv.dandelion.service.impl.BookServiceImpl" autowire="byType"/>
      
  • 按名称

    • 注意事项

      1. 被注入的 bean 所在的类必须实现 setter
      2. 被注入的 bean 的 id 属性必须和类中的成员名称,准确说是 setter 名称保持一致,否则会给一个空对象,导致空指针异常
      3. 变量名与配置耦合
    • 配置代码

      <bean id="bookDao" class="priv.dandelion.dao.impl.BookDaoImpl"/>
      <bean id="bookService" class="priv.dandelion.service.impl.BookServiceImpl" autowire="byName"/>
      

5.4 集合注入

  • List 和 Array 可以相互通用

  • 如果集合的元素是引用类型则不使用 <value></value>

    <ref bean="bean的id"/>
    
  • Dao

    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 setArray(int[] array) {
            this.array = array;
        }
    
        public void setList(List<String> list) {
            this.list = list;
        }
    
        public void setSet(Set<String> set) {
            this.set = set;
        }
    
        public void setMap(Map<String, String> map) {
            this.map = map;
        }
    
        public void setProperties(Properties properties) {
            this.properties = properties;
        }
    
        
        public void save() {
            System.out.println("book priv.dandelion.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);
        }
    }
    
  • 核心配置文件

    <?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="priv.dandelion.dao.impl.BookDaoImpl">
            <!--数组注入-->
            <property name="array">
                <array>
                    <value>100</value>
                    <value>200</value>
                    <value>300</value>
                </array>
            </property>
            <!--list集合注入-->
            <property name="list">
                <list>
                    <value>dande</value>
                    <value>dandel</value>
                    <value>dandelio</value>
                    <value>dandelion</value>
                </list>
            </property>
            <!--set集合注入-->
            <property name="set">
                <set>
                    <value>dande</value>
                    <value>dandel</value>
                    <value>dandelio</value>
                    <value>dandelio</value>
                </set>
            </property>
            <!--map集合注入-->
            <property name="map">
                <map>
                    <entry key="country" value="china"/>
                    <entry key="province" value="henan"/>
                    <entry key="city" value="kaifeng"/>
                </map>
            </property>
            <!--Properties注入-->
            <property name="properties">
                <props>
                    <prop key="country">china</prop>
                    <prop key="province">henan</prop>
                    <prop key="city">kaifeng</prop>
                </props>
            </property>
        </bean>
    </beans>
    
  • Main

    public class AppForDICollection {
        public static void main( String[] args ) {
            ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
            BookDao bookDao = (BookDao) ctx.getBean("bookDao");
    
            bookDao.save();
        }
        /**
         * book priv.dandelion.dao save ...
         * 遍历数组:[100, 200, 300]
         * 遍历List[dande, dandel, dandelio, dandelion]
         * 遍历Set[dande, dandel, dandelio]
         * 遍历Map{country=china, province=henan, city=kaifeng}
         * 遍历Properties{province=henan, city=kaifeng, country=china}
         */
    }
    
posted @ 2022-11-20 23:14  Dandelion_000  阅读(244)  评论(2编辑  收藏  举报