Spring6--IOC反转控制 / 基于XML管理bean

1. 容器IOC

先理解概念,再进行实际操作。概念比较偏术语化,第一次看可能看不懂,建议多看几遍,再尝试自己独立复述一遍,效果会好些


1.1. IOC容器

1.1.1. 控制反转(IOC)

IOC (Inversion of Control) ,中文译为"控制反转",是一种设计模式或者说设计原则,其核心理念是将原本在程序内部手动创建和管理对象(比如使用 `new` 关键字直接实例化对象)的责任转移给外部容器(如Spring框架中的IoC容器)。这种转移使得对象不再自己控制生命周期和相互依赖关系,而是由容器统一管理和维护。

在Spring框架中,IoC容器就像一个管家,负责创建对象(即所谓的"Spring Bean",我们将由 IoC 容器管理的 Java 对象称为 Spring Bean)、管理它们的生命周期,并在Bean之间自动装配依赖关系。这意味着开发者不需要显式编码去创建或管理对象间的连接,从而极大地降低了代码之间的耦合度,提高了模块的可复用性和可测试性。

耦合度:

假设你正在做饭,你需要切菜和炒菜。如果每次炒菜前,你都必须先自己种菜、摘菜、洗菜,然后再切菜,那说明你炒菜的步骤跟种菜、摘菜、洗菜的步骤紧紧绑在一起,这就像是程序中的高耦合度------每个部分的工作都直接影响着另一个部分。

但如果有一个"厨房助手"帮你完成了种菜、摘菜、洗菜的工作,并且把干净的菜直接送到你面前让你切,那你只需要专注于炒菜就好,这就是降低了耦合度。在软件开发中,"厨房助手"就像是IoC容器,它帮你处理对象的创建和维护依赖关系等工作,使得各个组件(类或模块)能够独立运作,互不影响。

所以,耦合度简单来说就是指各个部分相互关联、影响的程度。在软件设计中,我们希望各个部分尽量独立、解耦,这样当修改其中一个部分时,不会连带影响到其他部分,从而使程序更易于维护和升级。

举例来说,你不用再在代码里写 `new UserService()` 去创建用户服务类的实例,而是通过配置文件或者注解告诉Spring容器应该怎样创建和管理UserService,Spring容器会根据配置自动创建并注入到需要的地方。这就是"控制反转",控制权从程序员手中交给了容器,让容器成为组件生命周期和依赖关系的管理者。


反转的是什么?

  • 对象创建的权利反转:原本由代码直接创建对象的过程转变为由IoC容器负责创建对象,容器按照预先设定的配置或规则来实例化对象。
  • 对象间关系的维护权反转:对象间的依赖关系不再是硬编码在对象内部,而是由IoC容器动态地将依赖对象注入到需要它们的对象中。

如何实现控制反转?

  • 依赖注入(Dependency Injection, DI) 是实现控制反转的主要手段之一。通过依赖注入,对象声明它需要哪些依赖(通常是接口),然后依赖的具体实现是由容器在运行时注入的。这样,对象无需关心依赖对象的创建细节,只需使用容器提供的依赖即可。

1.1.2. 依赖注入(DI)

依赖注入:指Spring创建对象的过程中,将对象依赖属性通过配置进行注入


举个栗子:

想象一下你在运营一家餐厅,每天需要制作汉堡。传统的做法是你自己买面包、肉饼、生菜等材料,然后亲手组合起来制作汉堡。这就好比在编程中,对象A需要对象B时,就自己创建一个新的对象B来使用。

现在引入"控制反转"(IoC)的概念,就好比你雇佣了一个经理来帮你经营餐厅。这位经理不仅负责采购所有食材,而且在你需要做汉堡时,他会直接将组装好的汉堡所需的所有材料(面包、肉饼、生菜)准备好并递给你。这里的"反转"是指原来由你自己做的事情(找材料、组装汉堡),现在改由外部的"经理"(IoC容器)来完成。

而"依赖注入"(DI)就是这位"经理"为你准备材料的具体操作方式:

  1. Set注入 类似于经理告诉你:"你要做汉堡了,快拿好这块面包(通过setBread方法)。" 你有个方法叫setBread(Bread bread),经理就把面包塞进来了。
  2. 构造注入 则像经理直接拿着所有材料来到你面前说:"看,这是你需要的所有东西(通过构造函数传递)。" 你有一个构造函数MyHamburger(Bread bread, Patty patty, Lettuce lettuce),经理就是用这些材料来创建你的汉堡对象。

总结一下:

  • IoC 是一种设计理念,即不再由对象本身控制和管理依赖关系,而是交给外部容器。
  • DI 是实现IoC的具体方法,它通过构造函数或setter方法将依赖的对象"注入"到需要它的对象中,而不是对象自己去找或创建这些依赖。这样既简化了对象的创建过程,又降低了不同对象之间的耦合度,方便了后续的维护和扩展。

1.1.3. IOC容器在Spring的实现

在Spring框架中,IoC(Inversion of Control,控制反转)容器是用于实现依赖注入(DI)这一设计模式的核心组件。它负责管理应用程序中的对象(也就是所说的"bean"),包括对象的创建、初始化、装配依赖关系以及管理对象的生命周期。


BeanFactory

  • BeanFactory是IoC容器的原始基础实现,它是最底层的接口,提供了Spring框架用来创建和管理bean的基本功能,如获取、实例化和配置bean等。由于它是Spring内部使用的基础设施接口,通常不对最终开发人员暴露,主要用于Spring内部的实现细节,以保持框架的灵活性和扩展性。
  • 想象一下,BeanFactory就像是一个工厂,它知道如何制造和管理所有类型的玩具(这里比喻为bean)。但它主要是Spring框架内在使用的"幕后英雄",一般不直接推荐开发者直接操作。

ApplicationContext

  • ApplicationContext是BeanFactory的扩展接口,它继承了BeanFactory的所有功能,并在此基础上增加了许多额外的便利功能,更适合于实际的开发场景。例如,ApplicationContext会预加载所有的单例bean,支持国际化、事件发布/监听、AOP(面向切面编程)等功能,并且提供了更多的资源访问能力。
  • 而ApplicationContext则是BeanFactory的升级版超级工厂,不仅能完成BeanFactory的所有任务(制造和管理玩具),还能提供更多附加服务,比如提前准备好所有玩具、支持多语言环境、发送通知消息等。这就是为什么在大多数情况下,开发者会直接与ApplicationContext打交道,因为它更强大,更好用。

所以,在日常的Spring应用开发中,我们几乎总是直接使用ApplicationContext作为IoC容器,因为它提供了更加丰富的功能集和更高的便捷性。开发人员通常会通过ClassPathXmlApplicationContext、FileSystemXmlApplicationContext或其他方式来创建ApplicationContext实例,并通过它来获取和管理应用程序中的所有bean。

总的来说,IoC容器是用来帮我们创建和管理对象(bean)的,BeanFactory是基础版本,偏向于底层实现;而ApplicationContext是更高级、更全面的版本,是我们平时开发过程中接触和使用的主力工具。

ApplicationContext的主要实现类

|---------------------------------|----------------------------------------------------------------------------------------------|
| 类型名 | 简介 |
| ClassPathXmlApplicationContext | 通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象 |
| FileSystemXmlApplicationContext | 通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象 |
| ConfigurableApplicationContext | ApplicationContext 的子接口,包含一些扩展方法 refresh() 和 close() ,让 ApplicationContext 具有启动、关闭和刷新上下文的能力。 |
| WebApplicationContext | 专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中。 |


2. 基于XML管理Bean

这一个大章节的视频案例可以结合尚硅谷Spring6视频看 --> 我是链接,点我

在这之前,建议在父工程中的pom文件里导入我们需要的依赖,这样其里面的子工程会自动添加,会方便许多~

父工程(Spring6)的pom.xml文件

导入相关依赖

<!-- 依赖关系定义 -->
    <dependencies>
        
        <!--log4j2的依赖-->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.19.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j2-impl</artifactId>
            <version>2.19.0</version>
        </dependency>
        
        <!-- 引入Spring Context依赖,它是Spring框架的基础模块,提供了环境配置、Bean生命周期管理、事件传播等功能 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.2</version>
        </dependency>
        
        <!-- 引入JUnit5测试框架,用于编写和运行单元测试 -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.3.1</version>
        </dependency>
    </dependencies>

设置成功刷新后,Maven里应该是这样子的


2.1. 搭建子模块spring6-ioc-xml

在Spring6父工程中创建子模块

在子模块中创建配置文件--bean.xml

注意,是要创建在resources文件夹下,不然可能程序运行时找不到你的Spring配置文件,别问我是怎么知道的(T_T)

创建相关的User类,以便后续测试使用,注意有分包的好习惯

package com.sakurapaid.spring.iocxml;

public class User {
    private String name;
    private int age;

    public void test() {
        System.out.println("这是一个test测试输出~( ̄▽ ̄)/");
    }
}

2.2. 实验一:获取bean实例

首先在bean.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="user" class="com.sakurapaid.spring.iocxml.User">
    
    </bean>
</beans>

在Spring的XML配置文件中,<bean>元素用于定义一个Bean实例。id属性为Bean定义一个唯一标识符,可以通过这个标识符在其他地方引用这个Bean。class属性指定了创建Bean实例时使用的类,它告诉Spring容器要实例化哪个类。

例如,在示例中,<bean id="user" class="com.sakurapaid.spring.iocxml.User">表示Spring将实例化一个com.sakurapaid.spring.iocxml.User类的Bean,并将其标识符设置为"user"。(这样就省去了自己手动创建实例化对象相关的操作)

再创建一个相关的测试类UserTest.Java

package com.sakurapaid.spring.iocxml;

public class UserTest {
    public static void main(String[] args) {

    }
}

2.2.1. 根据id获取

// 方式一:根据Bean的id获取实例
User user1 = (User) context.getBean("user");
System.out.println(user1);

