首页 > 其他分享 >Spring Environment体系

Spring Environment体系

时间:2023-07-28 21:13:10浏览次数:26  
标签:体系 map String Spring db name Environment new 属性

PropertySource

类型继承图如下所示,只是挑选了一些常见的实现类。

PropertySource是一个抽象类,代表一个属性源,Spring Environment中所有的属性就存储在一个PropertySource列表中,属性源可以是一个MapResource(yaml文件, properties文件)、命令行(--形式)、系统变量等等。

注: PropertySource本身无法处理占位符,需要下面介绍的PropertyResolver组件。

PropertySourceLoader

PropertySourceLoader负责将一个Resource转换成PropertySource对象,目前仅仅只有两个实现。

  • PropertiesPropertySourceLoader,负责处理后缀为properties、xml的文件。
  • YamlPropertySourceLoader,负责处理后缀为yaml、yml文件。

PropertyResolver

简要介绍

类型继承图如下所示

PropertyResolver是一个顶层接口,定义了一些获取属性值,以及解析占位符的方法

public interface PropertyResolver {

    
    /**
     * 是否包含指定的属性
     */
    boolean containsProperty(String key);

   
    /**
     * 根据属性名获取对应的属性值, 如果属性值含有占位符且属性值为字符串, 并且占位符中的key存在, 会替换掉, 最后还会进行类型转换
     */
    @Nullable
    String getProperty(String key);

    /**
     * 如果没有对应的属性值, 则返回指定的默认值
     */
    
    String getProperty(String key, String defaultValue);

   
    /**
     * 根据属性名获取对应的属性值, 如果属性值含有占位符且属性值为字符串, 并且占位符中的key存在, 会替换掉, 最后还会进行类型转换
     */
    @Nullable
    <T> T getProperty(String key, Class<T> targetType);

   
    <T> T getProperty(String key, Class<T> targetType, T defaultValue);

    
    /**
     * 属性不存在抛异常
     */
    String getRequiredProperty(String key) throws IllegalStateException;

    
    <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;

    
    /**
     * 替换占位符, 占位符中的key不存在原样返回, 默认占位符为${}
     */
    String resolvePlaceholders(String text);

    
    /**
     * 替换占位符, 占位符中的key不存在抛异常
     */
    String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;

}

ConfigurablePropertyResolver接口则增加了一些配置方法,如占位符的前缀、后缀等。

使用

public class PropertyResolverTest {

    private static ConfigurablePropertyResolver propertyResolver;

    @BeforeClass
    public static void init() {
        Map<String, Object> map = new HashMap<>();
        map.put("age", "20");
        map.put("prefix", "user");
        map.put("name", "${prefix}-1");
        MutablePropertySources propertySources = new MutablePropertySources();
        PropertySource<?> propertySource = new MapPropertySource("mapSource", map);
        propertySources.addLast(propertySource);
        propertyResolver = new PropertySourcesPropertyResolver(propertySources);
    }

    @Test
    public void testApi() {
        // 自动类型转换
        Integer age = propertyResolver.getProperty("age", Integer.class);
        Assert.assertEquals(Integer.valueOf(20), age);

        // 自动替换属性值中的占位符
        String name = propertyResolver.getProperty("name");
        Assert.assertEquals("user-1", name);

        // 手动替换占位符
        String text = "${prefix}-1";
        String res = propertyResolver.resolvePlaceholders(text);
        Assert.assertEquals("user-1", res);
    }
}

Environment

简要介绍

类型继承图如下所示

可以看到Environment是继承了PropertyResolver接口的,只是增加了一些环境信息方法(profile)

  • ConfigurableEnvironment,增加了一些配置的方法,以及可以获取到内部的PropertySource列表。
  • StandardEnvironment,非web上下文使用的环境实例。
  • StandardServletEnvironment,web上下午使用的环境实例。

其中Environment实例关于PropertyResolver接口的方法是通过组合模式实现的,内部持有一个PropertySourcesPropertyResolver实例。

属性绑定

Spring提供了一个很强大的工具类,支持将Environment或者ConfigurationPropertySource中的属性绑定到一个Java对象中,这个类就是Binder,且支持占位符,类型转换,宽松绑定。

