2000字范文,分享全网优秀范文,学习好帮手!
2000字范文 > Spring Environment全解析

Spring Environment全解析

时间:2019-12-06 21:58:12

相关推荐

Spring Environment全解析

文章目录

一、前言二、Spring Environment简介2.1 env 的 profiles 不同环境配置分组2.1.1 env 的 profiles 基本使用2.1.2 配置环境三地方:启动类、application.properties配置文件、Run/Debug Configuration2.1.3 setActiveProfiles()参数是数组,可以设置多个2.2 env 的 properties:存放属性的环境或文件信息 @Value("{xxx}")2.2.1 用@Value取出普通配置2.2.2 用@Value取出系统配置,如java.version2.2.3 用Environment实例bean取出系统配置,如java.version2.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全解析,完成了。

天天打码,天天进步!!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。