2.2.2. 根据Bean的类型获取实例

// 方式二:根据Bean的类型获取实例
User user2 = (User) context.getBean(User.class);
System.out.println(user2);

2.2.3. 根据Bean的id和类型获取实例

//方式三:根据Bean的id和类型获取实例
User user3 = context.getBean("user", User.class);
System.out.println(user3);

输出结果都是一样的

/**
 * UserTest类用于演示通过XML配置文件进行Spring IOC(Inverse of Control)的简单示例。
 */
package com.sakurapaid.spring.iocxml;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class UserTest {
    /**
     * 主函数入口,演示了三种方式从Spring容器中获取Bean实例。
     */
    public static void main(String[] args) {
        // 创建Spring容器,使用ClassPathXmlApplicationContext加载名为"bean.xml"的配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");

        // 方式一:根据Bean的id获取实例
        User user1 = (User) context.getBean("user");
        System.out.println(user1);

        // 方式二:根据Bean的类型获取实例
        User user2 = (User) context.getBean(User.class);
        System.out.println(user2);

        // 方式三:根据Bean的id和类型获取实例
        User user3 = context.getBean("user", User.class);
        System.out.println(user3);
    }
}

在传统的编程方式中,我们通常需要在代码中通过 new 关键字创建对象实例,同时还需要手动管理对象之间的依赖关系。然而,在这段Spring的IoC示例中:

我们首先通过 ClassPathXmlApplicationContext 加载了名为 "bean.xml" 的Spring配置文件,这个配置文件中定义了Bean(这里是 User 类的实例)的创建方式和依赖关系。

然后,我们通过三种不同的方式从Spring容器中获取 User 类的实例:

  • 直接通过Bean的id("user")获取。
  • 根据Bean的类型(User.class)获取唯一匹配的Bean。
  • 同时指定Bean的id和类型获取实例。

在以上每一步中,都没有直接执行 new User() 来创建对象,而是由Spring IoC容器负责创建并管理这些对象。这就是所谓的"控制反转":对象的创建控制权从应用代码转移到了Spring容器,从而实现了对象的生命周期管理、依赖关系注入等高级功能,减少了代码之间的耦合度,增强了系统的可维护性和可扩展性。


2.2.4. 注意点

  1. 当在Spring的IoC容器中配置了两个相同类型的Bean,会怎么样?

当在Spring的IoC容器中配置了两个相同类型的Bean时,例如:

<bean id="user1" class="com.sakurapaid.spring.iocxml.User"></bean>
<bean id="user2" class="com.sakurapaid.spring.iocxml.User"></bean>

这时,如果你试图仅根据类型(User.class)来获取Bean,Spring容器会因为无法确定具体应该返回哪个Bean实例而抛出异常。具体异常如下所示:

org.springframework.beans.factory.NoUniqueBeanDefinitionException: 
No qualifying bean of type 'com.sakurapaid.spring.iocxml.User' available: 
expected single matching bean but found 2: user1, user2

这段错误信息表明,当Spring容器中存在多个同类型Bean定义时,若仅根据类型来查找,容器无法唯一确定应该返回哪一个Bean实例,因为按照类型查找期望得到的是唯一的匹配Bean。

在这种情况下,如果需要根据类型获取Bean,需要确保容器中对应类型的Bean只有一个,或者通过Bean的id来明确指定要获取的Bean。


  1. 如果就一个实现类实现了接口,根据接口类型可以获取 bean 吗?

UserDao接口

UserDaoImp01实现类

测试类

bean.xml配置文件

因为UserDao是一个接口,接口是不能有自己的对象,只能由实现它的类userDaoImp01来创建对象,所以下面

<bean id="userDaoImp01" 
  class="com.sakurapaid.spring.iocxml.interf.UserDaoImp01">
    
</bean>

<bean> 元素是用来声明和定义Spring IoC容器中托管的一个Bean实例。

id="userDaoImp01" **:**这部分指定了在Spring容器中的Bean的唯一标识符,通过这个ID,你可以从容器中获取或引用这个Bean实例。

class="com.sakurapaid.spring.iocxml.interf.UserDaoImp01" 这部分定义了Bean的实现类,即当Spring容器创建这个Bean时,会使用这个类的信息来生成Bean的实例。尽管UserDao是个接口,但在实际应用中,我们需要通过其实现类 UserDaoImp01 来创建对象,因为接口不能实例化,只有具体的实现类才能生成对象实例。

此配置意味着Spring IoC容器将在启动时根据UserDaoImp01类的无参构造函数来创建一个Bean实例,并将其注册到容器中,之后在任何需要UserDao接口的地方,都可以通过Spring容器自动注入或通过getBean方法根据Bean的ID(这里是"userDaoImp01")来获取到这个实现了UserDao接口的具体实例。这就是Spring框架中的依赖注入(DI)机制。

测试输出结果

所以回答最上面的问题,如果就一个实现类实现了接口,根据接口类型可以获取 bean 吗?

是的,如果只有一个实现类实现了接口,并且这个实现类已经在Spring的IoC容器中配置为一个Bean,那么根据接口类型是可以成功获取到对应的Bean实例的。

前提条件:

  1. 实现类已经在Spring配置文件(如:bean.xml)中被正确配置为一个Bean。
  2. 配置时指定了实现类的全限定名作为 class****属性的值。
  3. 在IoC容器中根据接口类型获取Bean时,容器中没有其他同样实现了该接口的Bean定义,即接口的实现类在容器中是唯一的。

例如在Spring配置文件中:

<bean id="userDao" class="com.sakurapaid.spring.iocxml.interf.UserDaoImp01"/>

然后在Java代码中,可以这样获取Bean:

ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
UserDao userDao = context.getBean(UserDao.class);

此时,由于只有 UserDaoImp01****实现了 UserDao****接口,并且已经被配置成Bean,因此根据 UserDao.class****类型可以从容器中顺利获取到对应的Bean实例。


  1. 如果一个接口有多个实现类,这些实现类都配置了 bean,根据接口类型可以获取 bean 吗?

不可以,如果一个接口有多个实现类,并且这些实现类都被配置成了Spring容器中的Bean,那么直接根据接口类型来获取Bean将会抛出NoUniqueBeanDefinitionException异常,因为Spring容器无法确定应该返回哪个Bean实例。

例如,有两个实现类UserDaoImp01和UserDaoImp02都实现了UserDao接口,并且都在Spring配置文件中配置为Bean:

<bean id="userDaoImp01" class="com.sakurapaid.spring.iocxml.interf.UserDaoImp01"/>
<bean id="userDaoImp02" class="com.sakurapaid.spring.iocxml.interf.UserDaoImp02"/>

2.3. 实验二:依赖注入之setter注入

总体结构

创建学生类Student

package com.sakurapaid.spring.iocxml.di;

public class Student {
    private String name;
    private int age;
    
    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }

    public String toString() {
        return "Student{name = " + name + ", age = " + age + "}";
    }
}

bean-di.xml 配置文件

这里的 <property> 元素对应于 Student 类中的setter方法

<?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">
    
    <!-- 定义一个名为student的bean,类型为com.sakurapaid.spring.iocxml.di.Student -->
    <bean id="student" class="com.sakurapaid.spring.iocxml.di.Student">
        <!-- 设置student的name属性为"张三" -->
        <property name="name" value="张三"> </property>
        <!-- 设置student的age属性为18 -->
        <property name="age" value="18"> </property>
    </bean>
</beans>

测试类

当Spring容器读取并解析bean-di.xml配置文件时,会创建一个Student实例,并通过调用对应的setter方法(setName和setAge),将值"张三"注入到name属性,将值18注入到age属性,从而完成依赖注入。

package com.sakurapaid.spring.iocxml.di;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * StudentTest类用于测试通过XML配置文件加载Bean的功能。
 */
public class StudentTest {

    /**
     * 测试方法,通过ApplicationContext从XML配置文件中获取Bean实例。
     */
    @Test
    public void test(){

        // 创建ClassPathXmlApplicationContext实例,并加载名为"bean-di.xml"的配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("bean-di.xml");

        // 通过上下文对象获取名为"student"的Bean实例,其类型为Student类
        Student student = context.getBean("student", Student.class);

        // 打印学生对象的信息
        System.out.println(student);
    }
}

输出结果


2.4. 实验三:依赖注入之构造器注入

在Student类中添加有参构造

package com.sakurapaid.spring.iocxml.di;

public class Student {
    private String name;
    private int age;

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Student(String name) {
    }

    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }

    public String toString() {
        return "Student{name = " + name + ", age = " + age + "}";
    }
}

配置bean------spring-di.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">
    
    <!-- 定义一个名为student的bean,类型为com.sakurapaid.spring.iocxml.di.Student -->
<!--    <bean id="student" class="com.sakurapaid.spring.iocxml.di.Student">
        &lt;!&ndash; 设置student的name属性为"张三" &ndash;&gt;
        <property name="name" value="张三"> </property>
        &lt;!&ndash; 设置student的age属性为18 &ndash;&gt;
        <property name="age" value="18"> </property>
    </bean>-->
    
    
    <!-- 定义一个名为studentcon的bean,类型为com.sakurapaid.spring.iocxml.di.Student -->
    <bean id="studentcon" class="com.sakurapaid.spring.iocxml.di.Student">
        <!-- 通过构造器参数name传入值"李四" -->
        <constructor-arg name="name" value="李四"> </constructor-arg>
        <!-- 通过构造器参数age传入值"19" -->
        <constructor-arg name="age" value="19"> </constructor-arg>
    </bean>
</beans>

这里使用了<constructor-arg>标签来指定构造函数的参数。根据注释,我们知道Student类应该有一个带有两个参数(name和age)的构造函数,类似于下面的Java代码:

public class Student {
    private String name;
    private int age;

    // 构造函数注入
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // 省略其他代码...
}

