文章目录

  • 一、前言
  • 二、Spring Environment简介
    • 2.1 env 的 profiles 不同环境配置分组
      • 2.1.1 env 的 profiles 基本使用
      • 2.1.2 配置环境三地方:启动类、application.properties配置文件、Run/Debug Configuration
      • 2.1.3 setActiveProfiles()参数是数组,可以设置多个
    • 2.2 env 的 properties:存放属性的环境或文件信息 @Value("{xxx}")
      • 2.2.1 用@Value取出普通配置
      • 2.2.2 用@Value取出系统配置,如java.version
      • 2.2.3 用Environment实例bean取出系统配置,如java.version
      • 2.2.4 为什么可以用Environment实例bean取出java.version这种系统配置
  • 三、Spring Environment源码解析
    • 3.1 从run()方法开始
    • 3.2 第一子方法:新建或获取环境getOrCreateEnvironment()方法
      • 3.2.1 AbstractEnvironment类
      • 3.2.2 StandardEnvironment类
      • 3.2.3 StandardServletEnvironment类
      • 3.2.4 MutablePropertySources类
    • 3.3 第二子方法:准备环境configureEnvironment()
      • 3.3.1 SpringApplication类中的configureEnvironment()
      • 3.3.2 解析properties属性:SpringApplication类的configurePropertySources()
      • 3.3.3 SpringApplication类的configureProfiles()
    • 3.4 第三子方法:开始加载springboot的配置listeners.environmentPrepared(environment)
      • 3.4.1 listeners.environmentPrepared(environment)
      • 3.4.2 onApplicationEnvironmentPreparedEvent()
      • RandomValuePropertySource类
  • 四、尾声

一、前言

二、Spring Environment简介

2.1 env 的 profiles 不同环境配置分组

2.1.1 env 的 profiles 基本使用

首先,我们知道,整个spring应用运行的环境信息:profiles + properties

先看profiles,在代码中的配置为:

spring.profiles.active=prd/test/dev...

profiles作用:对bean逻辑分组

先定义一个ProfileService类,包含一个私有属性profile,如下:

新建一个ProfileConfiguration配置类,将新建的ProfileService类的实例bean定义在这里,等到springboot项目启动的时候,将可以将ProfileService实例bean装载的ioc容器中去了,整个如下:

在ProfileConfiguraiton配置类的各个bean上,加上@Profile注解,模拟设置两种不同环境下的bean,声明不同环境的bean

金手指:使用@Profile对不同的bean逻辑分布 xml 和注解都可以

现在我们不使用SpringbootApplication类启动了,直接写一个main方法启动即可,如下,在启动之前,在Environment里面设置profile,setActiveProfile()蚕食是数组,可以设置躲着,这里测试之用,先设置“prd”,最后context.getBean可以取出ioc容器中的bean,打印到控制台就好。

一般来说,getBean和@Autowired两种方式都可以取出ioc容器中的bean.

运行springboot工程,如果打印出来的ioc容器中的bean,prd环境中的bean,就可以了。

至此,对于env 的 profiles设置成功。

2.1.2 配置环境三地方:启动类、application.properties配置文件、Run/Debug Configuration

我们能够配置环境的地方不止一个,不仅像上面在启动类中可以配置,还是在application.properties配置文件中配置,还可以在 Run/Debug Configuration 中配置

2.1.3 setActiveProfiles()参数是数组,可以设置多个

setActiveProfiles()参数是数组,可以设置多个,如下:

至此,对于 env 的 profiles 的讲解完成。

2.2 env 的 properties:存放属性的环境或文件信息 @Value(“{xxx}”)

2.2.1 用@Value取出普通配置

新建一个UserController类,打印出application.properties配置文件中的env属性即可,如下:

application.properties配置文件中的env属性为“hello world”,配置如下:

运行成功,取出来了,如下:

所以,我们看到,properties存放属性的环境或文件信息。

2.2.2 用@Value取出系统配置,如java.version

