Spring 三层架构与 IoC、依赖注入完全指南

文章目录

    • [一、前后端分离下的 Java 后端三层架构](#一、前后端分离下的 Java 后端三层架构)
      • [1.1 三层定义](#1.1 三层定义)
      • [1.2 代码分层对应示例](#1.2 代码分层对应示例)
      • [1.3 分层规范命名](#1.3 分层规范命名)
    • [二、Spring 两大核心:IoC 与 AOP(重点:IoC)](#二、Spring 两大核心:IoC 与 AOP(重点:IoC))
      • [2.1 IoC 概念](#2.1 IoC 概念)
      • [2.2 IoC 容器的两大操作](#2.2 IoC 容器的两大操作)
      • [2.3 IoC 容器的优势](#2.3 IoC 容器的优势)
    • [三、将 Bean 交给 Spring 管理的两类注解](#三、将 Bean 交给 Spring 管理的两类注解)
      • [3.1 五大类注解(写在类上)](#3.1 五大类注解(写在类上))
      • [3.2 @Bean 方法注解(写在配置类的方法上)](#3.2 @Bean 方法注解(写在配置类的方法上))
      • [3.3 容器获取 Bean](#3.3 容器获取 Bean)
    • [四、DI 依赖注入的三种方式](#四、DI 依赖注入的三种方式)
      • [4.1 三种注入方式代码示例](#4.1 三种注入方式代码示例)
      • [4.2 三种注入方式优缺点对比](#4.2 三种注入方式优缺点对比)
    • [五、@Autowired 多 Bean 冲突问题与解决方案](#五、@Autowired 多 Bean 冲突问题与解决方案)
      • [5.1 问题根源](#5.1 问题根源)
      • [5.2 三种解决方案](#5.2 三种解决方案)
      • [5.3 @Autowired vs @Resource 核心区别](#5.3 @Autowired vs @Resource 核心区别)
    • 全文总结
    • 核心知识点复盘
    • [常见问题 / 避坑指南](#常见问题 / 避坑指南)

本文以 Spring Boot 为基石,系统梳理后端三层架构设计思想、IoC 控制反转的底层逻辑、Bean 管理注解的分类与用法、DI 依赖注入的三种方式及其选型依据,最后深入剖析多 Bean 冲突的解决方案。全文以"概念 → 原理 → 实战 → 避坑"为主线,适合学习复盘与技术分享。


一、前后端分离下的 Java 后端三层架构

1.1 三层定义

在现代前后端分离架构中,后端 Java 项目的代码组织通常遵循三层架构 模式。每一层各司其职,边界清晰:

表现层(Controller)

距离用户最近的层级。负责接收前端发送的 HTTP 请求,调用业务层处理逻辑,最终将处理结果封装为响应数据返回前端。Controller 本身不应该包含复杂的业务判断------它只做"接待和转交"。

场景代入 :电商商品列表接口。Controller 接收 GET /product/list?page=1&size=20 请求,调用 Service 获取数据后,将包含商品图片、价格、店铺信息的列表以 JSON 格式返回给前端。

业务逻辑层(Service)

项目的核心大脑。所有业务规则、数据加工、复杂计算都在这一层完成。Service 接收 Controller 的调用,协调多个 Dao 完成数据读写,并在中间插入业务逻辑。

场景代入:商品原价叠加国家补贴计算、汇总用户评价数量和星级、将库存数字转为"可购/缺货"的文字标签。这些逻辑与数据存储无关,与页面展示也无关------它们属于"业务规则"。

数据层(Dao / Repository)

与数据库直接交互的唯一入口。执行 SQL 或 ORM 操作,完成数据的增删改查,将数据库记录映射为 Java 对象,供 Service 层使用。

场景代入:从数据库中查询商品原价、库存数量、店铺信息等原始数据,返回给 Service 做进一步加工。

三层之间的调用关系可以用下图直观理解:

复制代码
[前端请求]  →  Controller  →  Service  →  Dao  →  [数据库]
                ↑              ↑          ↑
             接收请求       业务处理    数据访问
             返回响应       数据加工    CRUD操作

核心原则:上层依赖下层,下层不感知上层。Controller 知道 Service 的存在,Service 知道 Dao 的存在,但反过来 Dao 完全不需要知道是谁在调用它。

1.2 代码分层对应示例

以下是一个简化但完整的图书管理场景,展示三层如何协作:

java 复制代码
// ======================== Dao 层:模拟数据库查询 ========================
// 实际项目中这里会使用 MyBatis / JPA 与真实数据库交互
public List<Book> mockData() {
    List<Book> books = new ArrayList<>();
    books.add(new Book("Spring实战", "张三", 1));   // status=1 表示可借阅
    books.add(new Book("Java编程思想", "李四", 0));  // status=0 表示已借出
    return books;
}

// ======================== Controller 层:接收请求、返回响应 ========================
@RequestMapping("/getList")
public List<Book> getList() {
    // 1. 调用 Dao 层获取原始数据
    List<Book> books = mockData();

    // 2. 调用 Service 层做业务处理(状态码 → 中文描述)
    for (Book book : books) {
        if (book.getStatus() == 1) {
            book.setStatusCN("可借阅");
        } else {
            book.setStatusCN("不可借阅");
        }
    }

    // 3. 封装结果返回前端
    return books;
}

代码思路解析

  1. Dao 层只做一件事:从"数据源"获取原始记录,不掺杂任何业务判断。
  2. Service 层的逻辑是"状态码转中文描述"------这是典型的业务加工,与展示方式无关。
  3. Controller 层编排流程:先取数据、再加工、最后返回。它不关心数据从哪来,也不关心加工细节。

关键点 :三层分离的本质是关注点分离。当需求变化时,只需要修改对应的层------比如数据库从 MySQL 换成 PostgreSQL,只改 Dao 层;业务规则调整,只改 Service 层;接口返回格式变化,只改 Controller 层。

1.3 分层规范命名

在 Spring 项目中,有一套约定俗成的命名规范,遵循它能让团队协作更高效:

规范项 说明 示例
类名 大驼峰(PascalCase) UserControllerBookServiceBookDao
Bean 默认名称 类名首字母小写(小驼峰) UserControlleruserController
特殊命名规则 类名前两位均大写时,Bean 名与类名一致 UControllerUController

第二条规则背后的原因:Java Bean 命名规范要求将类名首字母转为小写来生成默认 Bean 名称。但如果前两个字母都是大写(如 UController),则保持类名原样,因为 Spring 认为这可能是一个缩写词(如 URLParser),不应该破坏其大写结构。

java 复制代码
// 类名:UserController  →  Bean 默认名称:userController
@Controller
public class UserController { }

// 类名:UController  →  Bean 默认名称:UController(前两位大写,保持原样)
@Controller
public class UController { }

这个细节在实际开发中容易踩坑------当你用 getBean("uController") 获取 UController 时会报错,正确的名称是 "UController"


二、Spring 两大核心:IoC 与 AOP(重点:IoC)

Spring 框架有两大核心思想:IoC(控制反转)AOP(面向切面编程)。本文聚焦 IoC,它是 Spring 的基石,也是理解后续 DI(依赖注入)的前提。

2.1 IoC 概念

IoC(Inversion of Control,控制反转) :将对象(Bean)的创建、销毁、生命周期管理的控制权 ,从业务代码手中反转给 Spring 第三方容器。

这个概念比较抽象,我们用一个汽车制造的场景来对比说明。

传统方式(v1:上层控制下层)

java 复制代码
// Tire.java ------ 轮胎,最底层组件
public class Tire {
    private int size;
    public Tire(int size) {
        this.size = size;
        System.out.println("tire size: " + size);
    }
}

// Bottom.java ------ 底盘,依赖轮胎
public class Bottom {
    private Tire tire;
    public Bottom(int size) {
        this.tire = new Tire(size);    // ⚠️ 自己 new 轮胎
        System.out.println("bottom init...");
    }
}

// Framework.java ------ 车架,依赖底盘
public class Framework {
    private Bottom bottom;
    public Framework(int size) {
        this.bottom = new Bottom(size); // ⚠️ 自己 new 底盘
        System.out.println("framework init...");
    }
}

// Car.java ------ 整车,依赖车架
public class Car {
    private Framework framework;
    public Car(int size) {
        this.framework = new Framework(size); // ⚠️ 自己 new 车架
        System.out.println("car init...");
    }
    public void run() {
        System.out.println("car run...");
    }
}

// Main.java ------ 启动
public class Main {
    public static void main(String[] args) {
        Car car = new Car(18);
        car.run();
    }
}

问题分析 :Car 控制 Framework 的创建,Framework 控制 Bottom 的创建,Bottom 控制 Tire 的创建。每一层都主动 new 自己的依赖。当底层 Tire 的需求变化时------比如新增一个 color 参数------你会发现从 Bottom → Framework → Car 全部需要修改,牵一发而动全身。这就是高耦合的代价。

IoC 解耦后(v2:容器控制依赖,下层注入上层)

java 复制代码
// Tire.java ------ 新增 color 属性,但这就是唯一的改动点
public class Tire {
    private int size;
    private String color;
    public Tire(int size, String color) {
        this.size = size;
        this.color = color;
        System.out.println("tire size: " + size + ", color: " + color);
    }
}

// Bottom.java ------ 不再自己 new,改为接收注入
public class Bottom {
    private Tire tire;
    public Bottom(Tire tire) {      // 通过构造方法接收
        this.tire = tire;
        System.out.println("bottom init...");
    }
}

// Framework.java ------ 同理
public class Framework {
    private Bottom bottom;
    public Framework(Bottom bottom) {
        this.bottom = bottom;
        System.out.println("framework init...");
    }
}

// Car.java ------ 同理
public class Car {
    private Framework framework;
    public Car(Framework framework) {
        this.framework = framework;
        System.out.println("car init...");
    }
    public void run() {
        System.out.println("car run...");
    }
}

// Main.java ------ 由外部统一组装,控制权反转
public class Main {
    public static void main(String[] args) {
        // 先创建最底层,再逐层注入上层------这就是 IoC 的思想
        Tire tire = new Tire(18, "red");
        Bottom bottom = new Bottom(tire);
        Framework framework = new Framework(bottom);
        Car car = new Car(framework);
        car.run();
    }
}

对比总结

维度 传统方式(v1) IoC 方式(v2)
对象创建者 每个类自己 new 依赖 外部组装,通过构造方法传入
修改成本 Tire 加一个参数,所有上层全改 Tire 加 color,只改 Main 中的装配代码
耦合度 高(上层依赖下层具体实现) 低(上层只依赖下层抽象)
控制权 在业务代码手中 反转给了外部(容器/Main)

一句话总结 IoC :原来是我们自己 new 对象控制一切,现在是 Spring 容器帮我们创建和管理对象,我们需要时容器直接"送过来"------控制权反转了。

2.2 IoC 容器的两大操作

Spring IoC 容器本质上是一个"对象管家",它提供两个核心操作:

  1. 存 Bean(IoC) :把对象交给 Spring 容器管理。通过注解(@Component@Service 等)标记类,Spring 启动时会扫描并实例化它们。

  2. 取 Bean(DI,依赖注入) :容器自动将依赖对象赋值给需要它的类。通过 @Autowired 等注解,Spring 在运行时把对应的 Bean 注入到目标位置。

    存:@Component / @Service / @Bean ... → 告诉 Spring "这个类归你管"
    取:@Autowired / @Resource ... → 告诉 Spring "我需要那个 Bean"

2.3 IoC 容器的优势

  1. 资源集中管理:所有对象由容器统一创建、配置、销毁,无需在代码中分散管理生命周期。
  2. 降低耦合度 :依赖关系由容器维护,底层修改不影响上层代码------如 v2 示例中 Tire 新增 color 参数,Car、Framework、Bottom 的代码完全不变。
  3. 简化开发 :开发者不需要手动 new 对象和管理依赖链,专注业务逻辑即可。

三、将 Bean 交给 Spring 管理的两类注解

Spring 提供两种方式将对象注册为容器管理的 Bean:五大类注解@Bean 方法注解

3.1 五大类注解(写在类上)

五大注解中,@Controller@Service@Repository@Configuration 本质上都是 @Component衍生注解,功能完全一致,只是用于标记类所处的层级,增强代码可读性。

注解 分层归属 作用
@Controller 表现层 标记控制器,接收前端请求
@Service 业务层 标记业务逻辑类
@Repository 数据层 标记数据库操作类
@Configuration 配置层 标记配置类,存放 @Bean 定义
@Component 通用组件 不属于以上分层的工具类

实际项目中的使用示例:

java 复制代码
// 表现层 ------ 接收请求
@Controller
public class UserController {
    // 处理用户相关 HTTP 请求
}

// 业务层 ------ 处理业务逻辑
@Service
public class UserService {
    public void hello() {
        System.out.println("hello, UserService...");
    }
}

// 配置层 ------ 存放项目配置和 @Bean 定义
@Configuration
public class UserConfiguration {
    public void hello() {
        System.out.println("hello, UserConfiguration...");
    }
}

// 通用组件 ------ 不属于上面任何层的工具类
@Component("userComponent2")   // 可自定义 Bean 名称
public class UserComponent {
    public void hello() {
        System.out.println("hello, UserComponent...");
    }
}

关键理解 :这五个注解在 Spring 容器眼中没有本质区别,换成 @Component 项目一样能跑。使用 @Service 而非 @Component 纯粹是为了让读代码的人一眼看出这个类的职责。

3.2 @Bean 方法注解(写在配置类的方法上)

@Bean 注解用于方法级别 ,必须放在带有五大注解(通常是 @Configuration@Component)的类中,将方法的返回值注册为 Spring 容器中的一个 Bean。

适用场景

  1. 管理第三方库的类 :第三方 jar 包中的类,你无法修改源码添加 @Component,只能用 @Bean 方法返回它的实例。
  2. 同一类型需要多个不同实例:比如配置多个数据源、多个 Redis 连接,每个实例用不同的方法名区分。
java 复制代码
@Component
public class UserComponent {

    // 方法名 "userInfo" 即 Bean 的默认名称
    @Bean
    public UserInfo userInfo() {
        return new UserInfo("zmt", "123456");   // 用户名 zmt,密码 123456
    }

    // @Primary 标记此 Bean 为同类型中的首选
    @Primary
    @Bean
    public UserInfo userInfo2() {
        return new UserInfo("zm", "12345");     // 另一个配置的用户
    }
}

代码思路解析 :上面的代码在 Spring 容器中创建了两个 UserInfo 类型的 Bean------一个叫 userInfo,一个叫 userInfo2。当通过类型获取 UserInfo 时,Spring 默认会选 @Primary 标记的 userInfo2,避免因多个同类型 Bean 而报错。

Bean 命名规则补充

注解方式 默认 Bean 名称 示例
五大类注解 类名首字母小写 UserServiceuserService
五大类注解(前两位大写) 保持类名不变 UControllerUController
@Bean 方法注解 方法名 userInfo()userInfo
自定义名称 通过 value 属性 @Component("myName")@Bean("myName")

补充:value 属性是字符串数组,单个值时可以省略大括号 {}。一个 Bean 可以设置多个别名(如 @Bean({"userInfo1", "u1"})),同一个类型可以在容器中存在多个不同名称的 Bean。

3.3 容器获取 Bean

Spring 通过 ApplicationContext(应用上下文)提供 getBean() 方法来获取容器中的 Bean。有三种重载方式:

java 复制代码
@SpringBootApplication
public class SpringIoCApplication {
    public static void main(String[] args) {
        // 启动 Spring 容器,返回上下文对象
        ConfigurableApplicationContext context =
            SpringApplication.run(SpringIoCApplication.class, args);

        // 方式1:按类型获取(局限性:同类型多个 Bean 时会报错)
        UserController bean = context.getBean(UserController.class);

        // 方式2:按名称获取(需要强制类型转换)
        UserController bean1 = (UserController) context.getBean("userController");

        // 方式3:按名称 + 类型获取(推荐,类型安全且精准)
        UserController bean2 = context.getBean("userController", UserController.class);

        // 验证:三种方式获取的是同一个对象(Spring Bean 默认单例)
        System.out.println(bean == bean1);   // true
        System.out.println(bean == bean2);   // true
    }
}

注意@SpringBootApplication 默认只扫描启动类所在包及其子包。如果 Bean 定义在外层包中,需要额外使用 @ComponentScan("com.zmt.springioc") 指定扫描路径,否则 getBean() 会抛出 NoSuchBeanDefinitionException


四、DI 依赖注入的三种方式

DI(Dependency Injection,依赖注入)是 IoC 的具体实现------容器在创建 Bean 时,自动将它所依赖的其他 Bean 注入进来。Spring 中主要通过 @Autowired 实现自动装配。

4.1 三种注入方式代码示例

方式一:属性注入

java 复制代码
@Controller
public class UserController {
    @Autowired
    private UserService userService;   // 直接在字段上标注

    public void hello() {
        userService.hello();
    }
}

最简洁的写法,Spring 通过反射直接把 Bean 赋值给字段。

方式二:构造方法注入(推荐)

java 复制代码
@Controller
public class UController {
    private final UserService userService;   // ✅ 支持 final 修饰

    // 当类只有一个构造方法时,@Autowired 可以省略
    public UController(UserService userService) {
        this.userService = userService;
    }

    public void hello() {
        userService.hello();
    }
}

Spring 在实例化 Bean 时调用构造方法,将依赖作为参数传入。这是目前最推荐的方式。

方式三:Setter 方法注入

java 复制代码
@Controller
public class UController {
    private UserService userService;

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

    public void hello() {
        userService.hello();
    }
}

Spring 先通过无参构造创建对象,再调用 Setter 方法注入依赖。

4.2 三种注入方式优缺点对比

维度 属性注入 构造方法注入 Setter 注入
代码简洁度 ✅ 最简洁 ❌ 多依赖时代码较长 中等
支持 final ❌ 不支持 ✅ 支持 ❌ 不支持
不可变性 ❌ 注入后可被修改 ✅ 构造后不可变 ❌ setter 可多次调用
空指针风险 ❌ 使用时才暴露 ✅ 初始化时即完成,无空指针 中等
脱离框架可用 ❌ 仅 IoC 容器可用 ✅ 可手动 new 传参 中等
运行时修改 ❌ 不支持 ❌ 不支持 ✅ 可重新配置

推荐结论 :日常开发优先使用构造方法注入。理由有三:

  1. 依赖明确:构造方法的参数列表清楚告诉你这个类需要哪些依赖,不会出现"依赖越加越多而无人察觉"的情况。
  2. 不可变性 :配合 final 关键字,依赖一旦注入就不会被篡改,安全性更高。
  3. 测试友好 :单元测试时可以直接 new UserController(mockUserService) 传入 mock 对象,无需启动 Spring 容器。

补充说明:历史版本中 Spring 3.x 推荐 Setter 注入,Spring 4.x 起官方推荐构造方法注入。如果你看到旧项目大量使用 Setter 注入,那是历史原因,新代码建议统一使用构造方法注入。


五、@Autowired 多 Bean 冲突问题与解决方案

5.1 问题根源

@Autowired 的默认匹配规则是按类型(byType )查找。当容器中同一个接口(或父类)存在多个实现类的 Bean 时,Spring 无法判断该选哪一个,抛出 NoUniqueBeanDefinitionException 异常。

java 复制代码
// 容器中存在两个 UserInfo 类型的 Bean:userInfo 和 userInfo2
// 以下代码会直接报错 ------ Spring 不知道你要哪个
@Autowired
private UserInfo userInfo;   // ❌ NoUniqueBeanDefinitionException

5.2 三种解决方案

方案一:@Primary ------ 设置默认首选

在其中一个 Bean 上标注 @Primary,当按类型匹配到多个 Bean 时,优先选择它。

java 复制代码
@Component
public class UserComponent {

    @Bean
    public UserInfo userInfo() {
        return new UserInfo("zmt", "123456");
    }

    @Primary    // 标记为同类型的默认选择
    @Bean
    public UserInfo userInfo2() {
        return new UserInfo("zm", "12345");
    }
}

适用场景 :大部分情况下都用这个 Bean,少数特殊情况单独指定。比如项目中 90% 的地方用主数据源,只有报表模块用只读数据源------主数据源加 @Primary,报表模块用 @Qualifier 单独指定。

方案二:@Qualifier ------ 精确指定 Bean 名称

搭配 @Autowired,通过 Bean 名称精确指定要注入哪一个。

java 复制代码
@Controller
public class UserController {

    @Autowired
    @Qualifier("userInfo2")   // 指定注入名为 "userInfo2" 的 Bean
    private UserInfo userInfo;

    public void hello() {
        System.out.println(userInfo);   // 输出 userInfo2 的实例
    }
}

适用场景 :需要精确控制注入哪个 Bean,不受 @Primary 影响。

方案三:@Resource ------ JDK 原生注解,默认按名称匹配

java 复制代码
@Controller
public class UserController {

    @Resource(name = "userInfo2")   // JDK 原生,直接指定 Bean 名称
    private UserInfo userInfo;

    public void hello() {
        System.out.println(userInfo);
    }
}

@Resource 是 Java 标准注解(javax.annotation.Resource),不依赖 Spring,默认按名称 匹配。如果 name 匹配失败,会退化为按类型匹配。

5.3 @Autowired vs @Resource 核心区别

维度 @Autowired @Resource
来源 Spring 框架提供 JDK 原生自带(jakarta.annotation.Resource
默认匹配规则 优先按类型匹配 优先按 Bean 名称匹配
名称指定方式 name 属性,需配合 @Qualifier("xxx") 直接通过 name 属性指定 @Resource(name="xxx")
找不到时行为 抛异常(required=false 除外) 名称找不到时退化为按类型查找
框架耦合 与 Spring 强耦合 与 Spring 解耦,可迁移到其他 IoC 框架

选型建议

  • 项目确定长期使用 Spring → @Autowired + 构造方法注入是标准组合。
  • 需要框架无关性或代码可移植性 → 使用 @Resource
  • 团队规范优先,两个都能用,保持一致性最重要------不要在一个项目中混用两种风格。

全文总结

本文以 Spring Boot 为实践环境,从三层架构设计 出发,梳理了 Controller → Service → Dao 的职责边界与命名规范;进而深入 Spring 两大核心之一 IoC 控制反转 ,通过汽车装配的 v1→v2 演进直观展示了"控制反转"如何降低代码耦合;接着系统讲解了 Bean 管理的两类注解 (五大类注解 + @Bean 方法注解)及 getBean() 的三种获取方式;然后对比了 DI 依赖注入的三种写法 ,给出"优先使用构造方法注入"的推荐结论;最后分析了多 Bean 冲突的三种解决方案@Autowired@Resource 的差异选型。


核心知识点复盘

知识点 一句话总结
三层架构 Controller 接待,Service 加工,Dao 存取------各司其职,单向依赖
IoC 控制反转 对象创建权从业务代码反转到 Spring 容器,底层修改不影响上层
五大类注解 @Component 及其衍生注解,功能相同,仅用于分层语义
@Bean 方法注解 管理第三方类或同一类型多个实例,Bean 名默认等于方法名
Bean 命名规则 类注解默认小驼峰;前两位大写保持原样;@Bean 默认方法名
getBean() 支持按类型、按名称、按名称+类型三种方式获取
属性注入 简洁但不支持 final,脱离容器无法使用
构造方法注入 支持 final、不可变、无空指针------官方推荐方式
Setter 注入 支持运行时修改,但有被篡改的风险
@Primary 同类型多 Bean 时设置默认首选
@Qualifier 搭配 @Autowired 精确指定 Bean 名称
@Resource JDK 原生,默认按名称匹配,框架无关

常见问题 / 避坑指南

Q1:三层架构中,Service 层的逻辑能不能直接写在 Controller 里?

技术上可以,项目也能跑。但这样做会导致 Controller 臃肿、代码无法复用、单元测试困难。正确的做法是 Controller 只做参数校验和结果封装,业务逻辑必须下沉到 Service

Q3:@Bean 方法注解能脱离五大注解单独使用吗?

不能。@Bean 必须放在被 Spring 管理的类中(即该类本身需要标注 @Component 或其衍生注解),否则 Spring 根本不会扫描到这个类,方法上的 @Bean 也不会生效。

Q4:构造方法注入时,@Autowired 什么时候可以省略?

当类只有一个构造方法 时,Spring 会自动将它识别为注入点,@Autowired 可以省略。但如果有多个构造方法,就必须在你想使用的那个上面标注 @Autowired

Q5:@Resource 和 @Autowired 应该选哪个?

如果项目确定长期使用 Spring 生态,用 @Autowired + 构造方法注入是最佳实践。如果代码可能需要移植到其他 IoC 框架(如 Quarkus),或者团队要求减少 Spring 耦合,用 @Resource两者选其一,统一风格,不要混用。

Q6:@Primary 和 @Qualifier 同时存在时,哪个优先级更高?

@Qualifier 的优先级高于 @Primary@Qualifier 是精确指定,"我说要哪个就哪个";@Primary 是默认值,"没特别说明时用这个"。当两者同时出现,以 @Qualifier 为准。

相关推荐
mldong1 小时前
jeeflow:98KB 的工作流引擎长什么样
后端
paopaokaka_luck2 小时前
基于springboot3+vue3的企业考勤管理系统(部门树递归、Echarts图形化分析)
开发语言·spring boot·学习·echarts·mybatis·需求分析·代码规范
Pluchon2 小时前
Java个人综合项目——萌部落社区V2.0
java·python·spring·spring cloud·postman·idea
梦雨生生3 小时前
java开发工具(学习第一天)
java·开发语言·学习
她说可以呀3 小时前
Spring-ai-alibaba文生图
java·人工智能·spring
程序员cxuan4 小时前
速度太快了!本地可以跑 DeepSeek-V4-Flash 了
人工智能·后端·程序员
条tiao条5 小时前
MVVM架构与ArkUI状态管理
华为·架构·harmonyos·鸿蒙·mvvm
阿祖zu5 小时前
芝士就是力量!开源私有化部署与 GitHub 双向同步的个人知识笔记 App
前端·后端·ios
松仔log5 小时前
Java中级——组合和继承
android·java·开发语言
不才不才不不才5 小时前
Spring 源码系列(16): doDispatch 全流程——一次请求的主干链路
java·后端·spring