IoC 容器:
Spring IoC 容器,负责实例化、配置和组装 bean(组件)核心容器。容器通过读取配置元数据来获取有关要实例化、配置和组装组件的指令。

IoC(Inversion of Control)控制反转
IoC 主要是针对对象的创建和调用控制而言的,也就是说,当应用程序需要使用一个对象时,不再是应用程序直接创建该对象,而是由 IoC 容器来创建和管理,即控制权由应用程序转移到 IoC 容器中,也就是“反转”了控制权。这种方式基本上是通过依赖查找的方式来实现的,即 IoC 容器维护着构成应用程序的对象,并负责创建这些对象。

DI (Dependency Injection) 依赖注入
DI 是指在组件之间传递依赖关系的过程中,将依赖关系在容器内部进行处理,这样就不必在应用程序代码中硬编码对象之间的依赖关系,实现了对象之间的解耦合。在 Spring 中,DI 是通过 XML 配置文件或注解的方式实现的。它提供了三种形式的依赖注入:构造函数注入、Setter 方法注入和接口注入。

基于XML配置方式组件管理

组件(Bean)信息声明配置(IoC)

  1. 准备项目
    a.创建maven工程(ssm-spring-part)作为父工程
    设置打包方式为<packaging>pom</packaging>,删除src目录
    b.导入SpringIoC相关依赖(pom.xml)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <dependencies>
    <!--spring context依赖-->
    <!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>6.0.6</version>
    </dependency>
    <!--junit5测试-->
    <dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.3.1</version>
    </dependency>
    </dependencies>
  2. 基于无参构造函数
    a.创建子模块(spring-ioc-xml-01)
    b.准备组件类(HappyComponent)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package cn.xnj.ioc_01;

    public class HappyComponent {

    //默认包含无参数构造函数

    public void doWork() {
    System.out.println("HappyComponent.doWork");
    }
    }
    c.在resource目录下创建携带spring约束的xml配置文件(spring-01.xml)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <?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.使用无参数构造函数实例化的组件
    <bean -一个组件信息 -一个组件对象
    id -组件的标识 -唯一 -方便后期读取
    class - 组件类的权限定服 - 包名+类名
    />
    将一个组件类 - 声明两个组件信息 -【默认是单例模式】 -会实例化两个组件对象 new new
    -->
    <bean id="happyComponent01" class="cn.xnj.ioc_01.HappyComponent"/>

    <bean id="happyComponent02" class="cn.xnj.ioc_01.HappyComponent"/>

    </beans>
    • bean标签:通过配置bean标签告诉IOC容器需要创建对象的组件信息
    • id属性:bean的唯一标识,方便后期获取Bean!
    • class属性:组件类的全限定符!
    • 注意:要求当前组件类必须包含无参数构造函数!
  3. 基于静态工厂方法实例化
    a.准备组件类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class ClientService {
    private static ClientService clientService = new ClientService();
    private ClientService() {}

    public static ClientService createInstance() {

    return clientService;
    }
    }
    b.编写xml配置文件(spring-01.xml)
    1
    2
    3
    4
    5
    6
    7
    8
    <!--2.基于静态工厂方法进行ioc的配置
    <bean -一个组件信息 -一个组件对象
    id -组件的标识 -唯一 -方便后期读取
    class - 工厂类的权限定服 - 包名+类名
    factory-method - 工厂类中的静态方法名
    />
    -->
    <bean id="clientService" class="cn.xnj.ioc_01.CilentService" factory-method="createInstance"/>
    • class属性:指定工厂类的全限定符!
    • factory-method: 指定静态工厂方法,注意,该方法必须是static方法。
  4. 基于实例工厂方法实例化
    a.准备组建类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class ClientServiceImpl { }

    package cn.xnj.ioc_01;

    public class DefaultServiceLocator {

    private static ClientServiceImpl clientService = new ClientServiceImpl();

    public ClientServiceImpl createClientServiceInstance() {
    return clientService;
    }
    }
    b.编写xml配置文件(spring-01.xml)
    1
    2
    3
    4
    5
    <!--3.基于非静态工厂方法进行ioc的配置-->
    <!--3.1配置工厂类的组件信息-->
    <bean id="defaultServiceLocator" class="cn.xnj.ioc_01.DefaultServiceLocator"/>
    <!--3.2通过指定非静态工厂对象和方法名 来配置生成的ioc信息-->
    <bean id="clientServiceImp" factory-bean="defaultServiceLocator" factory-method="createClientServiceInstance" />
    • factory-bean属性:指定当前容器中工厂Bean 的名称。
    • factory-method: 指定实例工厂方法名。注意,实例方法必须是非static的!

组件(Bean)依赖注入配置(DI)

  1. 基于构造函数的依赖注入(单个构造参数)
    a.准备组件类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package cn.xnj.ioc_02;
    public class UserDao {
    }

    package cn.xnj.ioc_02;
    public class UserService {

    private UserDao userDao;

    public UserService(UserDao userDao) {
    this.userDao = userDao;
    }
    }
    b.准备配置文件(spring-02.xml)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <!--1.单个构造参数注入-->
    <!-- 引用类bean声明 -->
    <bean id="userService" class="cn.xnj.ioc_02.UserService">
    <!-- 构造参数传值di的配置
    <constructor-arg 构造参数传值的id配置
    value= 直接属性值 String name ="张三" int age=23
    ref = 引用其他的bean beanId的值
    />
    -->
    <constructor-arg ref="userDao"/>
    </bean>
    <!-- 被引用类bean声明 -->
    <bean id="userDao" class="cn.xnj.ioc_02.UserDao"/>
  2. 基于构造函数的依赖注入(多个构造参数)
    a.准备组件类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class UserDao {
    }

    public class UserService {

    private UserDao userDao;

    private int age;

    private String name;

    public UserService(int age , String name ,UserDao userDao) {
    this.userDao = userDao;
    this.age = age;
    this.name = name;
    }
    }
    b.编写配置文件(spring-02.xml)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    <!--2.多个构造参数注入-->
    <!-- 场景1: 多参数,可以按照相应构造函数的顺序注入数据
    UserService(int age , String name ,UserDao userDao)
    -->
    <bean id="userService" class="cn.xnj.ioc_02.UserService">
    <!-- value直接注入基本类型值 -->
    <constructor-arg value="18"/>
    <constructor-arg value="赵伟风"/>
    <constructor-arg ref="userDao"/>
    </bean>
    <!-- 被引用类bean声明 -->
    <bean id="userDao" class="cn.xnj.ioc_02.UserDao"/>


    <!-- 场景2: 多参数,可以按照相应构造函数的名称注入数据 -->
    <bean id="userService" class="cn.xnj.ioc_02.UserService">
    <!-- value直接注入基本类型值 -->
    <constructor-arg name="name" value="赵伟风"/>
    <constructor-arg name="age" value="18"/>
    <constructor-arg name="userDao" ref="userDao"/>
    </bean>
    <!-- 被引用类bean声明 -->
    <bean id="userDao" class="cn.xnj.ioc_02.UserDao"/>


    <!-- 场景2: 多参数,可以按照相应构造函数的角标注入数据
    index从0开始 构造函数(0,1,2....)
    -->
    <bean id="userService" class="cn.xnj.ioc_02.UserService">
    <!-- value直接注入基本类型值 -->
    <constructor-arg index="0" value="18"/>
    <constructor-arg index="1" value="赵伟风"/>
    <constructor-arg index="2" ref="userDao"/>
    </bean>
    <!-- 被引用类bean声明 -->
    <bean id="userDao" class="cn.xnj.ioc_02.UserDao"/>
    • constructor-arg标签:指定构造参数和对应的值
    • constructor-arg标签:name属性指定参数名、index属性指定参数角标、value属性指定普通属性值
  3. 基于Setter方法依赖注入
    a.准备组件类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public Class MovieFinder{

    }

    public class SimpleMovieLister {

    private MovieFinder movieFinder;

    private String movieName;

    public void setMovieFinder(MovieFinder movieFinder) {
    this.movieFinder = movieFinder;
    }

    public void setMovieName(String movieName){
    this.movieName = movieName;
    }

    // business logic that actually uses the injected MovieFinder is omitted...
    }
    b.编写配置文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <!--3.触发setter方法进行注入-->
    <bean id="movieFinder" class="cn.xnj.ioc_02.MovieFinder"/>

    <bean id="simpleMovieLister" class="cn.xnj.ioc_02.SimpleMovieLister">
    <!-- setter方法,注入movieFinder对象的标识id
    name = 属性名 setter方法的名去掉set和首字母小写的值 setMovieFinder -> movieFinder
    value | ref 二选一 value="直接属性值" ref= 引用bean的id值
    -->
    <property name="movieFinder" ref="movieFinder" />

    <property name="movieName" value="消失的她"/>
    </bean>

