ConfigurationProperties注解在方法上

今天遇到如下需求:我需要将如下application.yml配置读取到相应的Properties配置文件中:

1
2
3
4
5
6
7
8
9

templates:
  - template: "${beanClass}.ftl"
    module: ${project.modules.server}
    packet: ${packages.service}
    packets-to-import:
      - ${packages.request}
      - ${packages.response}

这个非常难搞,因为@ConfigurationProperties必须要指定一个前缀,而这个前缀有需要作为Properties的一个字段,几经折腾我开发了如下的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

@Data
@Component
public class TemplatesProperties implements ApplicationContextAware {

    private List<Template> templates;

    @Data
    public static class Template {

        /**
         * 模板文件
         */
        private String template;

        /**
         * 当前模板所属的模块
         */
        private String module;

        /**
         * 当前模板生成的类所属的包
         */
        private String packet;

        /**
         * 当前模板生成的类额外需要导入的包
         */
        private List<String> packetsToImport;
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        //noinspection unchecked
        templates = (List<Template>) applicationContext.getBean("templates");
    }

    @Configuration
    public static class TemplatesPropertiesInternalConfiguration {

        @Bean
        @ConfigurationProperties(prefix = "templates")
        public List<Template> templates(List<Template> templates) {
            return templates;
        }

    }
}

这个利用了ConfigurationProperties可以放在方法上,为什么说解决这个问题是靠想象力呢,我查了很多资料,没有找到相关需求的解决方案,所以我自己观察@ConfigurationProperties,发现它可以注解到方法上,所以我就大胆尝试并查找了一些资料,解决了这个需求。

为什么不在application.yml中再加一个字段呢?因为我有强迫症!

优化与遇到的问题

我想将上面的代码优化成下面的写法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

@Data
@NoArgsConstructor
@AllArgsConstructor
public class TemplatesProperties {

    private List<Template> templates;

    @Data
    public static class Template {

        /**
         * 模板文件
         */
        private String template;

        /**
         * 当前模板所属的模块
         */
        private String module;

        /**
         * 当前模板生成的类所属的包
         */
        private String packet;

        /**
         * 当前模板生成的类额外需要导入的包
         */
        private List<String> packetsToImport;
    }

    @Configuration
    public static class TemplatesPropertiesInternalConfiguration {

        @Bean
        @ConfigurationProperties(prefix = "templates")
        public TemplatesProperties templates(List<Template> templates) {
            return new TemplatesProperties(templates);
        }

    }
}

但是我发现此时获取的templates是一个长度为零的数组,同时Idea提示无法注入templates,我对这个问题产生了兴趣,于是还原代码进行测试,我有如下收获:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    //noinspection unchecked
    tables = (List<Table>) applicationContext.getBean("tables");
    System.out.println("");
}

@Configuration
public static class TablePropertiesConfiguration {

    @Bean
    @ConfigurationProperties(prefix = "tables")
    public List<Table> tables(List<Table> tables) {
        return tables;
    }
}

上面这段代码的调用时序为:

  1. tables = (List<Table>) applicationContext.getBean("tables");
  2. return tables;
  3. System.out.println("");

(我没有从上面的调用时序收获到任何有意思的东西,只是觉得有趣而已)

此时return tables;返回的是一个疮毒为0的tables,所以说,我一顿操作猛如虎,最后发现对@ConfigurationProperties用于方法上理解是错误的。加了@ConfigurationProperties并不是说此时方法注入的Bean的属性是从配置文件中获取的,而是说此时@Bean方法返回的东东是从配置文件获取的,所以正确的优化方法只能是下面的这种写法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

@Data
@Component
public class TableProperties implements ApplicationContextAware {

    private List<Table> tables;


    @Data
    public static class Table {

        /**
         * 表逻辑名称
         */
        private String logicName;

        /**
         * 对应的实体名称
         */
        private String entityName;

        /**
         * 不需要生成的模板
         */
        private List<String> templatesExclude;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        //noinspection unchecked
        tables = (List<Table>) applicationContext.getBean("tables");
        System.out.println("");
    }

    @Configuration
    public static class TablePropertiesConfiguration {

        @Bean
        @ConfigurationProperties(prefix = "tables")
        public List<Table> tables() {
            return new ArrayList<>(0);
        }
    }

}

相对一开始的写法,省去了@Bean方法的方法参数,虽然只是小小的一点改动,但代表着对@ConfigurationProperties更正确的理解。

参考资料

  1. Spring Boot中注解@ConfigurationProperties的三种使用场景