为了简化开发,我部门经常会封装一些通用的类库给业务研发使用,因为业务方的根包路径很经常和我们部门项目的根包是不一样的,因此我们会让业务方在使用我们封装的包时,扫描一下我们的根包,形如下
@ComponentScan(basePackages = {"com.aaa","com.bbb"})
不过这样就导致了业务方想使用我们的类库,就必须知道我们的根包。这其实是一种间接的耦合。后面我们就全面使用springboot的自动装配,让业务方无需知道我们的根包,也可以使用我们的类库。然而在我们封装的过程中,也遇到一些坑。本文就来复盘一次我们使用spring javaconfig踩到的坑。本文主要是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也是可以,毕竟用注解的方式相对也简洁一些