Spring Boot 依赖注入说明文档

Spring Boot 依赖注入说明文档

1. 依赖注入的基本概念

依赖注入的英文是 Dependency Injection,简称 DI。

依赖注入需要先理解两个词:

  • 依赖;
  • 注入。

所谓依赖,是指一个类完成自己的工作时,需要使用另一个对象。

例如,UserController 需要调用 UserService 查询用户信息:

java 复制代码
@RestController
public class UserController {

    private final UserService userService;

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

在这个例子中:

text 复制代码
UserController 依赖 UserService

因为如果没有 UserServiceUserController 就无法完成用户查询等业务操作。

所谓注入,是指这个 UserService 对象不由 UserController 自己创建,而是由外部创建后传入 UserController

因此,依赖注入可以定义为:

一个类需要使用另一个对象时,不在类内部直接创建该对象,而是由外部创建后,通过构造方法、Setter 方法或字段等方式传入。

在 Spring Boot 中,这个"外部"通常就是 Spring 容器。


2. 不使用依赖注入的方式

假设有一个用户业务类:

java 复制代码
public class UserService {

    public String getUserName() {
        return "张三";
    }
}

如果不使用依赖注入,UserController 可以直接在内部创建 UserService

java 复制代码
public class UserController {

    private final UserService userService;

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

也可以简写为:

java 复制代码
public class UserController {

    private final UserService userService =
            new UserService();
}

这种写法的特点是:

text 复制代码
UserController 自己创建 UserService
UserController 自己决定使用哪个实现类
UserController 自己管理 UserService 对象

也就是说,UserController 不仅负责使用 UserService,还负责创建 UserService

这种方式会使对象的使用逻辑和创建逻辑绑定在一起。


3. 使用依赖注入的方式

将代码修改为:

java 复制代码
public class UserController {

    private final UserService userService;

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

此时,UserController 内部不再出现:

java 复制代码
new UserService();

它只声明:

text 复制代码
我需要一个 UserService 对象。

至于这个对象:

  • 由谁创建;
  • 什么时候创建;
  • 创建哪个实现类;
  • 是否只创建一个;
  • 是否需要代理;

这些问题不再由 UserController 处理。

在普通 Java 程序中,可以手动完成依赖注入:

java 复制代码
public static void main(String[] args) {

    UserService userService =
            new UserService();

    UserController userController =
            new UserController(userService);
}

这里,main 方法创建了 UserService,然后将它传入 UserController

因此,依赖注入并不是 Spring 独有的语法。它首先是一种对象设计方式:

类只负责声明和使用依赖,依赖对象由外部创建和提供。


4. 构造方法中的赋值是什么意思

下面这段代码看起来只是普通的构造方法赋值:

java 复制代码
public UserController(UserService userService) {
    this.userService = userService;
}

它本身确实只是普通 Java 代码。

其中有两个同名变量:

java 复制代码
private final UserService userService;

这是 UserController 的成员变量。

而构造方法中的:

java 复制代码
UserService userService

是外部传进来的构造方法参数。

下面这行代码:

java 复制代码
this.userService = userService;

表示:

text 复制代码
将外部传进来的 UserService 对象,
保存到当前 UserController 对象的成员变量中。

为了更容易理解,可以暂时将参数改名:

java 复制代码
public UserController(
        UserService serviceFromOutside
) {
    this.userService = serviceFromOutside;
}

这样就可以清楚地看到:

text 复制代码
serviceFromOutside
        ↓
保存到 this.userService

依赖注入的关键不在这行赋值本身,而在于:

传进来的 UserService 对象是谁创建的,又是谁调用构造方法将它传进来的。

在 Spring Boot 中,这个工作由 Spring 容器完成。


5. Spring Boot 中的依赖注入

在 Spring Boot 中,可以将相关类注册到 Spring 容器。

例如:

java 复制代码
@Service
public class UserService {

    public String getUserName() {
        return "张三";
    }
}

控制器:

java 复制代码
@RestController
public class UserController {

