跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

Spring IoC 与 DI 详解:@Bean 注解、扫描路径及依赖注入方式

Spring IoC 容器中的 @Bean 注解用于定义外部对象实例,配合扫描路径配置使 Bean 生效。依赖注入提供属性、构造方法和 Setter 三种方式,各自在代码侵入性、final 支持及灵活性上各有优劣。构造方法注入推荐用于确保依赖不可变且初始化完成,属性注入简洁但需容器环境,Setter 注入支持动态替换依赖。掌握这些机制有助于构建松耦合的 Spring 应用。

并发大师发布于 2026/2/22更新于 2026/7/2442 浏览
Spring IoC 与 DI 详解:@Bean 注解、扫描路径及依赖注入方式

文章配图

文章配图

一、方法注解 @Bean

类注解需要添加到类上,但是有些外部包里面的类是只读的,或者一个类需要 new 多个对象。这时可以使用方法注解 @Bean。

1.1. 需要配合类注解使用

package com.yang.test1_26_1.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserInfo {
    private int age;
    private String name;
}
package com.yang.test1_26_1.model;
import org.springframework.context.annotation.Bean;
import com.yang.test1_26_1.model.UserInfo;

public class Config {
    // 表示这个方法返回的实例将被 Spring 管理
    @Bean
    public UserInfo userInfo() {
        return new UserInfo(18, "Jack");
    }
}
package com.yang.test1_26_1;
import com.yang.test1_26_1.model.UserInfo;
 org.springframework.boot.SpringApplication;
 org.springframework.boot.autoconfigure.SpringBootApplication;
 org.springframework.context.ApplicationContext;


   {
        {
           SpringApplication.run(Test1261Application.class, args);
           context.getBean(UserInfo.class);
        System.out.println(bean);
    }
}
import
import
import
@SpringBootApplication
public
class
Test1261Application
public
static
void
main
(String[] args)
ApplicationContext
context
=
UserInfo
bean
=

运行会出现 NoSuchBeanDefinitionException。只需加上对应的组件注解就可以解决这个问题。

1.2. 定义多个对象

在多数据源场景下,如果类相同但配置不同,会指向不同的数据源。

package com.yang.test1_26_2;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Controller
public class UserInfo {
    private int age;
    private String name;

    @Bean
    public UserInfo userInfo1() {
        return new UserInfo(20, "Crane");
    }

    @Bean
    public UserInfo userInfo2() {
        return new UserInfo(18, "Larry");
    }
}
package com.yang.test1_26_2;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class Test1262Application {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(Test1262Application.class, args);
        UserInfo bean1 = (UserInfo) context.getBean("userInfo1");
        System.out.println(bean1);
        UserInfo bean2 = (UserInfo) context.getBean("userInfo2");
        System.out.println(bean2);
    }
}

报错信息显示期望只有一个匹配,结果发现了两个对象 userInfo1 和 userInfo2。通过 @Bean 定义的对象,Bean Name 默认就是方法名。

1.3. 重命名 Bean

可以通过设置 name 属性给 Bean 对象进行重命名操作。 类注解的重命名:

package com.yang.test1_27_1;
import org.springframework.stereotype.Controller;

@Controller("userController1")
public class UserController {
    public void hello() {
        System.out.println("hello Controller...");
    }
}

方法注解的重命名:

package com.yang.test1_27_1;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Controller
public class UserInfo {
    private int age;
    private String name;

    @Bean("userInfoOne")
    public UserInfo userInfo1() {
        return new UserInfo(20, "Crane");
    }

    @Bean("userInfoTwo")
    public UserInfo userInfoT2() {
        return new UserInfo(18, "Larry");
    }
}

@Bean 注解的属性值,可以起多个别名。默认情况下,Bean 的名字是方法名,但是,一旦显式指定了 value 或 name 属性,方法名将不再是 Bean 的名字,Bean 的名字将严格由数组中的元素决定。这里的别名集合实际上是一个字符串数组,在 Spring 中,如果数组里面只有一个元素,"{}" 可以省略不写。

package com.yang.test1_28_1;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Controller
public class UserInfo {
    private int age;
    private String name;

    @Bean({"userInfoOne", "u1"})
    public UserInfo userInfo1() {
        return new UserInfo(20, "Crane");
    }

    @Bean({"userInfoTwo", "u2"})
    public UserInfo userInfoT2() {
        return new UserInfo(18, "Larry");
    }
}

二、扫描路径

SpringBoot 有个特点:约定大于配置。每个 Spring Framework 的启动类的路径非常关键,这个决定了哪个路径对应下的对象有效。使用五大类注解声明的 Bean,并非一定能在 Spring 容器中生效,关键前提是这些 Bean 所在的类必须被 Spring 扫描到。若未被扫描,即使添加了类注解,Spring 也无法识别并管理该 Bean,会出现'找不到 Bean'的异常。 扫描路径的配置方式,使用 @ComponentScan 注解。