IoC容器创建和使用

  1. 介绍
    想要配置文件中声明组件类信息真正的进行实例化成Bean对象和形成Bean之间的引用关系,我们需要声明IoC容器对象,读取配置文件,实例化组件和关系维护的过程都是在IoC容器中实现的!
  2. 容器实例化
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    //创建ioc容器并且读取配置文件
    public void createIoC(){
    //创建容器,选择合适的容器实现
    /**
    * 接口
    * BeanFactory
    * ApplicationContext
    * 实现类
    * 可以直接通过构造函数实例化
    * ClassPathXmlApplicationContext 读取类路径下的xml配置方式 classes
    * FileSystemXmlApplicationContext 读取指定文件位置的xml配置方式
    * AnnotationConfigApplicationContext 读取配置类方式的ioc容器
    * WebApplicationContent web项目专属的配置的ioc容器
    */

    //方式1:直接创建容器并且指定配置文件【推荐】
    //构造函数(String...配置文件) 可以写一个或多个
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-03.xml");

    //方式2:先创建ioc容器,再指定配置文件,再刷新
    //源码的配置过程:先创建容器【spring】 和配置文件指定分开【自己指定】
    ClassPathXmlApplicationContext applicationContext1 = new ClassPathXmlApplicationContext();
    applicationContext1.setConfigLocation("spring-03.xml");//设置配置配置文件,方法参数为可变参数,可以设置一个或者多个配置
    applicationContext1.refresh();//后配置的文件,需要调用refresh方法,触发刷新配置
    }
  3. bean对象读取
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
       //在Ioc容器中获取组件bean
    @Test
    public void getBeanFromIoC(){
    //1.创建容器对象
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext();
    applicationContext.setConfigLocation("spring-03.xml");
    applicationContext.refresh();

    //2.读取ioc容器组件
    //方式1:直接根据beanId获取即可 返回值类型是Object 需要强转【不推荐】
    HappyComponent happyComponent = (HappyComponent) applicationContext.getBean("happyComponent");

    //方式2:根据beanID,同时指定bean的类型Class
    HappyComponent happyComponent1 = applicationContext.getBean("happyComponent", HappyComponent.class);

    //方案3:直接根据类型获取
    //如果容器中存在多个类型相同的组件,那么会报错:NoUniqueBeanDefinitionException(无唯一 Bean 定义异常)
    HappyComponent happyComponent2 = applicationContext.getBean(HappyComponent.class);

    happyComponent2.doWork();//输出:HappyComponent.doWork

    System.out.println(happyComponent==happyComponent1);//输出:true
    System.out.println(happyComponent1==happyComponent2);//输出:true

    /**
    * ioc的配置一定是实现类,但是可以根据接口类型获取值:
    * 如B为A接口的实现类 ,B配置了ioc容器:<bean id=b class=xx.xx.B/>
    * A b = applicationContext.getBean(A.class);
    */

    }
    • 根据bean类型获取,同一个类型,在ioc容器中只能有一个bean!
    • 如果ioc容器存在多个同类型的bean,会出现:NoUniqueBeanDefinitionException
    • ioc的配置一定是实现类,但是可以根据接口类型获取值

组件(Bean)作用域和周期方法配置

  1. 周期方法配置
    a.周期方法概念
    我们可以在组件类中定义方法,然后当IoC容器实例化和销毁组件对象的时候进行调用!这两个方法我们称为生命周期方法!类似于Servlet的init/destroy方法,我们可以在周期方法完成初始化和释放资源等工作。
    b.周期方法声明
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class JavaBean {
    //周期方法要求: 方法命名随意,但是要求方法必须是 public void 无形参列表
    public void init(){
    //初始化方法,在对象创建完成之后,并且属性赋值完成之后,会自动调用
    System.out.println("JavaBean.init");
    }

    public void destroy(){
    //销毁方法,在对象销毁之前,会自动调用
    System.out.println("JavaBean.destroy");
    }
    }
    c.周期方法配置(spring-04.xml)
    1
    2
    3
    4
    5
    6
    <!--
    init-method="初始化方法名"
    destroy-method="销毁方法名"
    spring ioc容器会在对应的时间节点调用对应的方法,在对应的方法中写对应的业务逻辑即可
    -->
    <bean id="javaBean" class="cn.xnj.ioc_04.JavaBean" init-method="init" destroy-method="destroy"/>
    b.测试类展示
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Test //测试生命周期方法
    public void test_04(){
    //1.创建ioc容器 就会进行组件对象的实例化 -> init
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-04.xml");
    //控制台打印: JavaBean.init

    //如果不正常结束ioc容器,ioc会立即释放,不会调用destory方法

    //2.正常结束ioc容器
    applicationContext.close();
    //控制台打印: JavaBean.destroy
    }
  2. 组件作用域配置
    a.Bean作用域概念
    <bean 标签声明Bean,只是将Bean的信息配置给SpringIoC容器!在IoC容器中,这些<bean标签对应的信息转成Spring内部 BeanDefinition 对象,BeanDefinition 对象内,包含定义的信息(id,class,属性等等)!这意味着,BeanDefinition概念一样,SpringIoC容器可以可以根据BeanDefinition对象反射创建多个Bean对象实例。
    b.作用域可选值

     |取值|含义|创建对象的时机|默认值|
     |-|-|-|-|
     |singleton|在 IOC 容器中,这个 bean 的对象始终为单实例|IOC 容器初始化时|是|
     |prototype|这个 bean 在 IOC 容器中有多个实例|获取 bean 时|否|
    

    如果是在WebApplicationContext环境下还会有另外两个作用域(但不常用):

     |取值|含义|创建对象的时机|默认值|
     |---|---|---|---|
     |request|请求范围内有效的实例|每次请求|否|
     |session|会话范围内有效的实例|每次会话|否|
    

    c.作用域配置(配置scope范围)

    1
    2
    3
    4
    5
    6
    7
    8
        <!--bean的作用域
    准备两个引用组件类:SingletonBean、PrototypeBean
    -->
    <!-- scope属性:取值singleton(默认值),bean在IOC容器中只有一个实例,IOC容器初始化时创建对象 -->
    <!-- scope属性:取值prototype,bean在IOC容器中可以有多个实例,getBean()一次就会创建一个对象 -->
    <bean id="singletonBean" scope="singleton" class="cn.xnj.ioc_04.SingletonBean"/>

    <bean id="prototypeBean" scope="prototype" class="cn.xnj.ioc_04.PrototypeBean"/>

    d.作用域测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public void test_04(){
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-04.xml");

    //单例:singleton
    SingletonBean singletonBean1 = applicationContext.getBean(SingletonBean.class);
    SingletonBean singletonBean2 = applicationContext.getBean(SingletonBean.class);
    System.out.println(singletonBean2==singletonBean1);//输出:true

    //多例:prototype
    PrototypeBean prototypeBean1 = applicationContext.getBean(PrototypeBean.class);
    PrototypeBean prototypeBean2 = applicationContext.getBean(PrototypeBean.class);
    System.out.println(prototypeBean1==prototypeBean2);//输出:false
    }