实际上,@Value注解还可以访问到系统环境变量的信息,如java.version,取出当前jdk版本,试一试:

看,打印出来了。

2.2.3 用Environment实例bean取出系统配置,如java.version

我们用@Autowired取出spring ioc容器中的Environment实例bean,然后直接用Environment实例bean(不用@Value)来取,如下:

取出spring ioc容器中的实例bean有两种方式,getBean方法 或 @Autowired注解。


看,也打印出来了。

2.2.4 为什么可以用Environment实例bean取出java.version这种系统配置

那问题来了,为什么可以用Environment取出java.version这种系统配置呢?一起来看看Spring中的Environment接口,如下:


理由很简单,因为Environment实例bean已经在ioc容器中了,所以要取出系统配置java.version很简单,因为这个系统配置java.version就是写在Environment的实例bean里面的。

其他的,如BeanFactory、ApplicationContext实例bean也是在ioc容器里的,还实现了Environment接口,如下:

其实,作为Spring容器的内置接口,Environment中的配置有很多来源:系统的环境变量(如上面提到的java.version)、系统变量 System.propreties等。

大体上来说,Environment中的配置来源大体包括两大类:系统属性源 + springboot属性源,如下:

这个图很重要,本文接下来就讲这个图,讲Environment是如何处理 “系统属性源” 和 “springboot属性源” 的。

金手指:springboot启动类自动扫描所在包及其子包下spring+springmvc注解,但是不会扫描ibatis的@Mapper注解

三、Spring Environment源码解析

先找到Environment接口

3.1 从run()方法开始

找打springboot工程的run方法,如下:

不断ctrl+左键,进入到真正有意义的run方法中,如下:

在这个真正有意义的run方法中,使用prepareEnvironment方法得到一个env实例,然后将这个实例设置到context中去,如下:

金手指:源码中,spring或springboot初始化加载时,env 在 context 之前,合乎常理。这里看的springboot的源码,其实spring的源码也一样。

选择prepareEnvironment方法

进入prepareEnvironment方法,先使用getOrCreateEnvironment方法新建或获得一个环境变量,然后将这个env实例放到configureEnvironment方法中去完成相关配置,最后通过listeners.environmentPrepared方法,将env实例交给监听事件,如下:

接下来看一下SpringApplication类prepareEnvironment()方法的三个子方法。

3.2 第一子方法:新建或获取环境getOrCreateEnvironment()方法

可以看到,如果类变量env不为null,就直接返回,如果为null,就根据类变量webApplicationType的实例类型,new一个Env实现类实例返回。

所以说,getOrCreateEnvironment()方法中涉及三个类:StandardEnvironment StandardServletEnvironment StandardReactiveWebEnvironment,这三个类都继承AbstractEnvironment,AbstractEnvironment又继承于Environment接口,关系如下:

先看AbstractEnvironment类

3.2.1 AbstractEnvironment类

在AbstractEnvironment的构造方法中,调用一个自定义属性源的方法,如下:

这个自定义属性源的方法,在AbstractEnvironment中是空实现,只能看它的子类了。

值得注意的是,对于AbstractEnvironment构造函数,

public AbstractEnvironment() {customizePropertySources(this.propertySources);
}

这是一个很优美的设计,可以将customizePropertySources的实现交给子类的处理,是模板模式。

AbstractEnvironment类就到这里的,我们看其子类StandardEnvironment类。

3.2.2 StandardEnvironment类

既然AbstractEnvironment类的customizePropertySources()是空实现,看看其子类StandardEnvironment类。

StandardEnvironment类只有一个customizePropertySources方法实现,也看完了,且看StandardServletEnvironment类。

3.2.3 StandardServletEnvironment类

直接转到customizePropertySources方法实现,如下:

关于customizePropertySources()方法,customizePropertySources()接收可变参数源作为输入。