如Java对象中的字段名为driverClassName,而属性对应的key不管为driverClassName、driverclassname、driver-class-name,都能绑定成功。

下面来看下几个重要的类。

ConfigurationPropertyName

ConfigurationPropertyName,属性key的抽象,支持.符合,key只能包含a-z、0-9、.、-这几个字符。注意不能包含大写字母。其中username与user-name被认为是等价的。

ConfigurationPropertySource

ConfigurationPropertySource,这是一个新的接口,不过它的实现类也是借助了PropertySource来实现,尽管他们之间没有任何继承关系。

@FunctionalInterface
public interface ConfigurationPropertySource {
    
    /**
     * 根据属性名获取对应的值
     * 注:
     * 内部维护的属性名为driverClassName,则参数driverclassname、driver-class-name可以获取到值
     * 参数为driverClassName不能获取到,并且会报错,因为参数不支持大写字母
     *
     * 内部维护的属性名为driverclassname,则参数driverclassname、driver-class-name可以获取到值
     *
     * 内部维护的属性名为driver-class-name,则参数driverclassname、driver-class-name可以获取到值
     */
    ConfigurationProperty getConfigurationProperty(ConfigurationPropertyName name);

    /**
     * 判断是否包含后代属性
     * 内部维护了user.name属性名, 如果参数为user,则返回存在
     * 虽然getConfigurationProperty(ConfigurationPropertyName.of("user"))为空
     * 但是containsDescendantOf(ConfigurationPropertyName.of("user"))存在
     */
    default ConfigurationPropertyState containsDescendantOf(ConfigurationPropertyName name) {
        return ConfigurationPropertyState.UNKNOWN;
    }

    
    /**
     * 注册别名
     */
    default ConfigurationPropertySource withAliases(ConfigurationPropertyNameAliases aliases) {
        return new AliasedConfigurationPropertySource(this, aliases);
    }

}

使用例子

public class ConfigurationPropertySourceTest {

    private static ConfigurationPropertySource configurationPropertySource;

    @BeforeClass
    public static void init() {
        Map<String, Object> map = new HashMap<>();
        map.put("db.url", "jdbc:mysql://127.0.0.1/test");
        map.put("db.driverClassName", "com.mysql.cj.jdbc.Driver");
        map.put("db.init-size", 20);
        PropertySource<?> propertySource = new MapPropertySource("map", map);
        /*
         * 使用PropertySource构建
         */
        Iterable<ConfigurationPropertySource> it = ConfigurationPropertySources.from(propertySource);
        if (it.iterator().hasNext()) {
            configurationPropertySource = it.iterator().next();
        } else {
            throw new IllegalStateException();
        }
        // 使用MapConfigurationPropertySource直接构建
        // configurationPropertySource = new MapConfigurationPropertySource(map);

        // 注册别名(注意返回的是一个新对象)
        configurationPropertySource = configurationPropertySource.withAliases(new ConfigurationPropertyNameAliases("db.url", "db.jdbc-url"));
    }

    @Test
    public void testBuildKey() {
        ConfigurationPropertyName.of("driver-class-name");
        try {
            ConfigurationPropertyName.of("driverClassName");
        } catch (Exception e) {
            Assert.assertTrue(e instanceof InvalidConfigurationPropertyNameException);
        }
    }

    @Test
    public void testApi() {
        Assert.assertNotNull(configurationPropertySource.getConfigurationProperty(ConfigurationPropertyName.of("db.url")));
        // 别名
    Assert.assertNotNull(configurationPropertySource.getConfigurationProperty(ConfigurationPropertyName.of("db.jdbc-url")));

        // 宽松形式(注意不能使用大写作为参数)
        Assert.assertNotNull(configurationPropertySource.getConfigurationProperty(ConfigurationPropertyName.of("db.driver-class-name")));
        Assert.assertNotNull(configurationPropertySource.getConfigurationProperty(ConfigurationPropertyName.of("db.driverclassname")));

        Assert.assertNotNull(configurationPropertySource.getConfigurationProperty(ConfigurationPropertyName.of("db.init-size")));
        Assert.assertNotNull(configurationPropertySource.getConfigurationProperty(ConfigurationPropertyName.of("db.initsize")));

        // 判断后续属性
        ConfigurationPropertyState state = configurationPropertySource.containsDescendantOf(ConfigurationPropertyName.of("db"));
        Assert.assertEquals(ConfigurationPropertyState.PRESENT, state);
    }
}

