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

