Spring 概述

54 阅读44分钟

Spring框架Java的一个开源的全栈(FUKK-STACK)程序框架,由Rod Johnson在2003年发布,在Java社区中起着重要作用,对于Java EE(企业版)的提供了大量的拓展资源,基本上是对于EJB模型的优化。

本次介绍以Spring 6.x,Spring Framework Documentation :: Spring Framework

image-20251214123213123

一、Spring 概述

1.1 Spring 家族

(1)官网Spring | Home,Spring 灵活且全面的扩展和第三方库集,让开发者能够构建几乎任何可以想象的应用。

Spring Framework 的核心是**控制反转(IoC)依赖注入(DI)**特性 ,为一系列广泛的功能和功能提供了基础。

IOC百科上的一种解释

(2)Spring 家族Spring | Projects

一系列项目和框架,它们覆盖了企业级应用开发的各个方面。这些项目共同构成了一个完整的生态系统,帮助开发者构建从简单到复杂、从单体到分布式微服务的各种应用。

Spring全家桶是以Spring Framework为核心基础,通过Spring Boot实现快速开发和自动配置,借助Spring Data统一简化各类数据存储的访问,利用Spring Security提供全面的安全认证与授权保护,并结合Spring Cloud构建分布式微服务架构的完整企业级开发生态系统。

Spring-项目组合-全家桶

1.2 Spring Framework - 优势

  1. 控制反转与依赖注入:核心机制,由框架容器管理对象和依赖,实现组件间的松耦合,提升可测试性与维护性。
  2. 面向切面编程:将日志、事务等横切关注点与业务逻辑分离,通过声明方式集中处理,减少代码重复。
  3. 声明式事务管理:通过注解或配置即可管理复杂事务,无需编写底层代码,简化开发并保证数据一致性。
  4. 模块化与轻量设计:由一系列独立模块组成,可按需引入,避免应用臃肿,灵活性强。
  5. 模板抽象简化开发:提供多种模板类(如JdbcTemplate),封装了大量重复性样板代码,让开发者专注于业务逻辑。
  6. 强大集成与测试支持:无缝集成主流第三方库,并提供专门的测试模块,便于构建和测试复杂的企业级应用。

1.3 Spring 系统架构

当前Spring Framework属于Spring生态中的底层项目,当前从4.3后没有做出改变持续到6的大版本。

图片出处:2. Introduction to the Spring Framework

1.3.1 🧱 核心容器(Core Container)

  • 位置与角色:整个框架的基石,所有上层功能都直接或间接建立于此。功能体系中起着支撑作用。

  • 模块组成Beans, Core, Context, SpEL

    image-20251214023242824

  • 核心作用:提供 IoC(控制反转)和 DI(依赖注入) 的引擎。它负责创建、配置和管理应用中的所有组件(称为 Bean),并管理它们之间的依赖关系,是Spring实现松耦合的关键。

1.3.2 🔩 横向能力(AOP, Test等)

  • 位置与角色:贯穿整体的增强系统。它不直接处理业务,但为核心和上层提供关键基础设施。

  • 模块组成AOP, Aspects, Instrumentation, Messaging, Test

    image-20251214024718382

  • 核心作用AOP实现面向切面编程,让日志、事务等逻辑可集中管理;Test提供强大的集成测试支持,确保各层组件能方便地被测试。

1.3.3 💾 数据访问与集成层(Data Access/lntegration)

  • 位置与角色:基于核心与支撑层构建的一个功能,专门处理与外部数据系统的交互。用于访问和操作数据库中的数据。

  • 模块组成JDBC, ORM, OXM, JMS, Transactions

    JdbcTemplate

  • 核心作用:统一简化对数据库、消息中间件等技术的操作。JDBC/ORM用于数据库,JMS用于消息队列,Transactions确保数据操作的原子性和一致性

1.3.4 🌐 Web层

  • 位置与角色:与数据层并列的另一个功能,专门处理Web请求和构建用户界面。基于AppcationCantext提供了Web应用的各种工具类。

  • 模块组成Web, Servlet(Spring MVC), WebSocket, Portlet

    image-20251214024504933

  • 核心作用:构建Web应用的核心。Servlet模块(即Spring MVC)是经典的MVC框架,用于处理HTTP请求;WebSocket支持实时双向通信。

1.4 Spring 项目的构建

(1)我们首先需要JDK 17+,有一个Maven或者Gradle的构建工具

image-20251214150533419

(2)在配置文件pom.xml中添加

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>6.2.7</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>6.2.11</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>6.2.10</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-expression</artifactId>
        <version>6.2.10</version>
    </dependency>
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.2</version>
    </dependency>
</dependencies>

(3)在java层下创建HelloWorldMainApp两个Java文件

public class HelloWorld {
    private String message;

    public void setMessage(String message) {
        this.message = message;
    }

    public void getMessage() {
        System.out.println("message : " + message);
    }
}
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {

    public static void main(String[] args) {

        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");

        HelloWorld obj = (HelloWorld) context.getBean("Hello Spring");
        obj.getMessage();
    }
}

(4)在resources层下创建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="Hello Spring" class="HelloWorld">
        <property name="message" value="Hello, This My First Spring Programe!" />
    </bean>
</beans>

(5)启动 - 演示效果

PixPin_2025-12-14_15-42-53

二、控制反转和依赖注入

IOC:控制反转,控制反转的是对象的创建权

DI:依赖注入,绑定对象与对象之间的依赖关系

IOC容器Spring专门负责创建、配置、组装和管理应用程序中所有对象(特指Bean)的复杂软件系统。

Bean:任何被Spring 容器识别、实例化并纳入其管理范围内的对象实体

2.1 控制反转 (Inversion of Control,缩写 IOC)

2.1.1 定义

这是面向对象编程中的一种设计原则,用来降低代码之间的耦合度。它将程序的控制权从应用程序代码“反转”交给框架或容器。

