• @ConfigurationProperties注解原理与实战


    https://blog.csdn.net/yanluandai1985/article/details/99446060
    @ConfigurationProperties注解原理与实战

    一、@ConfigurationProperties 基本使用

            在 SpringBoot 中,当想需要获取到配置文件数据时,除了可以用 Spring 自带的 @Value 注解外,SpringBoot 还提供了一种更加方便的方式:@ConfigurationProperties。只要在 Bean 上添加上了这个注解,指定好配置文件的前缀,那么对应的配置文件数据就会自动填充到 Bean 中。

            比如在application.properties文件中有如下配置文件

    1. config.username=jay.zhou
    2. config.password=3333

            那么按照如下注解配置,SpringBoot项目中使用@ConfigurationProperties的Bean,它的username与password就会被自动注入值了。就像下面展示的那样

    1. import org.springframework.boot.context.properties.ConfigurationProperties;
    2. import org.springframework.stereotype.Component;
    3. @Component
    4. @ConfigurationProperties(prefix = "config")
    5. public class TestBean{
    6. private String username;
    7. private String password;
    8. }

    二、@ConfigurationProperties 源码的探索及启发

             SpringBoot为什么能够获取到它的值呢?如果让我从无到有设计,我应该怎么设计呢?

             首先通过源码发现,SpringBoot主要帮助我们做了两件事情。

             第一件事情就是获取到使用@ConfigurationProperties的类。

             第二件事就是解析配置文件,并把对应的值设置到我们的Bean中。

             按照源码提供的实现思路,其核心就是对Bean的声明周期的管理。主要涉及一个叫做 BeanPostProcessor 的接口,可以在Bean初始化的时候,我们做一些文章。下面的两个方法,很简单,大致意思就是,在Spring的Bean初始化之前与之后执行。

    1. public interface BeanPostProcessor {
    2. Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    3. Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
    4. }

              好了,作为程序员,看了半天源码,扯了半天淡,不如自己实现一个类似的效果来的实在。

    三、手撸代码

            第一步,定义自己的注解。这个注解最后实现的功能希望与@ConfigurationProperties 类似。

    1. import java.lang.annotation.ElementType;
    2. import java.lang.annotation.Retention;
    3. import java.lang.annotation.RetentionPolicy;
    4. import java.lang.annotation.Target;
    5. @Target({ElementType.TYPE})
    6. @Retention(RetentionPolicy.RUNTIME)
    7. public @interface Config {
    8. /**
    9. * 配置属性 的前缀
    10. */
    11. String prefix();
    12. }

            第二步,定义配置测试实体。用上了我们刚才定义的@Config注解,并指明其prefix属性的值为 " default "

    1. import lombok.Data;
    2. import org.springframework.stereotype.Component;
    3. @Config(prefix = "default")
    4. @Component
    5. @Data
    6. public class TestDataSource {
    7. private String username;
    8. private String password;
    9. private int maxActiveCount;
    10. }

            第三步,编写我们的自己的配置文件。为了便于操作(实际上其它的我不会解析),使用properties这种文件类型。

            我们的目标就是把 " default." 后面的对应的字段,注入到上面的TestDataSource里面。

            config.properties

    1. default.username = root
    2. default.password = 3333
    3. default.maxActiveCount = 10
    4. default.maxActiveCount2 = 10

             第四步,编写处理逻辑。

                     (1)获取使用了我们自定义注解@Config的类

                     (2)解析配置文件,并将对应的值使用反射技术,注入到Bean中。

             下面是最主要的处理代码。实现BeanPostProcessor接口的方法,检查每一个初始化成功的Bean,如果使用了我们的自定义注解,那么就把从配置文件中解析出来的数据,使用反射技术注入进去。

    1. import org.slf4j.Logger;
    2. import org.slf4j.LoggerFactory;
    3. import org.springframework.beans.BeansException;
    4. import org.springframework.beans.factory.config.BeanPostProcessor;
    5. import org.springframework.core.annotation.AnnotationUtils;
    6. import org.springframework.stereotype.Component;
    7. import java.io.IOException;
    8. import java.io.InputStream;
    9. import java.lang.reflect.Field;
    10. import java.util.*;
    11. @Component
    12. public class ConfigPostProcess implements BeanPostProcessor {
    13. private static final Logger LOGGER = LoggerFactory.getLogger(ConfigPostProcess.class);
    14. private static final String FILE_NAME = "config.properties";
    15. @Override
    16. public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    17. //获取使用了我们自定义注解@Config的类
    18. Config configAnnotation = AnnotationUtils.findAnnotation(bean.getClass(), Config.class);
    19. //如果这个对象使用了此注解
    20. if (configAnnotation != null) {
    21. LOGGER.info("当前操作的类:{}", beanName);
    22. //解析配置文件,并将解析结果放入Map中
    23. Map<String, String> configProperties = getConfigPropertiesFromFile(configAnnotation);
    24. //将对应的值,使用反射技术,注入到这个bean中
    25. bindBeanValue(bean, configProperties);
    26. }
    27. return bean;
    28. }
    29. ...
    30. }

     四、实现效果

            SpringBoot很多都是通过注解来实现功能的。只要我们能够学习其源码实现思路,我们也可以做出很多很多类似的功能。尽管我们的代码健壮性做不到跟大佬一样,但是在特定业务场景下,比如对某个特别重要的单例Bean进行操作,或者为某一类特定的接口实现,做一些特定的处理,可以考虑这种技术。

             另外,对于反射的使用,在SpringBoot框架学习过程中也是一个非常重要的部分。

             只有经常手敲代码,才能孰能生巧,切忌纸上谈兵。只有切实自己实现了特定功能,回头查看SpringBoot替我们做的事情,才能做到心中有数,否则被新手程序员问一句 " 为什么要这么配置 " 就哑口无言,实在是很尴尬。

             点击动图后,效果可以更加清楚哦!!!

     附录私有方法

    1. /**
    2. * 将对应的值,使用反射技术,注入到这个bean中
    3. */
    4. private void bindBeanValue(Object bean, Map<String, String> configProperties) {
    5. if (configProperties.size() > 0) {
    6. configProperties.forEach((key, value) -> {
    7. setFieldValueByFieldName(key, bean, value);
    8. });
    9. }
    10. }
    11. /**
    12. * 从配置文件中读取配置好的键值对,并放入到Map中
    13. */
    14. private Map<String, String> getConfigPropertiesFromFile(Config configAnnotation) {
    15. //get prefix from annotation
    16. String prefix = configAnnotation.prefix();
    17. //read value from resource file
    18. Properties properties = getClassNameFromResource(FILE_NAME);
    19. Map<String, String> configProperties = new HashMap<>();
    20. Set<String> keys = properties.stringPropertyNames();
    21. List<String> keyList = new ArrayList<>(keys);
    22. for (String key : keyList) {
    23. if (key.startsWith(prefix)) {
    24. //default.password ==> password
    25. String realKey = key.substring(key.indexOf(prefix) + prefix.length() + 1);
    26. String value = properties.getProperty(key);
    27. configProperties.put(realKey, value);
    28. }
    29. }
    30. return configProperties;
    31. }
    32. /**
    33. * 读取配置文件,返回一个流对象
    34. */
    35. private Properties getClassNameFromResource(String fileName) {
    36. Properties properties = new Properties();
    37. ClassLoader classLoader = ConfigPostProcess.class.getClassLoader();
    38. InputStream inputStream = classLoader.getResourceAsStream(fileName);
    39. try {
    40. properties.load(inputStream);
    41. } catch (IOException e) {
    42. e.printStackTrace();
    43. }
    44. return properties;
    45. }
    46. /**
    47. * 为指定字段使用反射技术设值(只支持String 与 int 类型)
    48. */
    49. private void setFieldValueByFieldName(String fieldName, Object object, String value) {
    50. try {
    51. Class c = object.getClass();
    52. if (checkFieldExists(fieldName, c)) {
    53. Field f = c.getDeclaredField(fieldName);
    54. f.setAccessible(true);
    55. //如果不是String,那么就是int。其它类型不支持
    56. if(f.getType().equals(String.class)){
    57. f.set(object, value);
    58. }else{
    59. int number = Integer.valueOf(value);
    60. f.set(object, number);
    61. }
    62. }
    63. } catch (Exception e) {
    64. LOGGER.error("设置" + fieldName + "出错");
    65. }
    66. }
    67. /**
    68. * 检查这个Bean是否有配置文件中配置的字段
    69. * 没有就不设置了
    70. */
    71. private boolean checkFieldExists(String fieldName, Class c) {
    72. Field[] fields = c.getDeclaredFields();
    73. for (Field field : fields) {
    74. if (field.getName().equals(fieldName)) {
    75. return Boolean.TRUE;
    76. }
    77. }
    78. return Boolean.FALSE;
    79. }
  • 相关阅读:
    系统并发报too much open files 错误
    plsql 安装Some Oracle Net versions cannot connect from a path with parentheses
    mysql登录报错
    web.xml配置文件详解之一servlet配置
    hibernate createQuery查询传递参数的两种方式
    mysql登录连接远程数据库命令行
    java 项目打包部署 过程
    flex ArrayCollection的新增与删除的同步
    加大eclipse以及jvm的内存
    2017 八月 UFED Series Releases 系列 6.3 重大更新发布
  • 原文地址:https://www.cnblogs.com/sunny3158/p/16574126.html
Copyright © 2020-2023  润新知