Springboot1.x和2.x 通过@ConfigurationProperties对bean刷新自定义属性的实现方法和用法区别

发布时间:2021-09-07 09:20:46 作者:chen
来源:亿速云 阅读:163

这篇文章主要介绍“Springboot1.x和2.x 通过@ConfigurationProperties对bean刷新自定义属性的实现方法和用法区别”,在日常操作中,相信很多人在Springboot1.x和2.x 通过@ConfigurationProperties对bean刷新自定义属性的实现方法和用法区别问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Springboot1.x和2.x 通过@ConfigurationProperties对bean刷新自定义属性的实现方法和用法区别”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

一点理解

  对于springboot 1.x和2.x配置绑定部分源码原理上的理解,简单提一下。springboot自发布以来就提供@ConfigurationProperties注解操作配置类进行宽松绑定(Relaxed Binding),有趣的是两个大版本中Relaxed Binding的具体实现是不一样的,看过部分文档后觉得springboot 2.0是想为使用者提供更严谨的API,所以重新设计了绑定发生的方式。2.0为我们添加了几个新的抽象,并且开发了一个全新的绑定API,而部分旧包旧代码不再使用。主要以下几点

1、PropertySources和ConfigurationPropertySources

  对于PropertySource你一定不陌生,结合接口Environment,这个接口是一个PropertyResolver,它可以让你从一些底层的PropertySource实现中解析属性。Spring框架为常见的配置提供PropertySource实现,例如系统属性,命令行标志和属性文件。 Spring Boot 会以对大多数应用程序有意义的方式自动配置这些实现(例如,加载application.properties)。

  在Spring Boot 2.0不再直接使用现有的PropertySource接口进行绑定,而是引入了一个新的ConfigurationPropertySource接口。同时提供了一个合理的方式来实施放松绑定规则,这些规则以前是活页夹的一部分。该接口的主要API非常简单: ConfigurationProperty getConfigurationProperty(ConfigurationPropertyName name);还有一个IterableConfigurationPropertySource变相的实现了Iterable接口,以便可以发现源包含的所有名称的配置。

通过使用以下代码 Iterable<ConfigurationPropertySource> sources = ConfigurationPropertySources.get(environment); 可以获取外部源数据;或者根据需要,还提供一个简单的MapConfigurationPropertySource实现,项目内重构源用到这种方式,很容易上手。

2、Relaxed Binding的具体实现

   springboot 1.5和2.0中,属性与配置值的绑定逻辑都始于ConfigurationPropertiesBindingPostProcessor类的postProcessBeforeInitialization函数。

  其中1.5版本细看源码发现,postProcessBeforeInitialization函数执行时,属性值绑定的工作被委派给了PropertiesConfigurationFactory<T>类(这哥们是我们在2.0压根找不到的货,所以其之下细节不展开讲了);

  而2.0版本postProcessBeforeInitialization函数调用时,属性值绑定的工作则被委派给了ConfigurationPropertiesBinder类,调用了bind函数,但ConfigurationPropertiesBinder类并不是一个public类,实际上它只相当于ConfigurationPropertiesBindingPostProcessor的一个内部静态类,表面上负责处理@ConfigurationProperties注解的绑定任务。从源码中可以看出,具体的工作委派给了另一个Binder类的对象。Binder类是SpringBoot 2.0版本后加入的类,它是负责处理对象与多个ConfigurationPropertySource之间的绑定的执行者,后面的代码示例中我们会见到。

至此基本springboot 1.x和2.x版本在属性配置绑定上的差异简单说明了个七七八八,后面我们开始从使用上开始填坑:

场景:签名请求,服务端需要解析header信息中的签名字段的过程。此类字段的key一定是服务端事先定义好的,解析过程需要反复使用的。

签名头信息类:

@Data
@ToString
@ConfigurationProperties(prefix="openapi.validate")
public class SignatureHeaders {
    private static final String SIGNATURE_HEADERS_PREFIX = "openapi-validate-";
    