IOC容器负责创建对象(“Bean"对象)、维护其生命周期、配置他们之间的依赖关系。

传统程序流程由开发者直接控制(比如 new 对象、调用方法等),而使用 IoC 后,这些控制权交由外部容器管理。(从直接控制的常见出来,交给了IOC容器处理)

2.1.2 举例说明为什么要选择

控制权转移:从"业务层控制创建哪个具体类"反转为"外部容器决定注入哪个实现类"。

image-20251215001145867

(1)传统 - 主动提供对象

传统方式中,依赖关系的控制权在使用方手中,编译时确定,运行时不可变

业务层在编译时将抽象需求固化为具体实现类的硬绑定,导致系统紧耦合、难以测试和维护。

Untitled-2025-12-11-2114

  • 数据访问层
// 数据访问层 - 01 - MySQL实现
public class DatabaseUserDao {
    public String getUserById(int id) {
        return "MySQL用户" + id;
    }
}

// 数据访问层 - 02 - API实现  
public class ApiUserDao {
    public String getUserById(int id) {
        return "API用户" + id;
    }
}
  • 业务层
// 业务层 - 直接创建具体实现
public class UserService {
    // 问题:将抽象需求"获取用户数据"固化为"必须使用DatabaseUserDao"
    private DatabaseUserDao userDao = new DatabaseUserDao();
    
    public String getUserInfo(int id) {
        return userDao.getUserById(id);
    }
}

image-20251215214926773

(2)IOC - 容器提供对象

IOC方式中,依赖关系的控制权转移到了外部容器,编译时仅定义契约,运行时动态注入

业务层仅依赖抽象接口,具体实现在运行时通过外部容器注入,实现模块间的松耦合,提高系统的可测试性、可扩展性和可维护性。

控制权转移:从"业务层控制创建哪个具体类"反转为"外部容器决定注入哪个实现类"。

// 1. 定义接口 - 抽象能力
public interface UserDao {
    String getUserById(int id);  // 抽象:获取用户数据的能力
}
// 2. 具体实现
public class DatabaseUserDao implements UserDao {
    @Override
    public String getUserById(int id) {
        return "MySQL用户" + id;
    }
}

public class MockUserDao implements UserDao {
    @Override
    public String getUserById(int id) {
        return "测试用户" + id;
    }
}
// 3. 业务层 - 依赖抽象
public class UserService {
    // 仅依赖抽象接口,不依赖具体实现
    private UserDao userDao;// 不new了,该注入
    
    // 通过构造函数注入具体实现
    public UserService(UserDao dao) {
        this.userDao = dao;  // 运行时决定使用哪个实现
    }
    
    public String getUserInfo(int id) {
        return userDao.getUserById(id);
    }
}
// 4. 配置层 - 决定使用哪个实现
public class AppConfig {
    public static void main(String[] args) {
        // 开发环境:使用数据库
        UserDao devDao = new DatabaseUserDao();
        UserService devService = new UserService(devDao);
        
        // 测试环境:使用模拟对象
        UserDao testDao = new MockUserDao();
        UserService testService = new UserService(testDao);
        
        // 生产环境:使用API
        UserDao prodDao = new ApiUserDao();
        UserService prodService = new UserService(prodDao);
        
        // 所有UserService代码完全一样,仅注入的实现不同
        System.out.println(devService.getUserInfo(1));   // MySQL用户1
        System.out.println(testService.getUserInfo(1));  // 测试用户1
        System.out.println(prodService.getUserInfo(1));  // API用户1
    }
}
// 5. 单元测试 - 可以隔离测试
public class UserServiceTest {
    @Test
    public void testGetUserInfo() {
        // 使用模拟对象,不依赖真实数据源
        UserDao mockDao = new MockUserDao();
        UserService service = new UserService(mockDao);
        
        String result = service.getUserInfo(1);
        assertEquals("测试用户1", result);  // 快速、可靠的测试
    }
}

下图表述了IoC(控制反转)的精髓——将对象的创建与管理权移交至容器,从而解耦依赖。其简明流程清晰阐述了“容器检查、创建依赖、完成注入”这一核心机制。

然而,作为对现代企业级开发架构的描绘,此图存在一定的抽象简化。它并未映射至Spring等主流框架中广为人知的注解驱动分层模型,例如 @Controller@Service@Repository 这一标志性的三层架构契约。

图中使用的“业务逻辑类”、“业务服务类”等泛化术语,虽有助于聚焦概念,却模糊了实际项目中各层的明确职责与边界。这使得图表停留在理想化的核心思想演示,而未能勾勒出其在实际编码中赖以实现的、丰富而具体的骨架与肌理。

IOC

2.2 依赖注入 (Dependency Injection,缩写为 DI)

依赖注入把对象构建与对象注入分开。致使创建对象的 new 关键字也可消失了。"依赖”是指接收方所需的对象。“注入”是指将“依赖”传递给接收方的过程。

依赖注入(Dependency Injection,DI) 是一种设计模式,也是控制反转(IoC)的一种实现方式。它的核心思想是:将对象的依赖关系从内部创建改为外部注入,从而实现对象之间的解耦。

image-20251215234025975

在这种模式中,一个对象所依赖的其他对象并不是由该对象自身在内部直接创建或实例化,而是通过某种外部机制,通常是一个称为"容器"或"依赖注入框架"的专门组件,将这些依赖对象以参数、属性设置器或接口方法的形式,从外部主动地、明确地传递或"注入"给该对象,从而使得该对象无需关心其依赖的具体创建过程、生命周期管理和依赖关系的解析,而只需要关注自身的核心业务逻辑,这样的设计模式的主要目的是为了降低组件之间的耦合度,使得系统更加模块化,提高代码的可测试性、可维护性和可扩展性,因为各个组件不再直接依赖于具体实现,而是依赖于抽象接口或基类,这使得在不变更组件代码的情况下,可以轻松地替换依赖的具体实现,适应不同的运行环境或需求变化。

image-20251214000338761

2.2.1 举例子

  • DAO层:负责数据访问操作
  • Service层:处理业务逻辑,依赖于DAO层
  • Spring配置文件:定义对象间的依赖关系
  • 主程序:演示依赖注入的实际效果
Spring6
├── pom.xml
├── src/
│   └── main/
│       ├── java/
│       │   └── com/
│       │       └── voyager/
│       │           └── spring6/
│       │               ├── Main.java
│       │               ├── dao/
│       │               │   ├── UserDao.java
│       │               │   └── impl/
│       │               │       └── UserDaoImpl.java
│       │               └── service/
│       │                   ├── UserService.java
│       │                   └── impl/
│       │                       └── UserServiceImpl.java
│       └── resources/
│           └── applicationContext.xml
└── target/ (编译后生成的目录)
1. 核心概念解析
什么是依赖注入(Dependency Injection)

依赖注入是一种设计模式,它实现了控制反转(Inversion of Control, IoC)。传统的做法是一个类自己创建它所需要的依赖对象,而依赖注入则是由外部(通常是IoC容器)来创建这些依赖并将其注入到需要它们的对象中。

  1. UserServiceImpl类需要使用 UserDao 来完成数据访问操作
  2. 不是让 UserServiceImpl自己创建 UserDao实例,而是通过setter方法接收外部注入的 UserDao实例
2. 具体实现步骤
第一步:创建依赖对象(UserDao)
// UserDao接口
public interface UserDao {
    void save(String userName);
}

// UserDao实现类
public class UserDaoImpl implements UserDao {
    @Override
    public void save(String userName) {
        System.out.println("保存用户信息: " + userName);
    }
}
第二步:创建被注入对象(UserService)
public interface UserService {
    void registerUser(String userName);
}

public class UserServiceImpl implements UserService {
    // 通过setter方式注入UserDao依赖
    private UserDao userDao;
    
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    
    @Override
    public void registerUser(String userName) {
        System.out.println("注册用户: " + userName);
        userDao.save(userName);  // 使用注入的UserDao实例
    }
}
第三步:在Spring配置文件中定义依赖关系
<!-- applicationContext.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 定义UserDao bean -->
    <bean id="userDao" class="com.voyager.spring6.dao.impl.UserDaoImpl"/>

    <!-- 定义UserService bean,并通过setter注入userDao -->
    <bean id="userService" class="com.voyager.spring6.service.impl.UserServiceImpl">
        <property name="userDao" ref="userDao"/>
    </bean>

</beans>

在这个配置中:

  • <bean id="userDao"> 定义了一个UserDaoImpl实例
  • <bean id="userService"> 定义了一个UserServiceImpl实例
  • <property name="userDao" ref="userDao"/> 表示将id为"userDao"的bean注入到userService的userDao属性中(实际上是调用setUserDao方法)
第四步:在主程序中使用依赖注入
public class Main {
    public static void main(String[] args) {
        // 初始化Spring容器
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        
        // 获取UserService bean
        UserService userService = (UserService) context.getBean("userService");
        
        // 调用业务方法
        userService.registerUser("张三");
    }
}

当运行程序时,输出结果如下:

注册用户: 张三
保存用户信息: 张三

image-20251220235654389

2.2.2 解释一下userDao

applicationContext.xml 中的 userDao 配置详解

applicationContext.xml配置文件中,userDao 是一个 Spring Bean 的定义:

<bean id="userDao" class="com.voyager.spring6.dao.impl.UserDaoImpl"/>
配置项含义分解
  1. <bean> 标签

    • 这是 Spring 框架中最基本的配置标签,用于定义一个 Bean(即一个被 Spring 容器管理的对象)
  2. id="userDao"

    • 这是该 Bean 的唯一标识符,在整个 Spring 容器中必须是唯一的
    • 通过这个 ID,其他 Bean 或应用程序代码可以引用这个特定的 Bean 实例
    • 在我们的例子中,UserServiceImpl通过这个 ID 来引用并注入这个 Bean
  3. class="com.voyager.spring6.dao.impl.UserDaoImpl"

    • 指定了要实例化的具体类的全限定名
    • Spring 容器会使用 Java 反射机制创建这个类的实例
    • 注意这里指定的是实现类而不是接口,因为接口无法直接实例化
工作原理

当 Spring 容器启动时,它会:

  1. 解析配置文件中的所有 <bean> 定义
  2. 对于userDao这个 Bean 定义,Spring 会:
    • 使用反射机制加载 com.voyager.spring6.dao.impl.UserDaoImpl类
    • 调用该类的无参构造函数创建一个新的实例
    • 将这个实例以 "userDao" 为键存储在容器的 Bean 缓存中
在依赖注入中的作用

在下面的配置中,userDaoBean 被注入到 UserServiceImpl中:

<bean id="userService" class="com.voyager.spring6.service.impl.UserServiceImpl">
    <property name="userDao" ref="userDao"/>
</bean>

其中:

  • ref="userDao" 表示引用 ID 为 "userDao" 的 Bean
  • Spring 会自动调用UserServiceImpl setUserDao()方法,并将之前创建的 UserDaoImpl实例作为参数传入
总结

userDao配置定义了一个数据访问层的 Bean,它是整个依赖注入链条的起点。通过这个配置,Spring 容器能够创建并管理 UserDaoImpl 实例,并将其自动注入到需要它的其他组件中,从而实现了控制反转和依赖注入的设计原则。

2.3 Bean之间的联系

在Spring框架的上下文中,Bean和Bean之间建立联系的过程是一个由IOC容器全权负责管理的复杂但自动化的过程,具体来说,当Spring容器启动时,它会首先扫描所有被标记为需要管理的组件(通常通过注解如@Component、@Service、@Repository等或XML配置定义),并为这些组件创建相应的Bean定义(BeanDefinition)。

这些定义包含了Bean的类信息、作用域、初始化方法、销毁方法以及最重要的依赖关系信息,然后容器会根据这些Bean定义,按照特定的顺序(主要考虑依赖关系和可能存在的循环依赖)来实例化这些Bean,在实例化的过程中,如果某个Bean的定义表明它需要依赖于其他Bean(这通常通过构造函数参数、Setter方法上的@Autowired注解或直接在字段上的@Autowired注解来表达)。

那么容器会暂停当前Bean的完全实例化,转而去查找、创建或获取它所依赖的那些Bean的实例,这个过程可能是递归的,因为被依赖的Bean可能又依赖于其他Bean,一旦所有必需的依赖Bean都准备就绪,容器就会通过反射机制将这些依赖Bean的引用设置到当前Bean的对应字段或传递给构造函数,从而建立起Bean与Bean之间的完整联系,整个过程中,Bean本身是完全被动的,它不需要主动去查找或创建依赖,只需要声明自己需要什么,而容器则承担了所有依赖解析、查找、创建和连接的工作。

2.4 IOC和DI

Spring框架中控制反转(IoC)和依赖注入(DI)的最终目的是从根本上改变传统软件开发中组件间直接、硬编码的依赖关系管理方式,实现一种更加灵活、松耦合、可管理的组件协作模式。

首先,通过将对象的创建、依赖关系的组装和生命周期的控制权从应用程序代码中"反转"或"转移"给一个外部的、专门的容器(即IoC容器),使得应用程序的各个组件不再需要关心如何获取其依赖,而只需专注于自身的核心业务功能实现,这极大地简化了组件代码,提高了其内聚性。

其次,通过依赖注入机制,强制性地要求组件通过接口或抽象类来表达其依赖,而不是依赖于具体实现类,这促进了面向接口编程的原则,使得系统更加模块化,各个模块之间的依赖关系更加清晰、可管理。

第三,这种设计使得系统的配置和组装变得更加灵活和可配置,因为可以在不修改任何组件代码的情况下,仅仅通过修改配置文件或注解,就能改变组件所使用的具体实现,或者调整组件间的依赖结构,这非常适合于不同环境的部署(如开发、测试、生产环境使用不同的数据库连接)和功能切换(如使用不同的支付网关实现)。

第四,极大地提高了代码的可测试性,因为在单元测试时,可以轻松地使用模拟对象(Mock Object)来替代真实的依赖,从而将待测试的组件与其依赖环境隔离开来,实现真正独立的单元测试。

最后,通过IoC容器的集中管理,可以实现对应用程序中所有组件的统一生命周期管理、资源管理、事务管理和安全控制等横切关注点(Cross-Cutting Concerns)的集中处理,提高了系统的可维护性和一致性。

三、Bean管理 - IOC 相关内容

Java Bean是一个广义的、复合的可具备统一访问方式、承载数据的目的。(无参构造、私有属性、get+set、可序列化)

Spring Bean是由Spring IOC容器 **创建、组装、管理的Bean对象。**而这些bean是使用了容器配置的元数据(XML形式的<bean/>)。

3.1 Bean的两个核心接口

  • BeanFactory 是 Spring 的核心容器,提供最基本的 IoC 功能(依赖注入、Bean 管理),采用懒加载,适合资源受限场景。

image-20251221161211488

  • ApplicationContext 是 BeanFactory 的扩展,在继承其所有功能的基础上,增加了国际化、事件机制、资源访问、AOP 集成等企业级特性,默认预加载 Bean,是 Spring 应用的标准容器。

image-20251221162040875

ListableBeanFactoryHierarchicalBeanFactory接口继承了BeanFactory,而ApplicationContext继承了这两个接口。故:ApplicationContextBeanFactory的拓展接口而生。

在日常开发实践中,一般而言很少直接调用 BeanFactory 接口,而是倾向于使用其更便捷的具体实现,例如 ClassPathXmlApplicationContextAnnotationConfigApplicationContext 或 Spring Boot 提供的 ConfigurableApplicationContext

image-20251221200307398

3.2 Bean的配置

Spring Framework 中,使用 XML 配置元数据的传统方式是通过 <bean> 元素来定义 bean,通常放在 XML 文件中(例如 applicationContext.xmlbeans.xml)。

3.2.1 使用 <bean> 配置 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
                           https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 这里放置 bean 定义 -->

</beans>

3.2.2 Spring XML 配置中 <bean> 元素的常用属性

<bean id="Bean的唯一标识"
      class="Bean的类全名"
      scope="Bean的作用范围"
      name="Bean的别名"/>

在 Spring Framework 的 XML 配置中,<bean> 元素用于定义一个 Bean。以下是常用的属性表格:

属性名称是否必需默认值描述
idBean 的唯一标识符(XML ID 类型,不能包含特殊字符)。推荐使用,便于引用。
nameBean 的别名,可以多个(用逗号、分号或空格分隔),允许特殊字符(如 /service)。
class是(非抽象/工厂时)Bean 的全限定类名(除非使用 factory-bean 或 factory-method)。
scopesingletonBean 的作用域:singleton(默认)、prototype、request、session 等。
init-method指定 Bean 初始化回调方法(在属性注入后调用)。
destroy-method无(或 infer)指定 Bean 销毁回调方法(容器关闭时调用,singleton 生效)。
autowireno自动装配模式:no(默认)、byName、byType、constructor、autodetect。
lazy-initfalse是否懒加载:true 表示首次使用时创建(singleton 生效)。
depends-on指定该 Bean 依赖的其他 Bean(先初始化它们)。
parent指定父 Bean,实现配置继承(子 Bean 继承父的属性、property 等)。
abstractfalse是否抽象 Bean:true 表示仅作为模板,不能实例化。
factory-method指定工厂方法(静态工厂时结合 class,非静态时结合 factory-bean)。
factory-bean指定工厂 Bean 的引用(用于非静态工厂方法)。

3.2.3 <bean> 元素的常用子元素

<bean> 可以包含多个子元素,用于注入依赖或额外配置。以下是常用子元素的表格说明:

子元素名称用途描述常用属性/子元素示例子元素参数说明
constructor-arg构造函数注入通过构造函数向 Bean 注入依赖,支持按索引、类型、参数名匹配参数顺序。<constructor-arg index="0" ref="myDao"/>
<constructor-arg name="name" value="test"/>
<constructor-arg><bean class="..."/></constructor-arg>
index:参数索引(从0开始)
type:参数类型(全限定名)
name:参数名称(推荐)
ref:引用其他 Bean
value:注入基本类型/字符串
可内嵌 <bean><list><map><set> 等集合
propertySetter 属性注入通过 setter 方法向 Bean 的属性注入值或引用其他 Bean,是最常用的注入方式。<property name="name" value="hello"/>
<property name="dao" ref="myDao"/>
<property name="items"><list><value>1</value><value>2</value></list></property>
name:属性名称(必须)
ref:引用其他 Bean
value:注入基本类型/字符串
可内嵌 <bean><list><map><set><props> 等集合或复杂类型
meta存储自定义元数据提供键值对形式的额外元信息,不影响 Bean 实例化,可通过 BeanFactory 程序化获取。<meta key="cacheTimeout" value="300"/>key:元数据键(必须)
value:元数据值(必须)
lookup-method方法注入(查找方法注入)允许在 singleton Bean 中动态获取 prototype 或其他作用域的 Bean,常用于每次调用都需要新实例的场景。<lookup-method name="getPrototypeBean" bean="prototypeBean"/>name:要覆盖的方法名(必须)
bean:返回的 Bean 引用(必须)
replaced-method方法替换用自定义实现完全替换 Bean 中原有方法的逻辑(需实现 MethodReplacer 接口)。<replaced-method name="computeValue" replacer="replacerBean"/>name:要替换的方法名(必须)
replacer:实现 MethodReplacer 的 Bean 引用(必须)
可包含 <arg-type> 子元素指定参数类型(重载方法时)
description添加描述信息纯文本描述,仅用于配置文件可读性,容器忽略。<description>这是一个服务 Bean</description>无属性,直接文本内容
qualifier限定符(配合 autowire 使用)在自动装配时指定具体的 Bean(避免多个候选 Bean 导致的歧义),常与 <qualifier> 配合使用。<qualifier type="org.springframework.beans.factory.annotation.Qualifier" value="mainDao"/>type:限定符类型(通常为注解全限定名)
value:限定符值

3.2.4 Spring XML 配置中 <bean> 元素的常用属性

以下是 Spring XML 配置(applicationContext.xml 或类似文件中)<bean> 元素的常用属性表格,包括每个属性的说明和示例:

属性名描述示例值备注
idBean 的唯一标识符,在容器中通过这个 ID 获取 Bean。id 必须符合 XML ID 规范(以字母开头,不能包含特殊字符)。id="userService"推荐使用,唯一性强
nameBean 的别名,可以定义多个别名,用逗号、分号或空格分隔。不受 XML ID 限制,可以包含特殊字符。name="userService,us,svc"可与 id 同时使用,用于别名
class指定 Bean 的全限定类名(必须属性)。class="com.example.UserService"必需属性
scope定义 Bean 的作用域(scope)。默认为 singleton。scope="singleton"prototype详见下文作用域说明
lazy-init是否延迟初始化(仅对 singleton 有效)。true 表示容器启动时不创建,直到首次使用。lazy-init="true"默认 false
init-method指定 Bean 初始化后调用的方法(相当于实现 InitializingBean 的 afterPropertiesSet)。init-method="init"可选
destroy-method指定 Bean 销毁前调用的方法(相当于实现 DisposableBean 的 destroy)。仅对 singleton 有效。destroy-method="cleanup"可选
factory-method使用静态工厂方法创建 Bean 时指定工厂方法名。factory-method="createInstance"与 factory-bean 互斥
factory-bean使用实例工厂方法创建 Bean 时,指定工厂 Bean 的名称。factory-bean="factory"与 factory-method 配合使用
autowire自动装配模式(已较少使用,推荐注解)。autowire="byName"byType默认 no
depends-on指定当前 Bean 依赖的其他 Bean,确保依赖的 Bean 先创建。depends-on="dataSource"可多个,用逗号分隔
primary当存在多个同类型 Bean 时,指定首选的候选 Bean(配合 @Autowired 使用)。primary="true"Spring 4.0+ 支持
Bean 作用域(scope)详解

<bean>scope 属性用于控制 Bean 的创建方式和生命周期。常用取值如下表:

scope 值描述创建时机与实例数量适用场景
singleton默认作用域。容器中只有一个共享实例,所有请求返回同一个 Bean。容器启动时创建(除非 lazy-init),唯一实例,需要确保线程安全无状态服务(如 Service、DAO)
prototype每次请求(getBean)都创建新的实例。请求时创建,多个实例有状态对象(如每次需要新实例的控制器、表单对象)
requestWeb 环境专用。每个 HTTP request 一个 Bean 实例,请求结束后销毁。请求到来时创建,request 作用域内唯一Web 应用中需要 request 级别的对象
sessionWeb 环境专用。每个 HTTP session 一个 Bean 实例,session 结束后销毁。session 创建时,session 作用域内唯一用户会话级别的数据(如用户登录信息、购物车)
applicationWeb 环境专用。整个 ServletContext 一个 Bean 实例(类似 servlet 的 application 作用域)。应用启动时创建,应用范围内唯一,需要确保线程安全全局共享配置
websocketWebSocket 环境专用。每个 WebSocket session 一个实例。WebSocket 连接建立时创建WebSocket 应用

示例 XML 配置:

现代 Spring 项目更推荐使用 Java 配置(@Configuration + @Bean)或注解(@Component、@Scope 等),但 XML 配置在遗留项目中仍很常见。

<!-- singleton(默认,可省略 scope) -->
<bean id="userService" class="com.example.UserService" />

<!-- prototype -->
<bean id="userDao" class="com.example.UserDao" scope="prototype" />

<!-- 带 init/destroy 方法和 lazy-init -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
      destroy-method="close" lazy-init="true" />

3.3 Spring Bean配置方式的演进

3.3.1 XML 配置(经典方式,Spring 1.x ~ 今)

  • 特点:最早的方式,所有 Bean 定义、依赖注入、作用域、生命周期回调等都通过 XML 文件(如 applicationContext.xml)声明。

    • 优点:配置与代码分离,便于非开发者修改;支持复杂配置(如工厂方法、依赖检查)。
  • 历史地位:Spring 早期(Java 5 前无注解支持)唯一主流方式。即使现在仍支持,但已不推荐在新项目中使用。

    <!-- applicationContext.xml -->
    <beans>
        <bean id="userService" class="com.example.UserServiceImpl">
            <property name="userDao" ref="userDao"/>
        </bean>
        
        <bean id="userDao" class="com.example.UserDaoImpl">
            <property name="dataSource" ref="dataSource"/>
        </bean>
    </beans>
    

3.3.2 注解(混合配置模式:XML + 注解)

  • 特点:从 Spring 2.0 开始支持注解(如 @Required),2.5 引入组件扫描(@Component 等),3.0+ 广泛使用。结合 <context:component-scan> 自动扫描包下标注注解的类。
    • 优点:简洁、类型安全、减少 XML;与组件扫描结合,实现“零配置”。
    • 缺点:仍需少量 XML 或 Java Config 激活扫描;注解散布在代码中,可能影响可读性。
  • 常见注解
    • @Component(通用)、@Service(服务层)、@Repository(DAO 层,支持异常翻译)、@Controller(MVC 控制器)。
    • 依赖注入:@Autowired(按类型)、@Resource(按名称)、@Qualifier
    • 生命周期:@PostConstruct@PreDestroy
  • 混合配置模式:XML + 注解
<!-- XML中开启注解支持 -->
<context:component-scan base-package="com.example"/>
// 使用注解定义Bean
@Service
public class UserService {
    @Autowired
    private UserDao userDao;
}

3.3.3 JavaConfig

  • 特点:用@Configuration类和@Bean方法取代XML,配置全部类型安全,支持IDE智能提示,适合复杂依赖。
    • 优点:纯 Java、类型安全、支持条件配置(@Profile@Conditional)、易于编程式控制;可混合注解。
    • 缺点:相对注解稍显冗长,但更灵活(尤其第三方 Bean 或复杂逻辑)。
  • 历史地位:Spring Boot(2014+)默认推荐,几乎实现“零 XML”。现代项目(如 Spring Boot 3+)多采用此方式或混合注解。
@Configuration
@ComponentScan("com.example")  // 可结合注解扫描
public class AppConfig {
    @Bean
    public UserService userService() {
        return new UserService(userDao());
    }
    
    @Bean
    public UserDao userDao() {
        return new UserDao();
    }
}

image-20251222230718822

3.3.4 自动装配/约定优于配置(Springboot)

  • 特点:Spring Boot 在 Java 配置和注解配置的基础上,进一步引入“约定优于配置”(Convention over Configuration)理念。通过 @EnableAutoConfiguration(或 @SpringBootApplication 复合注解)自动扫描 classpath 中的依赖(如 starter JAR),并使用大量预定义的 @Configuration 类(标注 @AutoConfiguration)结合 @Conditional 条件(如 @ConditionalOnClass、@ConditionalOnMissingBean)动态注册 Bean。只有当依赖存在且用户未手动定义对应 Bean 时,才应用默认配置。

    • 优点:极大减少 boilerplate 配置、开箱即用、快速启动项目;用户配置优先(可轻松覆盖自动配置);支持外部化配置(application.properties/yaml)。
    • 缺点:魔法过多可能导致“黑盒”感,调试条件匹配时需查看条件报告;不适合所有极端自定义场景。
  • 历史地位:Spring Boot(基于 Spring Framework 4+)的核心创新,推动了现代微服务开发。目前是 Spring 生态主流方式,几乎所有新项目均采用自动配置 + 注解扫描 + 少量自定义 Java Config 的混合模式。

  • 示例(启动类+配置)

    @SpringBootApplication  // 等价于 @Configuration + @EnableAutoConfiguration + @ComponentScan
    public class Application {
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    }
    
    server:
      port: 8080
    spring:
      datasource:
        url: jdbc:mysql://localhost/test
    

3.3.5. 函数式配置

函数式配置(Functional Bean Registration,Spring Framework 5.0+,5.1+ 增强,Spring 6 继续支持)

  • 特点:使用 Lambda 或函数式接口(如 GenericApplicationContextregisterBean)动态注册 Bean。无需 @Configuration 类,支持更快启动(减少反射开销)。

    • 优点:启动更快、函数式风格、更轻量;适合微服务、Serverless 或小应用(如 Spring Cloud Function)。
    • 缺点:不如 Java Config 直观;类型信息需显式提供;与传统自动配置兼容性需额外处理。
  • 示例(Kotlin DSL 或 Java):

    public class FunctionalConfig implements ApplicationContextInitializer<GenericApplicationContext> {
        @Override
        public void initialize(GenericApplicationContext context) {
            context.registerBean(UserService.class, () -> new UserService(context.getBean(UserDao.class)));
            context.registerBean(UserDao.class, UserDao::new);
        }
    }
    

    或 Kotlin DSL:

    beans {
        bean<UserService>()
        bean<UserDao>()
    }
    
  • 历史地位:Spring 5(2017)引入,针对函数式编程和性能优化。目前多用于特定场景(如 Spring Fu 孵化项目或 Cloud Function),主流项目仍偏好 Java Config + 注解。

    image-20251222232433109

    package com.voyager.spring6;
    
    import com.voyager.spring6.dao.UserDao;
    import com.voyager.spring6.dao.impl.UserDaoImpl;
    import com.voyager.spring6.service.UserService;
    import com.voyager.spring6.service.impl.UserServiceImpl;
    import org.springframework.context.support.GenericApplicationContext;
    
    public class FunctionalMain {
        public static void main(String[] args) {
            // 使用函数式方式注册Bean
            GenericApplicationContext context = new GenericApplicationContext();
            
            // 注册UserDao bean
            context.registerBean(UserDao.class, () -> new UserDaoImpl());
            
            // 注册UserService bean,并注入UserDao依赖
            context.registerBean(UserService.class, () -> {
                UserServiceImpl userService = new UserServiceImpl();
                userService.setUserDao(context.getBean(UserDao.class));
                return userService;
            });
            
            // 刷新上下文,完成Bean的初始化
            context.refresh();
            
            // 获取UserService bean并调用业务方法
            UserService userService = context.getBean(UserService.class);
            userService.registerUser("王五");
            
            // 关闭上下文
            context.close();
        }
    }
    

3.4 Bean的实例化(主要三种)

示例代码库在:

https://github.com/lzyzy1214/Spring-Bean-Instance.git

3.4.1 环境搭建

初始化 Spring IoC 容器(通常是 ApplicationContext)开始,主要步骤包括:

  • 从 XML 文件、Java 注解或 Java 配置加载配置元数据(Bean 定义)。
  • 创建 BeanFactory(核心容器)并注册 Bean 定义为 BeanDefinition 对象,BeanDefinition 不是旁观者,而是实例化方式的“总导演”
    • XmlBeanDefinitionReader(XML 配置)、AnnotationConfigApplicationContext(注解/Java Config)等读取配置。
    • 每解析一个<bean>@Bean 或 @Component,都会创建一个 BeanDefinition 对象(通常是 GenericBeanDefinition 或 AnnotatedBeanDefinition)。
  • 准备环境(属性、配置文件等)。
  • 刷新容器:注册后处理器、(默认)急切实例化单例 Bean,并发布事件。
Untitled-2025-12-11-2114

3.4.2 构造方法(最常见方式)

容器直接通过反射调用 Bean 的构造函数(无参或带参)创建实例。

  • 原理:Spring 调用 Bean 类的默认无参构造函数直接 new 一个对象。如果类有带参构造,Spring 会尝试自动注入,但默认优先无参。

  • 适用场景:简单 POJO 类,没复杂依赖。

  • 示例

    <!-- beans.xml -->
    <bean id="user" class="com.example.User" />
    
    public class User {
        public User() { // 无参构造
            System.out.println("User 无参构造被调用!");
        }
    }
    
    • 获取:User user = context.getBean("user"); → 直接 new User()。

3.4.3 静态工厂方法实例化

在工厂类中定义一个静态方法,返回 Bean 实例。XML 中使用 <bean class="Factory" factory-method="createInstance"/>

  • 原理:不直接 new 类,而是调用类的静态方法来创建对象。Spring 在 BeanDefinition 中记录 factory-method,实例化时反射调用它。

  • 适用场景:想复用现有工厂逻辑,或隐藏构造细节。适用于遗留代码或创建逻辑复杂的情况。

  • 示例

    <bean id="user" class="com.example.UserFactory" factory-method="createUser" />
    
    public class UserFactory {
        public static User createUser() { // 静态工厂方法
            return new User();
        }
    }
    
    public class User { /* ... */ }
    
    • 获取:User user = context.getBean("user"); → 调用 UserFactory.createUser()。

image-20251227235707498

3.4.4 实例工厂方法(或 FactoryBean)

使用工厂 Bean 实例的非静态方法,或实现 FactoryBean 接口。容器调用 FactoryBeangetObject() 方法获取实际 Bean。FactoryBean 特别强大,用于自定义对象创建(如 Spring AOP 中的代理对象)。

  • 原理:先创建一个工厂 Bean,然后调用其非静态方法返回目标 Bean。需要两个 Bean 定义:工厂和目标。Spring 先实例化工厂 Bean(用上面方式),然后调用其 factory-method。支持参数注入。

  • 适用场景:工厂本身需要依赖注入,或动态控制创建。

  • 示例

    <bean id="userFactory" class="com.example.UserFactory" />
    <bean id="user" factory-bean="userFactory" factory-method="createUser" />
    
    public class UserFactory {
        public User createUser() { // 实例方法
            return new User();
        }
    }
    
    public class User { /* ... */ }
    
    • 获取:User user = context.getBean("user"); → 先 get userFactory,然后 userFactory.createUser()。

3.4.5 运行代码

(1)实例化策略

当调用 getBean() 或容器预实例化单例 Bean 时,进入 AbstractAutowireCapableBeanFactory 的 createBean()createBeanInstance() 方法。

容器正是通过读取当前 BeanDefinition 的属性,来决定采用哪种实例化策略

实例化方式BeanDefinition 中的关键属性容器行为(createBeanInstance() 源码逻辑)
构造方法(默认)- class 属性指定目标类
- 无 factory-method / factory-bean
- 可有 constructor-arg(带参构造)
直接使用反射调用构造函数(无参优先,或自动匹配带参)。这是最常见方式。
静态工厂方法- class 属性指定工厂类
- factory-method 属性指定静态方法名
- 可有方法参数
反射调用 factoryClass.getMethod(factoryMethod).invoke(null, args),无需先实例化工厂类。
实例工厂方法- factory-bean 属性指定工厂 Bean 的 name
- factory-method 属性指定非静态方法名
先递归 getBean(factory-bean) 实例化工厂对象 → 再反射调用工厂实例的 factory-method 方法。
FactoryBean(扩展)- class 属性实现 FactoryBean 接口特殊处理:调用 FactoryBean 的 getObject() 方法返回真实对象(Spring 内部大量使用,如 ProxyFactoryBean)。
  • 注解方式运行代码

    image-20251228000121114

  • XML方式运行代码

    • image-20251228000258992
(2)具体示例中 BeanDefinition 的体现
<!-- 1. 构造方法 -->
<bean id="user" class="com.example.User"/>

<!-- 2. 静态工厂 -->
<bean id="userStatic" class="com.example.UserFactory" factory-method="createUser"/>

<!-- 3. 实例工厂 -->
<bean id="userFactory" class="com.example.UserFactory"/>
<bean id="userInstance" factory-bean="userFactory" factory-method="createUser"/>

容器加载后,beanDefinitionMap 中:

  • "user" 的 BeanDefinition:class = User.class,无 factory-method → 走构造器实例化。
  • "userStatic" 的 BeanDefinition:class = UserFactory.class,factory-method = "createUser" → 走静态工厂。
  • "userInstance" 的 BeanDefinition:factory-bean = "userFactory",factory-method = "createUser" → 走实例工厂。

当执行 getBean("userInstance") 时:

  1. 读取 BeanDefinition,发现有 factory-bean。
  2. 先 getBean("userFactory")(递归实例化工厂 Bean,使用其自己的 BeanDefinition,通常是构造器)。
  3. 反射调用工厂实例的 createUser() 方法。

3.5 Bean的生命周期

代码展示:

https://github.com/lzyzy1214/Spring-Lifecycle

3.5.1 环境准备

  1. 创建一个Maven或Gradle项目(推荐Spring Boot 3.x版本,对应Spring Framework 6.x+)。

  2. 添加依赖(pom.xml示例):

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
    </dependencies>
    
  3. 创建一个Bean类,用于演示初始化和销毁。

  4. 使用AnnotationConfigApplicationContext或Spring Boot的SpringApplication.run()启动容器。

Untitled-2025-12-11-2114.excalidraw

3.5.2 关键代码

让我们创建一个生动的主角Bean:MyLifecycleBean,它会“大声宣告”自己生命周期的每一个阶段,就像一个活泼的孩子在成长过程中不停汇报一样。

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

@Component  // 让Spring自动扫描管理
public class MyLifecycleBean implements InitializingBean, DisposableBean {

    public MyLifecycleBean() {
        System.out.println("🎉 1. 构造函数:Bean实例化了!我出生了!");
    }

    // 模拟属性注入(实际由Spring注入)
    public void setName(String name) {
        System.out.println("🔗 2. 属性注入:我的名字被设置为 " + name);
    }

    @PostConstruct
    public void postConstruct() {
        System.out.println("🚀 3. @PostConstruct:初始化前置,我准备好了!");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("⚙️ 4. InitializingBean.afterPropertiesSet():属性设置完成后,我正式启动!");
    }

    // 自定义init方法(可选,在@Bean中指定)
    public void customInit() {
        System.out.println("🛠️ 5. 自定义init-method:额外初始化完成!");
    }

    public void doWork() {
        System.out.println("💼 Bean就绪:我在努力工作中……");
    }

    @PreDestroy
    public void preDestroy() {
        System.out.println("😢 6. @PreDestroy:容器要关闭了,我要清理资源了!");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("🧨 7. DisposableBean.destroy():正式销毁,我在释放资源!");
    }

    // 自定义destroy方法(可选)
    public void customDestroy() {
        System.out.println("🔥 8. 自定义destroy-method:最后清理完毕,再见!");
    }
}

启动类(非Boot纯Spring示例):

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class App {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(App.class);
        MyLifecycleBean bean = context.getBean(MyLifecycleBean.class);
        bean.doWork();
        // 后面我们会关闭context触发销毁
    }
}