FactoryBean特性和使用

  1. FactoryBean 简介
    FactoryBean 接口是Spring IoC容器实例化逻辑的可插拔性点。用于配置复杂的Bean对象,可以将创建过程存储在FactoryBean 的getObject方法!
    FactoryBean<T> 接口提供三种方法:
    • T getObject():
      返回此工厂创建的对象的实例。该返回值会被存储到IoC容器!
    • boolean isSingleton():
      如果此 FactoryBean 返回单例,则返回 true ,否则返回 false 。此方法的默认实现返回 true (注意,lombok插件使用,可能影响效果)。
    • Class<?> getObjectType(): 返回 getObject() 方法返回的对象类型,如果事先不知道类型,则返回 null
  2. FactoryBean使用场景
    1. 代理类的创建
    2. 第三方框架整合
    3. 复杂对象实例化等
  3. FactoryBean应用
    a. 准备组件类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     public class JavaBean {
    private String name;

    public void setName(String name){
    this.name = name;
    }

    public String getName(){
    return name;
    }
    }
    b.准备FactoryBean实现类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    public class JavaBeanFactoryBean implements FactoryBean<JavaBean> {

    private String value;

    public void setValue(String value) {
    this.value = value;
    }

    @Override
    public JavaBean getObject() throws Exception {
    // 方法内部模拟创建、设置一个对象的复杂过程
    JavaBean javaBean = new JavaBean();
    javaBean.setName(value);
    return javaBean;
    }

    @Override
    public Class<?> getObjectType() {
    // 返回要生产的对象的类型
    return JavaBean.class;
    }

    @Override
    public boolean isSingleton() {
    return true;
    }
    }
    c.配置FactoryBean实现类(spring-05.xml)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <!--
    id -> getObject方法返回的对象的标识
    工厂bean的标识 &id值
    class -> factoryBean标准化工厂类
    -->
    <bean id="javaBean" class="cn.xnj.ioc_05.JavaBeanFactoryBean">
    <!--此位置的属性:JavaBean工厂类配置,而不是getObject方法-->
    <property name="value" value="张三"/>
    </bean>
    d.测试读取FactoryBean和FactoryBean.getObject对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Test
    public void test_05(){
    //1.创建ioc容器
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-05.xml");
    //2.读取组件
    JavaBean javaBean = applicationContext.getBean("javaBean", JavaBean.class);
    System.out.println("javaBean="+javaBean);//javaBean=cn.xnj.ioc_05.JavaBean@446293d
    //重写toString方法能看到:javaBean=JavaBean{name='张三'}

    //FactoryBean工厂也会加载到ioc容器! 名字 &id
    Object bean = applicationContext.getBean("&javaBean");
    System.out.println(bean);//cn.xnj.ioc_05.JavaBeanFactoryBean@69997e9d
    }
  4. FactoryBean和BeanFactory区别
    FactoryBean是 Spring 中一种特殊的 bean,可以在 getObject() 工厂方法自定义的逻辑创建Bean!是一种能够生产其他 Bean 的 Bean。FactoryBean 在容器启动时被创建,而在实际使用时则是通过调用 getObject() 方法来得到其所生产的 Bean。因此,FactoryBean 可以自定义任何所需的初始化逻辑,生产出一些定制化的 bean。
    一般情况下,整合第三方框架,都是通过定义FactoryBean实现!!!
    BeanFactory 是 Spring 框架的基础,其作为一个顶级接口定义了容器的基本行为,例如管理 bean 的生命周期、配置文件的加载和解析、bean 的装配和依赖注入等。BeanFactory 接口提供了访问 bean 的方式,例如 getBean() 方法获取指定的 bean 实例。它可以从不同的来源(例如 Mysql 数据库、XML 文件、Java 配置类等)获取 bean 定义,并将其转换为 bean 实例。同时,BeanFactory 还包含很多子类(例如,ApplicationContext 接口)提供了额外的强大功能。
    总的来说,FactoryBean 和 BeanFactory 的区别主要在于前者是用于创建 bean 的接口,它提供了更加灵活的初始化定制功能,而后者是用于管理 bean 的框架基础接口,提供了基本的容器功能和 bean 生命周期管理。