    private final UserService userService;

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

Spring Boot 启动后,会在内部完成类似下面的操作:

java 复制代码
UserService userService =
        new UserService();

UserController userController =
        new UserController(userService);

这段代码通常不需要开发人员手动编写。

Spring 会自动完成以下过程:

text 复制代码
发现 UserService 类
        ↓
创建 UserService 对象
        ↓
发现 UserController 类
        ↓
分析 UserController 构造方法
        ↓
发现构造方法需要 UserService
        ↓
从容器中找到 UserService 对象
        ↓
将 UserService 传入 UserController

最后完成:

java 复制代码
new UserController(userService);

这一过程就是 Spring Boot 中的构造方法依赖注入。


6. 什么是 Spring 容器

Spring 容器可以理解为一个负责创建、保存和管理对象的对象管理中心。

Spring Boot 启动时:

java 复制代码
SpringApplication.run(
        Application.class,
        args
);

会创建 Spring 应用上下文。

常见的核心接口是:

java 复制代码
ApplicationContext

Spring 容器中可能保存如下对象:

text 复制代码
userController
userService
userMapper
dataSource
objectMapper
transactionManager

从便于理解的角度,可以暂时将 Spring 容器想象成一个对象集合:

java 复制代码
Map<String, Object> beans;

例如:

text 复制代码
userController → UserController 对象
userService    → UserService 对象
dataSource     → 数据库连接池对象

真实的 Spring 容器比一个普通的 Map 复杂得多,但这种理解有助于认识 Bean 和依赖注入。


7. 什么是 Bean

Bean 是指:

被 Spring 容器创建、保存和管理的对象。

例如:

java 复制代码
@Service
public class UserService {
}

这里:

java 复制代码
@Service

是注解。

Spring 根据这个注解创建出来的:

java 复制代码
UserService 对象

才是 Bean。

因此需要区分:

text 复制代码
@Service:注解
UserService:类
new UserService() 得到的对象:实例
由 Spring 管理的 UserService 实例:Bean

Bean 不是注解,注解只是告诉 Spring:

text 复制代码
这个类需要由 Spring 创建和管理。

8. 常见的 Bean 注册方式

8.1 @Component

@Component 是通用组件注解:

java 复制代码
@Component
public class FileUtils {
}

表示该类需要由 Spring 管理。


8.2 @Service

用于标记业务层组件:

java 复制代码
@Service
public class UserService {
}

从 Bean 注册角度看,@Service 本质上属于一种特殊的 @Component

它主要用于表达:

text 复制代码
这是一个业务逻辑组件。

8.3 @Repository

用于数据访问层:

java 复制代码
@Repository
public class UserRepository {
}

通常负责数据库访问操作。


8.4 @Controller

用于传统 Spring MVC 控制器:

java 复制代码
@Controller
public class PageController {
}

通常用于返回页面。


8.5 @RestController

用于 REST 接口控制器:

java 复制代码
@RestController
public class UserController {
}

通常用于接收 HTTP 请求并返回 JSON 数据。


8.6 @Configuration@Bean

有些对象不能直接在类上添加 @Component,或者对象创建过程比较复杂,可以通过配置类注册:

java 复制代码
@Configuration
public class AppConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

这里:

java 复制代码
new BCryptPasswordEncoder()

返回的对象会由 Spring 容器管理,因此也是一个 Bean。

Bean 的默认名称为方法名:

text 复制代码
passwordEncoder

9. 依赖注入与控制反转

依赖注入经常和控制反转一起出现。

控制反转的英文是 Inversion of Control,简称 IoC。

传统方式中,对象由开发人员自己创建:

java 复制代码
UserService userService =
        new UserService();

开发人员负责:

  • 创建对象;
  • 选择实现类;
  • 管理对象关系;
  • 决定对象数量;
  • 控制对象生命周期。

使用 Spring 后,对象的创建和管理交给 Spring 容器。

因此,控制反转可以理解为:

对象的创建权和管理权,从业务代码转移给 Spring 容器。

依赖注入是实现控制反转的主要方式。

二者的关系可以概括为:

text 复制代码
IoC:对象由 Spring 管理
DI:Spring 将对象传给需要它的其他对象

10. 依赖注入并不是完全解耦

依赖注入的作用不是让两个类完全没有联系。

例如:

java 复制代码
private final UserService userService;

说明 UserController 仍然需要 UserService

它仍然会调用:

java 复制代码
userService.getUserName();

所以:

text 复制代码
UserController 仍然依赖 UserService

依赖注入真正分离的是:

text 复制代码
对象的使用
对象的创建

使用依赖注入后,UserController 只负责使用 UserService,不再负责创建 UserService

因此,更准确的说法是:

依赖注入降低了类之间的耦合,但并不是完全消除依赖关系。


11. 面向接口的依赖注入

实际开发中,通常推荐依赖接口,而不是直接依赖具体实现类。

接口:

java 复制代码
public interface UserService {