运行后,你会看到控制台“绘声绘色”地输出初始化日志;销毁日志则取决于如何关闭容器。

3.5.3 两种方式关闭容器

  • close():立即、显式、同步关闭。
  • registerShutdownHook():延迟、在 JVM shutdown 时自动关闭,适合非 web 独立应用。

image-20251230231720828

(1)close关闭容器,显式关闭容器

当调用close()时,Spring容器会触发所有Bean的销毁回调方法。

  • 显式关闭:需要手动调用close()方法
  • 立即执行:调用后立即触发销毁流程
  • 适用于:需要精确控制容器关闭时机的场景
context.close()
(2)注册钩子关闭容器,容器在程序退出前正确关闭

通过注册JVM关闭钩子(Shutdown Hook),可以在JVM正常关闭时自动关闭Spring容器。

  • 自动关闭:JVM正常关闭时自动触发
  • 优雅关闭:确保容器在程序退出前正确关闭
  • 适用于:需要确保容器在程序退出时自动关闭的场景(如Web应用、长时间运行的服务)
ctx.registerShutdownHook() ;

四、Bean管理 - DI 依赖注入

项目:

https://github.com/lzyzy1214/Spring-DI-Comparison.git

Bean是构成Spring应用骨架的核心对象。Bean本质上是由Spring IoC(控制反转)容器实例化、装配并管理的对象。容器负责Bean的生命周期管理,包括创建、配置、依赖注入、初始化、使用和销毁。