    public static final Set<String> SIGNATURE_PARAMETER_SET = new HashSet<String>();
    private static String HEADER_APPID = SIGNATURE_HEADERS_PREFIX + "appid";
    private static String HEADER_TIMESTAMP = SIGNATURE_HEADERS_PREFIX + "timestamp";
    private static String HEADER_NONCE = SIGNATURE_HEADERS_PREFIX + "nonce";
    private static String HEADER_SIGNATURE = SIGNATURE_HEADERS_PREFIX + "signature";
    
    
    static {
        SIGNATURE_PARAMETER_SET.add(HEADER_APPID);
        SIGNATURE_PARAMETER_SET.add(HEADER_TIMESTAMP);
        SIGNATURE_PARAMETER_SET.add(HEADER_NONCE);
        SIGNATURE_PARAMETER_SET.add(HEADER_SIGNATURE);
    }
    
    /** 分配appid */
    private String appid;
    /** 分配appsecret */
    private String appsecret;
    /** 时间戳:ms */
    private String timestamp;
    /** 流水号/随机串:至少16位,有效期内防重复提交 */
    private String nonce;
    /** 签名 */
    private String signature;
    
    
}

一、1.x的使用

解析头信息

// 筛选头信息
Map<String, Object> headerMap = Collections.list(request.getHeaderNames())
                .stream()
                .filter(headerName -> SignatureHeaders.HEADER_NAME_SET.contains(headerName))
                .collect(Collectors.toMap(headerName -> headerName.replaceAll("-", "."), headerName -> request.getHeader(headerName)));
PropertySource propertySource = new MapPropertySource("signatureHeaders", headerMap);
SignatureHeaders signatureHeaders = RelaxedConfigurationBinder.with(SignatureHeaders.class).setPropertySources(propertySource).doBind();

绑定辅助类

public class RelaxedConfigurationBinder<T> {
    private final PropertiesConfigurationFactory<T> factory;

    public RelaxedConfigurationBinder(T object) {
        this(new PropertiesConfigurationFactory<>(object));
    }

    public RelaxedConfigurationBinder(Class<T> type) {
        this(new PropertiesConfigurationFactory<>(type));
    }

    public static <T> RelaxedConfigurationBinder<T> with(T object) {
        return new RelaxedConfigurationBinder<>(object);
    }

    public static <T> RelaxedConfigurationBinder<T> with(Class<T> type) {
        return new RelaxedConfigurationBinder<>(type);
    }

    public RelaxedConfigurationBinder(PropertiesConfigurationFactory<T> factory) {
        this.factory = factory;
        ConfigurationProperties properties = getMergedAnnotation(factory.getObjectType(), ConfigurationProperties.class);
        javax.validation.Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
        factory.setValidator(new SpringValidatorAdapter(validator));
        factory.setConversionService(new DefaultConversionService());
        if (null != properties) {
            factory.setIgnoreNestedProperties(properties.ignoreNestedProperties());
            factory.setIgnoreInvalidFields(properties.ignoreInvalidFields());
            factory.setIgnoreUnknownFields(properties.ignoreUnknownFields());
            factory.setTargetName(properties.prefix());
            factory.setExceptionIfInvalid(properties.exceptionIfInvalid());
        }
    }

    public RelaxedConfigurationBinder<T> setTargetName(String targetName) {
        factory.setTargetName(targetName);
        return this;
    }

    public RelaxedConfigurationBinder<T> setPropertySources(PropertySource<?>... propertySources) {
        MutablePropertySources sources = new MutablePropertySources();
        for (PropertySource<?> propertySource : propertySources) {
            sources.addLast(propertySource);
        }
        factory.setPropertySources(sources);
        return this;
    }

    public RelaxedConfigurationBinder<T> setPropertySources(Environment environment) {
        factory.setPropertySources(((ConfigurableEnvironment) environment).getPropertySources());
        return this;
    }

    public RelaxedConfigurationBinder<T> setPropertySources(PropertySources propertySources) {
        factory.setPropertySources(propertySources);
        return this;
    }