constructor-arg标签还有两个属性可以进一步描述构造器参数:

index属性:指定参数所在位置的索引(从0开始)

name属性:指定参数名

测试结果

package com.sakurapaid.spring.iocxml.di;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * StudentTest类用于测试通过XML配置文件加载Bean的功能。
 */
public class StudentTest {

    /**
     * 测试方法,通过ApplicationContext从XML配置文件中获取Bean实例。
     */
/*    @Test
    public void test(){

        // 创建ClassPathXmlApplicationContext实例,并加载名为"bean-di.xml"的配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("bean-di.xml");

        // 通过上下文对象获取名为"student"的Bean实例,其类型为Student类
        Student student = context.getBean("student", Student.class);

        // 打印学生对象的信息
        System.out.println(student);
    }*/

    /**
     * 测试方法test01:通过Spring上下文加载并获取Bean实例。
     * 本方法不接受参数,也不返回值,主要演示如何从Spring配置文件中加载并获取Bean实例。
     */
    @Test
    public void test01(){
        // 创建ClassPathXmlApplicationContext实例,并加载名为"bean-di.xml"的配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("bean-di.xml");

        // 通过上下文对象获取名为"student"的Bean实例,其类型为Student类
        Student student = context.getBean("studentcon", Student.class);

        // 打印学生对象的信息
        System.out.println(student);
    }
}

当Spring容器加载并解析XML配置文件时,它会找到studentcon这个bean,并根据<constructor-arg>标签提供的信息,调用Student类的相应构造函数,传入"李四"作为name参数,传入19作为age参数,从而创建并初始化Student实例。

最终,通过context.getBean("studentcon", Student.class)获取到了通过构造器注入生成的Student实例,并将其打印出来。


2.5. 实验四:特殊值处理

某些XML配置片段在Spring框架中的意义

当然,很高兴为您详细解释这些XML配置片段在Spring框架中的意义:

  1. 字面量赋值

    <property name="name" value="张三"/>

在Spring的XML配置中,<property>标签是用来设置Bean属性值的。这里的value="张三"就是将字面量"张三"赋给名为"name"的属性。这意味着当你在Bean中引用该属性时,它的值就是字符串"张三",而不是一个变量或者表达式的结果。


  1. 设置null值

    <property name="name"> <null /> </property>

这种方式是用来给Bean的属性设置null值的。与直接使用value="null"不同,后者会被当作字符串"null"而非实际的Java null值处理。 <null />标签告诉Spring容器将此属性值设为空引用。

注意下面的这样写法

<property name="name" value="null"></property>

这样的写法,为name所赋的值是字符串null


  1. XML实体

    <property name="expression" value="a < b"/>

在XML中,"<" 和 ">" 等特殊字符有特殊的含义,它们分别表示标签的开始和结束。如果你想在属性值中包含这样的字符,需要使用XML实体来转义。

例如,XML实体如 &lt; 代替小于号**<**,这样解析器就会知道这是一个小于符号,而不是标签的起始。

XML实体如 &gt; 代替大于号**>**


  1. CDATA节

CDATA(Characters Data)是一种在XML文档中嵌入大段文本的方式,其中的内容不会被XML解析器解析为标记或实体。在这里,如果你的属性值可能包含大量的特殊字符或者需要包含XML语法结构,为了防止解析错误,可以将这部分内容放在<![CDATA[ ... ]]>之中。因此,<![CDATA[a < b]]>意味着"a < b"被当作纯文本对待,里面的"<"字符不会被解析成标签开始。


总结来说,在Spring的XML配置中,这些不同的配置方式都是为了正确地传递和解析属性值,确保在初始化Bean时能获得预期的数据类型和内容。


2.6. 实验五:为对象类型属性赋值

准备工作

部门类Department

package com.sakurapaid.spring.iocxml.diobj;

public class Department {
    private String name;

    public void sout() {
        System.out.println("部门名: " + name);
    }

    public String getName() {
        return name;
    }

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

员工类Employee

package com.sakurapaid.spring.iocxml.diobj;

public class Employee {
    private Department department;
    private String name;
    private int age;

    public void work() {
        System.out.println("员工: " + name + ",年龄: " + age);
        department.sout();
    }

    public Department getDepartment() {
        return department;
    }

    public void setDepartment(Department department) {
        this.department = department;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

2.6.1. 引用外部bean

配置bean文件------bean-diobj.xml

<property name="department" ref="department"/> 这一行表示:当Spring容器初始化"employee"这个Bean时,会自动查找ID为"department"的Bean,并将该Bean的实例注入到Employee类的department属性上,实现了依赖注入。

<?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,命名为 "department" -->
    <bean id="department" class="com.sakurapaid.spring.iocxml.diobj.Department">
        <!-- 设置部门名称为 "开发部" -->
        <property name="name" value="开发部"/>
    </bean>
    
    <!-- 定义一个员工 bean,命名为 "employee" -->
    <bean id="employee" class="com.sakurapaid.spring.iocxml.diobj.Employee">
        <!-- 设置员工姓名为 "张三" -->
        <property name="name" value="张三"/>
        <!-- 设置员工年龄为 20 -->
        <property name="age" value="20"/>
        
        <!--通过ref属性指定依赖注入的对象-->
        <!-- 将该员工所属部门设置为 "department",实现依赖注入 -->
        <property name="department" ref="department"/>
    </bean>

</beans>

测试输出

在测试类EmployeeTest中,我们通过Spring应用上下文加载XML配置文件,然后获取"employee"这个Bean的实例,并调用其work方法。由于依赖注入已经完成,此时调用employee.work()方法时,不仅会输出员工的基本信息,还会通过注入的department对象输出部门名称。

package com.sakurapaid.spring.iocxml.diobj;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class EmployeeTest {
    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean-diobj.xml");
        Employee employee = context.getBean("employee", Employee.class);
        employee.work();
    }
}

总结起来,这种引用外部bean的方式就是在配置文件中通过ref属性指定了依赖对象,Spring容器负责自动装配这些依赖关系,使得类之间的耦合度降低,提高了程序的可维护性和灵活性。


2.6.2. 内部bean

内部bean(Inner Bean)是在Spring配置文件中直接定义并初始化的一个Bean,它不是顶级Bean(Root Bean),而是作为另一个Bean的属性值出现。在给出的示例中,我们展示了如何通过内部bean的方式来注入Employee类的department属性。

在bean配置上和内部bean有所区别,但能达到一样的效果

在XML配置文件中,对于employee2这个Bean的定义,我们不再通过ref属性引用已经定义好的外部Bean,而是直接在其department属性内部定义一个新的Department Bean。

这里的新Department Bean没有显式定义ID,因为它是一个内部Bean,由Spring容器自动生成一个唯一的ID。同时,它拥有自己的属性设置,如name属性被设置为"开发部"。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <!--外部bean方式-->
    <bean id="department" class="com.sakurapaid.spring.iocxml.diobj.Department">
        <property name="name" value="开发部"/>
    </bean>
    <bean id="employee1" class="com.sakurapaid.spring.iocxml.diobj.Employee">
        <property name="name" value="张三"/>
        <property name="age" value="20"/>
        <property name="department" ref="department"/>
    </bean>
    
    <!--内部bean方式-->
    <bean id="employee2" class="com.sakurapaid.spring.iocxml.diobj.Employee">
        <property name="name" value="张三"/>
        <property name="age" value="20"/>
        <property name="department">
               <bean class="com.sakurapaid.spring.iocxml.diobj.Department">
                   <property name="name" value="开发部"/>
               </bean>
        </property>
    </bean> 

</beans>

测试输出

当Spring容器加载配置文件并初始化employee2这个Bean时,它会发现department属性需要注入一个Department类型的Bean。于是,它会在当前上下文中查找匹配的Bean定义,发现内部定义的Department Bean,然后创建并初始化这个内部Bean,并将其实例注入到employee2的department属性上。

在测试类EmployeeTest中,我们仅获取并操作了使用内部bean方式注入的Employee实例(employee2),同样能够达到与外部bean方式相同的效果,即输出员工基本信息以及所属部门名称。

内部bean方式的优势在于可以在同一个配置块内清晰地展示和组织Bean之间的依赖关系,简化配置文件结构。但在某些场景下,如果某个Bean会被多个其他Bean共享,使用外部bean并通过ref引用则更为合适。

package com.sakurapaid.spring.iocxml.diobj;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class EmployeeTest {
    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean-diobj.xml");
        Employee employee = context.getBean("employee2", Employee.class);
        employee.work();
    }
}

2.6.3. 级联属性赋值

这种方式不常用,知道即可

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <!--外部bean方式-->
    <bean id="department" class="com.sakurapaid.spring.iocxml.diobj.Department">
        <property name="name" value="开发部"/>
    </bean>
    <bean id="employee1" class="com.sakurapaid.spring.iocxml.diobj.Employee">
        <property name="name" value="张三"/>
        <property name="age" value="20"/>
        <property name="department" ref="department"/>
    </bean>
    
    <!--内部bean方式-->
    <bean id="employee2" class="com.sakurapaid.spring.iocxml.diobj.Employee">
        <property name="name" value="张三"/>
        <property name="age" value="20"/>
        <property name="department">
               <bean class="com.sakurapaid.spring.iocxml.diobj.Department">
                   <property name="name" value="开发部"/>
               </bean>
        </property>
    </bean>
    
    <!--级联属性赋值-->
    <bean id="department3" class="com.sakurapaid.spring.iocxml.diobj.Department">
        <property name="name" value="开发部"/>
    </bean>
    
    <bean id="employee3" class="com.sakurapaid.spring.iocxml.diobj.Employee">
        <property name="name" value="张三"/>
        <property name="age" value="20"/>
        <property name="department" ref="department3"/>
        <property name="department.name" value="测试部"/>
    </bean>

</beans>

测试输出

package com.sakurapaid.spring.iocxml.diobj;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class EmployeeTest {
    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean-diobj.xml");
        Employee employee = context.getBean("employee3", Employee.class);
        employee.work();
    }
}