基于 XML 方式整合三层架构组件

  1. 需求分析
    搭建一个三层架构案例,模拟查询全部学生(学生表)信息,持久层使用JdbcTemplate和Druid技术,使用XML方式进行组件管理!
  2. 数据库准备
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    create database studb;

    use studb;

    CREATE TABLE students (
    id INT PRIMARY KEY,
    name VARCHAR(50) NOT NULL,
    gender VARCHAR(10) NOT NULL,
    age INT,
    class VARCHAR(50)
    );

    INSERT INTO students (id, name, gender, age, class)
    VALUES
    (1, '张三', '男', 20, '高中一班'),
    (2, '李四', '男', 19, '高中二班'),
    (3, '王五', '女', 18, '高中一班'),
    (4, '赵六', '女', 20, '高中三班'),
    (5, '刘七', '男', 19, '高中二班'),
    (6, '陈八', '女', 18, '高中一班'),
    (7, '杨九', '男', 20, '高中三班'),
    (8, '吴十', '男', 19, '高中二班');
  3. 项目准备
    a.项目创建
    spring-xml-practice-02
    b.依赖导入
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    <!--spring context依赖-->
    <!--当你引入SpringContext依赖之后,表示将Spring的基础依赖引入了-->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>6.0.6</version>
    </dependency>

    <!-- 数据库驱动和连接池-->
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.25</version>
    </dependency>

    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.8</version>
    </dependency>

    <!-- spring-jdbc -->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>6.0.6</version>
    </dependency>
    c.实体类准备(cn.xnj.pojo.Student)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    public class Student {

    private Integer id;
    private String name;
    private String gender;
    private Integer age;
    private String classes;

    public Integer getId() {
    return id;
    }

    public void setId(Integer id) {
    this.id = id;
    }

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public String getGender() {
    return gender;
    }

    public void setGender(String gender) {
    this.gender = gender;
    }

    public Integer getAge() {
    return age;
    }

    public void setAge(Integer age) {
    this.age = age;
    }

    public String getClasses() {
    return classes;
    }

    public void setClasses(String classes) {
    this.classes = classes;
    }

    @Override
    public String toString() {
    return "Student{" +
    "id=" + id +
    ", name='" + name + '\'' +
    ", gender='" + gender + '\'' +
    ", age=" + age +
    ", classes='" + classes + '\'' +
    '}';
    }
    }
  4. jdbcTemplate
    创建配置文件resources/jdbc.properties提取数据库连接信息
    1
    2
    3
    4
    jdbc.url=jdbc:mysql://localhost:3306/studb
    jdbc.driver=com.mysql.cj.jdbc.Driver
    jdbc.username=root
    jdbc.password=123456
    spring-ioc.xml配置文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 导入外部属性文件 -->
    <context:property-placeholder location="classpath:jdbc.properties" />

    <!-- 配置数据源 -->
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="url" value="${jdbc.url}"/>
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
    </bean>

    <!-- 配置 JdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <!-- 装配数据源 -->
    <property name="dataSource" ref="druidDataSource"/>
    </bean>
    </beans>
    基于jdbcTemplate的CRUD使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    public class JdbcTemplateTest {


    /**
    * 使用jdbcTemplate进行DML动作
    */
    @Test
    public void testDML(){

    ApplicationContext applicationContext =
    new ClassPathXmlApplicationContext("spring-ioc.xml");

    JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);

    //TODO 执行插入一条学员数据
    String sql = "insert into students (id,name,gender,age,class) values (?,?,?,?,?);";
    /*
    参数1: sql语句
    参数2: 可变参数,占位符的值
    */
    int rows = jdbcTemplate.update(sql, 9,"十一", "男", 18, "二年三班");

    System.out.println("rows = " + rows);

    }


    /**
    * 查询单条实体对象
    */
    @Test
    public void testDQLForPojo(){

    String sql = "select id , name , age , gender , class as classes from students where id = ? ;";

    ApplicationContext applicationContext =
    new ClassPathXmlApplicationContext("spring-ioc.xml");

    JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);

    //根据id查询
    Student student = jdbcTemplate.queryForObject(sql, (rs, rowNum) -> {
    //自己处理结果映射
    Student stu = new Student();
    stu.setId(rs.getInt("id"));
    stu.setName(rs.getString("name"));
    stu.setAge(rs.getInt("age"));
    stu.setGender(rs.getString("gender"));
    stu.setClasses(rs.getString("classes"));
    return stu;
    }, 2);

    System.out.println("student = " + student);
    }


    /**
    * 查询实体类集合
    */
    @Test
    public void testDQLForListPojo(){

    String sql = "select id , name , age , gender , class as classes from students ;";

    ApplicationContext applicationContext =
    new ClassPathXmlApplicationContext("spring-ioc.xml");

    JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);
    /*
    query可以返回集合!
    BeanPropertyRowMapper就是封装好RowMapper的实现,要求属性名和列名相同即可
    */
    List<Student> studentList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Student.class));

    System.out.println("studentList = " + studentList);
    }

    }

  5. 三层架构搭建和实现
    a.持久层(cn.xnj.dao.StudentDao)(cn.xnj.dao.impl.StudentDaoImpl)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    //接口
    public interface StudentDao {
    /**
    * 查询全部学生数据
    * @return
    */
    List<Student> queryAll();
    }

    //实现类
    public class StudentDaoImpl implements StudentDao {

    private JdbcTemplate jdbcTemplate;

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
    this.jdbcTemplate = jdbcTemplate;
    }

    /**
    * 查询全部学生数据
    * @return
    */
    @Override
    public List<Student> queryAll() {
    String sql = "select id , name , age , gender , class as classes from students ;";

    /*
    query可以返回集合!
    BeanPropertyRowMapper就是封装好RowMapper的实现,要求属性名和列名相同即可
    */
    List<Student> studentList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Student.class));

    return studentList;
    }
    }
    b.业务层(cn.xnj.service.StudentService)(cn.xnj.service..impl.StudentServiceImpl)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    //接口
    public interface StudentService {
    /**
    * 查询全部学员业务
    * @return
    */
    List<Student> findAll();
    }

    //实现类
    public class StudentServiceImpl implements StudentService {

    private StudentDao studentDao;

    public void setStudentDao(StudentDao studentDao) {
    this.studentDao = studentDao;
    }

    /**
    * 查询全部学员业务
    * @return
    */
    @Override
    public List<Student> findAll() {

    List<Student> studentList = studentDao.queryAll();

    return studentList;
    }
    }
    c.表述层(cn.xnj.controller.StudentController)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class StudentController {

    private StudentService studentService;

    public void setStudentService(StudentService studentService) {
    this.studentService = studentService;
    }

    public void findAll(){
    List<Student> studentList = studentService.findAll();
    System.out.println("studentList = " + studentList);
    }
    }
  6. 三层架构IoC配置(spring-ioc.xml)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <bean id="studentDao" class="cn.xnj.dao.impl.StudentDaoImpl">
    <property name="jdbcTemplate" ref="jdbcTemplate" />
    </bean>

    <bean id="studentService" class="cn.xnj.service.impl.StudentServiceImpl">
    <property name="studentDao" ref="studentDao" />
    </bean>

    <bean id="studentController" class="cn.xnj.controller.StudentController">
    <property name="studentService" ref="studentService" />
    </bean>
  7. 运行测试(test/java/cn.xnj.test.ControllerTest)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class ControllerTest {

    @Test
    public void testRun(){
    ApplicationContext applicationContext =
    new ClassPathXmlApplicationContext("spring-ioc.xml");
    StudentController studentController = applicationContext.getBean (StudentController.class);
    studentController.findAll();
    }
    }
  8. XMLIoC方式问题总结
    1. 注入的属性必须添加setter方法、代码结构乱!
    2. 配置文件和Java代码分离、编写不是很方便!
    3. XML配置文件解析效率低

基于注解方式管理Bean