Binder

终于来到重头戏,前面的铺垫都是为了该类的使用。因为Binder依赖ConfigurationPropertySource

该类可以将ConfigurationPropertySource维护的属性根据名称绑定到Java对象中。

下面来介绍用法。

public class BinderTest {

    private static Binder binder;

    @Data
    private static class DbProperties {
        private String url;

        private String driverClassName;

        private Integer initSize;

        private String name;
    }

    @BeforeClass
    public static void init() {
        Map<String, Object> map = new HashMap<>();
        map.put("db.db-name", "test");
        map.put("db.url", "jdbc:mysql://127.0.0.1/${db.db-name}");
        map.put("db.driverClassName", "com.mysql.cj.jdbc.Driver");
        map.put("db.init-size", "20");
        PropertySource<?> propertySource = new MapPropertySource("map", map);
        MutablePropertySources propertySources = new MutablePropertySources();
        propertySources.addLast(propertySource);
        /*
         * 使用PropertySource构建, 并指定placeholdersResolver, 否则无法处理占位符
         */
        PropertySourcesPlaceholdersResolver placeholdersResolver = new PropertySourcesPlaceholdersResolver(propertySources);
        binder = new Binder(ConfigurationPropertySources.from(propertySources), placeholdersResolver);
    }

    @Test
    public void testApi() {
        // 创建一个新对象
        DbProperties db1 = binder.bindOrCreate("db", DbProperties.class);
        check(db1);

        // 基于已有的对象重新映射
        DbProperties db2 = new DbProperties();
        db2.setName("user");
        // 补齐其他字段
        DbProperties db3 = binder.bindOrCreate("db", Bindable.ofInstance(db2));
        // 返回的是同一个实例
        Assert.assertSame(db2, db3);
        check(db2);
    }

    private void check(DbProperties db) {
        Assert.assertEquals("jdbc:mysql://127.0.0.1/test", db.getUrl());
        Assert.assertEquals("com.mysql.cj.jdbc.Driver", db.getDriverClassName());
        Assert.assertEquals(Integer.valueOf(20), db.getInitSize());

    }
}

Binder类还提供一个静态方法来构造Binder实例,从Environment实例获取PropertySources

public static Binder get(Environment environment) {  
    return get(environment, null);  
}

public static Binder get(Environment environment, BindHandler defaultBindHandler) {
    Iterable<ConfigurationPropertySource> sources = ConfigurationPropertySources.get(environment);
    PropertySourcesPlaceholdersResolver placeholdersResolver = 
        new PropertySourcesPlaceholdersResolver(environment);
    return new Binder(sources, placeholdersResolver, null, null, defaultBindHandler);
}

属性绑定简要过程:
根据Java类型找到所有的字段,将字段名称变成宽松类型,本例会变成url、driver-class-name、init-size、name,然后一个个根据宽松字段名称去获取对应的值,当然这个值会替换其中的占位符,会做类型转换。

@ConfigurationProperties

@ConfigurationProperties注解在Spring Boot中常用来绑定属性到Java Bean中,不难猜出内部原理便是使用上面所介绍的Binder类来实现的。使用该注解时常常搭配@EnableConfigurationProperties注解一起使用,@EnableConfigurationProperties的主要作用就是将指定的class注册到Spring容器中,同时注册了一个ConfigurationPropertiesBindingPostProcessor用来将Environment实例中的属性绑定到Java Bean中。ConfigurationPropertiesBindingPostProcessor是一个BeanPostProcessor

有时候我们并没有使用@EnableConfigurationProperties注解,而是使用@Compoment注解也能生效,因为本质上绑定属性是ConfigurationPropertiesBindingPostProcessor做的,只要我们的Bean在Spring容器中即可。而Spring Boot自动装配时内部也到处使用了@EnableConfigurationProperties注解,因此ConfigurationPropertiesBindingPostProcessor类也是在Spring容器(只会注册一次)中的。