该方法在AbstractEnvironment类中为空实现,在StandardEnvironment类中为将系统配置和系统环境变量放到env中,在StandardServletEnvironment类中为servlet配置属性、servlet上下文属性、JNDI属性,并调用StandardEnvironment的customizePropertySources()方法,所以同时有了servlet配置属性、servlet上下文属性、JNDI属性、系统配置和系统环境变量五种属性。

源码设计的优美之处1:StandardServletEnvironment类中的customizePropertySources()方法调用了StandardEnvironment的customizePropertySources()方法,因为是它里面将系统配置和系统环境变量放到env中;StandardEnvironment的customizePropertySources()方法没有调用AbstractEnvironment的customizePropertySources()方法,因为是它里面将里面方法体为空,不需要调用;这体现了源码的优美。

源码设计优美之处2:

public AbstractEnvironment() {customizePropertySources(this.propertySources);
}

AbstractEnvironment类的构造方法中调用customizePropertySources(),以后customizePropertySources()一层层被重写,反正调用最后子类的,具体环境生产具体对象,这体现了源码设计优美。

3.2.4 MutablePropertySources类

customizePropertySources()接收可变参数源作为输入,实际上就是一个MutablePropertySources类对象,让我们来看一下这个MutablePropertySources类,其定义如下:


让我们看看StandardServletEnvironment添加的三个属性是什么


在我们使用spring+springmvc的时候,需要配置一个web.xml,web.xml里面需要配置两个标签< context-params>< /context-params>和< init-params>< /init-params>,当时我们只是这样这样使用,实际上,web.xml中这两个标签作为属性配置到env里面去了。

public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = “servletContextInitParams”; 就是 < context-params>< /context-params>
public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = “servletConfigInitParams”; 就是 < init-params>< /init-params>

我们知道,StandardEnvironment:系统变量+系统环境变量

/** System environment property source name: {@value}. */
public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";/** JVM system properties property source name: {@value}. */
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";

上面,前者表示系统环境变量,所以我们刚才使用@Value(“${java.version}”)访问到了系统环境变量,后者表示系统属性。

我们现在完善图,一图小结源码结构:

最后一个问题,配置顺序,优先级如何确定?

回答:不管是StandardEnvironment类和还是StandardServletEnvironment类,都是使用addLast添加属性的,所有属性的顺序就是源代码从上到下。


好了,ConfigurableEnvironment environment = getOrCreateEnvironment(); 这行代码完了,回到SpringApplication类,看configureEnvironment(environment, applicationArguments.getSourceArgs());这行代码。

3.3 第二子方法:准备环境configureEnvironment()

3.3.1 SpringApplication类中的configureEnvironment()

configureEnvironment()是在新建/获取env实例后执行的,如下:

在configureEnvironment()方法中,第一个实参就是ConfigurableEnvironment类对象,就是env对象,刚刚看过了,第二个参数是一个DefaultApplicationArguments类对象.getSourceArgs(),它是在源码中new出来的,如下(简答了解就好,只要知道命令行参数也是可以被识别的就好):

进入到重点,看到configureEnvironment方法,如下:

类变量addConversionService 默认为true,

private boolean addConversionService = true;

它表示统一类型转换,当其为true,执行if代码块,得到一个conversionService,并设置到Spring框架的env中去。

接着往下看,可以看到,configureEnvironment方法先后执行了两个方法,就是 configurePropertySources(environment, args); 和 configureProfiles(environment, args); ,就是整个spring应用运行的环境信息:profiles + properties,就是在这两个方法里面解析出来的。

3.3.2 解析properties属性:SpringApplication类的configurePropertySources()

看到configurePropertySources()方法,这个方法解析开发者配置的所有properties属性。

先获得env实例中的属性源,如果默认属性不为空,将其添加到sources中,addLast就是在list末尾添加,如果启动的时候,命令行中也传入了参数,也添加进来,没传入也没关系。

小结,继续更新env图

