Spring中PropertySource属性源配置文件的优先级、顺序问题大解析(加载流程)【享学Spring】(上)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: Spring中PropertySource属性源配置文件的优先级、顺序问题大解析(加载流程)【享学Spring】(上)

前言


关于Spring的配置文件的优先级、加载顺序一直是个老生常谈的问题。但即使经常被提起,却还是经常被忘记或者弄混。有一种听了很多道理但仍过不好这一生的赶脚有木有。


如果你度娘上去搜索:Spring Boot 配置顺序关键字,会发现相关的文章非常之多,足以可见这个问题的热度。但我们普遍遇到的一个问题是:看的时候都知道,但用的时候又有很多的不确定~


怎么破?我个人经验是若想把这块变成永久记忆,一种方式是理解作者的设计意图,掌握其设计思想**(推荐,因为设计思想都是相通的)**。另外一种就是把它落实在源码上,毕竟查源码找顺序比在度娘看别人的二手信息来得踏实。


另外,我把这篇文章定位为:Spring Boot配置文件加载顺序先行篇。因为只有SpringBoot才会自动的加载对应的属性源,而Spring Framework是没有这么自动化的(都是手动的)。


若想要快速知道结论,你也可以直接阅读:

【小家Spring】一篇文章彻底搞懂Spring Boot配置文件的加载顺序(项目内部配置和外部配置)


PropertySource

此处指的是org.springframework.core.env.PropertySource,而不是注解org.springframework.context.annotation.PropertySource,注意区分


PropertySource是抽象类,表示一个键值对,代表着属性源。Spring内部是通过它来承载来自不同地方都的属性源的。


Spring认为每个属性源都应该是有名称的,也就是作为属性源的key~


// @since 3.1 
public abstract class PropertySource<T> {
  protected final String name; // 该属性源的名字
  protected final T source;
  public PropertySource(String name, T source) {
    Assert.hasText(name, "Property source name must contain at least one character");
    Assert.notNull(source, "Property source must not be null");
    this.name = name;
    this.source = source;
  }
  // 若没有指定source 默认就是object  而不是null
  public PropertySource(String name) {
    this(name, (T) new Object());
  }
  // getProperty是个抽象方法  子类去实现~~~
  // 小细节:若对应的key存在但是值为null,此处也是返回false的  表示不包含~
  public boolean containsProperty(String name) {
    return (getProperty(name) != null);
  }
  @Nullable
  public abstract Object getProperty(String name);
  // 此处特别特别注意重写的这两个方法,我们发现它只和name有关,只要name相等  就代表着是同一个对象~~~~ 这点特别重要~
  @Override
  public boolean equals(Object other) {
    return (this == other || (other instanceof PropertySource &&
        ObjectUtils.nullSafeEquals(this.name, ((PropertySource<?>) other).name)));
  }
  @Override
  public int hashCode() {
    return ObjectUtils.nullSafeHashCode(this.name);
  }
  // 静态方法:根据name就创建一个属性源~  ComparisonPropertySource是StubPropertySource的子类~
  public static PropertySource<?> named(String name) {
    return new ComparisonPropertySource(name);
  }
}


该类重写了equals()和hashCode()方法,所以对于List的remove、indexOf方法都是有影响的~~~后续会看到


PropertySource提供了一个named(String name)方法用于构造基于name的PropertySource的空实现,从而便于PropertySource 集合中查找指定属性命的PropertySource(毕竟上面说了它只和name有关~)。


这个抽象类告诉我们,PropertySource的name非常的重要。接下来重点就是它的实现们,它的继承树如下:


image.png


JndiPropertySource


显然它和Jndi有关。JNDI:Java Naming and Directory Interface Java命名和目录接口

题外话:我认为它是IoC的鼻祖,在Tomcat中有大量的应用,Spring或许都是抄它的~


// @since 3.1  它的source源是JndiLocatorDelegate
public class JndiPropertySource extends PropertySource<JndiLocatorDelegate> {
  public Object getProperty(String name) {
    ...
    Object value = this.source.lookup(name);
    ...
  }
}


它的lookup方法就是依赖查找的精髓。由于现在是Spring的天下,Jndi确实使用太少了,此处一笔带过。


