Skip to content
java
@Slf4j
@RestController
@RequestMapping("/wechat/notify")
@ConditionalOnProperty(name = "wechat.pay.enabled", havingValue = "true", matchIfMissing = false) //只有在微信支付启用时才被创建
public class WechatPayNotifyController {
    
    /**
     * 微信支付配置(用于验签和解密)
     */
    @Resource
    private NotificationConfig notificationConfig;
java
@Slf4j
@Configuration  // 临时注释掉,等有密钥文件后再打开
@ConditionalOnProperty(name = "wechat.pay.enabled", havingValue = "true", matchIfMissing = false) //只有当 wechat.pay.enabled=true 时,这个配置类才会生效
public class WechatPayClientConfig {
	
		/**
     * NotificationConfig Bean(用于回调验签)
     * <p>
     * RSAAutoCertificateConfig 实现了 NotificationConfig 接口
     */
    @Bean
    public NotificationConfig notificationConfig(RSAAutoCertificateConfig rsaAutoCertificateConfig) {
        return rsaAutoCertificateConfig;
    }

当我在WechatPayNotifyController 没有加@ConditionalOnProperty(name = "wechat.pay.enabled", havingValue = "true", matchIfMissing = false)注解时,项目启动就会报错

bash
26-01-05.08:23:48.038 [main            ] WARN  deprecation            - HHH90000025: MySQLDialect does not need to be specified explicitly using 'hibernate.dialect' (remove the property setting and it will be selected by default)
26-01-05.08:23:55.438 [main            ] WARN  DnsServerAddressStreamProviders - Can not find io.netty.resolver.dns.macos.MacOSDnsServerAddressStreamProvider in the classpath, fallback to system defaults. This may result in incorrect DNS resolutions on MacOS. Check whether you have a dependency on 'io.netty:netty-resolver-dns-native-macos'
26-01-05.08:23:58.050 [main            ] WARN  AnnotationConfigServletWebServerApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'wechatPayNotifyController': Injection of resource dependencies failed
26-01-05.08:23:58.286 [main            ] ERROR LoggingFailureAnalysisReporter - 

***************************
APPLICATION FAILED TO START
***************************

Description:

A component required a bean of type 'com.wechat.pay.java.core.notification.NotificationConfig' that could not be found.


Action:

Consider defining a bean of type 'com.wechat.pay.java.core.notification.NotificationConfig' in your configuration.

看了新的错误日志,现在的问题不是 RabbitAdmin 了,而是 微信支付的配置问题:

A component required a bean of type 'com.wechat.pay.java.core.notification.NotificationConfig' that could not be found.

这说明之前的 RabbitAdmin 问题已经解决了(或者被条件注解跳过了),现在是 WechatPayNotifyController 需要注入 NotificationConfig Bean,但找不到。


关于你的问题:为什么要在 MessageQueueConfig 中注入 RabbitAdmin?

你的理解有偏差,让我解释一下:

1. 不是"注入",而是"创建 Bean"

在 MessageQueueConfig 中,我们不是"注入" RabbitAdmin,而是创建/定义 RabbitAdmin Bean。这是 Spring 的配置类职责:

java
@Bean
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
    return new RabbitAdmin(connectionFactory);
}

这段代码的意思是:告诉 Spring 容器如何创建 RabbitAdmin 对象。

2. 哪里用哪里注入?

你说的"哪里用哪里注入"是对的,但有个前提:Bean 必须先被定义/创建。

  • 定义 Bean(@Bean):告诉 Spring "这个对象怎么创建",通常在 @Configuration 类中

  • 注入 Bean(@Resource/@Autowired):告诉 Spring "我需要这个对象",在使用的地方

例如:

java
// Step 1: 在配置类中定义 Bean(MessageQueueConfig)
@Bean
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
    return new RabbitAdmin(connectionFactory);
}

