记一次使用spring javaconfig踩到的坑

1,060 阅读4分钟

前言

为了简化开发,我部门经常会封装一些通用的类库给业务研发使用,因为业务方的根包路径很经常和我们部门项目的根包是不一样的,因此我们会让业务方在使用我们封装的包时,扫描一下我们的根包,形如下

@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也是可以,毕竟用注解的方式相对也简洁一些