备注:上篇文章我们已经知道了,web环境默认情况下的StandardServletEnvironment初始化的时候是会把JndiPropertySource放进环境里去的,name为:jndiProperties

JndiTemplate是Spring提供的对JNDI的访问模版


EnumerablePropertySource


这是PropertySource的一个最重要分支,绝大部分配置源都继承于它。Enumerable:可枚举的


public abstract class EnumerablePropertySource<T> extends PropertySource<T> {
  public EnumerablePropertySource(String name, T source) {
    super(name, source);
  }
  protected EnumerablePropertySource(String name) {
    super(name);
  }
  @Override
  public boolean containsProperty(String name) {
    return ObjectUtils.containsElement(getPropertyNames(), name);
  }
  // 返回所有Property的names(keys)
  public abstract String[] getPropertyNames();
}


该抽象类主要提供抽象方法getPropertyNames()表示每个key都应该是可以枚举的


ServletContextPropertySource


它的属性源是ServletContext,此属于源头用于暴露和访问Servlet上下文的一些InitParameters

public class ServletContextPropertySource extends EnumerablePropertySource<ServletContext> {
  public ServletContextPropertySource(String name, ServletContext servletContext) {
    super(name, servletContext);
  }
  @Override
  public String[] getPropertyNames() {
    return StringUtils.toStringArray(this.source.getInitParameterNames());
  }
  @Override
  @Nullable
  public String getProperty(String name) {
    return this.source.getInitParameter(name);
  }
}


ervletConfigPropertySource

source源为ServletConfig,源码逻辑同上。


ConfigurationPropertySource

需要注意:这个不是Spring提供的,你导入了commons-configuration2这个jar时才会有这个类。source源为:org.apache.commons.configuration2.Configuration


MapPropertySource

这是一个较为常用的属性源,一般我们自己new往里添加时,会使用它。

它的source源为:Map<String, Object>,还是非常的通用的~


public class MapPropertySource extends EnumerablePropertySource<Map<String, Object>> {
  public MapPropertySource(String name, Map<String, Object> source) {
    super(name, source);
  }
  @Override
  @Nullable
  public Object getProperty(String name) {
    return this.source.get(name);
  }
  @Override
  public boolean containsProperty(String name) {
    return this.source.containsKey(name);
  }
  // map里所有的key就行~
  @Override
  public String[] getPropertyNames() {
    return StringUtils.toStringArray(this.source.keySet());
  }
}


PropertiesPropertySource


继承自MapPropertySource,不解释~


ResourcePropertySource:我们注解导入使用的是它


ResourcePropertySource它继承自PropertiesPropertySource。它处理用org.springframework.core.io.Resource装载的Properties文件

 // @since 3.1  若你的Properties资源使用的Resource装机进来的  直接使用它即可
 public class ResourcePropertySource extends PropertiesPropertySource {
  @Nullable
  private final String resourceName;
  public ResourcePropertySource(String name, EncodedResource resource) throws IOException {
    // 注意此处加载的最好是EncodedResource,因为Properties文件是需要处理乱码的~
    super(name, PropertiesLoaderUtils.loadProperties(resource));
    this.resourceName = getNameForResource(resource.getResource());
  }
  public ResourcePropertySource(EncodedResource resource) throws IOException {
    super(getNameForResource(resource.getResource()), PropertiesLoaderUtils.loadProperties(resource));
    this.resourceName = null;
  }
  public ResourcePropertySource(String name, Resource resource) throws IOException {
    super(name, PropertiesLoaderUtils.loadProperties(new EncodedResource(resource)));
    this.resourceName = getNameForResource(resource);
  }
  public ResourcePropertySource(String name, String location, ClassLoader classLoader) throws IOException {
    this(name, new DefaultResourceLoader(classLoader).getResource(location));
  }
  public ResourcePropertySource(String location, ClassLoader classLoader) throws IOException {
    this(new DefaultResourceLoader(classLoader).getResource(location));
  }
  public ResourcePropertySource(String name, String location) throws IOException {
    this(name, new DefaultResourceLoader().getResource(location));
  }
  ...
 }


它有非常多的重载构造函数,这是Spring设计中最为常用的模式之一~~~目的权是为了让使用者越简单、越方便越好


CommandLinePropertySource


