前言
为了简化开发,我部门经常会封装一些通用的类库给业务研发使用,因为业务方的根包路径很经常和我们部门项目的根包是不一样的,因此我们会让业务方在使用我们封装的包时,扫描一下我们的根包,形如下
@ComponentScan(basePackages = {"com.aaa","com.bbb"})
不过这样就导致了业务方想使用我们的类库,就必须知道我们的根包。这其实是一种间接的耦合。后面我们就全面使用springboot的自动装配,让业务方无需知道我们的根包,也可以使用我们的类库。然而在我们封装的过程中,也遇到一些坑。本文就来复盘一次我们使用spring javaconfig踩到的坑。本文主要是demo示例演示
demo示例
假设我们封装了一个类库DemoService。示例如下
public class DemoService {
private DemoProperties demoProperties;
private String version;
public DemoService(DemoProperties demoProperties) {
this.demoProperties = demoProperties;
}
public String print(){
return "version:" + version + ">>>>>>>>>>" + demoProperties;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
}
DemoProperties 类如下
@ConfigurationProperties(prefix = "demo.hello")
public class DemoProperties {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "DemoProperties{" +
"name='" + name + '\'' +
'}';
}
}
有个针对DemoService的扩展后置处理器
public class DemoBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinition helloService = beanFactory.getBeanDefinition("demoService");
helloService.getPropertyValues().add("version","V1");
System.out.println(">>>>>>>>>>>> demoService demoBeanFactoryPostProcessor");
}
}
javaconfig配置如下
@Configuration
@EnableConfigurationProperties(DemoProperties.class)
public class DemoConfig implements InitializingBean {
@Autowired
private DemoProperties demoProperties;
@Bean
@ConditionalOnMissingBean
public DemoService demoService(){
return new DemoService(demoProperties);
}
@PostConstruct
public void init(){
System.out.println("模拟业务初始化。。。");
}
@Bean
@ConditionalOnMissingBean
public DemoBeanFactoryPostProcessor demoBeanFactoryPostProcessor(){
return new DemoBeanFactoryPostProcessor();
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("xxxxxxxx:" + demoProperties);
}
}
配置的application.properties如下
server.port=8086
demo.hello.name=zhangsan
启动类如下
@SpringBootApplication
public class DemoLoaderApplication implements ApplicationRunner {
@Autowired
private DemoService demoService;
public static void main(String[] args) {
SpringApplication.run(DemoLoaderApplication.class, args);
}
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println(demoService.print());
}
}
以上就是完整的示例demo,看完示例demo,可以回答如下问题
1、javaconfig中的
@PostConstruct
public void init(){
System.out.println("模拟业务初始化。。。");
}
是否会执行?
2、javaconfig中的
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("xxxxxxxx:" + demoProperties);
}
是否会执行,如果会执行,demoProperties是否有值?
3、启动类中的
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println(demoService.print());
}
打印的值是多少?
demoService的print方法如下
public String print(){
return "version:" + version + ">>>>>>>>>>" + demoProperties;
}
我们可以看下输出的结果
由输出的信息,我们可以发现@PostConstruct没生效、afterPropertiesSet方法生效,由afterPropertiesSet打印的内容,我们可以得出DemoProperties 依赖注入失效,即 @Autowired失效,由print()方法我们可以得出DemoBeanFactoryPostProcessor生效了
排坑
答案就在我截图圈红的地方
@PostConstruct和@Autowired失效的原因是spring在进行ioc时,会先调用bean工厂的后置处理器进行beanFactory增强,spring会根据bean工厂的beanName去取beanFactory后置增强器,如果beanFactory后置增强器的bean此时还不存在,spring就会走doCreateBean进行创建,在创建的时候,会判断是否需要使用工厂方法进行实例化,我们使用@Bean时,它采用就是工厂方法。在通过工厂实例方法创建beanFactory后置增强器时,他会调用
org.springframework.beans.factory.support.SimpleInstantiationStrategy#instantiate(org.springframework.beans.factory.support.RootBeanDefinition, java.lang.String, org.springframework.beans.factory.BeanFactory, java.lang.Object, java.lang.reflect.Method, java.lang.Object...)
此时factoryBean就必须一定得有值,否则会报错。而这个factoryBean就是示例中的DemoConfig 。这就意味着DemoConfig在DemoBeanFactoryPostProcessor在实例化前,就得先创建好。而此时
registerBeanPostProcessors还没执行到,意味着各种spring的bean后置处理器还没准备好。比如解析@Autowired注解的AutowiredAnnotationBeanPostProcessor以及解析@PostConstruct注解的CommonAnnotationBeanPostProcessor都还没准备好。因此@Autowired和@PostConstruct自然就不会生效
解决方法
方法一:DemoBeanFactoryPostProcessor的创建方法改为静态方法
@Bean
@ConditionalOnMissingBean
public static DemoBeanFactoryPostProcessor demoBeanFactoryPostProcessor(){
return new DemoBeanFactoryPostProcessor();
}
因为是静态方法,他依赖就是类本身而非类实例对象,DemoConfig此时就会让正常的spring bean的生命周期来
方法二:DemoBeanFactoryPostProcessor单独使用一个配置类
示例
@Configuration
public class DemoBeanFactoryPostProcessorConfig {
@Bean
@ConditionalOnMissingBean
public DemoBeanFactoryPostProcessor demoBeanFactoryPostProcessor(){
return new DemoBeanFactoryPostProcessor();
}
}
方法三:使用@Import注入
@Configuration
@EnableConfigurationProperties(DemoProperties.class)
@Import(DemoBeanFactoryPostProcessor.class)
public class DemoConfig implements InitializingBean {
@Autowired
private DemoProperties demoProperties;
@Bean
@ConditionalOnMissingBean
public DemoService demoService(){
return new DemoService(demoProperties);
}
方法四:使用@Component + @ComponentScan
@Component
public class DemoBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinition helloService = beanFactory.getBeanDefinition("demoService");
helloService.getPropertyValues().add("version","V1");
System.out.println(">>>>>>>>>>>> demoService demoBeanFactoryPostProcessor");
}
}
@Configuration
@EnableConfigurationProperties(DemoProperties.class)
@ComponentScan(basePackageClasses = DemoBeanFactoryPostProcessor.class)
public class DemoConfig implements InitializingBean {
@Autowired
private DemoProperties demoProperties;
@Bean
@ConditionalOnMissingBean
public DemoService demoService(){
return new DemoService(demoProperties);
}
}
总结
其实本文的解决思路就是对spring bean的创建过程要有一定了解。其次我们在利用spring的扩展点时候,我们多使用spring自带的内置扩展对象,比如我们在bean初始化时,要做一些扩展时,尽量使用InitializingBean而非使用@PostConstruct。这样可以避免当出现上面示例的坑时,导致代码不执行而出现bug,而这种bug往往隐藏比较深。当然如果有自信不会出现这种问题,用@PostConstruct也是可以,毕竟用注解的方式相对也简洁一些