Bean注解标记和扫描(IoC)

  1. 准备Spring项目和组件
    a.创建项目
    spring-ioc-annoaction-03
    b.准备项目pom.xml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <!--spring context依赖-->
    <!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>6.0.6</version>
    </dependency>
    <!--junit5测试-->
    <dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.3.1</version>
    </dependency>
    c.准备组件类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //Controller组件
    public class XxxController {}

    //Service组件
    public interface XxxService {}

    public class XxxServiceImpl implements XxxService {}

    //Dao组件
    public class XxxDao {}

    //普通组件
    public class CommonComponent {}

  2. 组件添加标记注解
    a.组件标记注解和区别
    Spring提供了以下多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean。
    |注解|说明|
    |@Component|该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。 使用时只需将该注解标注在相应类上即可。|
    |@Repository|该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。|
    |@Service|该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。|
    |@Controller|该注解通常作用在控制层(如SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。|
    
    @Controller、@Service、@Repository这三个注解只是在@Component注解的基础上起了三个新的名字。对于Spring使用IOC容器管理这些组件来说没有区别,也就是语法层面没有区别。但严格使用@Controller、@Service、@Repository这三个注解,能让我们能够便于分辨组件的作用。
    b.使用注解标记
    直接将注解添加到类名上方即可
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //普通组件
    @Component //<bean id="commonComponent" class="CommonComponent">
    public class CommonComponent {}

    //controller类型组件
    @Controller
    public class XxxController {}

    //service类型组件
    @Service
    public class XxxServiceImpl implements XxxService {}

    //dao类型组件
    @Repository
    public class XxxDao {}
  3. 配置文件确定扫描范围
    情况1:基本扫描配置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"

    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www. springframework.org/schema/beans/spring-beans.xsd http://www.springframework. org/schema/context https://www.springframework.org/schema/context/ spring-context.xsd">

    <!--1.普通配置包扫描
    base-package 指定ioc容器去哪些包下查找注解类 ->ioc容器
    一个包或多个包 cn.xnj,ioc_01,cn.xnj.ioc_02 包,包
    指定包,相当于指定了子包内所有类
    -->
    <context:component-scan base-package="cn.xnj.ioc_01"/>
    </beans>
    情况2:指定排除组件
    1
    2
    3
    4
    5
    6
    <!-- 2.指定包,但是排除注解 -->
    <context:component-scan base-package="cn.xnj.ioc_01">
    <!--排除包下的注解-->
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    情况3:指定扫描组件
    1
    2
    3
    4
    5
    6
    <!--3.指定包,指定包含注解-->
    <!--base-package包下的注解都生效! use-default-filters="false" 指定包下所有的注解先不生效-->
    <context:component-scan base-package="cn.xnj.ioc_01" use-default-filters="false">
    <!--只扫描包下的注解-->
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
    </context:component-scan>
  4. 组件BeanName问题
    在我们使用 XML 方式管理 bean 的时候,每个 bean 都有一个唯一标识——id 属性的值,便于在其他地方引用。现在使用注解后,每个组件仍然应该有一个唯一标识。
    默认情况:
    类名首字母小写就是 bean 的 id。例如:SoldierController 类对应的 bean 的 id 就是 soldierController。
    使用value属性指定:(当注解中只设置一个属性时,value属性的属性名可以省略)
    1
    2
    3
    //@Controller("tianDog")
    @Controller(value = "tianDog")
    public class SoldierController {}
  5. 测试类(cn.xnj.test.springIocTest)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class springIoCTest {

    @Test
    public void test_01(){

    //1.创建ioc容器
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext ("spring-01.xml");
    //获取组件
    CommonComponent bean = context.getBean(CommonComponent.class);
    System.out.println(bean);// cn.xnj.ioc_01.CommonComponent@60db1c0e
    //添加ioc注解,默认组件的名为 类的首字母小写
    Object bean1 = context.getBean("commonComponent");
    System.out.println(bean==bean1);// true

    //当该注解被排除,运行报错;.NoSuchBeanDefinitionException
    XxxService service = context.getBean(XxxService.class);
    System.out.println(service);//cn.xnj.ioc_01.service.XxxServiceImpl@769a1df5
    }
    }
  6. 总结
    1. 注解方式IoC只是标记哪些类要被Spring管理
    2. 最终,我们还需要XML方式或者后面讲解Java配置类方式指定注解生效的包
    3. 现阶段配置方式为 注解 (标记)+ XML(扫描)

组件(Bean)作用域和周期方法注解

  1. 组件周期方法配置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @Component //注册到ioc容器中,默认组件的名为 类的首字母小写
    public class BeanOne {
    //周期方法要求: 方法命名随意,但是要求方法必须是 public void 无形参列表
    @PostConstruct //注解制指定初始化方法
    public void init() {
    // 初始化逻辑
    System.out.println("BeanOne 初始化");
    }

    @PreDestroy //注解指定销毁方法
    public void destory() {
    // 释放资源逻辑
    System.out.println("BeanOne 销毁");
    }
    }
  2. 组件作用域配置
    1
    2
    3
    4
    5
    6
    @Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON) //单例,默认值
    @Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE) //多例 二选一
    @Component
    public class BeanOne {

    }
  3. 扫描配置(创建配置文件spring-02.xml)
    <context:component-scan base-package="cn.xnj.ioc_02"/>
  4. 测试类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    @Test
    public void test_02() {
    //1.创建ioc容器
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-02.xml");
    //获取组件
    Object bean = context.getBean("beanOne");
    System.out.println(bean);

    //测试单例(这里测试时设置的单例:@Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON))
    Object bean1 = context.getBean("beanOne");
    System.out.println(bean == bean1);//false

    //2.关闭容器
    context.close();

    /*控制台打印结果:
    BeanOne 初始化
    cn.xnj.ioc_02.BeanOne@3e2055d6
    true
    BeanOne 销毁
    */
    }