env中添加命令行参数很好懂,但是默认参数defaultproperties是什么?从哪里来的?先看这个defaultproperties的定义,它在SpringApplication类中,默认是一个map结构,定义如下:


值得注意的是,这个defaultproperties仅仅在springboot才有的,spring是没有的。

关于这个defaultproperties,可以自己设置 setDefautProperties,尝试一下,如下面springboot启动类中。

运行启动起来,看,在源码中断点,真的走到了这个地方,取出defaultProperties变量,看到自己手动添加的属性。


好了,configurePropertySources()方法完成了,下面看configureProfiles()方法。

3.3.3 SpringApplication类的configureProfiles()

先打开configureProfiles()的源码,看到就是根据类变量additionalProfiles新建一个LinkedHashSet,然后将env变量中的profiles属性都放到这个新建的set中,最后将这个set变为字符串又放到env中去。

好了,我们进入到setActiveProfiles()方法,如下:

setActiveProfiles()设置env中的profile


关于configureProfiles方法,看起来好像没什么意义?将env中的profiles放到set中,然后又将set放到env中。

但是,请注意,这里这个AbstractEnvironment类中的setActiveProfiles()方法接收String类型可变数组,同时将字符串类型的profile放到一个Set类型的集合activeProfiles中,因为set集合,可以配置多个,但是不能字符串重名。

实际上,这个方法我们一开始就用到,在env中设置profile,接受一个数组,包含两个字符串 “prd” 和 “env” ,保证既是数组又不重名。

其实,在application.properties文件中设置和在Run/Debug Configurations中设置都是一个道理,底层都是调用这个方法setActiveProfiles()。

3.4 第三子方法:开始加载springboot的配置listeners.environmentPrepared(environment)

3.4.1 listeners.environmentPrepared(environment)

这里就是一个监听逻辑了,复习一下,在Spring中,通用的监听逻辑是:自定义一个事件类,事件发布者发布一个自定义事件,时间接受者listener接收自定义事件及其子类事件。

进入listeners.environmentPrepared(environment);

再进入environmentPrepared方法,如下:
看一下广播事件的具体逻辑,如下:

再进去invokeListener方法,如下:

这个listener.onApplicationContext(event)就是表示监听者监听到事件触发后要完成的相应逻辑。

好了,我们重温一下这个调用关系,如下:

好了,我们来看看这个listener监听到事件发生后的具体操作,进入onApplicationEvent方法,但是这种方法有很多个,如下:

任意找一个,这里找ConfigFileApplicationListener类,可以看到对于通过event类型,执行了不同操作,如下:

对于onApplicationEvent的第二个方法onApplicationPreparedEvent,如下:

EnvironmentPostProcessor有很多实现类,如下:

找到几个EnvironmentPostProcessor的实现类,这里选择SpringApplicationJsonEnvironmentPostProcessor类,这个类中,实现了两个接口,EnvironmentPostProcessor接口是扩展,Ordered接口是排序,如下:

3.4.2 onApplicationEnvironmentPreparedEvent()

关于onApplicationEvent的第一个方法onApplicationEnvironmentPreparedEvent()方法,如下:

再次进入到postProcessEnvironment方法,如下:

好了,整理一下调用关系,如下:

继续进入到addPropertySources方法,如下:

我们注意到,在ConfigFileApplicationListener类中,里面有三个重要的类变量,DEFAULT_PROPERTIES默认属性,DEFAULT_SEARCH_LOCATIONS默认扫描位置(开发者的文件应该存放的路径位置),DEFAULT_NAMES默认名称(开发者的文件的默认名称),如下:

在addPropertySources方法中,三步走,先添加,然后新建一个Loader,最后执行load方法,如下:

第一步,先添加,进入RandomValuePropertySource.addToEnvironment方法,如下:

RandomValuePropertySource类

我们先来认识一下这个RandomValuePropertySource类,类上的注释就告诉我们怎么配置,如下:

类中有两个常量,默认随机属性名称为random,前缀为random.,如下:

getProperty方法:当实参name前缀不为random.,直接返回为null;当实参name的前缀为random.,调用getRandomValue方法返回一个随机值。

getRandomValue方法:根据实参name取前缀长度的字符串匹配,返回具体的随机值,


好了,我们按照RandomValuePropertySource类的配置,自动动手试一试,配置一个name为randomLong,值为random.long,底层是由RandomValuePropertySource类的getRandomValue方法生成的,配置如下:

主代码中通过@Value或者Environment的bean实例取出属性值,这里使用Environment的bean实例,如下:

运行,真的取出来了

刷新一次又变了,每一次都会生成一个随机long,哈哈


好了,玩够了,回到ConfigFileApplicationListener类的addPropertySources方法:添加,新建Loader,执行load方法。

进入到addToEnvironment()方法,这里的addAfter就是在后面添加随机属性。

env如图:

回到ConfigFileApplicationListener类,查看new Loader(environment, resourceLoader).load();

Loader是资源文件加载器


这个Loader类的构造方法就是设置四个类属性,如下:

看到第四句,如下:

this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,getClass().getClassLoader());

先看到这个SpringFactoriesLoader.loadFactories方法,这个方法就是加载spring.factories配置文件中的数据,

再看PropertySourceLoader接口本身,这个接口仅包含两个方法,获取文件后缀名方法和加载方法。

看到load方法

进入FilteredPropertySource.apply()方法,如下:

FilteredPropertySource.javastatic void apply(ConfigurableEnvironment environment, String propertySourceName, Set<String> filteredProperties,Consumer<PropertySource<?>> operation) {// 取出env实例bean中的属性源,放到可变属性源propertySources中MutablePropertySources propertySources = environment.getPropertySources();  // 在可变属性源propertySources中,取得指定propertySourceNamePropertySource<?> original = propertySources.get(propertySourceName);if (original == null) {operation.accept(null);  // 如果取出为null,执行operation.acceptreturn;}// 如果取出不为null,即当前存在propertySourceName,新建一个过滤属性源替换当前的propertySourceNamepropertySources.replace(propertySourceName, new FilteredPropertySource(original, filteredProperties));try {operation.accept(original);}finally {propertySources.replace(propertySourceName, original);}
}



回到load方法,看到initializeProfiles方法

进入到initializeProfiles方法,该方法初始化profiles属性,逻辑如下:


进入到getOtherActiveProfiles方法,如下:

经历了Loader构造函数,有了Loader对象,看一下load方法,如下:


在进入load方法,如下:


debug执行,如下:


进入到loadForFileExtension方法,如下:




四、尾声

Spring Environment全解析,完成了。

天天打码,天天进步!!

