找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1233

积分

0

好友

165

主题
发表于 16 小时前 | 查看: 5| 回复: 0

两个嵌套的蓝色菱形

这个问题,几乎所有做过一段时间 Spring Boot 的人都遇到过。

代码能启动,@Autowired 不报错,但运行时的行为就是不对——日志不对、实现不对、配置没生效,甚至感觉像换了一个类在跑。最难受的是:你去debug,它又“看起来”是对的。

这篇文章就专门讲一件事:Bean注入成功,但注入的并不是你以为的那个,到底是怎么发生的?

蓝色背景白色对勾

1 最常见的现象

先看一个很典型的场景。

@Service
public class OrderService {
    @Autowired
    private PayService payService;

    public void pay() {
        payService.pay();
    }
}

你非常确定 PayService 只有一个实现,而且你也在实现类里打了日志。

但线上日志却告诉你:走的不是你以为的那套逻辑。

这种时候,大概率不是你看错代码,而是——Spring帮你选了另一个Bean。

2 场景一:同接口多个实现,Spring默认选了“另一个”

这是最常见、也最隐蔽的一种。

public interface PayService {
    void pay();
}
@Service
public class AliPayService implements PayService { }
@Service
public class WxPayService implements PayService { }

此时再注入:

@Autowired
private PayService payService;

如果你没加任何限定条件,结果只有两种:

  • 启动直接报错(运气好)
  • 被某个实现“悄悄选中”(运气差)

在某些组合条件下(比如某个被标了 @Primary), Spring 会非常“贴心”地替你做决定

解决方式(最直接、最稳)

@Autowired
@Qualifier("aliPayService")
private PayService payService;

或者在实现类上明确主实现:

@Primary
@Service
public class AliPayService implements PayService { }

3 场景二:你以为是单例,其实拿到了“新对象”

这个坑,通常和 配置类 / 工厂方法 有关。

@Component
public class PayConfig {
    @Bean
    public PayService payService() {
        return new AliPayService();
    }
}

再注入:

@Autowired
private PayService payService;

看起来一切正常。

但如果你在别的地方:

@Autowired
private PayConfig payConfig;

public void test() {
    PayService p1 = payConfig.payService();
    PayService p2 = payConfig.payService();
}

你以为拿的是同一个Bean,实际上,你可能拿到了 两个不同对象

原因只有一个:配置类被当成普通组件使用了。

正确写法

@Configuration
public class PayConfig {
    @Bean
    public PayService payService() {
        return new AliPayService();
    }
}

@Configuration 会确保:

  • @Bean 方法走容器
  • 不会被普通方法调用绕开

4 场景三:条件装配导致Bean被“悄悄替换”

这是线上最难排查的一类。

@Bean
@ConditionalOnProperty(name = "pay.type", havingValue = "ali")
public PayService aliPayService() { }
@Bean
@ConditionalOnProperty(name = "pay.type", havingValue = "wx")
public PayService wxPayService() { }

如果你在本地:

pay:
  type: ali

但线上配置变成了:

pay:
  type: wx

结果就是:

  • 注入没问题
  • 行为完全变了
  • 代码一行都没动

这类问题只看代码是永远看不出来的

实际排查方式

  • 打启动日志,看哪些Bean被加载
  • 明确哪些是条件装配
  • 不要假设“线上配置和本地一样”

5 场景四:包扫描范围不一致

@SpringBootApplication
@ComponentScan("com.example")

而你的实现类在:

com.demo.pay.impl

此时可能出现两种情况:

  • 你以为的Bean根本没被扫描
  • Spring找到了“另一个能用的Bean”

然后你就开始怀疑人生。

解决方式

  • 确认启动类扫描路径
  • 不要过度拆包后忘记扫描范围
  • 尽量保持 启动类在最顶层包

6 场景五:测试环境和正式环境不一致

很多人都踩过这个坑。

@Profile("test")
@Service
public class MockPayService implements PayService { }
@Profile("prod")
@Service
public class AliPayService implements PayService { }

如果你没注意当前激活的profile,你看到的注入结果,可能完全不是你写的那个。

7 如何快速确认“我到底注入了谁”

这是一个非常实用的排查技巧

@PostConstruct
public void init() {
    log.info("PayService 实现类:{}", payService.getClass().getName());
}

比你盯着IDEA看半天更有效。

因为Spring的设计目标是:尽量让系统跑起来,而不是尽量让你意识到选错了。

只要满足条件:

  • 类型匹配
  • 能创建Bean

Spring就会帮你兜底。但业务正确与否,它不负责。

写在最后

Bean注入成功,只能说明一件事:容器里有对象。 但并不保证:

  • 是你期望的实现
  • 是你理解中的生命周期
  • 是你测试过的那一套逻辑

当你发现“行为不对但代码没问题”时,第一反应不该是怀疑业务逻辑,而是先问一句:我现在用的,到底是哪一个Bean?

理解这些场景和排查方法,是掌握Spring依赖注入原理的关键一步。想了解更多类似的实践技巧和深度解析,可以关注云栈社区的技术专栏。

开心的卡通形象




上一篇:Go + Vue 3 构建轻量级定时任务管理系统 baihu-panel,开箱即用
下一篇:mysqldump常用参数详解与避坑指南:生产备份与数据恢复实战
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-14 18:54 , Processed in 0.449783 second(s), 37 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表