Bean属性赋值:引用类型自动装配(DI)

  1. 设定场景
    • UserController 需要 UserService
    • UserServcie 需要 UserDao
  2. 自动装配实现
    a.前提
    参与自动装配的组件(需要装配、被装配)全部都必须在IoC容器中。
    注意:不区分IoC的方式!XML和注解都可以!
    
    b.@Autowired注解
    在成员变量上直接标记@Autowired注解即可,不需要提供setXxx()方法。
    c.给Controller装配Service
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Controller
    public class UserController {
    //<property userService -> 对应的bean装配
    //自动装配注解<DI> :1.ioc容器中查找符合类型的组件对象,2.设置给当前属性(di)
    @Autowired
    private UserService userService;

    public void show(){
    // 调用业务层方法
    userService.show();
    }
    }
    d.给Service装配Dao
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //接口
    public interface UserService {
    public void show();
    }

    //实现类
    @Service
    public class UserServiceImpl implements UserService{
    @Autowired
    private UserDao userDao;

    @Override
    public void show() {
    userDao.show();
    }
    }
    1
    2
    3
    4
    @Component
    public class UserDao {
    public void show() {System.out.println("UserDao show");}
    }
    • 与xml进行bean ref引用不同,他不需要有set方法!
      e.测试类
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      @Test
      public void test_03(){
      //1.创建ioc容器
      ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-03.xml");
      //获取组件
      UserController userController = context.getBean(UserController.class);
      //场景1: ioc容器中有一个UserService接口对应的实现类对象
      userController.show();

      /*控制台输出
      UserDao show*/

      //场景2: ioc容器中没有UserService接口对应的实现类对象
      //@Autowired 使用它进行装配 【默认】情况下至少要求有一个bean,否则会报错!可以指定佛系装配

      //场景3:同一个类型有多个对应的组件@Autowired也会报错! 无法选择
      //解决1: 成员属性指定@Autowired 多个组件的时候,默认会根据成员属性名查找
      //解决2:使用@Qualifier(value="userServiceImpl") 明确指定装配的组件的id,必须配合@Autowired一起使用
      //解决3:使用@Resource(name="userServiceImpl")=@Autowired+@Qualifier(value="userServiceImpl")
      //使用解决3需要引入相关依赖
      }
      f.同一个类型有多个对应的组件(UserServiceImpl和UserServiceImpl1)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      @Controller
      public class UserController {
      @Autowired
      @Qualifier(value = "userServiceImpl1")//指定使用UserServiceImpl1
      private UserService userService;

      public void show(){
      // 调用业务层方法
      userService.show();
      }
      }
      d.@Autowired的工作流程
      首先根据所需要的组件类型到 IOC 容器中查找:
      • 能够找到唯一的 bean:直接执行装配,如果完全找不到匹配这个类型的 bean:装配失败。
      • 如果和所需类型匹配的 bean 不止一个:
        • 没有 @Qualifier 注解:根据 @Autowired 标记位置成员变量的变量名作为 bean 的 id 进行匹配,
        • 有 @Qualifier 注解:根据 @Qualifier 注解中指定的名称作为 bean 的id进行匹配
  3. 佛系装配
    1
    2
    3
    4
    @Autowired(required = false) //佛系装配 boolean required() default false;
    // 佛系装配,不要求有对应的bean对象
    // 不推荐使用佛系装配,因为如果没有对应的bean对象,会出现空指针异常!
    private UserService userService;
  4. 扩展JSR-250注解@Resource
    • @Resource注解默认根据Bean名称装配,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型装配。
    • @Autowired注解默认根据类型装配,如果想根据名称装配,需要配合@Qualifier注解一起用。
      @Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引入以下依赖:【高于JDK11或低于JDK8需要引入以下依赖】
      1
      2
      3
      4
      5
      6
      <!--jsr-250注解 Java提供的注解,spring提供了一个@Resource-->
      <dependency>
      <groupId>jakarta.annotation</groupId>
      <artifactId>jakarta.annotation-api</artifactId>
      <version>2.1.1</version>
      </dependency>
      @Resource使用
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      @Controller
      public class UserController {
      //@Autowired
      //@Qualifier(value = "userServiceImpl1")
      @Resource(name = "userServiceImpl1")
      private UserService userService;

      public void show(){
      // 调用业务层方法
      userService.show();
      }
      }
      • Resource别导错包:import jakarta.annotation.Resource;

Bean属性赋值:基本类型属性赋值(DI)

  1. @Value直接给属性赋值
    a.准备演示的组件类(cn.xnj.ioc_04/User)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Component
    public class User {
    //直接赋值 ,不推荐,这种建议直接private int age=18;即可
    @Value("18")
    private int age;

    private String username;
    private String password;

    @Override
    public String toString() {
    return "User{" +
    "age=" + age +
    ", username='" + username + '\'' +
    ", password='" + password + '\'' +
    '}';
    }
    }
    b.配置文件扫描注解(resource/spring-04.xml)
    <context:component-scan base-package="cn.xnj.ioc_04"/>
    • 这种使用不推荐,一般使用@Value注解是为了读取外部配置
  2. @Value读取外部配置
    a.声明外部配置(resource/user.properties)
    1
    2
    user.username="admin"
    user.password="123456"
    b.配置文件(spring-04.xml)
    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"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <!--1.扫描-->
    <context:component-scan base-package="cn.xnj.ioc_04"/>
    <!--2.引入外部配置文件-->
    <context:property-placeholder location="classpath:user.properties"/>
    <!--
    <property name="" value="${}">
    -->
    </beans>
    c.使用@Value
    1
    2
    3
    4
    @Value("${user.username}")
    private String username;
    @Value("${user.password}")
    private String password;
    d.测试类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Test
    public void test_04(){
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-04.xml");
    User user = context.getBean(User.class);
    System.out.println(user);

    /*控制台输出
    User{age=18, username='"admin"', password='"123456"'}
    */
    }
  3. 默认值写法
    当你使用@Value来引用外部配置但外部配置的属性可能不存在时,可以使用默认值写法
    • @Value(${key:value默认值})
    • 如:@Value("${user.username:张三}")
    • 当配置文件没有user.username属性时,会被赋予默认值张三

基于注解+XML方式整合三层架构组件

和前面基于XMl方式整合三层架构组件没有区别,只是在配置的时候,不需要再在spring.xml配置文件中来注入service,controller等的bean了,只需要扫描项目的包,然后在相应的类上加上@Controller,@Service,@Repository注解即可,装配就使用@Autowired,但第三方的jdbcTemplatedruidDataSource仍然按配置文件的方式配置。这样也就能看出来注解+XMLIOC方法的问题:

  • 自定义类可以使用注解方式,但是第三方依赖的类依然使用XML方式!
  • XML格式解析效率低!

基于配置类方式管理Bean

使用配置类方式管理Bean就可以摆脱xml配置文件,实现全注解开发

配置类和扫描注解

原本我们使用注解,需要在xml配置文件中扫描包
引入外部配置文件,需要在xml配置文件中配置外部文件

  1. 创建基本模块(cn.xnj.ioc_01)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    @Controller
    public class StudentController {
    @Autowired
    private StudentService studentService;

    public void show(){
    studentService.show();
    }
    }

    public interface StudentService {

    public void show();
    }

    @Service
    public class StudentServiceImpl implements StudentService{

    @Override
    public void show() {
    System.out.println("studnetService show");
    }
    }
  2. 创建配置类(cn.xnj.config)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /**
    * 1.@Configuration java配置类注解,代替xml配置文件
    * 2.@ComponentScan 包扫描注解,可扫描多个包@ComponentScan({"com.xx.xx", "com.xx.xx"})
    * 3.@PropertySource 加载外部配置文件 @PropertySource(value="classpath:jdbc. properties")
    * 4.
    */

    @Configuration
    @ComponentScan("cn.xnj.ioc_01")
    @PropertySource(value="classpath:jdbc.properties")
    public class MyConfiguration {
    }
  3. 测试创建容器
    1
    2
    // AnnotationConfigApplicationContext 根据配置类创建 IOC 容器对象
    ApplicationContext iocContainerAnnotation = new AnnotationConfigApplicationContext(MyConfiguration.class);
    可以使用 no-arg 构造函数实例化 AnnotationConfigApplicationContext ,然后使用 register()方法对其进行配置。此方法在以编程方式生成 AnnotationConfigApplicationContext 时特别有用。以下示例演示如何执行此操作:
    1
    2
    3
    4
    5
    6
    7
    // AnnotationConfigApplicationContext-IOC容器对象
    ApplicationContext iocContainerAnnotation =
    new AnnotationConfigApplicationContext();
    //外部设置配置类
    iocContainerAnnotation.register(MyConfiguration.class);
    //刷新后方可生效!!
    iocContainerAnnotation.refresh();
  4. 测试类(test/java/cn.xnj.test.SpringIoCTest)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class SpringIoCTest {
    @Test
    public void test_01(){
    //1.创建容器
    //ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfiguration.class);
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    applicationContext.register(MyConfiguration.class);
    applicationContext.refresh();
    //2.获取bean
    StudentController studentController = applicationContext.getBean (StudentController.class);
    studentController.show();
    }
    }

@Bean定义组件