2.7. 实验六:为数组类型属性赋值

修改Employee员工类

主要是新增了数组成员和及其输出

package com.sakurapaid.spring.iocxml.diobj;

import java.util.Arrays;

public class Employee {
    private Department department;
    private String name;
    private int age;
    private String[] hobby;

    public Employee() {
    }

    public Employee(Department department, String name, int age, String[] hobby) {
        this.department = department;
        this.name = name;
        this.age = age;
        this.hobby = hobby;
    }
    
    public void work() {
        System.out.println("员工: " + name + ",年龄: " + age);
        department.sout();

        // 遍历数组
        System.out.println(Arrays.toString(hobby));

    }

    /**
     * 获取
     * @return department
     */
    public Department getDepartment() {
        return department;
    }

    /**
     * 设置
     * @param department
     */
    public void setDepartment(Department department) {
        this.department = department;
    }

    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }

    /**
     * 获取
     * @return hobby
     */
    public String[] getHobby() {
        return hobby;
    }

    /**
     * 设置
     * @param hobby
     */
    public void setHobby(String[] hobby) {
        this.hobby = hobby;
    }

    public String toString() {
        return "Employee{department = " + department + ", name = " + name + ", age = " + age + ", hobby = " + hobby + "}";
    }
}

编辑spring配置文件

<property>标签用于设置Employee类中对应的属性值。对于数组类型的"hobby"属性,我们使用了<array>标签来进行嵌套注入。在<array>标签内部,通过多个<value>标签来分别指定数组中的每个元素值。

所以当Spring容器创建"employee"这个Bean时,它会自动将这些值("敲代码"、"阅读"和"打豆豆")注入到Employee对象的"hobby"数组属性中,最终得到的数组内容即为["敲代码", "阅读", "打豆豆"]。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <!-- 定义一个部门 bean -->
    <bean id="department" class="com.sakurapaid.spring.iocxml.diobj.Department">
        <!-- 部门名称 -->
        <property name="name" value="开发部" />
    </bean>
    
    <!-- 定义一个员工 bean -->
    <bean id="employee" class="com.sakurapaid.spring.iocxml.diobj.Employee">
        <!-- 员工姓名 -->
        <property name="name" value="SakuraPaid"/>
        <!-- 员工年龄 -->
        <property name="age" value="20"/>
        <!-- 员工所属部门,通过引用 "department" bean 来关联 -->
        <property name="department" ref="department"/>
        
        <!-- 员工爱好,使用数组形式来定义多个爱好 -->
        <property name="hobby">
            <array>
                <value>敲代码</value>
                <value>阅读</value>
                <value>打豆豆</value>
            </array>
        </property>
    </bean>

</beans>

测试输出

package com.sakurapaid.spring.iocxml.diobj;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class EmployeeTest {
    /**
     * 测试从Spring应用上下文中获取并使用Bean。
     * 该方法不接受参数,也不返回值。
     */
    @Test
    public void test() {

        // 创建一个ClassPathXmlApplicationContext,加载名为"bean-diarry.xml"的应用上下文配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("bean-diarry.xml");

        // 从应用上下文中获取一个类型为Employee的Bean,Bean的名称为"employee"
        Employee employee = context.getBean("employee", Employee.class);

        // 调用获取的Employee Bean的work方法,模拟员工进行工作
        employee.work();
    }
}

2.8. 实验七:为集合类型属性赋值

2.8.1. 为List集合类型属性赋值

修改Department部门类

多了个List集合,一个部门有很多人,再定义个输出方法

package com.sakurapaid.spring.iocxml.diobj;

import java.util.List;

public class Department {

    //一个部门有很多人
    private List<Employee> employees;

    //部门名称
    private String name;

    public void sout() {
        System.out.println("部门名: " + name);
        for (Employee emp : employees){
            System.out.println(emp.getName());
        }
    }

    public String getName() {
        return name;
    }

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

    public List<Employee> getEmployees() {
        return employees;
    }

    public void setEmployees(List<Employee> employees) {
        this.employees = employees;
    }
}

定义Spring配置文件

两个员工bean和一个部门bean

在XML配置文件中,首先定义了两个Employee类型的Bean(emp1和emp2),它们分别具有不同的属性值,包括名字、年龄和兴趣爱好列表(hobby)。这里的hobby属性是一个列表类型,使用<list>标签进行配置,通过多个<value>子标签为列表添加元素。

然后,定义了一个Department类型的Bean(dept),它具有一个名字属性和一个employees集合属性。employees属性也是一个列表类型,用于存放Department下的多个Employee对象。这里使用 <list> 标签,并通过 <ref> 子标签引用前面定义好的Employee Bean(emp1和emp2),从而将这两个Employee对象注入到Department的employees集合属性中。

<?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="emp1" class="com.sakurapaid.spring.iocxml.diobj.Employee">
        <!-- 员工的名字 -->
        <property name="name" value="Tom" />
        <!-- 员工的年龄 -->
        <property name="age" value="20" />
        <!-- 员工的爱好,以列表形式 -->
        <property name="hobby">
            <list>
                <value>吃饭</value>
                <value>睡觉</value>
                <value>学习</value>
            </list>
        </property>
    </bean>
    
    <!-- 定义另一个员工对象,员工2 -->
    <bean id="emp2" class="com.sakurapaid.spring.iocxml.diobj.Employee">
        <!-- 员工的名字 -->
        <property name="name" value="Lucy" />
        <!-- 员工的年龄 -->
        <property name="age" value="21" />
        <!-- 员工的爱好,以列表形式 -->
        <property name="hobby">
            <list>
                <value>睡觉</value>
                <value>学习</value>
                <value>吃饭</value>
            </list>
        </property>
    </bean>
    
    <!-- 定义一个部门对象 -->
    <bean id="dept" class="com.sakurapaid.spring.iocxml.diobj.Department">
        <!-- 部门的名字 -->
        <property name="name" value="财政部" />
        <!-- 部门下的员工列表 -->
        <property name="employees">
            <list>
                <!-- 引用员工1对象 -->
                <ref bean="emp1" />
                <!-- 引用员工2对象 -->
                <ref bean="emp2" />
            </list>
        </property>
    </bean>

</beans>

测试输出

package com.sakurapaid.spring.iocxml.diobj;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class EmployeeTest {
    /**
     * 测试从Spring应用上下文中获取并使用Bean的示例方法。
     * 该方法不接受任何参数。
     * 该方法也不返回任何值。
     */
    @Test
    public void test() {

        // 加载Spring应用上下文配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("bean-dilist.xml");

        // 从应用上下文中获取指定名称的Bean
        Department dept = context.getBean("dept", Department.class);

        // 使用获取到的Bean执行某些操作
        dept.sout();
    }

}

2.8.2. 为Map集合类型属性赋值

准备工作

创建一个学生类

package com.sakurapaid.spring.iocxml.dimap;

import java.util.Map;

public class Student {
    private Map<String, Teacher> mapTeachers;
    private int stu_id;
    private String stu_name;

    public void sout(){
        System.out.println("学生id : " + stu_id + ", " +
                "学生姓名 : " + stu_name);
        System.out.print("教师集合 : ");
        System.out.println(mapTeachers);
    }

    public Map<String, Teacher> getMapTeachers() {
        return mapTeachers;
    }

    public void setMapTeachers(Map<String, Teacher> mapTeachers) {
        this.mapTeachers = mapTeachers;
    }

    public int getStu_id() {
        return stu_id;
    }

    public void setStu_id(int stu_id) {
        this.stu_id = stu_id;
    }

    public String getStu_name() {
        return stu_name;
    }

    public void setStu_name(String stu_name) {
        this.stu_name = stu_name;
    }
}

创建一个老师类

package com.sakurapaid.spring.iocxml.dimap;

public class Teacher {
    private int teacherId;
    private String teacherName;

    public int getTeacherId() {
        return teacherId;
    }

    public void setTeacherId(int teacherId) {
        this.teacherId = teacherId;
    }

    public String getTeacherName() {
        return teacherName;
    }

    public void setTeacherName(String teacherName) {
        this.teacherName = teacherName;
    }

    //toString
    @Override
    public String toString() {
        return "Teacher [teacherId=" + teacherId + ", teacherName=" + teacherName + "]";
    }
}

配置Spring文件

在Spring配置文件中,首先定义了两个Teacher类型的Bean(teacher1和teacher2),分别设置了他们的teacherId和teacherName属性。

然后,定义了一个Student类型的Bean(student),该Bean拥有stu_id和stu_name属性,同时还有一个名为mapTeachers的Map集合属性,用于存储不同科目的老师。

为mapTeachers属性赋值时,使用了 <map> 标签,该标签用于表示Map集合。在 <map> 标签内部,使用了多个 <entry> **子标签来代表Map中的键值对。**键(key)是字符串类型,表示老师的名称;值(value)通过value-ref属性引用了预先定义好的Teacher Bean,这样就实现了将Teacher对象作为Map的值注入到Student的mapTeachers属性中。

<?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,教师编号为 1001,姓名为"语文老师" -->
    <bean id="teacher1" class="com.sakurapaid.spring.iocxml.dimap.Teacher">
        <property name="teacherId" value="1001" />
        <property name="teacherName" value="语文老师" />
    </bean>
    
    <!-- 定义一个教师 bean,教师编号为 1002,姓名为"数学老师" -->
    <bean id="teacher2" class="com.sakurapaid.spring.iocxml.dimap.Teacher">
        <property name="teacherId" value="1002" />
        <property name="teacherName" value="数学老师" />
    </bean>
    
    <!-- 定义一个学生 bean,学生编号为 1001,姓名为"张三",并配置其对应的老师映射 -->
    <bean id="student" class="com.sakurapaid.spring.iocxml.dimap.Student">
        <property name="stu_id" value="1001" />
        <property name="stu_name" value="张三" />
        <!-- 老师映射,用以表示学生对应的不同科目老师 -->
        <property name="mapTeachers">
            <map>
                <!-- 映射"语文老师"到 teacher1 bean -->
                <entry key="语文老师" value-ref="teacher1" />
                <!-- 映射"数学老师"到 teacher2 bean -->
                <entry key="数学老师" value-ref="teacher2" />
            </map>
        </property>
    </bean>