// Step 2: 在使用的地方注入 Bean(RabbitMQServiceImpl)
@Service
public class RabbitMQServiceImpl {
    private final RabbitAdmin rabbitAdmin;  // 这里是注入
    
    public RabbitMQServiceImpl(RabbitAdmin rabbitAdmin) {
        this.rabbitAdmin = rabbitAdmin;
    }
}
  1. 为什么在 MessageQueueConfig 中定义?因为:MessageQueueConfig 是消息队列的配置类,专门负责 RabbitMQ 相关的 Bean 定义遵循单一职责原则:配置类负责配置,服务类负责业务逻辑符合 Spring Boot 的最佳实践:相关的配置集中管理现在的问题:微信支付配置缺失让我检查一下微信支付的配置:

Explored2 files 2 searches

明白了!问题很清楚:问题分析在 WechatPayClientConfig.java 第 30 行有条件注解:

java
@ConditionalOnProperty(name = "wechat.pay.enabled", havingValue = "true", matchIfMissing = false)

这意味着:

  • 只有当 wechat.pay.enabled=true 时,这个配置类才会生效

  • 而你的配置文件中是 wechat.pay.enabled: false(第 488 行)

但是,WechatPayNotifyController 没有加条件注解,它始终会被创建,并尝试注入 NotificationConfig,导致找不到 Bean。

解决方案

有两个方案:

方案 1:给 Controller 也加条件注解(推荐)

在 WechatPayNotifyController 类上添加相同的条件注解,让它只在微信支付启用时才被创建:

java
@Slf4j
@RestController
@RequestMapping("/wechat/notify")
@ConditionalOnProperty(name = "wechat.pay.enabled", havingValue = "true", matchIfMissing = false)
public class WechatPayNotifyController {
    // ... 现有代码 ...
}

这样,当微信支付未启用时,整个 Controller 都不会被创建,就不会报错了。

方案 2:启用微信支付(需要配置密钥)

如果你想测试微信支付功能,需要:

  1. 将 application-dev.yml 中的 wechat.pay.enabled 改为 true

  2. 配置所有必需的环境变量(在 env.properties 或环境变量中):

  • WECHAT_PAY_MCH_ID:商户号

  • WECHAT_PAY_MERCHANT_SERIAL_NUMBER:商户证书序列号

  • WECHAT_PAY_API_V3_KEY:APIv3 密钥

  1. 将商户私钥文件放到 src/main/resources/certs/wechat/apiclient_key.pem

回到你的本质问题

总结:"哪里用哪里注入" vs "统一配置"

概念说明示例
定义 Bean在 @Configuration 类中用 @Bean 创建对象MessageQueueConfig 中创建 RabbitAdmin
注入 Bean在使用的类中用 @Resource/@Autowired 获取对象RabbitMQServiceImpl 中注入 rabbitAdmin
为何统一配置1. 集中管理相关配置
2. 避免重复创建
3. 便于维护和测试
所有 RabbitMQ 相关 Bean 都在 MessageQueueConfig 中定义

你的理解"哪里用哪里注入"是对的,但注入的前提是 Bean 已经被定义。Spring 的设计哲学是:

  • 配置类(@Configuration):负责定义 Bean,说明"有哪些对象"

  • 业务类(@Service/@Controller):负责使用 Bean,说明"需要哪些对象"


A:

我在 WechatPayNotifyController 中没看到关于WechatPayClientConfig的引用,为什么WechatPayClientConfig不创建,WechatPayNotifyController还会报错

好问题!让我解释一下这个间接依赖关系:

依赖链条分析

虽然 WechatPayNotifyController 没有直接引用 WechatPayClientConfig 类,但它依赖了这个配置类创建的 Bean:

java
// WechatPayNotifyController.java 第 48-49 行
@Resource
private NotificationConfig notificationConfig;

这里注入的 NotificationConfig 是在 WechatPayClientConfig 中创建的:

