组件扫描前填充Spring Environment失败,@Value注入报错求排查
核心错误:@Value注解用法错误
你的EmailBean中@Value的写法完全错误,直接写属性名mail.retry.count会让Spring把这个字符串本身当作值赋值给int类型的retryCount,这就导致了类型转换异常——系统尝试把字符串"mail.retry.count"转成整数,必然失败。
正确写法必须用${}包裹属性名,告诉Spring去环境中解析对应的属性值:
@Component public class EmailBean { @Value("${mail.retry.count}") // 修正此处,添加${} private int retryCount; public int getRetryCount() { return retryCount; } }
两种尝试的时机/逻辑问题
尝试1:@PostConstruct方法时机过晚
@PostConstruct是在Conf配置类对应的Bean初始化完成后才执行的,但@ComponentScan触发的组件扫描、EmailBean的初始化,是在配置类的Bean定义处理阶段就完成了。也就是说,等你往Environment里加属性源的时候,EmailBean早就尝试过注入属性了,完全赶不上。
尝试2:PropertySourcesPlaceholderConfigurer未整合环境属性源
你创建的PropertySourcesPlaceholderConfigurer只用到了自定义属性源,没有合并Spring环境中原有的属性源(比如@PropertySource加载的aux.properties)。而且如果不修正@Value的写法,这个配置本身也解决不了问题。
正确实现方式
如果要在组件扫描前给环境添加属性,推荐用ApplicationContextInitializer实现,它会在Spring容器初始化早期、Bean定义加载前执行:
1. 自定义ApplicationContextInitializer
package com.enterprise.samples.configuration; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MutablePropertySources; import java.util.HashMap; public class CustomPropertyInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override public void initialize(ConfigurableApplicationContext applicationContext) { MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources(); HashMap<String, Object> emailProperties = new HashMap<>(); emailProperties.put("mail.retry.count", 3); MapPropertySource emailPropertySource = new MapPropertySource("emailProperties", emailProperties); propertySources.addFirst(emailPropertySource); // 让自定义属性优先级最高 } }
2. 容器启动时注册Initializer
修改测试类的容器初始化逻辑,先注册Initializer再加载配置类:
public class BeanPropertyResolutionTest { @Test public void test() { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); // 先注册属性初始化器 applicationContext.addApplicationContextInitializer(new CustomPropertyInitializer()); // 再注册配置类 applicationContext.register(Conf.class); // 刷新容器 applicationContext.refresh(); try { EmailBean emailBean = applicationContext.getBean(EmailBean.class); Assert.assertEquals(3, emailBean.getRetryCount()); } finally { applicationContext.close(); } } }
简化方案:配置类中用静态@Bean提前注入属性源
如果不想写Initializer,也可以在配置类中用静态@Bean方法(BeanFactoryPostProcessor类型)修改环境,因为静态@Bean会在配置类实例化前执行:
@Configuration @PropertySource(value="classpath:aux.properties") @ComponentScan(basePackages={ "com.enterprise.samples.email" }) public class Conf { @Bean public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer(ConfigurableEnvironment env) { HashMap<String, Object> emailProperties = new HashMap<>(); emailProperties.put("mail.retry.count", 3); MapPropertySource emailPropertySource = new MapPropertySource("emailProperties", emailProperties); env.getPropertySources().addFirst(emailPropertySource); PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer(); configurer.setEnvironment(env); return configurer; } }
修正@Value写法后,再配合上述任一方案,就能保证属性源在组件扫描前被加载,正确解析属性值。
内容的提问来源于stack exchange,提问作者sweetjonnie