</beans>

测试输出

package com.sakurapaid.spring.iocxml.dimap;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MapTest {
    /**
     * 测试从配置文件中获取Bean并调用其方法。
     */
    @Test
    public void test(){
        // 加载配置文件,创建ApplicationContext上下文
        ApplicationContext context = new ClassPathXmlApplicationContext("bean-dimap.xml");

        // 通过上下文获取名为"student"的Bean实例
        Student student = context.getBean("student", Student.class);

        // 调用Student实例的sout方法
        student.sout();
    }
}

2.8.3. 引用集合类型的bean

修改学生类

新增一个List集合,管理课程

package com.sakurapaid.spring.iocxml.dimap;

import java.util.List;
import java.util.Map;

public class Student {
    private Map<String, Teacher> mapTeachers;

    private List<Lesson> listLessons;
    private int stu_id;
    private String stu_name;

    /**
     * 此方法用于输出学生的详细信息,包括学生ID、学生姓名,以及该学生所属的教师集合和课程集合。
     * 该方法不接受参数,也不返回任何值。
     */
    public void sout(){
        // 输出学生的基本信息
        System.out.println("学生id : " + stu_id + ", " +
                "学生姓名 : " + stu_name);

        // 输出教师集合
        System.out.print("教师集合 : "); //只输出老师名字
        for(Map.Entry<String, Teacher> entry : mapTeachers.entrySet()){
            System.out.print(entry.getValue().getTeacherName() + " ");
        }
        System.out.println();
        // 输出课程集合
        System.out.print("课程集合 : ");
        for(Lesson lesson : listLessons){
            System.out.print(lesson.getLessonName() + " ");
        }
    }

    public Map<String, Teacher> getMapTeachers() {
        return mapTeachers;
    }

    public void setMapTeachers(Map<String, Teacher> mapTeachers) {
        this.mapTeachers = mapTeachers;
    }

    public int getStu_id() {
        return stu_id;
    }

    public void setStu_id(int stu_id) {
        this.stu_id = stu_id;
    }

    public String getStu_name() {
        return stu_name;
    }

    public void setStu_name(String stu_name) {
        this.stu_name = stu_name;
    }

    public List<Lesson> getListLessons() {
        return listLessons;
    }

    public void setListLessons(List<Lesson> listLessons) {
        this.listLessons = listLessons;
    }
}

其对应的Lesson课程类

package com.sakurapaid.spring.iocxml.dimap;

public class Lesson {
    private String lessonName;

    public String getLessonName() {
        return lessonName;
    }

    public void setLessonName(String lessonName) {
        this.lessonName = lessonName;
    }
}

配置Spring文件

使用util:list、util:map标签必须引入相应的命名空间

代码样式放在上的,需要用时直接复制粘贴即可

<?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:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/util
    http://www.springframework.org/schema/util/spring-util.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">

完整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"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/util
    http://www.springframework.org/schema/util/spring-util.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <!-- 定义一个学生 bean,学生编号为 1001,姓名为"张三" -->
    <bean id="student" class="com.sakurapaid.spring.iocxml.dimap.Student">
        <property name="stu_id" value="1001" />
        <property name="stu_name" value="张三" />
        <property name="listLessons" ref="listLessons" />
        <property name="mapTeachers" ref="mapTeachers" />
    </bean>
    
    <!-- 定义一个教师列表,包含两个教师 bean -->
    <util:list id="listLessons" >
        <ref bean="lesson1" />
        <ref bean="lesson2" />
    </util:list>
    
    <!-- 定义一个教师映射,key为教师编号,value为教师 bean 的引用 -->
    <util:map id="mapTeachers" >
        <entry key="1001" value-ref="teacher1" />
        <entry key="1002" value-ref="teacher2" />
    </util:map>
    
    <!-- 定义一个教师 bean,教师编号为 1001,姓名为"语文老师" -->
    <bean id="teacher1" class="com.sakurapaid.spring.iocxml.dimap.Teacher">
        <property name="teacherId" value="1001" />
        <property name="teacherName" value="语文老师" />
    </bean>
    
    <!-- 定义一个教师 bean,教师编号为 1002,姓名为"数学老师" -->
    <bean id="teacher2" class="com.sakurapaid.spring.iocxml.dimap.Teacher">
        <property name="teacherId" value="1002" />
        <property name="teacherName" value="数学老师" />
    </bean>
    
    <!-- 定义一个课程 bean,课程名称为"语文课"-->
    <bean id="lesson1" class="com.sakurapaid.spring.iocxml.dimap.Lesson">
        <property name="lessonName" value="语文课" />
    </bean>
    
    <!-- 定义一个课程 bean,课程名称为"数学课"-->
    <bean id="lesson2" class="com.sakurapaid.spring.iocxml.dimap.Lesson">
        <property name="lessonName" value="数学课" />
    </bean>

</beans>

测试输出

package com.sakurapaid.spring.iocxml.dimap;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MapAndListTest {
    /**
     * 测试从XML配置文件中获取Bean实例并进行操作的方法。
     * 该方法不接受参数,也不返回值,但通过ApplicationContext从XML配置文件中获取Student类型的Bean实例,并对其进行操作。
     */
    @Test
    public void test(){
        // 加载名为"bean-diMapAndList.xml"的XML配置文件,创建ApplicationContext上下文
        ApplicationContext context = new ClassPathXmlApplicationContext("bean-diMapAndList.xml");

        // 从上下文中获取名为"student"的Bean实例,其类型为Student
        Student student = context.getBean("student", Student.class);

        // 调用Student实例的sout方法,输出相关信息
        student.sout();
    }
}

2.9. 实验八:p命名空间

引入p命名空间

<?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:util="http://www.springframework.org/schema/util"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/util
       http://www.springframework.org/schema/util/spring-util.xsd
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

引入p命名空间后,可以通过以下方式为bean的各个属性赋值

<!-- p命名空间方式 -->
<bean id="student1" class="com.sakurapaid.spring.iocxml.dimap.Student"
    p:stu_id="1411" p:stu_name="李四" p:listLessons-ref="lesson1" p:mapTeachers-ref="mapTeachers">
</bean>

完整配置Spring文件

<?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:util="http://www.springframework.org/schema/util"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/util
       http://www.springframework.org/schema/util/spring-util.xsd
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <!-- p命名空间方式 -->
    <bean id="student1" class="com.sakurapaid.spring.iocxml.dimap.Student"
        p:stu_id="1411" p:stu_name="李四" p:listLessons-ref="lesson1" p:mapTeachers-ref="mapTeachers">
    </bean>
    
    <!-- 定义一个学生 bean,学生编号为 1001,姓名为"张三" -->
    <bean id="student" class="com.sakurapaid.spring.iocxml.dimap.Student">
        <property name="stu_id" value="1001" />
        <property name="stu_name" value="张三" />
        <property name="listLessons" ref="listLessons" />
        <property name="mapTeachers" ref="mapTeachers" />
    </bean>
    
    <!-- 定义一个教师列表,包含两个教师 bean -->
    <util:list id="listLessons" >
        <ref bean="lesson1" />
        <ref bean="lesson2" />
    </util:list>
    
    <!-- 定义一个教师映射,key为教师编号,value为教师 bean 的引用 -->
    <util:map id="mapTeachers" >
        <entry key="1001" value-ref="teacher1" />
        <entry key="1002" value-ref="teacher2" />
    </util:map>
    
    <!-- 定义一个教师 bean,教师编号为 1001,姓名为"语文老师" -->
    <bean id="teacher1" class="com.sakurapaid.spring.iocxml.dimap.Teacher">
        <property name="teacherId" value="1001" />
        <property name="teacherName" value="语文老师" />
    </bean>
    
    <!-- 定义一个教师 bean,教师编号为 1002,姓名为"数学老师" -->
    <bean id="teacher2" class="com.sakurapaid.spring.iocxml.dimap.Teacher">
        <property name="teacherId" value="1002" />
        <property name="teacherName" value="数学老师" />
    </bean>
    
    <!-- 定义一个课程 bean,课程名称为"语文课"-->
    <bean id="lesson1" class="com.sakurapaid.spring.iocxml.dimap.Lesson">
        <property name="lessonName" value="语文课" />
    </bean>
    
    <!-- 定义一个课程 bean,课程名称为"数学课"-->
    <bean id="lesson2" class="com.sakurapaid.spring.iocxml.dimap.Lesson">
        <property name="lessonName" value="数学课" />
    </bean>

</beans>

输出测试

package com.sakurapaid.spring.iocxml.dimap;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MapAndListTest {
    /**
     * 测试从XML配置文件中获取Bean实例并进行操作的方法。
     * 该方法不接受参数,也不返回值,但通过ApplicationContext从XML配置文件中获取Student类型的Bean实例,并对其进行操作。
     */
    @Test
    public void test(){
        // 加载名为"bean-diMapAndList.xml"的XML配置文件,创建ApplicationContext上下文
        ApplicationContext context = new ClassPathXmlApplicationContext("bean-diMapAndList.xml");

        // 从上下文中获取名为"student"的Bean实例,其类型为Student
        Student student = context.getBean("student1", Student.class);

        // 调用Student实例的sout方法,输出相关信息
        student.sout();
    }
}

2.10. 实验九:引入外部属性文件