(1)IoC主要负责 Bean管理 负责:注册Bean定义、实例化Bean、管理作用域(例如单例模式——默认方式,原型模式)、处理生命周期回调(例如初始化方法和销毁方法)

(2)定义Bean的方式:

  • 注解(现代Spring中最常用:@Component@Service@Repository@Controller@Configuration + @Bean
  • Java配置类
  • 传统的XML配置(现已较少使用)

依赖注入是IoC的关键实现方式,容器将依赖项注入到Bean中,而不是由Bean自行创建。这种方式能够促进低耦合、便于测试并提升可维护性。

“依赖”是指接收方所需的对象。“注入”是指将“依赖”传递给接收方的过程。在“注入”之后,接收方才会调用该“依赖”。

链接:github.com/lzyzy1214/S…


Bean 管理概述

在 Spring 框架中,Bean 是指由 Spring IoC 容器管理的对象。这些对象被称为 Bean,是因为它们遵循 JavaBean 规范,通常具有:

  • 无参构造函数
  • 私有属性(字段)
  • 公共的 getter 和 setter 方法

Bean 的生命周期

Spring 容器管理 Bean 的完整生命周期,包括:

  1. 实例化(Instantiation):容器创建 Bean 实例
  2. 属性注入(Populate Properties):注入依赖关系
  3. 初始化(Initialization):调用初始化方法(如 @PostConstruct
  4. 使用(In Use):Bean 处于可用状态
  5. 销毁(Destruction):容器关闭时调用销毁方法(如 @PreDestroy

Bean 的作用域(Scope)

Spring 支持以下 Bean 作用域:

  • singleton(默认):整个容器中只有一个 Bean 实例
  • prototype:每次获取时都创建新实例
  • request:每个 HTTP 请求创建一个实例(Web 应用)
  • session:每个 HTTP Session 创建一个实例(Web 应用)
  • application:每个 ServletContext 创建一个实例(Web 应用)

依赖注入(DI)核心概念

依赖注入(Dependency Injection,DI) 是一种设计模式,它实现了控制反转(Inversion of Control,IoC) 原则。核心思想是:

对象不再自己创建或查找依赖,而是由外部容器在创建对象时注入所需的依赖。

// 传统方式:对象自己创建依赖
public class OrderService {
    private PaymentService paymentService;
    
    public OrderService() {
        // 紧耦合:直接创建依赖对象
        this.paymentService = new PaymentService();
    }
}
  • 紧耦合:OrderService 直接依赖 PaymentService 的具体实现
  • 难以测试:无法使用 Mock 对象
  • 不灵活:无法动态切换实现

依赖注入的优势

// 依赖注入方式:由容器注入依赖
public class OrderService {
    private PaymentService paymentService;
    
    // 通过构造函数注入,解耦依赖
    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }
}
  • 解耦:组件之间通过接口或抽象类进行依赖
  • 可测试性:可以轻松注入 Mock 对象进行单元测试
  • 可配置性:依赖关系可以在运行时动态配置
  • 可维护性:依赖关系由容器统一管理

Spring 容器与 IoC

Spring IoC 容器是 Spring 框架的核心,负责:

  • Bean 的创建
  • Bean 之间的依赖关系管理
  • Bean 的生命周期管理
  • 依赖注入的执行

容器的类型

1. BeanFactory(基础容器)
  • 延迟加载:只有在请求 Bean 时才创建
  • 轻量级:适合资源受限的环境
2. ApplicationContext(高级容器)
  • 立即加载:容器启动时创建所有单例 Bean
  • 功能丰富:支持国际化、事件发布等
  • 推荐使用:大多数应用场景

配置方式

Spring 支持三种配置方式:

  1. XML 配置(传统方式)
  2. Java 配置类(推荐,类型安全)
  3. 注解配置(最常用,简洁)

Bean 的声明方式

1. 注解方式(@Component 及其派生注解)

// @Component:通用组件注解
@Component
public class BusinessService {
    // ...
}

// @Service:业务层组件
@Service
public class UserService {
    // ...
}

// @Repository:数据访问层组件
@Repository
public class UserRepository {
    // ...
}

// @Controller:控制器组件(Web 层)
@Controller
public class UserController {
    // ...
}

2. Java 配置类方式(@Bean 方法)

@Configuration
public class AppConfig {
    
    @Bean
    public BusinessService businessService() {
        return new BusinessService();
    }
    
    @Bean
    public ClientComponent clientComponent(BusinessService businessService) {
        // 方法参数自动注入依赖
        return new ClientComponent(businessService);
    }
}

3. XML 配置方式(传统方式)

<beans>
    <bean id="businessService" class="com.example.BusinessService"/>
    <bean id="clientComponent" class="com.example.ClientComponent">
        <constructor-arg ref="businessService"/>
    </bean>
</beans>

依赖注入的四种方式

1. 构造函数注入(Constructor Injection)

通过类的构造函数来注入依赖,Spring 容器在创建 Bean 时调用构造函数并传入所需的依赖。

// 业务服务类
@Service
public class BusinessService {
    public String executeBusinessLogic() {
        return "通过构造函数注入执行的业务逻辑";
    }
}

// 客户端组件 - 使用构造函数注入
@Component
public class ClientComponent {
    private final BusinessService businessService;
    
    // @Autowired 可以省略(Spring 4.3+)
    @Autowired
    public ClientComponent(BusinessService businessService) {
        this.businessService = businessService;
    }
    
    public void doSomething() {
        System.out.println("【构造函数注入】" + businessService.executeBusinessLogic());
    }
}
特点

优点

  • 不可变性:使用 final 关键字,确保依赖不可变
  • 完全初始化:对象创建时所有依赖都已注入,不会出现部分初始化状态
  • 可测试性强:构造函数参数明确,易于编写单元测试
  • 避免 null 值:依赖在对象创建时就必须提供
  • Spring 官方推荐:Spring 团队推荐的首选方式

缺点

  • ❌ 依赖过多时构造函数参数列表过长
  • ❌ 可能导致循环依赖问题(需要特殊处理)

适用场景

  • 必需的依赖项
  • 不可变对象
  • 需要保证完全初始化的场景

2. Setter 方法注入(Setter Injection)

通过 Setter 方法来注入依赖,Spring 容器在创建 Bean 后调用 Setter 方法设置依赖。

@Component
public class ClientComponent {
    private BusinessService businessService;
    
    // Setter 方法注入
    @Autowired
    public void setBusinessService(BusinessService businessService) {
        this.businessService = businessService;
    }
    
    public void doSomething() {
        System.out.println("【Setter方法注入】" + businessService.executeBusinessLogic());
    }
}
特点

优点

  • 支持可选依赖:可以只注入部分依赖
  • 适合可变依赖:可以在运行时重新设置依赖
  • 构造函数简洁:不需要在构造函数中处理所有依赖

缺点

  • 对象可能不完整:对象创建后可能处于不完整状态(依赖未全部注入)
  • 依赖关系不明确:需要查看所有 Setter 方法才能了解完整依赖
  • 可测试性较差:可能遗漏某些依赖的注入

适用场景

  • 可选的依赖项
  • 需要后期修改的依赖
  • 有合理默认值的依赖

3. 字段注入(Field Injection)

直接在字段上使用 @Autowired 注解,Spring 通过反射机制直接设置字段值。

@Component
public class ClientComponent {
    // 字段注入 - 直接在字段上使用 @Autowired
    @Autowired
    private BusinessService businessService;
    
    public void doSomething() {
        System.out.println("【字段注入】" + businessService.executeBusinessLogic());
    }
}
特点

优点

  • 代码简洁:不需要构造函数或 Setter 方法
  • 使用方便:直接声明即可使用

缺点

  • 无法保证不可变性:字段不能使用 final 关键字
  • 可测试性差:无法通过构造函数注入 Mock 对象,必须使用反射或 Spring 测试框架
  • 隐藏依赖关系:依赖关系不够明确,需要查看字段声明
  • 破坏封装性:直接访问私有字段,违反面向对象封装原则
  • 不推荐使用:Spring 官方不推荐作为最佳实践

适用场景

  • ⚠️ 快速原型开发
  • ⚠️ 临时解决方案
  • 不推荐在生产环境使用

4. 方法注入(Method Injection)

在任意方法上使用 @Autowired 注解,Spring 会在创建 Bean 后调用该方法并注入依赖。可以是 Setter 方法,也可以是其他自定义方法。

@Component
public class ClientComponent {
    private BusinessService businessService;
    
    // 方法注入 - 可以是任意方法
    @Autowired
    public void injectBusinessService(BusinessService businessService) {
        this.businessService = businessService;
        // 可以在这里执行额外的初始化逻辑
        System.out.println("业务服务已注入并初始化");
    }
    
    public void doSomething() {
        System.out.println("【方法注入】" + businessService.executeBusinessLogic());
    }
}
特点

优点

  • 灵活性高:可以在注入时执行额外的初始化逻辑
  • 支持复杂初始化:适合需要复杂初始化过程的依赖
  • 批量注入:可以在一个方法中注入多个依赖

缺点

  • 代码复杂度较高:方法名和用途可能不够清晰
  • 依赖关系不够清晰:需要查看方法实现才能了解完整逻辑

适用场景

  • 需要复杂初始化的依赖
  • 批量依赖注入
  • 配置类中的 @Bean 方法(最常见)

注入方式对比与选择

对比表格

特性构造函数注入Setter 注入字段注入方法注入
不可变性✅ 支持 final❌ 不支持❌ 不支持❌ 不支持
完全初始化✅ 保证❌ 不保证❌ 不保证❌ 不保证
代码简洁性⚠️ 中等✅ 简洁✅✅ 最简洁⚠️ 中等
可测试性✅✅ 优秀⚠️ 中等❌ 较差⚠️ 中等
依赖明确性✅✅ 最明确⚠️ 中等❌ 不明确⚠️ 中等
Spring 推荐✅✅ 推荐⚠️ 可选❌ 不推荐⚠️ 特定场景
循环依赖⚠️ 可能有问题✅ 支持✅ 支持✅ 支持
可选依赖❌ 不支持✅ 支持✅ 支持✅ 支持

选择建议

推荐优先级
  1. 🥇 构造函数注入(首选)

    • 适用于:必需的、不可变的依赖
    • 理由:最安全、最明确、最易测试
  2. 🥈 Setter 注入(次选)

    • 适用于:可选的、可变的依赖
    • 理由:灵活性好,适合可选依赖
  3. 🥉 方法注入(特定场景)

    • 适用于:需要复杂初始化的依赖
    • 理由:灵活性最高,但复杂度也高
  4. ❌ 字段注入(不推荐)

    • 适用于:仅限快速原型或临时方案
    • 理由:虽然简洁,但存在诸多问题

混合使用策略

在实际项目中,可以混合使用不同的注入方式:

@Component
public class OrderService {
    // 必需依赖:使用构造函数注入
    private final PaymentService paymentService;
    private final OrderRepository orderRepository;
    
    // 可选依赖:使用 Setter 注入
    private EmailService emailService;
    
    // 构造函数注入必需依赖
    public OrderService(PaymentService paymentService, 
                       OrderRepository orderRepository) {
        this.paymentService = paymentService;
        this.orderRepository = orderRepository;
    }
    
    // Setter 注入可选依赖
    @Autowired(required = false)
    public void setEmailService(EmailService emailService) {
        this.emailService = emailService;
    }
}

建议

1. 优先使用构造函数注入

// ✅ 推荐:构造函数注入
@Component
public class UserService {
    private final UserRepository userRepository;
    
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

2. 使用 final 关键字

// ✅ 推荐:使用 final 确保不可变性
private final BusinessService businessService;

// ❌ 不推荐:可变字段
private BusinessService businessService;

3. 避免字段注入

// ❌ 不推荐:字段注入
@Component
public class BadExample {
    @Autowired
    private Service service;
}

// ✅ 推荐:构造函数注入
@Component
public class GoodExample {
    private final Service service;
    
    public GoodExample(Service service) {
        this.service = service;
    }
}

4. 处理可选依赖

// ✅ 使用 @Autowired(required = false) 或 Optional
@Component
public class Service {
    private final RequiredService requiredService;
    private Optional<OptionalService> optionalService;
    
    public Service(RequiredService requiredService) {
        this.requiredService = requiredService;
    }
    
    @Autowired(required = false)
    public void setOptionalService(OptionalService optionalService) {
        this.optionalService = Optional.ofNullable(optionalService);
    }
}

5. 使用接口而非具体类

// ✅ 推荐:依赖接口
@Component
public class UserService {
    private final UserRepository userRepository; // 接口类型
    
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

// ❌ 不推荐:依赖具体实现
@Component
public class BadService {
    private final UserRepositoryImpl repository; // 具体类
}

6. 避免循环依赖

// ❌ 问题:循环依赖
@Service
public class ServiceA {
    private final ServiceB serviceB;
    public ServiceA(ServiceB serviceB) { this.serviceB = serviceB; }
}

@Service
public class ServiceB {
    private final ServiceA serviceA;
    public ServiceB(ServiceA serviceA) { this.serviceA = serviceA; }
}

// ✅ 解决:使用 Setter 注入或重构设计
@Service
public class ServiceA {
    private ServiceB serviceB;
    
    @Autowired
    public void setServiceB(ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

常见问题与解决方案

1. 多个同类型 Bean 的注入

问题:当有多个相同类型的 Bean 时,Spring 不知道注入哪一个。

解决方案

// 方式1:使用 @Qualifier 指定 Bean 名称
@Component
public class ClientComponent {
    private final BusinessService businessService;
    
    public ClientComponent(@Qualifier("primaryBusinessService") 
                          BusinessService businessService) {
        this.businessService = businessService;
    }
}

// 方式2:使用 @Primary 指定默认 Bean
@Primary
@Service
public class PrimaryBusinessService implements BusinessService {
    // ...
}

// 方式3:使用 @Resource 按名称注入
@Component
public class ClientComponent {
    @Resource(name = "specificBusinessService")
    private BusinessService businessService;
}

2. 循环依赖问题

问题:两个 Bean 相互依赖,导致无法创建。

解决方案

  • 使用 Setter 注入替代构造函数注入
  • 重构代码,消除循环依赖
  • 使用 @Lazy 延迟初始化
@Service
public class ServiceA {
    private final ServiceB serviceB;
    
    public ServiceA(@Lazy ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

3. 可选依赖的处理

问题:某些依赖可能不存在,需要优雅处理。

解决方案

// 方式1:使用 @Autowired(required = false)
@Autowired(required = false)
private OptionalService optionalService;

// 方式2:使用 Optional 类型
private final Optional<OptionalService> optionalService;

public Service(Optional<OptionalService> optionalService) {
    this.optionalService = optionalService;
}

// 方式3:使用 @Nullable
@Autowired
@Nullable
private OptionalService optionalService;

4. 静态字段注入

问题:静态字段无法直接使用 @Autowired

解决方案

@Component
public class StaticService {
    private static BusinessService businessService;
    
    @Autowired
    public void setBusinessService(BusinessService businessService) {
        StaticService.businessService = businessService;
    }
}

注意:静态字段注入通常不是好的设计,应该尽量避免。


总结

在大多数情况下,优先使用构造函数注入。这是 Spring 官方推荐的方式,也是最佳实践。只有在处理可选依赖或需要特殊初始化逻辑时,才考虑使用其他注入方式。

核心要点

  1. Bean 管理:Spring 容器负责创建、管理和销毁 Bean
  2. 依赖注入:将依赖关系从代码内部转移到外部容器管理
  3. 四种方式:构造函数、Setter、字段、方法注入
  4. 最佳实践:优先使用构造函数注入,避免字段注入

选择原则

  • 必需依赖 → 构造函数注入
  • 可选依赖 → Setter 注入
  • 复杂初始化 → 方法注入
  • 快速原型 → 字段注入(仅限临时使用)

参考资源


### 2. 循环依赖问题

**问题**:两个 Bean 相互依赖,导致无法创建。

**解决方案**:

- 使用 Setter 注入替代构造函数注入
- 重构代码,消除循环依赖
- 使用 `@Lazy` 延迟初始化

```java
@Service
public class ServiceA {
    private final ServiceB serviceB;
    
    public ServiceA(@Lazy ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

3. 可选依赖的处理

问题:某些依赖可能不存在,需要优雅处理。

解决方案

// 方式1:使用 @Autowired(required = false)
@Autowired(required = false)
private OptionalService optionalService;

// 方式2:使用 Optional 类型
private final Optional<OptionalService> optionalService;

public Service(Optional<OptionalService> optionalService) {
    this.optionalService = optionalService;
}

// 方式3:使用 @Nullable
@Autowired
@Nullable
private OptionalService optionalService;

4. 静态字段注入

问题:静态字段无法直接使用 @Autowired

解决方案

@Component
public class StaticService {
    private static BusinessService businessService;
    
    @Autowired
    public void setBusinessService(BusinessService businessService) {
        StaticService.businessService = businessService;
    }
}

注意:静态字段注入通常不是好的设计,应该尽量避免。


总结

在大多数情况下,优先使用构造函数注入。这是 Spring 官方推荐的方式,也是最佳实践。只有在处理可选依赖或需要特殊初始化逻辑时,才考虑使用其他注入方式。

核心要点

  1. Bean 管理:Spring 容器负责创建、管理和销毁 Bean
  2. 依赖注入:将依赖关系从代码内部转移到外部容器管理
  3. 四种方式:构造函数、Setter、字段、方法注入
  4. 最佳实践:优先使用构造函数注入,避免字段注入

选择原则

  • 必需依赖 → 构造函数注入
  • 可选依赖 → Setter 注入
  • 复杂初始化 → 方法注入
  • 快速原型 → 字段注入(仅限临时使用)

参考资源