java
// WechatPayClientConfig.java 第 79-82 行
@Bean
public NotificationConfig notificationConfig(RSAAutoCertificateConfig rsaAutoCertificateConfig) {
    return rsaAutoCertificateConfig;
}

Spring 的 Bean 创建流程

1.Spring 启动时扫描所有 @Component、@Service、@Controller 等组件

  • 发现 WechatPayNotifyController 需要被创建

2.分析 Controller 的依赖

  • 发现需要注入 NotificationConfig 类型的 Bean

3.查找 NotificationConfig Bean

  • 在 Spring 容器中搜索 @Bean public NotificationConfig xxx() 的定义

  • 发现在 WechatPayClientConfig 中有定义

4.检查配置类的条件

  • WechatPayClientConfig 上有 @ConditionalOnProperty(name = "wechat.pay.enabled", havingValue = "true")

  • 当前配置是 wechat.pay.enabled: false

  • 条件不满足,配置类不生效,Bean 不会被创建

5.Bean 创建失败

  • NotificationConfig Bean 不存在

  • WechatPayNotifyController 无法完成依赖注入

  • 启动失败,报错:找不到 NotificationConfig 类型的 Bean


形象比喻

想象一个餐厅场景:

  • WechatPayClientConfig:厨房(负责制作食物 = 创建 Bean)

  • NotificationConfig:菜品(厨房做出来的菜 = Bean)

  • WechatPayNotifyController:服务员(需要菜品来服务客人 = 需要注入 Bean)

流程:

  1. 服务员(Controller)说:"我需要一份糖醋里脊(NotificationConfig)"

  2. 经理(Spring)去厨房找这道菜

  3. 但厨房(WechatPayClientConfig)因为条件不满足(enabled=false)今天不营业 ❌

  4. 找不到菜,服务员无法工作,餐厅开不了业(启动失败)


验证依赖关系

看这两处代码就能理解:

Controller 需要什么(第 48-49 行)

java
@Resource
private NotificationConfig notificationConfig;  // 我需要这个 Bean

配置类提供什么(第 79-82 行)

java
@Bean
public NotificationConfig notificationConfig(...) {
    return rsaAutoCertificateConfig;  // 我来创建这个 Bean
}

但配置类有条件限制(第 30 行):

java
@ConditionalOnProperty(name = "wechat.pay.enabled", havingValue = "true", matchIfMissing = false)
// 意思是:只有 wechat.pay.enabled=true 时,我才工作

为什么不直接报"找不到 WechatPayClientConfig"?

因为 Spring 的错误提示是面向 Bean 的,不是面向类的:

  • ❌ 不会说:"找不到 WechatPayClientConfig 类"(类存在,只是条件不满足)

  • ✅ 会说:"找不到 NotificationConfig 类型的 Bean"(Bean 不存在)

Spring 只关心:你需要的 Bean 在不在容器里,不关心这个 Bean 是在哪个配置类里定义的。


解决方案总结

所以正确的解决方法是:

java
@Slf4j
@RestController
@RequestMapping("/wechat/notify")
@ConditionalOnProperty(name = "wechat.pay.enabled", havingValue = "true", matchIfMissing = false)
public class WechatPayNotifyController {
    // ...
}

这样:

  • 当 wechat.pay.enabled=false 时

  • WechatPayClientConfig 不生效 → NotificationConfig Bean 不创建

  • WechatPayNotifyController 也不生效 → 不需要注入 NotificationConfig

  • 两者都不存在,没有依赖冲突 ✅

  • 当 wechat.pay.enabled=true 时

  • WechatPayClientConfig 生效 → NotificationConfig Bean 被创建

  • WechatPayNotifyController 生效 → 能成功注入 NotificationConfig

  • 完美配合 ✅

这就是为什么虽然 Controller 没有直接引用配置类,但配置类不生效时 Controller 会报错的原因!

A:

既然提到 spring 加载 bean,这个NotificationConfig是在我 maven 引入的wechatpay-apiv3:wechatpay-java-core包中的,我在WechatPayNotifyController中点击  @Resource
    private NotificationConfig notificationConfig;也是直接到wechatpay-apiv3:wechatpay-java-core包中的,所以你说的加载bean 找不到,是不是因为@Resource 注解的问题,用@Autowired注解是不是就不会报错,还有一点我不理解的就是明明这个包就在 maven 管理的 jar 包中,为什么WechatPayNotifyController非要去找WechatPayClientConfig中的 @Bean
    public NotificationConfig notificationConfig(RSAAutoCertificateConfig rsaAutoCertificateConfig) {
        return rsaAutoCertificateConfig;
    }, 难道是 spring 加载 bean 的机制,@Resource 只是注入,@Bean 才是交给 spring 管理

非常好的问题!你触及到了 Spring 依赖注入的核心概念。让我详细解释:

核心概念:类 vs Bean

1. 类存在 ≠ Bean 存在

java
// NotificationConfig 是一个接口(类),存在于 wechatpay-java-core.jar 中
public interface NotificationConfig {
    // ...
}

这个接口确实在 Maven 依赖的 jar 包中,你可以 import,可以点击跳转,但这不意味着 Spring 容器中有它的实例(Bean)。


Spring 依赖注入的工作原理

步骤分解:

  1. Maven 提供类定义:NotificationConfig 接口在 jar 包中
  2. 你需要创建实例:接口不能直接 new,需要一个实现类的实例
  3. 通过 @Bean 创建实例:在配置类中创建并放入 Spring 容器
  4. 通过 @Resource/@Autowired 注入:从 Spring 容器中取出来用

@Resource vs @Autowired 的区别

回答你的疑问:换成 @Autowired 也不会解决问题 ❌

java
// 这两种写法在这个场景下结果是一样的
@Resource
private NotificationConfig notificationConfig;  // 找不到 Bean ❌

@Autowired
private NotificationConfig notificationConfig;  // 还是找不到 Bean ❌

原因:无论用哪个注解,都是要求 Spring 容器中必须有这个 Bean。如果容器中没有,都会报错。

@Resource vs @Autowired 的真正区别

特性@Resource@Autowired
来源Java 标准(JSR-250)Spring 框架
匹配方式默认按名称匹配默认按类型匹配
指定名称@Resource(name="xxx")@Autowired + @Qualifier("xxx")
必需性默认必需可选:@Autowired(required=false)

但在你这个场景下,两者都需要容器中有对应的 Bean。


为什么必须通过 @Bean 创建?

问题:为什么不能直接用 Maven jar 包中的类?

答案:因为 NotificationConfig 是一个接口,你需要的是它的实现类的实例。

让我们看看微信支付 SDK 的设计:

java
// wechatpay-java-core.jar 中
public interface NotificationConfig {
    // 验签、解密需要的配置
    String getMerchantId();
    String getApiV3Key();
    // ...
}

// RSAAutoCertificateConfig 实现了这个接口
public class RSAAutoCertificateConfig implements NotificationConfig, Config {
    // 具体实现
}

你的配置类做了什么:

java
@Bean
public RSAAutoCertificateConfig rsaAutoCertificateConfig() {
    // Step 1: 读取你的商户私钥文件
    String privateKey = loadPrivateKey();
    
    // Step 2: 用你的商户配置创建实例
    return new RSAAutoCertificateConfig.Builder()
        .merchantId(properties.getMchId())          // 你的商户号
        .privateKey(privateKey)                      // 你的私钥
        .merchantSerialNumber(properties.getMerchantSerialNumber())
        .apiV3Key(properties.getApiV3Key())         // 你的密钥
        .build();
}

@Bean
public NotificationConfig notificationConfig(RSAAutoCertificateConfig config) {
    // 因为 RSAAutoCertificateConfig 实现了 NotificationConfig 接口
    // 所以可以作为 NotificationConfig 类型的 Bean 返回
    return config;
}