    String getUserName(Long id);
}

实现类:

java 复制代码
@Service
public class DatabaseUserService
        implements UserService {

    @Override
    public String getUserName(Long id) {
        return "从数据库查询用户";
    }
}

控制器:

java 复制代码
@RestController
public class UserController {

    private final UserService userService;

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

这里 UserController 依赖的是:

java 复制代码
UserService

而不是:

java 复制代码
DatabaseUserService

以后可以增加新的实现:

java 复制代码
@Service
public class CacheUserService
        implements UserService {

    @Override
    public String getUserName(Long id) {
        return "从缓存查询用户";
    }
}

这样,控制器不需要关心数据来自:

  • 数据库;
  • Redis 缓存;
  • 远程接口;
  • 模拟数据。

控制器只需要调用接口定义的方法:

java 复制代码
userService.getUserName(id);

这就是面向接口编程。


12. 接口只有一个实现类时

如果接口只有一个实现类:

java 复制代码
public interface UserService {
}

实现类:

java 复制代码
@Service
public class UserServiceImpl
        implements UserService {
}

控制器:

java 复制代码
@RestController
public class UserController {

    private final UserService userService;

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

Spring 会按照类型查找:

text 复制代码
UserService 类型的 Bean

由于容器中只有一个实现对象,所以 Spring 可以直接将它注入。

相当于:

java 复制代码
UserService userService =
        new UserServiceImpl();

UserController userController =
        new UserController(userService);

13. 接口存在多个实现类时

假设接口有两个实现类:

java 复制代码
public interface PaymentService {

    void pay();
}

支付宝实现:

java 复制代码
@Service
public class AlipayService
        implements PaymentService {

    @Override
    public void pay() {
        System.out.println("支付宝支付");
    }
}

微信支付实现:

java 复制代码
@Service
public class WechatPayService
        implements PaymentService {

    @Override
    public void pay() {
        System.out.println("微信支付");
    }
}

如果直接注入:

java 复制代码
@RestController
public class PaymentController {

    private final PaymentService paymentService;

    public PaymentController(
            PaymentService paymentService
    ) {
        this.paymentService = paymentService;
    }
}

Spring 会找到两个 PaymentService Bean:

text 复制代码
AlipayService
WechatPayService

此时 Spring 无法确定应该注入哪个对象,通常会启动失败,并提示需要一个 Bean,但实际找到了多个 Bean。

需要注意的是:

Spring 不是判断哪个类实现接口,因为两个类都已经实现了接口。Spring 判断的是,当前注入位置应该选择哪个实现对象。


14. 使用 @Qualifier 指定实现类

可以通过 @Qualifier 明确指定 Bean 名称:

java 复制代码
@RestController
public class PaymentController {

    private final PaymentService paymentService;

    public PaymentController(
            @Qualifier("alipayService")
            PaymentService paymentService
    ) {
        this.paymentService = paymentService;
    }
}

默认情况下,类名首字母小写通常作为 Bean 名称:

text 复制代码
AlipayService    → alipayService
WechatPayService → wechatPayService

因此:

java 复制代码
@Qualifier("alipayService")

表示明确选择 AlipayService 对象。

也可以主动指定 Bean 名称:

java 复制代码
@Service("alipay")
public class AlipayService
        implements PaymentService {
}

注入时:

java 复制代码
public PaymentController(
        @Qualifier("alipay")
        PaymentService paymentService
) {
    this.paymentService = paymentService;
}

15. 使用 @Primary 设置默认实现

如果多个实现类中有一个是默认实现,可以使用 @Primary

java 复制代码
@Service
@Primary
public class AlipayService
        implements PaymentService {

    @Override
    public void pay() {
        System.out.println("支付宝支付");
    }
}

另一个实现:

java 复制代码
@Service
public class WechatPayService
        implements PaymentService {
}

注入时不需要指定名称:

java 复制代码
public PaymentController(
        PaymentService paymentService
) {
    this.paymentService = paymentService;
}

Spring 会优先选择带有 @Primary 的实现。

可以这样理解:

text 复制代码
@Primary:默认选择
@Qualifier:明确点名

当二者同时存在时,通常以明确指定的 @Qualifier 为准。


16. 同时注入所有实现类

有时业务并不是只选择一个实现,而是需要得到全部实现。

可以注入 List

java 复制代码
@Service
public class PaymentManager {

    private final List<PaymentService> paymentServices;