顾名思义,它表示命令行属性源。它这个泛型T可能最简单的String[],也可以是OptionSet(依赖joptsimple这个jar)


在传统的Spring应用中,命令行参数一般存在于main方法的入参里就够了,但是在某些特殊的情况下,它需要被注入到Spring Bean中。

如下案例:我们手动把命令行参数放进Spring容器内:

    public static void main(String[] args) throws Exception {
        CommandLinePropertySource clps = new SimpleCommandLinePropertySource(args);
        // 启动容器
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ConfigurableEnvironment environment = ctx.getEnvironment();
        environment.getPropertySources().addFirst(clps);
        ctx.register(RootConfig.class);
        ctx.refresh();
        System.out.println(ArrayUtils.toString(args)); //{--server.port=8080}
        System.out.println(environment.getProperty("server.port")); //8080
    }

此处:Spring命令行参数为--server.port=8080,有的时候你会看到-D参数,这里用一个示例注意区分一下这两者的区别。


image.png


运行结果如下:

@Slf4j
public class Main {
    public static void main(String[] args) throws Exception {
        // vm参数里(其实就是java -Xmx512m -Dmyname=fsx)  的-D参数最终都会到System系统属性里面去
        System.out.println(System.getProperty("myname")); //fsx
        // --开头的命令行参数  是可以被spring应用识别的特定格式
        System.out.println(ArrayUtils.toString(args)); // {--server.port=8080,fsx}
    }
}


Enviroment环境内容值截图如下:


image.png


经过我这一番处理(放进容器)后,Environment被注入到Spring Bean内,就会含有这些命令行属性值,然后就直接可以在Spring Bean中使用了


使用Environment获取属性值的原理上篇博文有解释:属性源最终都被加入进Environment持有的属性:MutablePropertySources保存着。So,我们使用@Value也可以从它里面取值的~




相关文章
|
5天前
|
Java API Spring
在 Spring 配置文件中配置 Filter 的步骤
【10月更文挑战第21天】在 Spring 配置文件中配置 Filter 是实现请求过滤的重要手段。通过合理的配置,可以灵活地对请求进行处理,满足各种应用需求。还可以根据具体的项目要求和实际情况,进一步深入研究和优化 Filter 的配置,以提高应用的性能和安全性。
|
5天前
|
搜索推荐 Java Spring
Spring Filter深度解析
【10月更文挑战第21天】Spring Filter 是 Spring 框架中非常重要的一部分,它为请求处理提供了灵活的控制和扩展机制。通过合理配置和使用 Filter,可以实现各种个性化的功能,提升应用的安全性、可靠性和性能。还可以结合具体的代码示例和实际应用案例,进一步深入探讨 Spring Filter 的具体应用和优化技巧,使对它的理解更加全面和深入。
|
18天前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
|
18天前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
|
18天前
|
Java 测试技术 Spring
springboot学习三:Spring Boot 配置文件语法、静态工具类读取配置文件、静态工具类读取配置文件
这篇文章介绍了Spring Boot中配置文件的语法、如何读取配置文件以及如何通过静态工具类读取配置文件。
21 0
springboot学习三:Spring Boot 配置文件语法、静态工具类读取配置文件、静态工具类读取配置文件
|
13天前
|
XML Java 数据格式
Spring IOC容器的深度解析及实战应用
【10月更文挑战第14天】在软件工程中,随着系统规模的扩大,对象间的依赖关系变得越来越复杂,这导致了系统的高耦合度,增加了开发和维护的难度。为解决这一问题,Michael Mattson在1996年提出了IOC(Inversion of Control,控制反转)理论,旨在降低对象间的耦合度,提高系统的灵活性和可维护性。Spring框架正是基于这一理论,通过IOC容器实现了对象间的依赖注入和生命周期管理。
40 0
|
19天前
|
XML Java 数据格式
手动开发-简单的Spring基于注解配置的程序--源码解析
手动开发-简单的Spring基于注解配置的程序--源码解析
34 0
|
19天前
|
XML Java 数据格式
手动开发-简单的Spring基于XML配置的程序--源码解析
手动开发-简单的Spring基于XML配置的程序--源码解析
60 0
|
2月前
|
SQL 监控 druid
springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。
|
19天前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
98 2

推荐镜像

更多