Spring环境Environment
Spring环境Environment
标准环境
AbstractEnvironment
private final MutablePropertySources propertySources = new MutablePropertySources(this.logger);
public AbstractEnvironment() {
customizePropertySources(this.propertySources);
if (logger.isDebugEnabled()) {
logger.debug("Initialized " + getClass().getSimpleName() + " with PropertySources " + this.propertySources);
}
}
StandardEnvironment
public class StandardEnvironment extends AbstractEnvironment {
/** System environment property source name: {@value} */
String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
/** JVM system properties property source name: {@value} */
String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
}
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
标准环境默认会将如下信息放入到Environment.MultablePropertySource
JVM变量–System.getProperties()
系统变量–System.getEnv()
java命令的jVM系统参数
标准环境默认会将如下信息放入到Environment.MultablePropertySource
JVM变量–System.getProperties() 设置方式 java -jar -D name=value
系统变量–System.getEnv() 设置方式 比如vi etc/profiles
命令行参数 – 设置方式 java -jar xx.jar --spring.profiles.active=dev
那些可以设置呢?
比如SpringBoot中读配置文件位置spring.config.location这个变量来找配置文件的位置,配置文件还没开始找,就需要这个变量 那么就可以放到系统变量里边或者环境里边
java命令的命令行参数
–spring.profile.active
web环境顺序
0.commandLineArgs --> java -jar –spring.profiles.active=dev
下面的是未初始化的 执行initServletPropertySources初始化
1.servletConfigInitParams —> ServletConfig
2.servletContextInitParams —> ServletContext
jndiProperties
直接java带有的API放入的时候直接初始化了
3.systemProperties --> System.getProperties()
4.systemEnvironment --> System.getEnv()
调用时机:AbstractApplicationContext#refresh#prepareRefresh#initPropertySources
即当进入refresh方法就调用了 时机很早
public static void initServletPropertySources(MutablePropertySources sources,
@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {
Assert.notNull(sources, "'propertySources' must not be null");
String name = StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME;
if (servletContext != null && sources.contains(name) && sources.get(name) instanceof StubPropertySource) {
sources.replace(name, new ServletContextPropertySource(name, servletContext));
}
name = StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME;
if (servletConfig != null && sources.contains(name) && sources.get(name) instanceof StubPropertySource) {
sources.replace(name, new ServletConfigPropertySource(name, servletConfig));
}
}
MultablePropertySource
public class MutablePropertySources implements PropertySources {
List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
public MutablePropertySources(PropertySources propertySources) {
this();
for (PropertySource<?> propertySource : propertySources) {
addLast(propertySource);
}
}
}
本质是一个List装了多个PropertySource
PropertySource
public abstract class PropertySource<T> {
protected final Log logger = LogFactory.getLog(getClass());
protected final String name;
protected final T source;
}
PropertySource就是一个名字 key为字符串类型的名字 value是一个泛型对象对象
MultablePropertySource就维护一个List< PropertySource>
Environment维护一个MultablePropertySource
三者关系就是如此
准备环境prepareEnvironment
SpringApplication#run#prepareEnvironment阶段
创建环境并配置环境
配置环境其实就是交给程序员控制
protected void configurePropertySources(ConfigurableEnvironment environment,
String[] args) {
MutablePropertySources sources = environment.getPropertySources();
if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
sources.addLast(
new MapPropertySource("defaultProperties", this.defaultProperties));
}
if (this.addCommandLineProperties && args.length > 0) {
String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
if (sources.contains(name)) {
PropertySource<?> source = sources.get(name);
CompositePropertySource composite = new CompositePropertySource(name);
composite.addPropertySource(new SimpleCommandLinePropertySource(
"springApplicationCommandLineArgs", args));
composite.addPropertySource(source);
sources.replace(name, composite);
}
else {
sources.addFirst(new SimpleCommandLinePropertySource(args));
}
}
}
defautlProperties可以通过
SpringApplication#setDefaultProperties(Map<String, Object> defaultProperties) 来设置
AbstractEnvironment
- MutablePropertySources propertySources = new MutablePropertySources(this.logger);
MutablePropertySources
- List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
暂时的放入环境中的顺序问题
defaultProperties放到了List的最后
springApplicationCommandLineArgs参数放到了最前
BootstrapApplicationListener
引入SpringCloud之后(比如ConfigServer)会多一个BootstrapApplicationListener 在prepareEnvironment的广播了时间被它监听到执行如下:
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,
true)) {
return;
}
// don't listen to events in a bootstrap context
if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
return;
}
ConfigurableApplicationContext context = null;
String configName = environment
.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
for (ApplicationContextInitializer<?> initializer : event.getSpringApplication()
.getInitializers()) {
if (initializer instanceof ParentContextApplicationContextInitializer) {
context = findBootstrapContext(
(ParentContextApplicationContextInitializer) initializer,
configName);
}
}
if (context == null) {
context = bootstrapServiceContext(environment, event.getSpringApplication(),
configName);//创建bootContext上下文
}
apply(context, event.getSpringApplication(), environment);
}
bootstrap Context
可以通过配置
spring.cloud.bootstrap.enabled=false来禁用bootstrapContext
环境中如果已经有了
private ConfigurableApplicationContext bootstrapServiceContext(
ConfigurableEnvironment environment, final SpringApplication application,
String configName) {
StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
MutablePropertySources bootstrapProperties = bootstrapEnvironment
.getPropertySources();
for (PropertySource<?> source : bootstrapProperties) {
bootstrapProperties.remove(source.getName());
}
String configLocation = environment
.resolvePlaceholders("${spring.cloud.bootstrap.location:}");
Map<String, Object> bootstrapMap = new HashMap<>();
bootstrapMap.put("spring.config.name", configName);
// if an app (or test) uses spring.main.web-application-type=reactive, bootstrap will fail
// force the environment to use none, because if though it is set below in the builder
// the environment overrides it
bootstrapMap.put("spring.main.web-application-type", "none");
if (StringUtils.hasText(configLocation)) {
bootstrapMap.put("spring.config.location", configLocation);
}
bootstrapProperties.addFirst(
new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
for (PropertySource<?> source : environment.getPropertySources()) {
if (source instanceof StubPropertySource) {
continue;
}
bootstrapProperties.addLast(source);
}
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
List<String> names = new ArrayList<>(SpringFactoriesLoader
.loadFactoryNames(BootstrapConfiguration.class, classLoader));
for (String name : StringUtils.commaDelimitedListToStringArray(
environment.getProperty("spring.cloud.bootstrap.sources", ""))) {
names.add(name);
}
// TODO: is it possible or sensible to share a ResourceLoader?
SpringApplicationBuilder builder = new SpringApplicationBuilder()
.profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)
.environment(bootstrapEnvironment)
// Don't use the default properties in this builder
.registerShutdownHook(false).logStartupInfo(false)
.web(WebApplicationType.NONE);
if (environment.getPropertySources().contains("refreshArgs")) {
// If we are doing a context refresh, really we only want to refresh the
// Environment, and there are some toxic listeners (like the
// LoggingApplicationListener) that affect global static state, so we need a
// way to switch those off.
builder.application()
.setListeners(filterListeners(builder.application().getListeners()));
}
List<Class<?>> sources = new ArrayList<>();
for (String name : names) {
Class<?> cls = ClassUtils.resolveClassName(name, null);
try {
cls.getDeclaredAnnotations();
}
catch (Exception e) {
continue;
}
sources.add(cls);
}
AnnotationAwareOrderComparator.sort(sources);
builder.sources(sources.toArray(new Class[sources.size()]));
final ConfigurableApplicationContext context = builder.run();
// gh-214 using spring.application.name=bootstrap to set the context id via
// `ContextIdApplicationContextInitializer` prevents apps from getting the actual
// spring.application.name
// during the bootstrap phase.
context.setId("bootstrap");
// Make the bootstrap context a parent of the app context
addAncestorInitializer(application, context);
// It only has properties in it now that we don't want in the parent so remove
// it (and it will be added back later)
bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
return context;
}
主要逻辑:
-
创建BootstrapContext自己的StandardEnvironment
- 移除默认的propertySource
-
从启动类对应的Environment查找几个变量
- spirng.cloud.bootstrap.location 需要从系统变量或者命令行搞过来
- spring.config.location 需要从系统变量或者命令行搞过来
- 将这些信息包装到名字为
key为**bootstrap**的PropertySourcevalue为map
- 将这些信息包装到名字为
-
SPI加载META-INF/spring.factories key=BootstrapConfiguration的类,作为source类—可理解为启动类
-
创建contextId=bootstrap的SpringApplicationContext
- 执行SpringApplicationBuilder#run创建出当前SpringApplication的parent SpringApplicationContext的
-
合并当前SpringApplicationContext和bootstrap(parent)SpringApplicationContext的Environment
ConfigFileApplicationListener
先执行的BootstrapApplicationListener在执行ConfigFileApplicationListener 暂时为查找原因
先上一下调用链路
调用分析:
- 启动类main调用的SpringApplication#run
- prepareEnvironment
- 环境准备事件
- BootstrapApplicationListener监听到环境准备事件
- 通过SpringApplicationBuilder创建Boot SpringApplication
- 再次执行SpringApplication#run 相当于递归
- 执行prepareEnvironment
- 环境准备
- 监听到执行的时间
- ConfigFileApplicationListener执行
- xxx
- xxx
我们看下ConfigFileApplicationListener的监听逻辑
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent(
(ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
private void onApplicationEnvironmentPreparedEvent(
ApplicationEnvironmentPreparedEvent event) {
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
//将ConfigFileApplicationListener自己也作为了EnvironmentPreparedEvent
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(),
event.getSpringApplication());
}
}
List<EnvironmentPostProcessor> loadPostProcessors() {
return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class,
getClass().getClassLoader());
}
逻辑如下:
-
从META-INF/spring.factories加载EnvironmentPostProcess处理器列表
- ConfigFileApplicationListener自己也作为了EnvironmentPostProcessor
-
执行EnvironmentPostProcess#postProcessEnvironment方法
所以我们先看ConfigFileApplicationListener#postProcessEnvironment
EnvironmentPostProcess
private void onApplicationPreparedEvent(ApplicationEvent event) {
this.logger.replayTo(ConfigFileApplicationListener.class);
addPostProcessors(((ApplicationPreparedEvent) event).getApplicationContext());
}
protected void addPropertySources(ConfigurableEnvironment environment,
ResourceLoader resourceLoader) {
RandomValuePropertySource.addToEnvironment(environment);
new Loader(environment, resourceLoader).load();
}
Loader
专门用来读文件的,但是比较负载,可以从如下几个部分:
- 文件位置 从命令行、系统变量、jvm参数、默认的一些位置
- 文件名+profile (读到某个文件之后还可以再次获取profile再去读文件名+profile)
- 文件后缀 其实一种格式的如何解析
- 文件内部 占位符补充
- —这些占位符要从那些地方获取等
- 这么多的propertySource顺序是什么
- 再结合前面的流程还有一部merge操作
Loader构造方法
Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
this.environment = environment;
this.resourceLoader = (resourceLoader != null) ? resourceLoader
: new DefaultResourceLoader();
this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(
PropertySourceLoader.class, getClass().getClassLoader());
}
每个模块可以定义自己的PropertySourceLoader
PropertySourceLoader加载文件类型
SpringBoot自带支持的两个PropertySourceLoader
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
就是properties和Yaml,其实更确切的说是读取什么格式的!
加载
public void load() {
this.profiles = new LinkedList<>();
this.processedProfiles = new LinkedList<>();
this.activatedProfiles = false;
this.loaded = new LinkedHashMap<>();
initializeProfiles();
while (!this.profiles.isEmpty()) {
Profile profile = this.profiles.poll();
if (profile != null && !profile.isDefaultProfile()) {
addProfileToEnvironment(profile.getName());
}
load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
load(null, this::getNegativeProfileFilter,
addToLoaded(MutablePropertySources::addFirst, true));
addLoadedPropertySources();
}
主要逻辑:
- 初始profiles,没有加入default的profile (实际在prepareEnvironment的时候早就获取过了)
- load每个profile的配置信息
- load不是profile的配置信息
- 将加载的配置信息加入到bootStrapContext的environment的名为bootstrap的propertySource中
初始化Profiles
initializeProfiles
private void initializeProfiles() {
this.profiles.add(null);
Set<Profile> activatedViaProperty = getProfilesActivatedViaProperty();
this.profiles.addAll(getOtherActiveProfiles(activatedViaProperty));
addActiveProfiles(activatedViaProperty);
if (this.profiles.size() == 1) { // only has null profile
for (String defaultProfileName : this.environment.getDefaultProfiles()) {
Profile defaultProfile = new Profile(defaultProfileName, true);
this.profiles.add(defaultProfile);
}
}
}
到目前为止,获取spring.profiles.active都是从非文件—可能是命令行,可能是系统变量,jvm参数
实际上如果我们不设置的话是没有的 也就是有了上面代码的逻辑设置默认profile
this.environment.getDefaultProfiles()它返回的就是default
设置的话这里肯定就是多个profile了 多个profile对读取文件有什么影响
就需要多个profile对应的文件都要读取,读完了是不是还要merge?
获取读取位置
每个profile对应的配置文件的读取
private void load(Profile profile, DocumentFilterFactory filterFactory,
DocumentConsumer consumer) {
getSearchLocations().forEach((location) -> {
boolean isFolder = location.endsWith("/");
Set<String> names = isFolder ? getSearchNames() : NO_SEARCH_NAMES;
names.forEach(
(name) -> load(location, name, profile, filterFactory, consumer));
});
}
确定搜索位置
private Set<String> getSearchLocations() {
if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
return getSearchLocations(CONFIG_LOCATION_PROPERTY);
}
Set<String> locations = getSearchLocations(
CONFIG_ADDITIONAL_LOCATION_PROPERTY);
locations.addAll(
asResolvedSet(ConfigFileApplicationListener.this.searchLocations,
DEFAULT_SEARCH_LOCATIONS));
return locations;
}
逻辑:
-
每个profile下环境有
spring.config.location
变量指定的路径 就搜索指定的位置- 系统变量 、jvm参数、命令行参数?可以指定
-
读取
spring.config.additional-location
指定的或者系统默认位置- classpath:/
- classpath:/config/
- file:./
- file:./config/ 顺序倒过来的
-
每个位置都会搜索相应的文件名spring.config.name 变量指定的
- 系统变量 、jvm参数、命令行参数?可以指
-
-
上文如下,所以有了spring.config.name就是bootstrap
bootstrapProperties.addFirst( new MapPropertySource("bootstrap", bootstrapMap)
-
没指定的话就是读取spring.config.name=application文件名
读取本地文件的方法
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
for (ProtocolResolver protocolResolver : this.protocolResolvers) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
if (location.startsWith("/")) {
return getResourceByPath(location);
}
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// Try to parse the location as a URL...
URL url = new URL(location);
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location);
}
}
}