    public PaymentManager(
            List<PaymentService> paymentServices
    ) {
        this.paymentServices = paymentServices;
    }
}

Spring 会将所有 PaymentService 实现放入集合:

text 复制代码
AlipayService
WechatPayService

之后可以遍历调用:

java 复制代码
public void executeAll() {
    for (PaymentService service : paymentServices) {
        service.pay();
    }
}

17. 通过 Map 动态选择实现

还可以将同一接口的全部实现注入为 Map

java 复制代码
@Service
public class PaymentManager {

    private final Map<String, PaymentService>
            paymentServiceMap;

    public PaymentManager(
            Map<String, PaymentService>
                    paymentServiceMap
    ) {
        this.paymentServiceMap =
                paymentServiceMap;
    }
}

Spring 注入后的结构大致为:

text 复制代码
alipayService
    → AlipayService 对象

wechatPayService
    → WechatPayService 对象

可以根据参数动态选择:

java 复制代码
public void pay(String paymentType) {

    PaymentService paymentService =
            paymentServiceMap.get(paymentType);

    if (paymentService == null) {
        throw new IllegalArgumentException(
                "不支持的支付方式:" + paymentType
        );
    }

    paymentService.pay();
}

调用:

java 复制代码
paymentManager.pay("alipayService");

就会选择支付宝实现。

这种设计在以下场景中非常常见:

  • 多种支付方式;
  • 多种文件存储方式;
  • 多种消息发送方式;
  • 多种预测模型;
  • 多种数据源;
  • 多种业务处理策略。

18. 根据配置选择实现类

有时不同环境需要使用不同实现。

例如:

text 复制代码
开发环境使用模拟服务
生产环境使用真实远程服务

可以使用条件注解:

java 复制代码
@Service
@ConditionalOnProperty(
        name = "model.provider",
        havingValue = "local"
)
public class LocalModelService
        implements ModelService {
}

远程实现:

java 复制代码
@Service
@ConditionalOnProperty(
        name = "model.provider",
        havingValue = "remote"
)
public class RemoteModelService
        implements ModelService {
}

配置文件:

yaml 复制代码
model:
  provider: remote

此时只有符合条件的 RemoteModelService 会被注册为 Bean。

这样容器中可能只存在一个 ModelService 实现,其他业务类就可以正常注入。


19. 三种依赖注入方式

Spring 中常见的注入方式包括:

  • 构造方法注入;
  • Setter 方法注入;
  • 字段注入。

19.1 构造方法注入

java 复制代码
@RestController
public class UserController {

    private final UserService userService;

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

这是最推荐的方式。

优点包括:

  • 依赖关系明确;
  • 可以使用 final
  • 便于单元测试;
  • 可以保证对象创建后依赖完整;
  • 更容易发现循环依赖。

19.2 Setter 方法注入

java 复制代码
@RestController
public class UserController {

    private UserService userService;

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

Setter 注入适合表示可以后续更换或属于可选性质的依赖。


19.3 字段注入

java 复制代码
@RestController
public class UserController {

    @Autowired
    private UserService userService;
}

字段注入代码较短,但通常不推荐作为主要方式。

主要问题包括:

  • 依赖关系不够直观;
  • 无法方便地使用 final
  • 不启动 Spring 时不方便测试;
  • 容易让一个类隐藏过多依赖。

因此,实际开发中通常优先选择构造方法注入。


20. 构造方法是否需要 @Autowired

如果一个类只有一个构造方法,通常可以省略 @Autowired

java 复制代码
@RestController
public class UserController {

    private final UserService userService;

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

下面的写法也可以:

java 复制代码
@Autowired
public UserController(UserService userService) {
    this.userService = userService;
}

但通常没有必要。

如果类中存在多个构造方法,则可能需要使用 @Autowired 明确告诉 Spring 使用哪个构造方法。


21. Lombok 简化构造方法注入

使用 Lombok 时,可以使用:

java 复制代码
@RequiredArgsConstructor

例如:

java 复制代码
@RestController
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;
}

Lombok 会自动生成:

java 复制代码
public UserController(UserService userService) {
    this.userService = userService;
}

需要注意:

Lombok 只是帮助生成构造方法,真正完成 Bean 查找和依赖注入的仍然是 Spring。


22. Controller、Service 和 Mapper 的完整依赖关系

一个常见的 Spring Boot 项目结构为:

text 复制代码
Controller
    ↓
Service
    ↓
Mapper
    ↓
数据库

控制器:

java 复制代码
@RestController
@RequestMapping("/users")
public class UserController {

    private final UserService userService;

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

