You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring @ConfigurationProperties Bean注入及序列化异常问题求助

问题与解决方案

配置文件与Bean定义

application.yaml

mail:
  hostname: host@mail.com
  port: 9000
  from: mailer@mail.com

ClientProperties配置类

@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "mail")
public class ClientProperties{
    public String hostName;
    public int port;
    public String from;
    public String time = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));

    @Autowired
    private ObjectMapper mapper; 

    @JsonIgnore
    public String toJson() throws JsonProcessingException {
        return mapper.writeValueAsString(this);
    }
}

问题1:ObjectMapper的正确注入方式

直接用@Autowired注入ObjectMapper不推荐,原因是Spring环境中可能存在多个ObjectMapper实例,直接注入可能拿到不符合预期的实例。推荐以下两种方式:

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

构造方法注入能确保依赖在Bean初始化时就被注入,避免空指针问题,同时更便于单元测试:

@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "mail")
public class ClientProperties{
    public String hostName;
    public int port;
    public String from;
    public String time = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));

    private final ObjectMapper mapper; 

    // 构造方法注入ObjectMapper
    public ClientProperties(ObjectMapper mapper) {
        this.mapper = mapper;
    }

    @JsonIgnore
    public String toJson() throws JsonProcessingException {
        return mapper.writeValueAsString(this);
    }
}

方式二:通过Jackson2ObjectMapperBuilder构建自定义实例

如果需要对ObjectMapper做个性化配置,可以注入Jackson2ObjectMapperBuilder来构建实例:

@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "mail")
public class ClientProperties{
    public String hostName;
    public int port;
    public String from;
    public String time = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));

    private final ObjectMapper mapper; 

    public ClientProperties(Jackson2ObjectMapperBuilder mapperBuilder) {
        this.mapper = mapperBuilder.build();
        // 可在此添加自定义配置,比如日期格式化、忽略空值等
        this.mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
    }

    @JsonIgnore
    public String toJson() throws JsonProcessingException {
        return mapper.writeValueAsString(this);
    }
}

问题2:注入ClientProperties到配置类失败的报错解决

报错场景

在以下配置类中注入ClientProperties并调用toJson()时触发序列化错误:

@Configuration
public class SolisCloudConfig {
    @Value("${external.api.baseurl}")
    String baseURI;
    @Bean
    RestClient restClient(RestClient.Builder builder, ClientProperties properties) throws Exception {
        return builder
                .baseUrl(baseURI)
                .defaultHeader("mail-header", properties.toJson())
                .build();
    }
}

报错信息

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'restClient' defined in class path resource [com/connector/config/SolisCloudConfig.class]: Failed to instantiate [org.springframework.web.client.RestClient]: Factory method 'restClient' threw exception with message: No serializer found for class org.springframework.context.expression.StandardBeanExpressionResolver and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: com.experimental.connector.config.ClientProperties$$SpringCGLIB$$0["$$beanFactory"]->org.springframework.beans.factory.support.DefaultListableBeanFactory["beanExpressionResolver"])
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.web.client.RestClient]: Factory method 'restClient' threw exception with message: No serializer found for class org.springframework.context.expression.StandardBeanExpressionResolver and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: com.experimental.connector.config.ClientProperties$$SpringCGLIB$$0["$$beanFactory"]->org.springframework.beans.factory.support.DefaultListableBeanFactory["beanExpressionResolver"])
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.springframework.context.expression.StandardBeanExpressionResolver and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: com.experimental.connector.config.ClientProperties$$SpringCGLIB$$0["$$beanFactory"]->org.springframework.beans.factory.support.DefaultListableBeanFactory["beanExpressionResolver"])

2024-03-08T00:14:11.791+02:00  WARN 17207 --- [           main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'restClient' defined in class path resource [com/connector/config/SolisCloudConfig.class]: Failed to instantiate [org.springframework.web.client.RestClient]: Factory method 'restClient' threw exception with message: No serializer found for class org.springframework.context.expression.StandardBeanExpressionResolver and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: experimental.connector.config.ClientProperties$$SpringCGLIB$$0["$$beanFactory"]->org.springframework.beans.factory.support.DefaultListableBeanFactory["beanExpressionResolver"])

原因分析

报错核心是:ClientProperties标注了@Configuration,Spring会为其生成CGLIB代理类(ClientProperties$$SpringCGLIB$$0),代理类中包含Spring容器相关的隐藏属性(如$$beanFactory)。当调用mapper.writeValueAsString(this)时,Jackson会尝试序列化这些容器属性,但Spring内部类没有对应的序列化器,导致报错。

解决方法

方案1:序列化自定义属性而非整个对象

在toJson()方法中只序列化我们需要的字段,避免触发代理类隐藏属性的序列化:

@JsonIgnore
public String toJson() throws JsonProcessingException {
    Map<String, Object> serializableProps = new HashMap<>();
    serializableProps.put("hostName", this.hostName);
    serializableProps.put("port", this.port);
    serializableProps.put("from", this.from);
    serializableProps.put("time", this.time);
    return mapper.writeValueAsString(serializableProps);
}

方案2:禁用Jackson空Bean序列化失败检查

在ObjectMapper中配置FAIL_ON_EMPTY_BEANS为false,遇到无法序列化的空Bean时跳过报错:

// 在构造方法中配置
public ClientProperties(ObjectMapper mapper) {
    this.mapper = mapper;
    this.mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
}

也可以全局配置Jackson:

@Configuration
public class JacksonGlobalConfig {
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        return mapper;
    }
}

方案3:移除ClientProperties的@Configuration注解

配置属性类不需要@Configuration,仅需@ConfigurationProperties,配合启动类的@EnableConfigurationProperties即可注册为Bean,这样不会生成CGLIB代理:

// 修改ClientProperties,去掉@Configuration
@Getter
@Setter
@ConfigurationProperties(prefix = "mail")
public class ClientProperties{
    public String hostName;
    public int port;
    public String from;
    public String time = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));

    private final ObjectMapper mapper; 

    public ClientProperties(ObjectMapper mapper) {
        this.mapper = mapper;
    }

    @JsonIgnore
    public String toJson() throws JsonProcessingException {
        return mapper.writeValueAsString(this);
    }
}

然后在启动类添加:

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

内容的提问来源于stack exchange,提问作者Romillion

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 10:13:22