spring容器启动@Value属性无法注入


问题

一个同事基于Annotation配置了一段代码,结果有一个Configuration类的两个@Value标注的属性值没有注入进来,代码如下:

@Configuration
@PropertySource("mysql.properties")
public class GroupDataSourceAnatationSample {
    @Value("${zebra.jdbcref}")
    private String jdbcRef;

    @Value("${zebra.pooltype}")
    private String poolType;

    @Bean(destroyMethod = "close")
    public DataSource dataSource() {
        。。。。。。
    }

    @Bean
    public ZebraMapperScannerConfigurer mapperScannerConfigurer() {
        ZebraMapperScannerConfigurer mapperScannerConfigurer = new ZebraMapperScannerConfigurer();
        mapperScannerConfigurer.setBasePackage("com.sankuai.flight.flagship.mapper");
        return mapperScannerConfigurer;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactoryBean() throws Exception {
        。。。。。。
    }
}

原因

上面这段代码是基于java Annotation注解配置的zebra-dao的demo,上述存在一个问题,就是Value注解标注的两个属性jdbcType和poolType的值不能被注入进来。这个原因有点复杂,需要对spring容器的加载比较熟悉,之前我有两篇博客已经分析了spring容器加载的源码,有兴趣可以看一下《SpringIOC源码分析》《SpringIOC源码分析二(Bean实例化)》。这里稍微提一下。

spring容器初始化

上面这个图是spring容器的加载顺序。ZebraMapperScannerConfigurer这个类实现了BeanDefinitionRegistryPostProcessor,这是一个BeanFactoryPostProcessor可以看到若实现了这个processor,那么会在第5步调用的时候触发这个processor,所以ZebraMapperScannerConfigurer这个类会先初始化。(本来初始化是第完成第6个步骤向spring容器注册了若干个BeanPostProcessor)

注册BeanPostProcessor

注意第5个BeanPostProcessor,有了这个processor,才会在bean被实例化的时候调用这个processor来完成对@Value标注属性值的注入)。正因为现在在完成spring容器初始化第5个步骤的时候,由于实现了BeanDefinitionRegistryPostProcessor这个接口,会提前触发对ZebraMapperScannerConfigurer的实例化,可以把ZebraMapperScannerConfigurer看做GroupDataSourceAnatationSample的一个成员属性,所以也会提前触发GroupDataSourceAnatationSample的实例化,因为没有注册上述红框圈出的第5个BeanPostProcessor所以jdbcRef和poorType不会被注入。

这个图是在ZebraMapperScannerConfigurer(singletonObject的第5个属性)初始化过程中debug出来的数据,证明了上述分析。所以总结原因,就是因为ZebraMapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor这个接口造成后续spring容器的执行的一些顺序问题,引起了上述@Value属性不能注入

解决问题

那么我们该怎么解决,我提供以下3种解决方案,下述三种方法都可以解决问题,大家有兴趣可以去尝试一下。

嵌套Configuration

@Configuration
@PropertySource("annotation/mysql.properties")
public class GroupDataSourceAnnotationSample {

    @Configuration
    static class DataSourceConfig{
        @Value("${zebra.jdbcref}")
        //@Value("flagshipbiz_flagship_test")
        private String jdbcRef;

        @Value("${zebra.pooltype}")
        //@Value("tomcat-jdbc")
        private String poolType;

        @Bean(destroyMethod = "close")
        public DataSource dataSource() {
           。。。。。。
        }

        @Bean
        public SqlSessionFactory sqlSessionFactoryBean() throws Exception {
            。。。。。。
        }
    }

    @Bean
    public ZebraMapperScannerConfigurer mapperScannerConfigurer() {
        ZebraMapperScannerConfigurer mapperScannerConfigurer = new ZebraMapperScannerConfigurer();
        mapperScannerConfigurer.setBasePackage("com.dianping.zebra.sample.dao");
        return mapperScannerConfigurer;
    }
}

非单例(多例模式)

第二种方法@Scope(“protoType”)多例模式(这只是一个纯粹解决问题的方法,线上不推荐这里使用多例,还请注意)

@Configuration
@Scope("protoType")
@PropertySource("mysql.properties")
public class GroupDataSourceAnatationSample {
    @Value("${zebra.jdbcref}")
    private String jdbcRef;

    @Value("${zebra.pooltype}")
    private String poolType;

    @Bean(destroyMethod = "close")
    public DataSource dataSource() {
        。。。。。。
    }

    @Bean
    public ZebraMapperScannerConfigurer mapperScannerConfigurer() {
        ZebraMapperScannerConfigurer mapperScannerConfigurer = new ZebraMapperScannerConfigurer();
        mapperScannerConfigurer.setBasePackage("com.sankuai.flight.flagship.mapper");
        return mapperScannerConfigurer;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactoryBean() throws Exception {
        。。。。。。
    }
}

拆分配置类

把ZebraMapperScannerConfigurer这个bean的配置从上述DataSource中抽出来,单独放到另外一个独立的@Configuration类里面。

参考


文章作者: 叶明
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 叶明 !
评论
 上一篇
三天骑行青海湖 三天骑行青海湖
用脚步去丈量世界,感受城市的温度,和大学同学来一场说走就走的骑行,3天完成360公里环青海湖骑行,骑行没有终点,愿永远都在路上。
2017-05-30
下一篇 
樱花季的日本之旅 樱花季的日本之旅
用脚步去丈量世界,感受城市的温度,特意赶在樱花季开启一场期待已久的日本穷游之旅,趁着年轻去感受新海诚的秒速五厘米,去完成灌篮高手朝圣之旅。
2017-04-05
  目录