    @GetMapping("/{id}")
    public User getUser(
            @PathVariable Long id
    ) {
        return userService.findById(id);
    }
}

业务接口:

java 复制代码
public interface UserService {

    User findById(Long id);
}

业务实现:

java 复制代码
@Service
public class UserServiceImpl
        implements UserService {

    private final UserMapper userMapper;

    public UserServiceImpl(
            UserMapper userMapper
    ) {
        this.userMapper = userMapper;
    }

    @Override
    public User findById(Long id) {
        return userMapper.selectById(id);
    }
}

MyBatis Mapper:

java 复制代码
@Mapper
public interface UserMapper {

    User selectById(Long id);
}

Spring Boot 和 MyBatis 启动后,大致完成:

java 复制代码
UserMapper userMapper =
        MyBatis创建的代理对象;

UserService userService =
        new UserServiceImpl(userMapper);

UserController userController =
        new UserController(userService);

最终的依赖关系为:

text 复制代码
UserController
    ↓ 注入
UserServiceImpl
    ↓ 注入
UserMapper 代理对象
    ↓
数据库

23. Spring Boot 启动时的注入过程

Spring Boot 应用启动时,可以将依赖注入过程简化为:

text 复制代码
执行 main 方法
        ↓
调用 SpringApplication.run
        ↓
创建 Spring 容器
        ↓
扫描组件
        ↓
识别 @Controller、@Service 等注解
        ↓
注册 Bean 定义
        ↓
创建 Bean 对象
        ↓
分析构造方法参数
        ↓
查找匹配的依赖 Bean
        ↓
调用构造方法传入依赖
        ↓
完成 Bean 初始化
        ↓
启动内嵌 Web 服务器
        ↓
监听 HTTP 请求

因此,当用户请求到达时,Controller、Service、Mapper 等对象通常已经创建和注入完成。

不是每次请求到达后才重新执行依赖注入。


24. Spring Bean 的默认作用域

Spring Bean 默认是单例作用域:

text 复制代码
singleton

表示在同一个 Spring 容器中,一个 Bean 通常只有一个实例。

例如两个 Controller 都依赖同一个 UserService

text 复制代码
Controller A ──┐
               ├── 同一个 UserService 对象
Controller B ──┘

因此,Service 中通常不应该保存某个请求专用的可变状态。

例如下面的写法可能存在并发问题:

java 复制代码
@Service
public class UserService {

    private Long currentUserId;
}

多个请求可能共同访问同一个 UserService 对象。

业务方法中的局部变量通常更加安全:

java 复制代码
public User findById(Long id) {
    User user = userMapper.selectById(id);
    return user;
}

25. 循环依赖

如果两个类互相依赖:

java 复制代码
@Service
public class AService {

    private final BService bService;

    public AService(BService bService) {
        this.bService = bService;
    }
}
java 复制代码
@Service
public class BService {

    private final AService aService;

    public BService(AService aService) {
        this.aService = aService;
    }
}

依赖关系为:

text 复制代码
AService
    ↓
BService
    ↓
AService

Spring 创建 AService 时需要 BService,创建 BService 时又需要 AService,这就是循环依赖。

循环依赖通常说明职责划分不合理。

更合理的方式可能是提取公共业务:

text 复制代码
AService ──→ CommonService
BService ──→ CommonService

而不是让两个 Service 相互依赖。


26. 多实现类在实际开发中的常见场景

一个接口存在多个实现类在实际项目中非常常见。

支付方式

text 复制代码
PaymentService
├── AlipayService
├── WechatPayService
└── BankCardPaymentService

消息发送

text 复制代码
MessageService
├── EmailMessageService
├── SmsMessageService
└── WechatMessageService

文件存储

text 复制代码
StorageService
├── LocalStorageService
├── MinioStorageService
└── OssStorageService

登录认证

text 复制代码
AuthService
├── PasswordAuthService
├── LdapAuthService
└── OAuthAuthService

模型服务

text 复制代码
ModelService
├── MockModelService
├── LocalModelService
└── RemoteModelService

对应的常见选择方式为:

text 复制代码
固定选择某个实现
    → @Qualifier

设置默认实现
    → @Primary

根据业务参数动态选择
    → 注入 Map 或 List

根据配置或运行环境选择
    → @ConditionalOnProperty 或 @Profile

27. 依赖注入的主要价值

依赖注入的主要价值包括以下几个方面。

27.1 分离对象创建与对象使用

业务类只关心如何使用依赖,不需要关心依赖如何创建。


27.2 降低对具体实现类的耦合

通过依赖接口,可以方便地替换具体实现。


27.3 便于单元测试

例如:

java 复制代码
UserService mockUserService =
        new MockUserService();

UserController userController =
        new UserController(mockUserService);

不启动完整 Spring 容器,也可以测试 UserController


27.4 统一管理对象生命周期

Spring 可以统一处理:

  • 对象创建;
  • 单例管理;
  • 初始化;
  • 销毁;
  • 代理;
  • 事务;
  • 缓存;
  • 权限控制。

27.5 便于扩展多种实现

同一个接口可以对应多个实现,并根据配置、参数或注解选择具体对象。


28. 常见认识误区

误区一:依赖注入就是构造方法赋值

构造方法赋值只是保存依赖。

真正的依赖注入还包括:

text 复制代码
外部创建依赖对象
        ↓
调用构造方法
        ↓
将依赖对象传入

误区二:Bean 就是注解

Bean 是由 Spring 管理的对象。

@Service@Component 等只是帮助注册 Bean 的注解。


误区三:依赖注入可以完全解耦

依赖注入并不会消除类之间的业务依赖。

它主要分离对象创建与对象使用,并降低对具体实现的耦合。


误区四:Spring 会自动实例化所有类

Spring 只会创建已经注册到容器中的 Bean。

普通类如果没有通过注解、@Bean 或其他方式注册,Spring 通常不会自动管理。


误区五:有多个实现类时 Spring 会自动理解业务需求

Spring 只能根据类型、Bean 名称、@Primary@Qualifier 和条件配置进行选择。

它不会自动理解开发人员的业务意图。


29. 最终总结

Spring Boot 中的依赖注入可以概括为以下过程:

text 复制代码
开发人员定义类和接口
        ↓
使用注解或 @Bean 注册组件
        ↓
Spring 创建并管理对象
        ↓
这些对象成为 Bean
        ↓
Spring 分析构造方法参数
        ↓
查找匹配的 Bean
        ↓
将 Bean 传入构造方法
        ↓
完成对象之间的组装

最核心的定义是:

依赖注入就是一个类需要使用另一个对象时,不在类内部直接创建该对象,而是由外部创建后传入。在 Spring Boot 中,依赖对象通常由 Spring 容器创建和管理,并根据构造方法中的参数自动注入。

推荐使用的基本写法为:

java 复制代码
public interface UserService {

    User findById(Long id);
}
java 复制代码
@Service
public class UserServiceImpl
        implements UserService {

    private final UserMapper userMapper;

    public UserServiceImpl(
            UserMapper userMapper
    ) {
        this.userMapper = userMapper;
    }

    @Override
    public User findById(Long id) {
        return userMapper.selectById(id);
    }
}
java 复制代码
@RestController
public class UserController {

    private final UserService userService;

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

当接口存在多个实现时:

text 复制代码
只有一个实现
    → Spring 自动注入

多个实现并指定 @Qualifier
    → 注入指定 Bean

多个实现并设置 @Primary
    → 注入默认 Bean

需要全部实现
    → 注入 List 或 Map

多个实现但没有选择规则
    → Spring 启动报错

可以用一句话记忆:

我们负责定义类、接口、业务功能和依赖关系;Spring 负责创建对象、管理 Bean,并将正确的依赖对象注入需要它的类中。

相关推荐
tachibana21 小时前
hot100 数组中的第K个最大元素(215)
java·数据结构·算法·leetcode
our_times1 小时前
2026年Java开发者破局指南:Spring AI 2.0 与 Agent 开发实战
java·人工智能·spring
流烟默2 小时前
SpringBoot应用链路追踪 traceId 技术方案
spring boot·skywalking·traceid
梦想的初衷~2 小时前
植被遥感反演与数据同化算法体系教程:从PROSAIL前向模拟到作物估产
人工智能·python·机器学习·作物模型·遥感数据同化·prosail·植被参数反演
LadenKiller2 小时前
近期AI协作写量化规则,要按阶段安排任务
人工智能·python
天天进步20152 小时前
Python全栈项目--基于深度学习的图像超分辨率系统
开发语言·python·深度学习
糖果店的幽灵2 小时前
langgraph的四种state解析
java·前端·javascript·langgraph
杨充2 小时前
4.接口而非实现编程
java·后端·架构
梦梦代码精2 小时前
基于ThinkPHP6 + Vue3的家政预约系统全解析:从LBS定位到自动派单的完整实现
java·docker·开源·php·代码规范