    public RelaxedConfigurationBinder<T> setConversionService(ConversionService conversionService) {
        factory.setConversionService(conversionService);
        return this;
    }

    public RelaxedConfigurationBinder<T> setValidator(Validator validator) {
        factory.setValidator(validator);
        return this;
    }

    public RelaxedConfigurationBinder<T> setResolvePlaceholders(boolean resolvePlaceholders) {
        factory.setResolvePlaceholders(resolvePlaceholders);
        return this;
    }

    public T doBind() throws GeneralException {
        try {
            return factory.getObject();
        } catch (Exception ex) {
            throw new GeneralException("配置绑定失败!", ex);
        }
    }
}

坑点前面提到了,在辅助类中需要用到PropertiesConfigurationFactory来指定configurationPropertySource等设置、完成绑定动作等,而PropertiesConfigurationFactory在2.x中是不存在的。

二、2.x的使用

解析头信息

// 筛选头信息
  Map<String, Object> headerMap = Collections.list(request.getHeaderNames())
                .stream()
                .filter(headerName -> SignatureHeaders.SIGNATURE_PARAMETER_SET.contains(headerName))
                .collect(Collectors.toMap(headerName -> headerName.replaceAll("-", "."), headerName -> request.getHeader(headerName)));
  // 自定义ConfigurationProperty源信息
  ConfigurationPropertySource sources = new MapConfigurationPropertySource(headerMap);
  // 创建Binder绑定类
  Binder binder = new Binder(sources);
  // 绑定属性
  SignatureHeaders signatureHeaders = binder.bind("openapi.validate", Bindable.of(SignatureHeaders.class)).get();

2.x的使用抛开了构建属性配置工厂,我们自己通过MapConfigurationPropertySource实现了自定义属性配置源,然后直接通过新加的绑定类Binder加载源信息,做识别后直接绑定到bean属性,从代码实现上看省去大量初始化代码。

2.x加载外部属性配置实现:

// 读取自配置文件/配置中心 // environment可自动注入或上下文直接获取
Iterable<ConfigurationPropertySource> sources = ConfigurationPropertySources.get(environment);// 设置Binder
Binder binder = new Binder(sources);
// 属性绑定
SignatureHeaders signatureHeaders = binder.bind("openapi.validate", Bindable.of(SignatureHeaders.class)).get();

demo示例:将自定义Map的配置属性数据加载到头信息类中去

@RunWith(SpringRunner.class)
@SpringBootTest(classes = SignatureApp.class)
@Slf4j
public class ConfigurationPropertyTest {
    
    @Test
    public void testConfigurationPropertySources() {
        Map<String, Object> dataMap = new HashMap<String, Object>();
        dataMap.put("openapi.validate.appid", "123456789");
        dataMap.put("openapi.validate.timestamp", "1565062140111");
        dataMap.put("openapi.validate.nonce", "20190805180100102030");
        dataMap.put("openapi.validate.signature", "vDMbihw6uaxlhoBCBJAY9xnejJXNCAA0QCc+I5X9EYYwAdccjNSB4L4mPZXymbH+fwm3ulkuY7UBNZclV1OBoELCSUMn7VRLAVqBS4bKrTA=");
        
        ConfigurationPropertySource sources = new MapConfigurationPropertySource(dataMap);
        Binder binder = new Binder(sources);
        SignatureHeaders signatureHeaders = binder.bind("openapi.validate", Bindable.of(SignatureHeaders.class)).get(); 
        
        log.info("###Parse Result: {}", signatureHeaders);
    }
}

 

到此,关于“Springboot1.x和2.x 通过@ConfigurationProperties对bean刷新自定义属性的实现方法和用法区别”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!

推荐阅读:
  1. SpringBoot2.0 基础案例(06):引入JdbcTemplate,和多数据源配置
  2. 详解Spring依赖注入:@Autowired,@Resource和@Inject区别与实现原理

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

spring

上一篇:mysql怎么利用Join来优化SQL语句

下一篇:SSH远程执行命令需要注意什么

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》