@ComponentScan({"com.example.demo.component", "com.example.demo.service"}) // 扫描多个包
@SpringBootApplication
public class SpringIocDemoApplication { ... }

日常开发中,多数场景无需显式添加 @ComponentScan,核心原因是 @SpringBootApplication 的源码包含 @ComponentScan,默认扫描范围是 SpringBoot 启动类所在的包及其所有子包。

三、DI 详解

依赖注入是一个过程,是指 IoC 容器在创建 Bean 时,去提供运行时所依赖的资源,而资源指的就是对象。 关于依赖注入,Spring 提供了 3 种方式:

  1. 属性注入(Field Injection)
  2. 构造方法注入(Constructor Injection)
  3. Setter 注入(Setter Injection)

3.1. 属性注入

属性注入是 Spring 依赖注入(DI)的三种核心方式之一,其本质是通过 @Autowired 注解,将 IoC 容器中已管理的 Bean(对象)直接注入到目标类的成员属性中,实现目标类对依赖对象的使用,无需手动创建依赖对象实例。比如下面的代码将 UserService 类注入 UserRepository。在 UserRepository 类的私有成员属性上添加 @Autowired 注解,Spring 会自动从容器中找到对应类型的 Bean 并注入。

package com.yang.test1_28_2.service;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    public void hello() {
        System.out.println("hello, Service...");
    }
}
package com.yang.test1_28_2.repo;
import com.yang.test1_28_2.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@Repository
public class UserRepository {
    @Autowired
    private UserService userService;

    public void hello() {
        userService.hello();
        System.out.println("hello, Repository...");
    }
}
package com.yang.test1_28_2;
import com.yang.test1_28_2.repo.UserRepository;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class Test1282Application {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(Test1282Application.class, args);
        UserRepository bean = context.getBean(UserRepository.class);
        bean.hello();
    }
}

通过 Spring 上下文获取 UserRepository 对象并调用方法,可观察到注入成功。 如果去掉 @Autowired 的注解,Spring 不会注入依赖对象,调用方法时就会抛出空指针异常 NullPointerException。 优点:

  • 简洁易用:代码侵入性低,仅需在属性上添加 @Autowired 注解,无需编写构造方法或 Setter 方法,实现快速注入。 缺点:
  • 依赖 IoC 容器:属性注入仅在 Spring IoC 容器环境中生效,若脱离容器(如手动 new 目标类),依赖对象无法注入,会出现 NPE。
  • 无法注入 final 属性:如上文所述,final 属性需初始化时赋值,与属性注入的执行时机冲突。
  • 潜在的注入失败风险:注入逻辑隐藏在注解中,若依赖 Bean 不存在或类型不匹配,仅在运行时才会暴露异常(编译期无法感知)。

3.2. 构造方法注入

构造方法注入是 Spring 依赖注入(DI)的核心方式之一,其核心逻辑是:通过目标类的构造方法,将 IoC 容器中已管理的依赖 Bean 传入目标类。IoC 容器在创建目标类实例时,会自动解析构造方法的参数类型,从容器中找到匹配的依赖 Bean 并完成注入,确保目标类在初始化阶段就拥有所需的依赖资源。 如果只有一个构造方法,@Autowired 注解可以省略掉,因为 Spring 容器也会自动通过该构造方法注入匹配的依赖 Bean;若目标类存在多个构造方法,必须在需要用于注入的构造方法上添加 @Autowired 注解,明确指定容器使用的构造方法,否则会报错。

package com.yang.test1_28_2.controller;
import com.yang.test1_28_2.service.UserService;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {
    private UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    public void hello() {
        userService.hello();
        System.out.println("hello, Controller...");
    }
}
package com.yang.test1_28_2;
import com.yang.test1_28_2.controller.UserController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class Test1282Application {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(Test1282Application.class, args);
        UserController bean = context.getBean(UserController.class);
        bean.hello();
    }
}

优点:

  1. 注入的依赖对象不可修改:依赖对象通过构造方法赋值后,若未提供 Setter 方法,外部无法修改该依赖对象,避免了依赖被意外篡改的风险,保证了对象的稳定性。
  2. 依赖对象初始化完全:构造方法是类实例化的核心逻辑,依赖注入在构造方法中执行,意味着目标类在创建完成后,所有依赖已完全初始化,不会出现'依赖未就绪就被使用'的情况(避免了属性注入可能出现的运行时空指针风险)。
  3. 通用性强:构造方法是 JDK 原生支持的语法,不依赖 Spring 框架特性,即使更换其他 IoC 框架,构造方法注入的逻辑依然可用,兼容性更高。