Spring Environment全解析相关推荐

  1. 第四章:Spring项目文件上传两种方式(全解析)

    欢迎查看Java开发之上帝之眼系列教程,如果您正在为Java后端庞大的体系所困扰,如果您正在为各种繁出不穷的技术和各种框架所迷茫,那么本系列文章将带您窥探Java庞大的体系.本系列教程希望您能站在上帝 ...

  2. 你知道Spring是怎么解析配置类的吗?

    Spring执行流程图如下: Spring执行流程图 这个流程图会随着我们的学习不断的变得越来越详细,也会越来越复杂,希望在这个过程中我们都能朝着精通Spring的目标不断前进!  在上篇文章我们学 ...

  3. Spring源码解析【完整版】--【bilibili地址:https://www.bilibili.com/video/BV1oW41167AV】

    [本文为bilibili视频雷丰阳的Spring源码解析的完整版总结文章,其中文章前面大部分为他人博文的搬运,后面补充了其未总结的部分] 一.Java的注解 1. 注解的概念 注释:用文字描述程序,给 ...

  4. Java 面试全解析:核心知识点与典型面试题

    课程背景 又逢"金九银十",年轻的毕业生们满怀希望与忐忑,去寻找.竞争一个工作机会.已经在职的开发同学,也想通过社会招聘或者内推的时机争取到更好的待遇.更大的平台. 然而,面试人群 ...

  5. 2020年 Java 最常见200+ 面试题全解析:面试必备

    Java 最常见200+ 面试题全解析:面试必备 如想了解更多更全面的Java必备内容可以阅读:所有JAVA必备知识点面试题文章目录: JAVA必备知识点面试题 序 言 在本篇文章开始之前,我想先来回 ...

  6. Spring源码解析-applicationContext.xml加载和bean的注册

    applicationContext文件加载和bean注册流程 ​ Spring对于从事Java开发的boy来说,再熟悉不过了,对于我们这个牛逼的框架的介绍就不在这里复述了,Spring这个大杂烩,怎 ...

  7. java spring mvc_java spring mvc 全注解

    本人苦逼学生一枚,马上就要毕业,面临找工作,实在是不想离开学校.在老师的教导下学习了spring mvc ,配置文件实在繁琐,因此网上百度学习了spring mvc 全注解方式完成spring的装配工 ...

  8. Spring源码解析 -- SpringWeb请求映射Map初始化

    简介 在上篇文章中,大致解析了Spring如何将请求路径与处理方法进行映射,但映射相关的初始化对于我们来说还是一团迷雾 本篇文章就来探索下,请求路径和处理方法的映射,是如何进行初始化的 概览 基于上篇 ...

  9. Android异步加载全解析之引入二级缓存

    Android异步加载全解析之引入二级缓存 为啥要二级缓存 前面我们有了一级缓存,为啥还要二级缓存呢?说白了,这就和电脑是一样的,我们电脑有内存和硬盘,内存读取速度快,所以CPU直接读取内存中的数据, ...

  10. Spring源码解析-bean实例化

    Spring源码解析-bean实例化 ​ 本文介绍Spring创建 bean 过程中的第一个步骤:实例化 bean. 1. Bean实例化源码 ​ 虽然实例化Bean有多种方式(包括静态工厂和工厂实例 ...

最新文章

  1. 防火墙iptables介绍
  2. spring storedProcedure 使用
  3. 教你用 Newprep 一键封装工具 封装XP克隆系统- 视频教程
  4. 【数据结构作业—02】双链表
  5. 移动端去掉按钮点击热区
  6. 微软开源计算机视觉专题库,含分类、检测、分割、关键点、跟踪、动作识别等主流方向...
  7. 以太坊2.0质押流动性解决方案Lido上线治理工具Lido DAO
  8. RabbitMQ-AMQP术语介绍
  9. python字符映射与文件加密
  10. 阿里 java ide_纯JAVA版JAVA IDE环境(源码)
  11. [apk破解]AirPin,无告用户书,无升级提示
  12. JAVAFX_Effect效果介绍
  13. Ural_1671. Anansi's Cobweb(并查集)
  14. 互联网金融中的数据挖掘技术应用
  15. android开发关机代码,android代码实现关机
  16. Shell- 获取ESXI主机虚拟交换机中MAC表
  17. innodb的写缓存
  18. webgl_浏览器支持问题
  19. 联想拯救者r720折腾MacOS12.3.1记录
  20. div增加鼠标点透,css设置点击穿透

热门文章

  1. C300 OLT自动下发WAN连接指导配置
  2. android支付宝4000,Android支付——支付宝支付
  3. JSONObject 与 JSON 互转
  4. matlab一维数组操作,Matlab创建一维数组的具体操作讲解
  5. 一个并肩战斗的同事将离开团队,去家乡追寻梦想
  6. 飞机大战游戏python_《飞》字意思读音、组词解释及笔画数 - 新华字典 - 911查询...
  7. HTML入门笔记8-上脚注和下脚注标签
  8. 第22章 常用存储器介绍
  9. iOS:error: unable to read input file
  10. PDF转图片怎样转换?有哪些好用PDF转换的方法?