DEBUG.WIKI
V.PS:让项目靠近用户,亚太、欧洲、北美 KVM VPS
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国

Spring Boot 找不到 @ConfigurationProperties 配置类 Bean

配置键存在却报配置类 Bean 找不到时,区分属性绑定与 Bean 注册。按显式启用、属性扫描或 JavaBean 组件注册处理,核对包范围及 record 构造器绑定。

application.yml 里有配置,属性类上也写了 @ConfigurationProperties,应用却在构造器注入时退出:

required a bean of type 'com.example.config.FileProperties' that could not be found

这条错误指向容器里没有可注入的 FileProperties。还没到检查 upload-dir 是否绑定成功的阶段。@ConfigurationProperties 声明属性前缀和绑定规则,本身不会把一个普通类注册为 Bean。

下面以 Spring Boot 3.x、Java 17 的 JavaBean 属性类为例。@ConfigurationPropertiesScan 从 Boot 2.2 起提供;旧版本不能直接套用扫描写法。

给属性类一个明确的注册入口

假设配置为:

app:
  file:
    upload-dir: /srv/example/uploads

属性类保留无参构造器、getter 和 setter:

package com.example.config;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "app.file")
public class FileProperties {
    private String uploadDir;

    public String getUploadDir() {
        return uploadDir;
    }

    public void setUploadDir(String uploadDir) {
        this.uploadDir = uploadDir;
    }
}

项目只有少量配置类时,在已加载的配置类或启动类上逐个启用,注册范围比较清楚:

package com.example;

import com.example.config.FileProperties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

@SpringBootApplication
@EnableConfigurationProperties(FileProperties.class)
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

这里不能省略 FileProperties.class。空的 @EnableConfigurationProperties 会启用相关支持,却没有列出这个类,也不会扫描所有属性类。

如果项目按包集中管理配置类,可以将上面的启用注解换成:

import org.springframework.boot.context.properties.ConfigurationPropertiesScan;

@SpringBootApplication
@ConfigurationPropertiesScan(basePackageClasses = FileProperties.class)
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

这段是启动类的替换示例,其余 import 与前一段相同。basePackageClasses 扫描指定类所在的包;省略它时,从声明扫描注解的类所在包开始。启动类在 com.example.app、属性类在 com.example.config 时,两者是兄弟包,默认范围不会覆盖属性类。

已经使用组件扫描的 JavaBean 属性类也可以加 @Component,前提是它位于组件扫描范围内。三种入口择一即可,不需要把扫描、显式启用和组件注解全部叠上去。

Bean 已存在,但属性还是空值

注册修正后,如果构造器能接收到 FileProperties,而 getUploadDir() 返回 null,问题已经变了。此时核对前缀 app.file、实际激活的 profile、最终生效的配置值,以及 JavaBean 的 setter。调整扫描范围不能补上拼错的配置键。

upload-dir 能绑定到 uploadDir,不需要为这种连字符写法额外加 @Value。示例中的目录仅作为配置值展示,注册属性 Bean 本身不会创建目录,也不会检查文件系统权限。

如果注册注解写在一个没有被应用加载的 @Configuration 类上,它同样不会起作用。可在本机调试当前 ApplicationContext 中是否存在该类型的 Bean,先确认配置类是否被扫描或导入,再追属性值。不要用 new FileProperties() 绕过注入:自己创建的对象不会因为类上有绑定注解就自动收到外部配置。

换成 record 后别沿用 @Component

Boot 3.x 可以用 record 承接构造器绑定:

package com.example.config;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "app.file")
public record FileProperties(String uploadDir) {
}

它仍通过显式启用或属性扫描注册。不要为了让它成为 Bean 再加 @Component;普通组件创建会走依赖注入,可能转而寻找 String 类型的 Bean。普通 Java 配置类做构造器绑定时,实际编译任务应开启 -parameters。Java 17 的 javac 会为 record 的规范构造器保留参数名,因此不能把这个 record 能否绑定简单归结为有没有该选项;项目统一开启它,仍有助于其他依赖参数名的 Java 代码。

对于 JavaBean 版本,修改后的判断标准是:原来的缺失 Bean 错误消失,业务组件能取得属性对象,并且 getUploadDir() 等于当前环境配置的值。对于 record 版本,则读取 uploadDir()。若测试中能取得对象、正式启动仍失败,应比较两边加载的配置类和 profile,不要把 IDE 的提示当成运行时已经注册的证明。