支持注入 final 修饰的属性:final 修饰的属性需在对象初始化阶段(构造方法执行时)完成赋值,而构造方法注入恰好在此阶段执行,因此可注入 final 依赖(解决了属性注入无法注入 final 属性的问题)。示例:

@Controller
public class UserController2 {
    private final UserService userService; // final 修饰的依赖

    // 构造方法注入 final 属性
    public UserController2(UserService userService) {
        this.userService = userService;
    }
}

缺点:

  • 注入多个依赖时代码繁琐:若目标类需要依赖多个 Bean(如同时依赖 UserService、UserRepository),构造方法的参数会增多,代码可读性和维护性下降。

3.3. Setter 注入

Setter 注入是 Spring 依赖注入(DI)的三种核心方式之一,其核心逻辑是:通过目标类的 Setter 方法(属性的赋值方法)配合 @Autowired 注解,让 IoC 容器在创建目标类实例后,主动调用 Setter 方法将容器中的依赖 Bean 注入到目标类中。

package com.yang.test1_28_2.controller;
import com.yang.test1_28_2.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {
    private UserService userService;

    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public void hello() {
        userService.hello();
        System.out.println("hello, Controller...");
    }
}
package com.yang.test1_28_2;
import com.yang.test1_28_2.controller.UserController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class Test1282Application {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(Test1282Application.class, args);
        UserController bean = context.getBean(UserController.class);
        bean.hello();
    }
}

优点:

  1. 代码可读性较强:Setter 方法明确对应某个属性的赋值逻辑,通过方法名可直接关联依赖对象,便于理解'哪个依赖通过哪个方法注入'。
  2. 支持实例化后重新配置依赖:Setter 注入的核心优势是'灵活性':目标对象创建后,可通过再次调用 Setter 方法修改依赖对象(例如切换数据源、更新配置类等),适合依赖需要动态调整的场景。 缺点:
  3. 无法注入 final 修饰的属性:final 修饰的属性需在对象初始化阶段(构造方法执行时)完成赋值,而 Setter 注入在对象创建后执行,此时 final 属性已无法修改,因此 Setter 注入不支持 final 依赖。
  4. 注入对象存在被篡改的风险:由于 Setter 方法可被外部多次调用,若代码中存在误操作(如重复调用 Setter 方法覆盖依赖),可能导致依赖对象被意外修改,破坏对象的稳定性。
  5. 依赖对象可能未就绪:Setter 注入在对象创建后执行,若目标类在依赖注入前就调用了依赖对象的方法(如在构造方法中使用 userService),会因依赖未注入而抛出 NullPointerException,需额外注意代码执行顺序。
  6. 通用性较弱:Setter 注入依赖 Spring 的 @Autowired 注解,是 Spring 框架特有的注入方式,若脱离 Spring 容器(如手动 new 目标类),需手动调用 Setter 方法赋值,兼容性不如构造方法注入(构造方法是 JDK 原生支持)。

目录

  1. 一、方法注解 @Bean
  2. 1.1. 需要配合类注解使用
  3. 1.2. 定义多个对象
  4. 1.3. 重命名 Bean
  5. 二、扫描路径
  6. 三、DI 详解
  7. 3.1. 属性注入
  8. 3.2. 构造方法注入
  9. 3.3. Setter 注入
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • OpenClaw 边缘计算实战:树莓派与 Mac mini 部署本地 AI 管家
  • FPGA 光通信 Aurora 64B/66B 协议开发指南
  • Python 内置函数 enumerate() 详解
  • IDEA 插件开发:Swing 图形界面设计指南
  • ERNIE-4.5-0.3B:文心一言轻量级大模型的技术解析与部署
  • C++ string 类常用成员函数与全局函数详解
  • 大模型 AI 应用架构详解
  • spidev0.0 接口 C++ 读取数据全为 255 的排查实战
  • Spring Boot 自动配置原理解析
  • PHP 与 Java 协同集成方案及最新技术栈参考
  • 16 款 AI Agent 框架选型指南与实战对比
  • 基于 Sentry 自建前端错误监控系统实战
  • LangChain 框架快速入门指南
  • 贝佐斯比尔盖茨英伟达等押注,NASA 工程师打造通用机器人大脑,估值 20 亿美元
  • Claude-Code 2.1.88 源码结构解析:从 Source Map 揭秘 AI 编程助手内部实现
  • AI 扩展定律背后的神话与真相
  • Docker 镜像国内拉取加速方案:使用渡渡鸟镜像站
  • Venera 开源漫画阅读器使用指南与优化技巧
  • 2024 年常用网络资源镜像站实测与使用指南
  • 6 层高速 PCB 设计实战:立创逻辑派 FPGA-G1 开发板笔记

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online