在Spring框架中,引入外部属性文件是一种管理应用程序配置的有效方法。当我们在Spring的XML配置文件中直接编写大量的固定配置信息(如数据库用户名、密码、URL,或是服务器IP地址、端口号等),这些信息如果频繁变动,将会使得配置文件变得难以维护,而且每当配置信息发生变化时,开发人员就需要打开源代码去修改XML配置文件,然后再重新打包部署项目,这是一个繁琐的过程。

为了解决这个问题,Spring支持将这些容易变化的配置项提取出来,存放在单独的properties文件中,如database.propertiesapplication.properties。这些文件通常包含键值对的形式,例如:

database.properties文件

db.username=admin
db.password=secretpassword
db.url=jdbc:mysql://localhost:3306/mydatabase

接下来,在Spring的XML配置文件中,我们可以使用PropertyPlaceholderConfigurer(在较旧版本中)或<context:property-placeholder>(在Spring 3.1及以上版本推荐使用)来引入和解析这些外部属性文件:

<!-- Spring 3.1+ 使用context命名空间简化引入 -->
<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="...">
       
   <!-- 引入外部属性文件 -->
   <context:property-placeholder location="classpath:database.properties"/>

   <!-- 使用外部属性文件中的值注入Bean -->
   <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
       <property name="username" value="${db.username}"/>
       <property name="password" value="${db.password}"/>
       <property name="url" value="${db.url}"/>
   </bean>
</beans>

这样一来,当数据库的连接信息需要变更时,我们只需要修改database.properties文件,而不需要碰触到Spring的XML配置文件或Java代码。这样既实现了配置的模块化,又增强了配置的可维护性,同时降低了因配置变更导致的应用程序整体重新部署的频率。此外,这种模式还可以方便地根据不同的环境(如开发、测试、生产)切换不同的配置文件,实现灵活部署。

比如你下面的例子,你就会懂了


加入依赖

<!-- MySQL驱动 -->
<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.15</version>
</dependency>

创建外部属性文件 ------ jdbc.properties

jdbc.user=root
jdbc.password=atguigu
jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC
jdbc.driver=com.mysql.cj.jdbc.Driver

引入属性文件

引入context 名称空间,下面这是模版,可以直接复制

<?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
       http://www.springframework.org/schema/context/spring-context.xsd">

</beans>

其实你也可以发现,就是在用的最多的beans,改个单词而已,以后可以尝试自己手动修改试试

在spring配置文件中,引入外部属性文件

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

进行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"
       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
       http://www.springframework.org/schema/context/spring-context.xsd">
    
    <!-- 引入外部属性文件 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    
    <!--完成数据库的连接池的配置-->
    <bean id="dataSource" 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>

测试输出

package com.sakurapaid.spring.iocxml.jbdc;

import com.alibaba.druid.pool.DruidDataSource;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class JDBCtest {
    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("bean-jdbc.xml");
        DruidDataSource dataSource = (DruidDataSource) context.getBean("dataSource");
        System.out.println(dataSource.getUrl());;
    }
}

像这样,以后要改的话,直接从.properties文件中修改即可,省去了xml操作的麻烦步骤


2.11. 实验十:bean作用域/生命周期

2.11.1. 作用域

在Spring框架中,Bean(Java对象)的作用域指的是在应用程序的整个生命周期中,对于某个特定的Bean对象,Spring容器是如何管理和创建其实例的。Bean的作用域决定了容器在何时以及如何创建Bean实例,以及这些实例之间的关系。

作用域的视频例子可以看B站尚硅谷的视频 --> 尚硅谷Spring6

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


①singleton作用域:

  • 含义: 当指定一个Bean为singleton时,Spring容器会在启动时创建该Bean的一个实例,并在整个容器的生命周期内,任何对该Bean的请求都将返回同一个共享的实例。这意味着不论有多少个地方请求该Bean,始终只会有一个Bean实例存在。
  • 创建对象的时机: Spring容器在初始化时(加载配置文件并创建Bean的过程中),就会创建singleton作用域的Bean实例。

举个例子:

  • 就好像菜单上的一道招牌菜,无论多少客人点这道菜,厨房总是只制作一份,并且每位客人拿到的都是同一份菜肴。
  • 在Spring中,这意味着当配置一个Bean为singleton时,无论你的应用在何处、何时请求这个Bean,Spring容器只会创建一个该Bean的实例,并将其缓存起来,后续所有的请求都将返回这个共享的实例。

②prototype作用域:

  • 含义: 当指定一个Bean为prototype时,每次客户端请求一个Bean时,Spring容器都会创建一个新的Bean实例,也就是说,每次getBean()方法被调用时都会产生一个新的对象实例。
  • 创建对象的时机: prototype作用域的Bean实例是在客户端请求时创建的,即每次调用ApplicationContext.getBean()方法请求一个prototype类型的Bean时,Spring容器才会创建新的实例。

举个例子:

  • 类似于客人每次下单都要现做的个性化菜品,每桌客人要求的同一道菜实际上都是独立制作的新品。
  • 对应Spring中的情况,如果一个Bean被配置为prototype,那么每次应用请求该Bean时,Spring容器都会创建一个新的Bean实例给请求者,各个实例之间相互独立。

2.11.2. 生命周期

关于Bean的生命周期:

除了Bean的作用域之外,Bean还有一个完整的生命周期,它包括以下几个阶段:


  1. 实例化(Instantiation)
    • 这是Bean生命周期的起点,类似于开始备料,即调用Bean的无参构造函数创建Bean实例。Spring容器通过反射机制找到合适的构造函数并创建Bean对象。
  1. 依赖注入(Dependency Injection,DI)
    • 类似于加入必要的调料,Spring容器根据配置信息,通过setter方法、构造函数注入或其他注解方式(如@Autowired)将Bean依赖的其他对象注入到当前Bean中。
  1. Bean的后置处理器(Post Processor Before Initialization)
    • 在Bean初始化之前,Spring容器会对Bean进行一系列额外的处理。例如,Spring的BeanPostProcessor接口提供了两个方法postProcessBeforeInitialization()postProcessAfterInitialization(),在这一步骤中,Spring会调用postProcessBeforeInitialization()方法对Bean进行自定义的预处理。
  1. 初始化(Initialization)
    • 此阶段相当于烹饪前的准备工作,如预热烤箱。Spring容器在完成所有依赖注入后,会检查Bean是否实现了InitializingBean接口,若实现了则调用afterPropertiesSet()方法;或者在配置文件中通过init-method属性指定了初始化方法,此时Spring容器会调用该方法对Bean进行初始化。
  1. Bean的后置处理器(Post Processor After Initialization)
    • 初始化完成后,Spring容器会调用postProcessAfterInitialization()方法进一步处理Bean。这一阶段可用于在Bean正式投入使用前进行一些补充性的定制化工作。
  1. 使用(Use)
    • 到了这个阶段,Bean就如同完成烹饪的菜肴一样,已经准备好供客户(应用程序)使用。通过Spring容器的getBean()方法,可以在应用程序中任意位置获取并使用这个Bean实例。
  1. 销毁(Destruction)
    • 当Bean不再需要时,比如应用程序关闭或容器被销毁时,Spring容器会触发Bean的销毁过程。如果Bean实现了DisposableBean接口,则调用destroy()方法;或者在配置文件中通过destroy-method属性指定了销毁方法,Spring容器会调用该方法释放资源,如关闭数据库连接、清除缓存等,这就好比客人用餐结束后清理餐具、打扫卫生。
  1. IOC容器关闭
    • 当整个IoC容器需要关闭时,所有处于singleton作用域的Bean都会经历销毁阶段,以确保相关资源得到妥善释放,整个容器生命周期结束。

总的来说,在Spring框架中,Bean的作用域是指Bean实例的创建策略,而生命周期则是指Bean实例从创建到销毁的过程。通过理解并合理配置Bean的作用域和生命周期,可以更好地管理和控制Bean实例的行为,提高应用效率和资源利用率。

举个例子

User类

package com.sakurapaid.spring.iocxml.life;

public class User {
    private String name;

    //初始化方法
    public void init() {
        System.out.println("生命周期:3、初始化~");
    }

    //销毁方法
    public void destroy() {
        System.out.println("生命周期:5、销毁~");
    }

    public User() {
        System.out.println("生命周期:1、创建对象~");
    }

    public User(String name) {
        this.name = name;
    }

    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        System.out.println("生命周期:2、依赖注入~");
        this.name = name;
    }

    public String toString() {
        return "User{name = " + name + "}";
    }
}

配置Spring文件

<?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">
    
    <!-- 定义一个名为"user"的Bean,对应于com.sakurapaid.spring.iocxml.life.User类 -->
    <!-- 该Bean的生命周期由Spring容器管理 -->
    <bean id="user" class="com.sakurapaid.spring.iocxml.life.User"
        scope="singleton" init-method="init" destroy-method="destroy">
        
        <property name="name" value="sakura" />
        
    </bean>
    
    <!-- 注解解释:
    id属性:为Bean定义一个唯一标识符;
    class属性:指定Bean的实现类;
    scope属性:定义Bean的作用域,此处为"singleton",表示该Bean在容器中只存在一个实例;
    init-method属性:指定Bean初始化时要执行的方法,此处为"init";
    destroy-method属性:指定Bean销毁时要执行的方法,此处为"destroy"。 -->

</beans>

测试输出

package com.sakurapaid.spring.iocxml.life;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class UserTest {
    @Test
    public void test(){
        //想要实现bean生命周期的销毁,需要使用ClassPathXmlApplicationContext
        //而不是用ApplicationContext,它里面没有destroy方法
        ClassPathXmlApplicationContext context = new
                ClassPathXmlApplicationContext("bean-life.xml");

        User bean = context.getBean(User.class);
        System.out.println("生命周期:4、通过IOC容器获取bean并使用~");
        bean.destroy();
    }
}