注:@Bean注解只能用在配置类里面定义bean
场景需求:将Druid连接池对象存储到IoC容器
需求分析:第三方jar包的类,添加到ioc容器,无法使用@Component等相关注解!因为源码jar包内容为只读模式!
jdbc.properties

1
2
3
4
jdbc.url=jdbc:mysql://localhost:3306/studb
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.username=root
jdbc.password=123456

使用xml方式实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">


<!-- 引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>

<!-- 给bean的属性赋值:引入外部属性文件 -->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

</beans>

使用配置类的方式实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Configuration
@ComponentScan("cn.xnj.ioc_01")
@PropertySource(value="classpath:jdbc.properties")
public class MyConfiguration {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;

/**
* bean 一个方法
* 方法的返回值 == bean组件的类型或者他的接口和父类
* 方法的名字 = bean id
*
* 方法体可以自定义实现的过程即可
* @Bean 会真正让配置类的方法创建的组件存储到ioc容器
*
*/
@Bean
public DruidDataSource dataSource(){
//实现具体的实例化过程
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(url);
dataSource.setDriverClassName(driver);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}

@Bean注解的细节

  1. BeanName
    bean的名字默认为方法名,也可以在注解中指定名字,value | name
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //@Bean(name="dataSource1")
    @Bean(value = "dataSource1")
    public DruidDataSource dataSource(){
    //实现具体的实例化过程
    DruidDataSource dataSource = new DruidDataSource();
    dataSource.setUrl(url);
    dataSource.setDriverClassName(driver);
    dataSource.setUsername(username);
    dataSource.setPassword(password);
    return dataSource;
    }
  2. 指定周期方法
    在注解中指定initMethod,destroyMethod
    1
    2
    3
    4
    @Bean(value = "dataSource" ,initMethod="",destroyMethod = "")
    public DruidDataSource dataSource(){
    return dataSource;
    }
  3. 作用域
    方法上添加@Scope注解

    • @Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON)//单例
    • @Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE)//多例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Bean
    @Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON)//单例
    public DruidDataSource dataSource(){

    //实现具体的实例化过程
    DruidDataSource dataSource = new DruidDataSource();
    dataSource.setUrl(url);
    dataSource.setDriverClassName(driver);
    dataSource.setUsername(username);
    dataSource.setPassword(password);
    return dataSource;
    }
  4. Bean方法依赖

    • 直接调用(不推荐)
    • 形参列表引用(推荐)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    @Bean("zuzubase")
    public DruidDataSource dataSource1(){

    //实现具体的实例化过程
    DruidDataSource dataSource = new DruidDataSource();
    dataSource.setUrl(url);
    dataSource.setDriverClassName(driver);
    dataSource.setUsername(username);
    dataSource.setPassword(password);
    return dataSource;
    }

    @Bean("wangwangbase")
    public DruidDataSource dataSource2(){

    //实现具体的实例化过程
    DruidDataSource dataSource = new DruidDataSource();
    dataSource.setUrl(url);
    dataSource.setDriverClassName(driver);
    dataSource.setUsername(username);
    dataSource.setPassword(password);
    return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(){
    JdbcTemplate jdbcTemplate = new JdbcTemplate();
    //需要DataSource 需要ioc容器的其他组件
    //方式一:直接调用 需要其他组件也是@Bean方法
    jdbcTemplate.setDataSource(dataSource1());
    return jdbcTemplate;
    }

    @Bean
    public JdbcTemplate jdbcTemplate1(DataSource zuzubase,DataSource wangwangbase ){
    JdbcTemplate jdbcTemplate = new JdbcTemplate();
    //需要DataSource 需要ioc容器的其他组件
    //方式二:形参列表声明想要的组件类型,可以是一个也可以是多个!ioc容器也会注入
    //如果没有:形参变量注入,要求必须有对应的类型组件,如果没有会报异常!
    //如果有多个:可以使用形参名称等同域对应的beanid标识即可
    jdbcTemplate.setDataSource(zuzubase);
    return jdbcTemplate;
    }

@Import注解的扩展

@Import 注释允许从另一个配置类加载 @Bean 定义.如以下示例所示:

1
2
3
4
5
6
7
8
9
10
@Configuration
public class ConfigurationA {

}

@Configuration
@Import(ConfigurationA.class)
public class ConfigurationB {

}

现在,在实例化上下文时不需要同时指定 ConfigurationA.class 和 ConfigurationB.class ,只需显式提供 ConfigurationB ,如以下示例所示
1
2
3
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
}

此方法简化了容器实例化,因为只需要处理一个类,而不是要求在构造期间记住可能大量的 @Configuration 类。也可以不同的配置类管理不同种类的配置,最后交由一个配置类,既方便管理配置,也方便构造。