ConfigurationPropertiesBindingPostProcessor.java

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    bind(ConfigurationPropertiesBean.get(this.applicationContext, bean, beanName));
    return bean;
}

private void bind(ConfigurationPropertiesBean bean) {
    if (bean == null || hasBoundValueObject(bean.getName())) {
        return;
    }
    Assert.state(bean.getBindMethod() == BindMethod.JAVA_BEAN, "Cannot bind @ConfigurationProperties for bean '"
            + bean.getName() + "'. Ensure that @ConstructorBinding has not been applied to regular bean");
    try {
        // 绑定属性
        this.binder.bind(bean);
    }
    catch (Exception ex) {
        throw new ConfigurationPropertiesBindException(bean, ex);
    }
}

可以看到绑定属性的时机是在postProcessBeforeInitialization方法中,也就是会先于init-methodafterProperties这些钩子方法,这样也合情合理。

标签:体系,map,String,Spring,db,name,Environment,new,属性
From: https://www.cnblogs.com/wt20/p/17588895.html

相关文章

  • SpringBoot -
    1.环境搭建1.1Spring.io建立项目通过Spring.io网站,自动建立一个springboot项目,失败的常见问题列表:SpringInitailizer在创建的过程中,如果出现“java:警告:源发行版17需要目标发行版17”的错误,是JDK配置不对导致,解决方法参见:IDEA运行Spring工程报错:java:警告:源发......
  • SpringMVC异常处理
    1.SpringMVC提供了三种异常处理方式 简单异常处理器:使用springMVC内置的异常处理器处理SimpleMappingExceptionResolver; 自定义异常处理器:实现HandlerExceptionResolver接口,自定义异常进行处理; 注解方式:使用@ControllerAdvice+@ExceptionHandler来处理。  第三......
  • 【手撕 Spring】一键开启 Spring 入门之路
    ......
  • SpringBoot实践(十六):mark一个开源商品网站
    特点:前后端分离,vue框架,包含小程序、APP,界面风格类似小米商城;外贸网站源码:CRMEB官网-开源会员管理电商营销系统,助力企业发展!特点:thinkphp,待研究;......
  • Pipeline SpringBoot-deploy-CD
    pipeline{agent{kubernetes{cloud'kubernetes'yaml'''apiVersion:v1Kind:Podspec:imagePullSecrets:-name:harbor-admincontainers:-name:kubectlimage:harbor.oldxu.net/ops/kubectl:1.23.15imag......
  • Pipeline SpringBoot-deploy-CI
    pipeline{agent{kubernetes{cloud'kubernetes'yaml'''apiVersion:v1Kind:Podspec:imagePullSecrets:-name:harbor-adminvolumes:-name:datanfs:server:192.168.1.21path:/da......
  • Spring应用网卡选择配置
    ignored-interfaces:##写上要忽略的虚拟网卡关键字-SangforaTrustVNIC......
  • Java开发 - SpringCache初体验
    前言早些时候,博主介绍过Redis的使用:Java开发-Redis初体验,Redie是基于缓存的一项技术,对于Redis,博主此处不再赘述,不了解的可以去看这篇文章,但Redis缓存并不是顶峰,本文要讲的内容就是Redis的辅助工具:SpringCache——的使用。有了SpringCache,Redis便可如虎添翼,使用效果更上一层楼,下面......
  • SpringMVC
    SpringMVCssm:mybatis+Spring+SpringMVCMVC三层架构JavaSE:认真学习,老师带,入门快JavaWeb:认真学习,老师带,入门快SSM框架:研究官方文档,锻炼自学能力,锻炼笔记能力,锻炼项目能力SpringMVC+Vue+SpringBoot+SpringCloud+LinuxSSM=JavaWeb做项目;Spring:IOC和......
  • springcloud一些组件
    springcloud官网文档略记录下 1、springcloud阿里巴巴 Nacos:服务注册和发现,分布式配置数据存储事件驱动:可扩展的事件驱动微服务,连接rocketmq消息总线:rocketmq 连接节点分布式事务:seata?DubboRPC:服务间的连接协议2、springcloudbus 用轻量的消息中介,来连接分......