以上是没有设置后置处理器的bean基本生命周期

  • bean对象创建(调用无参构造器)
  • 给bean对象设置属性
  • bean的后置处理器(初始化之前)
  • bean对象初始化(需在配置bean时指定初始化方法)
  • bean的后置处理器(初始化之后)
  • bean对象就绪可以使用
  • bean对象销毁(需在配置bean时指定销毁方法)
  • IOC容器关闭

下面的例子就是加上后置处理器的样子

修改后的User类

package com.sakurapaid.spring.iocxml.life;

public class User {
    private String name;

    //初始化方法
    public void init() {
        System.out.println("生命周期:4、初始化~");
    }

    //销毁方法
    public void destroy() {
        System.out.println("生命周期:7、销毁~");
    }

    public User() {
        System.out.println("生命周期:1、创建对象~");
    }

    public User(String name) {
        this.name = name;
    }

    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        System.out.println("生命周期:2、依赖注入~");
        this.name = name;
    }

    public String toString() {
        return "User{name = " + name + "}";
    }
}

创建bean的后置处理器

package com.sakurapaid.spring.iocxml.life;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

/**
 * MyBeanProcessor 类实现了 BeanPostProcessor 接口,
 * 用于在 Spring 容器初始化 bean 之前和之后对 bean 进行处理。
 */
public class MyBeanProcessor implements BeanPostProcessor {