如果不通过 @Bean,你能做什么?

java
// 假设没有 @Bean,你在 Controller 中能这样写吗?
@RestController
public class WechatPayNotifyController {
    
    // ❌ 编译错误:接口不能实例化
    private NotificationConfig notificationConfig = new NotificationConfig();
    
    // ❌ 没有你的商户配置:你没法创建有效的实例
    private NotificationConfig notificationConfig = new RSAAutoCertificateConfig.Builder()
        .merchantId("???")  // 商户号从哪来?
        .privateKey("???")  // 私钥从哪来?
        .build();
}

完整的流程演示

场景 1:配置正确,启动成功 ✅

java
// 1. Maven 提供类定义
wechatpay-java-core.jar
  └── NotificationConfig.class (接口)
  └── RSAAutoCertificateConfig.class (实现类)

// 2. 配置类创建实例并放入 Spring 容器
@Configuration
@ConditionalOnProperty(name = "wechat.pay.enabled", havingValue = "true")
public class WechatPayClientConfig {
    @Bean
    public NotificationConfig notificationConfig(...) {
        // 创建实例,放入容器
        return new RSAAutoCertificateConfig(...);
    }
}

// 3. 启动时 wechat.pay.enabled=true
// Spring 容器中的 Bean:
// ┌─────────────────────────────────┐
// │ notificationConfig (Bean 实例)  │ ← 这个存在
// └─────────────────────────────────┘

// 4. Controller 注入成功
@RestController
@ConditionalOnProperty(name = "wechat.pay.enabled", havingValue = "true")
public class WechatPayNotifyController {
    @Resource
    private NotificationConfig notificationConfig;  // ✅ 从容器中找到了
}

场景 2:配置未启用,启动失败 ❌

java
// 1. Maven 提供类定义(还是存在的)
wechatpay-java-core.jar
  └── NotificationConfig.class (接口) ← 类定义存在

// 2. 配置类因条件不满足,不生效
@ConditionalOnProperty(name = "wechat.pay.enabled", havingValue = "true")
// wechat.pay.enabled=false → 配置类不加载

// 3. Spring 容器中的 Bean:
// ┌─────────────────────────────────┐
// │          (空的)                 │ ← notificationConfig Bean 不存在
// └─────────────────────────────────┘

// 4. Controller 注入失败
@RestController  // ← 没有条件注解,总是会被创建
public class WechatPayNotifyController {
    @Resource
    private NotificationConfig notificationConfig;  // ❌ 容器中找不到
}
// 启动失败:No qualifying bean of type 'NotificationConfig'

总结你的疑问

Q1: 用 @Autowired 是不是就不会报错?

A: ❌ 不会解决,两者都需要容器中有 Bean

Q2: 为什么不直接用 jar 包中的类?

A: 因为:

  1. NotificationConfig 是接口,不能直接实例化
  2. 需要用你的商户配置(商户号、密钥等)创建实例
  3. Spring 只认容器中的 Bean,不会自动把 jar 包中的类变成 Bean

Q3: @Resource 是注入,@Bean 是管理?

A: ✅ 完全正确!

java
@Bean           // 告诉 Spring:创建这个对象,放入容器管理
@Resource       // 告诉 Spring:从容器中取出对象,注入给我
@Autowired      // 同上,也是从容器中取出

核心理解

Spring 容器是一个"对象仓库":

  • @Bean / @Component / @Service 等:把对象放入仓库

  • @Resource / @Autowired:从仓库取出对象

Maven jar 包只是提供类定义(.class 文件),不会自动创建对象放入 Spring 容器。

所以即使 jar 包中有 NotificationConfig 类,你也必须:

  1. 创建它的实例(通过 @Bean)
  2. 放入 Spring 容器
  3. 才能注入使用

Powered by VitePress