基于注解和配置类方式整合三层架构组件

  1. 需求分析
    搭建一个三层架构案例,模拟查询全部学生(学生表)信息,持久层使用JdbcTemplate和Druid技术,使用注解+配置类方式进行组件管理!
  2. 数据库准备
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    create database studb;

    use studb;

    CREATE TABLE students (
    id INT PRIMARY KEY,
    name VARCHAR(50) NOT NULL,
    gender VARCHAR(10) NOT NULL,
    age INT,
    class VARCHAR(50)
    );

    INSERT INTO students (id, name, gender, age, class)
    VALUES
    (1, '张三', '男', 20, '高中一班'),
    (2, '李四', '男', 19, '高中二班'),
    (3, '王五', '女', 18, '高中一班'),
    (4, '赵六', '女', 20, '高中三班'),
    (5, '刘七', '男', 19, '高中二班'),
    (6, '陈八', '女', 18, '高中一班'),
    (7, '杨九', '男', 20, '高中三班'),
    (8, '吴十', '男', 19, '高中二班');
  3. 项目准备
    a.项目创建
    spring-java-partice-05
    b.导入依赖
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    <dependencies>
    <!--spring context依赖-->
    <!--当你引入SpringContext依赖之后,表示将Spring的基础依赖引入了-->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>6.0.6</version>
    </dependency>

    <!-- 数据库驱动和连接池-->
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.25</version>
    </dependency>

    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.8</version>
    </dependency>

    <dependency>
    <groupId>jakarta.annotation</groupId>
    <artifactId>jakarta.annotation-api</artifactId>
    <version>2.1.1</version>
    </dependency>

    <!-- spring-jdbc -->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>6.0.6</version>
    </dependency>

    </dependencies>
    c.实体类准备(cn.xnj.pojo)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    public class Student {

    private Integer id;
    private String name;
    private String gender;
    private Integer age;
    private String classes;

    public Integer getId() {
    return id;
    }

    public void setId(Integer id) {
    this.id = id;
    }

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public String getGender() {
    return gender;
    }

    public void setGender(String gender) {
    this.gender = gender;
    }

    public Integer getAge() {
    return age;
    }

    public void setAge(Integer age) {
    this.age = age;
    }

    public String getClasses() {
    return classes;
    }

    public void setClasses(String classes) {
    this.classes = classes;
    }

    @Override
    public String toString() {
    return "Student{" +
    "id=" + id +
    ", name='" + name + '\'' +
    ", gender='" + gender + '\'' +
    ", age=" + age +
    ", classes='" + classes + '\'' +
    '}';
    }
    }
    d.配置文件(jdbc.properties)
    1
    2
    3
    4
    jdbc.url=jdbc:mysql://localhost:3306/studb?useUnicode=true&characterEncoding=utf8&  useSSL=false
    jdbc.driver=com.mysql.cj.jdbc.Driver
    jdbc.username=root
    jdbc.password=123456
  4. 三层架构实现
    a.表述层(cn.xnj.controller)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Controller
    public class StudentController {
    @Autowired
    private StudentService studentService;

    public void findAll(){
    List<Student> studentList = studentService.findAll();
    System.out.println("studentList = " + studentList);
    }
    }
    b.业务层(cn.xnj.service)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //接口
    public interface StudentService {
    List<Student> findAll();
    }

    //实现类
    @Service
    public class StudentServiceImpl implements StudentService{
    @Autowired
    private StudentDao studentDao;

    @Override
    public List<Student> findAll() {
    return studentDao.queryAll();
    }
    }
    c.持久层(cn.xnj.dao)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    //接口
    public interface StudentDao {

    List<Student> queryAll();
    }

    //实现类
    @Repository
    public class StudentDaoImpl implements StudentDao{
    @Autowired
    private JdbcTemplate jdbcTemplate;
    /**
    * 查询全部学生数据
    * @return
    */
    @Override
    public List<Student> queryAll() {
    String sql = "select id , name , age , gender , class as classes from students ;";
    /*
    query可以返回集合!
    BeanPropertyRowMapper就是封装好RowMapper的实现,要求属性名和列名相同即可
    */
    List<Student> studentList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Student.class));
    return studentList;
    }
    }
  5. 三层架构IoC配置类(cn.xnj.config)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    @Configuration
    @ComponentScan("cn.xnj")
    @PropertySource("classpath:jdbc.properties")
    public class MyConfiguration {

    @Bean
    public DruidDataSource dataSource(@Value("${jdbc.url}") String url,
    @Value("${jdbc.driver}") String driver,
    @Value("${jdbc.username}")String username,
    @Value("${jdbc.password}") String password){
    DruidDataSource dataSource = new DruidDataSource();
    dataSource.setUrl(url);
    dataSource.setDriverClassName(driver);
    dataSource.setUsername(username);
    dataSource.setPassword(password);
    return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DruidDataSource dataSource){
    JdbcTemplate jdbcTemplate = new JdbcTemplate();
    jdbcTemplate.setDataSource(dataSource);
    return jdbcTemplate;
    }
    }
  6. 运行测试(test/java/cn.xnj.test)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class SpringIoCTest {
    @Test
    public void test(){
    ApplicationContext context = new AnnotationConfigApplicationContext (MyConfiguration.class);

    StudentController studentController = context.getBean(StudentController. class);
    studentController.findAll();
    }
    }
  7. 注解+配置类 IoC方式总结
    1. 完全摒弃了XML配置文件
    2. 自定义类使用IoC和DI注解标记
    3. 第三方类使用配置类声明方法+@Bean方式处理
    4. 完全注解方式(配置类+注解)是现在主流配置方式

三种配置方式总结

基于XML配置方式总结

  1. 所有内容写到xml格式配置文件中
  2. 声明bean通过<bean 标签
  3. <bean标签包含基本信息(id,class)和属性信息 <property name value / ref
  4. 引入外部的properties文件可以通过<context:property-placeholder
  5. IoC具体容器实现选择ClassPathXmlApplicationContext对象

基于XML配置文件和注解方式总结

  1. 注解负责标记IoC的类和进行属性装配
  2. xml文件依然需要,需要通过<context:component-scan标签指定注解范围
  3. 标记IoC注解:@Component,@Service,@Controller,@Repository
  4. 标记DI注解:@Autowired @Qualifier @Resource @Value
  5. IoC具体容器实现选择ClassPathXmlApplicationContext对象

基于配置类的完全注解方式总结

  1. 完全注解方式指的是去掉xml文件,使用配置类 + 注解实现
  2. xml文件替换成使用@Configuration注解标记的类
  3. 标记IoC注解:@Component,@Service,@Controller,@Repository
  4. 标记DI注解:@Autowired @Qualifier @Resource @Value
  5. <context:component-scan标签指定注解范围使用@ComponentScan(basePackages = {"com.atguigu.components"})替代
  6. <context:property-placeholder引入外部配置文件使用@PropertySource({"classpath:application.properties","classpath:jdbc.properties"})替代
  7. <bean 标签使用@Bean注解和方法实现
  8. IoC具体容器实现选择AnnotationConfigApplicationContext对象

整合Spring5-Test5搭建测试环境

  1. 整合测试环境的作用
    • 不再需要自己每次创建IoC容器
    • 任何需要的bean都可以在测试类中直接享受自动装配
  2. 导入相关依赖
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <!--junit5测试-->
    <dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.3.1</version>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>6.0.6</version>
    <scope>test</scope>
    </dependency>
  3. 创建演示项目
    a.项目创建
    spring-ioc-test5-06
    b.创建演示
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    //配置类(cn.xnj.config)
    @Configuration
    @ComponentScan("cn.xnj")
    public class MyConfiguration {
    }

    //组件(cn.xnj.ioc)
    @Component
    public class JavaBeanA {
    public void sayHello() {
    System.out.println("Hello JavaBeanA!");
    }
    }

    @Component
    public class JavaBeanB {
    public void sayHello() {
    System.out.println("Hello JavaBeanB!");
    }
    }
  4. 整合测试注解使用
    创建测试类(test/java/cn.xnj.test.SpringIoCTest)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    //@SpringJUnitConfig(locations = {"classpath:spring-context.xml"})  //指定配置文件xml
    @SpringJUnitConfig(value = {MyConfiguration.class}) //指定配置类
    public class SpringIoCTest {

    @Autowired
    private JavaBeanA javaBeanA;
    @Autowired
    private JavaBeanB javaBeanB;

    @Test
    public void testJunit5() {
    System.out.println(javaBeanA);
    javaBeanA.sayHello();
    System.out.println(javaBeanB);
    javaBeanB.sayHello();

    /* 控制台输出
    cn.xnj.ioc.JavaBeanA@934b6cb
    Hello JavaBeanA!
    cn.xnj.ioc.JavaBeanB@55cf0d14
    Hello JavaBeanB!
    */
    }
    }
  5. 拓展测试注解用法—@BeforeEach,@AfterEach

  • @BeforeEach 可以让每个测试方法在执行之前,先执行这个方法,
  • @AfterEach 可以让每次执行测试方法后执行该方法

当每个测试方法都有同样的开始和结束时,可以使用上面的注解来减少冗余代码。使用场景举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class MyBatisTest {

private SqlSession session;

@BeforeEach// 每次执行测试方法前执行该方法
public void before() throws IOException {
String mybatisConfigFilePath = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(mybatisConfigFilePath);
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
session = sessionFactory.openSession(true);// 自动提交事务

}

@AfterEach // 每次执行测试方法后执行该方法
public void clean(){
session.close(); //关闭会话
}


@Test // 测试方法:根据id查询员工数据方法
public void test_01() throws IOException {

// 只需写核心测试的逻辑了
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
Employee employee = employeeMapper.selectEmployeeById(1);
System.out.println("employee = " + employee);

}

}