    /**
     * 在 Spring 初始化 bean 之前调用此方法。
     *
     * @param bean 将要被初始化的 bean 对象。
     * @param beanName bean 的名称。
     * @return 返回经过处理的 bean 对象。
     * @throws BeansException 如果处理中发生错误。
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("生命周期:3、bean的后置处理器(初始化之前)");
        System.out.println("☆☆☆" + beanName + " = " + bean);
        return bean;
    }

    /**
     * 在 Spring 初始化 bean 之后调用此方法。
     *
     * @param bean 已经被初始化的 bean 对象。
     * @param beanName bean 的名称。
     * @return 返回经过处理的 bean 对象。
     * @throws BeansException 如果处理中发生错误。
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("生命周期:5、bean的后置处理器(初始化之后)");
        System.out.println("★★★" + beanName + " = " + bean);
        return bean;
    }
}

修改后的Spring配置文件

<!-- bean的后置处理器要放入IOC容器才能生效 -->
<bean id="myBeanProcessor" class="com.atguigu.spring6.process.MyBeanProcessor"/>

完整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 -->
    <!-- 该Bean表示一个用户实体,并通过Spring容器管理其生命周期 -->
    <bean id="user" class="com.sakurapaid.spring.iocxml.life.User"
        scope="singleton" init-method="init" destroy-method="destroy">
        
        <!-- 设置用户名称 -->
        <property name="name" value="sakura" />
        
    </bean>
    
    <!-- 注册一个Bean后置处理器,用于在Bean实例化后进行额外的操作 -->
    <bean id="myBeanProcessor" class="com.sakurapaid.spring.iocxml.life.MyBeanProcessor"/>
    
    <!-- Bean定义的注解解释:
    id属性:为Bean定义一个唯一标识符,用于在容器中识别和访问Bean;
    class属性:指定创建Bean实例的类,即Bean的实现类;
    scope属性:定义Bean的作用域,"singleton"表示该Bean在容器中只存在一个实例;
    init-method属性:指定在Bean实例化后需要执行的初始化方法;
    destroy-method属性:指定在Bean销毁前需要执行的清理或释放资源的方法。 -->
    
</beans>

测试输出

package com.sakurapaid.spring.iocxml.life;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class UserTest {
    @Test
    public void test(){
        //想要实现bean生命周期的销毁,需要使用ClassPathXmlApplicationContext
        //而不是用ApplicationContext,它里面没有destroy方法
        ClassPathXmlApplicationContext context = new
                ClassPathXmlApplicationContext("bean-life.xml");

        User bean = context.getBean(User.class);
        System.out.println("生命周期:6、通过IOC容器获取bean并使用~");
        bean.destroy();
    }
}

2.12. 实验十二:FactoryBean

普通Bean

想象一下,在一个家具工厂里,每种家具都有对应的生产线工人,他们负责制造特定的家具产品,比如椅子。在Spring IoC容器中,一个普通的Java类(如ComfyChair)被声明为bean后,Spring容器就像那个家具工厂,它会通过反射等机制,按照你在配置文件或者注解中的定义,实例化出一个ComfyChair对象放入容器中。当你请求容器获取一个名为"comfyChair"的bean时,容器直接返回一个ComfyChair类型的实例。

public class ComfyChair {}

// 配置文件或注解方式声明普通bean
<bean id="comfyChair" class="com.example.ComfyChair" />

// 或者使用注解
@Service("comfyChair")
public class ComfyChair {}

FactoryBean

现在,假设工厂引入了一台高级智能机器,这台机器本身就是一个生产家具的"工厂",我们称它为"智能椅制造机"(对应Spring中的FactoryBean)。这台机器实现了特殊接口FactoryBean,它的作用不是生产"智能椅制造机"自身,而是生产椅子。

在Spring中,如果你有一个类SmartChairFactory实现了FactoryBean接口,并且重写了getObject()方法,那么当Spring容器读取到这个bean定义时,它不会直接生成一个SmartChairFactory实例,而是调用SmartChairFactorygetObject()方法来获取实际的产品------一个ComfyChair实例或者其他类型的实例。

public class SmartChairFactory implements FactoryBean<ComfyChair> {
    @Override
    public ComfyChair getObject() throws Exception {
        // 这里可能包含复杂的创建逻辑,如依赖注入、组装等
        return new ComfyChair();
    }

    @Override
    public Class<?> getObjectType() {
        return ComfyChair.class;
    }
    
    // 其他必要方法...
}

// 声明FactoryBean
<bean id="chairFactory" class="com.example.SmartChairFactory" />

// 当你请求id为"chairFactory"的bean时
// Spring容器不会返回SmartChairFactory对象,而是返回getObject()方法创建的ComfyChair对象

总结:

  • 普通Bean:Spring IoC容器直接管理并实例化的对象,是我们通常定义的业务组件或者服务类。
  • FactoryBean :它是一种特殊的bean,它的作用是作为工厂来生产其他的bean。当我们从Spring容器中获取FactoryBean时,得到的实际上是FactoryBean内部通过getObject()方法创建的bean实例,而非FactoryBean本身的实例。FactoryBean常用于那些需要复杂初始化逻辑、代理对象生成或其他特殊场景的情况。

将来我们整合Mybatis时,Spring就是通过FactoryBean机制来帮我们创建SqlSessionFactory对象的。


再来个代码例子举例一下,你应该就会懂了( ̄▽ ̄)/

创建类UserFactoryBean

package com.sakurapaid.spring.iocxml.life;

import org.springframework.beans.factory.FactoryBean;

public class UserFactoryBean implements FactoryBean<User> {
    @Override
    public User getObject() throws Exception {
        return new User();
    }

    @Override
    public Class<?> getObjectType() {
        return User.class;
    }
}

配置spring的相关bean

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <!-- 定义一个Bean,其ID为"user",类型为"com.sakurapaid.spring.iocxml.life.UserFactoryBean" -->
    <!-- 这个Bean是通过工厂方式创建的,具体创建逻辑位于"UserFactoryBean"类中 -->
    <bean id="user" class="com.sakurapaid.spring.iocxml.life.UserFactoryBean" />
    
</beans>

测试输出

F:\Develop\JDK17\bin\java.exe -ea -Didea.test.cyclic.buffer.size=1048576 "-javaagent:F:\Develop\IDEA\IntelliJ IDEA 2023.3.3\lib\idea_rt.jar=11512:F:\Develop\IDEA\IntelliJ IDEA 2023.3.3\bin" -Dfile.encoding=UTF-8 -classpath "C:\Users\ASUS\.m2\repository\org\junit\platform\junit-platform-launcher\1.3.1\junit-platform-launcher-1.3.1.jar;C:\Users\ASUS\.m2\repository\org\apiguardian\apiguardian-api\1.0.0\apiguardian-api-1.0.0.jar;C:\Users\ASUS\.m2\repository\org\junit\platform\junit-platform-engine\1.3.1\junit-platform-engine-1.3.1.jar;C:\Users\ASUS\.m2\repository\org\junit\platform\junit-platform-commons\1.3.1\junit-platform-commons-1.3.1.jar;C:\Users\ASUS\.m2\repository\org\opentest4j\opentest4j\1.1.1\opentest4j-1.1.1.jar;C:\Users\ASUS\.m2\repository\org\junit\jupiter\junit-jupiter-engine\5.3.1\junit-jupiter-engine-5.3.1.jar;C:\Users\ASUS\.m2\repository\org\junit\jupiter\junit-jupiter-api\5.3.1\junit-jupiter-api-5.3.1.jar;F:\Develop\IDEA\IntelliJ IDEA 2023.3.3\lib\idea_rt.jar;F:\Develop\IDEA\IntelliJ IDEA 2023.3.3\plugins\junit\lib\junit5-rt.jar;F:\Develop\IDEA\IntelliJ IDEA 2023.3.3\plugins\junit\lib\junit-rt.jar;F:\Program\Java\Spring6\Spring6-ioc-xml\target\classes;F:\Develop\apache\LocalMavenWarehouse\org\apache\logging\log4j\log4j-core\2.19.0\log4j-core-2.19.0.jar;F:\Develop\apache\LocalMavenWarehouse\org\apache\logging\log4j\log4j-api\2.19.0\log4j-api-2.19.0.jar;F:\Develop\apache\LocalMavenWarehouse\org\apache\logging\log4j\log4j-slf4j2-impl\2.19.0\log4j-slf4j2-impl-2.19.0.jar;F:\Develop\apache\LocalMavenWarehouse\org\slf4j\slf4j-api\2.0.0\slf4j-api-2.0.0.jar;F:\Develop\apache\LocalMavenWarehouse\org\springframework\spring-context\6.0.2\spring-context-6.0.2.jar;F:\Develop\apache\LocalMavenWarehouse\org\springframework\spring-aop\6.0.2\spring-aop-6.0.2.jar;F:\Develop\apache\LocalMavenWarehouse\org\springframework\spring-beans\6.0.2\spring-beans-6.0.2.jar;F:\Develop\apache\LocalMavenWarehouse\org\springframework\spring-core\6.0.2\spring-core-6.0.2.jar;F:\Develop\apache\LocalMavenWarehouse\org\springframework\spring-jcl\6.0.2\spring-jcl-6.0.2.jar;F:\Develop\apache\LocalMavenWarehouse\org\springframework\spring-expression\6.0.2\spring-expression-6.0.2.jar;F:\Develop\apache\LocalMavenWarehouse\org\junit\jupiter\junit-jupiter-api\5.3.1\junit-jupiter-api-5.3.1.jar;F:\Develop\apache\LocalMavenWarehouse\org\apiguardian\apiguardian-api\1.0.0\apiguardian-api-1.0.0.jar;F:\Develop\apache\LocalMavenWarehouse\org\opentest4j\opentest4j\1.1.1\opentest4j-1.1.1.jar;F:\Develop\apache\LocalMavenWarehouse\org\junit\platform\junit-platform-commons\1.3.1\junit-platform-commons-1.3.1.jar;F:\Develop\apache\LocalMavenWarehouse\mysql\mysql-connector-java\8.0.25\mysql-connector-java-8.0.25.jar;F:\Develop\apache\LocalMavenWarehouse\com\google\protobuf\protobuf-java\3.11.4\protobuf-java-3.11.4.jar;F:\Develop\apache\LocalMavenWarehouse\com\alibaba\druid\1.2.15\druid-1.2.15.jar" com.intellij.rt.junit.JUnitStarter -ideVersion5 -junit5 com.sakurapaid.spring.iocxml.life.UserTest
2024-03-17 16:47:46 194 [main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@71454b9d
2024-03-17 16:47:46 302 [main] DEBUG org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 1 bean definitions from class path resource [bean-factorbean.xml]
2024-03-17 16:47:46 328 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'user'
生命周期:1、创建对象~
User{name = null}

2.13. 实验十三:基于xml自动装配

2.13.1. 概念

自动装配:

根据指定的策略,在IOC容器中匹配某一个bean,自动为指定的bean中所依赖的类类型或接口类型属性赋值


Spring自动装配,你可以理解为Spring框架的一种智能助手,它能在背后悄悄帮你处理Bean之间的关联关系。当你定义了一些Bean,并告诉Spring使用自动装配时,Spring会在创建这些Bean时,自动找出并填充它们所需要的依赖对象。

打个比方,假设你在装修房子,有电工、木工和油漆工三个工种。如果没有自动装配,你需要亲自安排电工去做电线安装,木工去做家具组装,油漆工去做刷漆工作。而有了自动装配功能,你只需要告诉Spring哪个房间需要装修,Spring会根据每个工人的技能(电工擅长电,木工擅长木工活,油漆工擅长刷漆)自动分配任务,不需要你一一指定谁去做什么。

在Spring中,不同的自动装配策略相当于不同的分工规则,比如按名字找合适的工人(byName)、按工种找合适的工人(byType)等。这样,你就可以更加专注于整体的设计和规划,而无需过于关注每个细小环节的具体实施。


主要有以下几种自动装配策略:

  1. byName
  • Spring容器会根据Bean的属性名查找IoC容器中ID(或name)与该属性名相同的Bean进行注入。
  • Spring会看你的Bean缺少什么"工具",然后去查找名字一样的工具。
  1. byType
  • Spring容器会查找IoC容器中类型与待注入属性类型匹配的Bean进行注入。如果有多个类型匹配的Bean,则会抛出异常,除非其中一个被标记为主Bean。
  • Spring会根据你的Bean需要的工具类型,去找仓库里拥有同样类型的工具。
  1. constructor
  • 类似于byType,但专门针对构造函数参数,根据构造函数参数类型查找并注入Bean。
  • 当你创建Bean时,Spring会检查构造函数需要哪些工具,然后自动提供正确的工具。
  1. default/no
  • 这是默认策略,表示不进行自动装配,所有依赖都需要显式配置。
  • 如果关闭自动装配,那就得你自己一个个地把工具交给Bean。

通过启用自动装配功能,Spring可以帮助开发者减少大量手动配置依赖注入的工作,提高开发效率,同时保持代码结构清晰。不过,过度依赖自动装配可能会导致问题不易排查,因此在实际项目中应合理权衡是否使用自动装配以及选择哪种装配策略。

总之,自动装配就是Spring帮你自动完成对象间的依赖关系建立,让你编写代码时更轻松,不过要注意避免因自动装配带来的不确定性,适当地结合显式配置更为稳健。


2.13.2. 代码准备

再再来个代码例子举例一下,你应该就会懂了( ̄▽ ̄)/

做一个UserController、UserService、UserDao三层之间的实际场景模拟

创建类UserController

package com.sakurapaid.spring.iocxml.auto.controller;

public class UserController {

    public void conMethod(){
        System.out.println("UserController方法执行了~");
    }

}

创建接口UserService

package com.sakurapaid.spring.iocxml.auto.service;

public interface UserService {
    public void serMethod();
}

创建类UserServiceImpl实现接口UserService

package com.sakurapaid.spring.iocxml.auto.service;

public class UserServiceImpl implements UserService {
    @Override
    public void serMethod() {
        System.out.println("UserService方法执行了~");
    }
}

创建接口UserDao

package com.sakurapaid.spring.iocxml.auto.controller;

public class UserController {

    public void conMethod();

}

创建类UserDaoImpl实现接口UserDao

package com.sakurapaid.spring.iocxml.auto.dao;

public class UserDaoImpl implements UserDao {

    @Override
    public void method() {
        System.out.println("UserDao方法执行了~");
    }

}

在类UserController中,生成service层对象,设置service属性set方法,调用service层方法

package com.sakurapaid.spring.iocxml.auto.controller;

import com.sakurapaid.spring.iocxml.auto.service.UserService;

public class UserController {

    //生成service层对象
    private UserService userService;

    //设置service属性set方法
    public void setUserService(UserService userService)
    {
        this.userService = userService;
    }

    public void conMethod(){
        //调用service层方法
        userService.serMethod();
        System.out.println("UserController方法执行了~");
    }

}

在类UserServiceImpl实现类同理

package com.sakurapaid.spring.iocxml.auto.service;

import com.sakurapaid.spring.iocxml.auto.dao.UserDao;

public class UserServiceImpl implements UserService {
    
    //生成dao层对象
    private UserDao userDao;
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    @Override
    public void serMethod() {
        //调用dao层方法
        userDao.method();
        System.out.println("UserService方法执行了~");
    }
}

上面的都还是准备工作,现在就实现真正的自动装配


2.13.3. 效果实现

使用bean标签的 autowire 属性设置自动装配效果

  1. 根据类型进行自动装配--byType

若在IOC中,没有任何一个兼容类型的bean能够为属性赋值,则该属性不装配,即值为默认值null

若在IOC中,有多个兼容类型的bean能够为属性赋值,则抛出异常NoUniqueBeanDefinitionException

配置spring bean

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <bean id="controller" class="com.sakurapaid.spring.iocxml.auto.controller.UserController"
          autowire="byType">
    </bean>
    
    <bean id="service" class="com.sakurapaid.spring.iocxml.auto.service.UserServiceImpl"
          autowire="byType">
    </bean>
    
    <bean id="dao" class="com.sakurapaid.spring.iocxml.auto.dao.UserDaoImpl"
          autowire="byType">
    </bean>
</beans>

测试输出

package com.sakurapaid.spring.iocxml.auto;

import com.sakurapaid.spring.iocxml.auto.controller.UserController;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class autoTest {
    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean-auto.xml");

        UserController controller = (UserController) context.getBean("controller");

        controller.conMethod();

    }
}
  1. 根据属性名进行自动装配--byName

    <?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="controller" class="com.sakurapaid.spring.iocxml.auto.controller.UserController"
           autowire="byName">
     </bean>
     
     <bean id="service" class="com.sakurapaid.spring.iocxml.auto.service.UserServiceImpl"
           autowire="byName">
     </bean>
     
     <bean id="dao" class="com.sakurapaid.spring.iocxml.auto.dao.UserDaoImpl"
           autowire="byName">
     </bean>
    
    </beans>

相关推荐
激流丶14 分钟前
【Kafka 实战】如何解决Kafka Topic数量过多带来的性能问题?
java·大数据·kafka·topic
Themberfue18 分钟前
Java多线程详解⑤(全程干货!!!)线程安全问题 || 锁 || synchronized
java·开发语言·线程·多线程·synchronized·
让学习成为一种生活方式35 分钟前
R包下载太慢安装中止的解决策略-R语言003
java·数据库·r语言
Mephisto.java35 分钟前
【大数据学习 | kafka高级部分】kafka中的选举机制
大数据·学习·kafka
晨曦_子画40 分钟前
编程语言之战:AI 之后的 Kotlin 与 Java
android·java·开发语言·人工智能·kotlin
Yawesh_best1 小时前
思源笔记轻松连接本地Ollama大语言模型,开启AI写作新体验!
笔记·语言模型·ai写作
南宫生1 小时前
贪心算法习题其三【力扣】【算法学习day.20】
java·数据结构·学习·算法·leetcode·贪心算法
Heavydrink1 小时前
HTTP动词与状态码
java
ktkiko111 小时前
Java中的远程方法调用——RPC详解
java·开发语言·rpc
计算机-秋大田2 小时前
基于Spring Boot的船舶监造系统的设计与实现,LW+源码+讲解
java·论文阅读·spring boot·后端·vue