Spring环境Environment

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这个变量来找配置文件的位置,配置文件还没开始找,就需要这个变量 那么就可以放到系统变量里边或者环境里边

Spring环境Environment

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 暂时为查找原因

先上一下调用链路

Spring环境Environment

调用分析:

  • 启动类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环境Environment

    • 上文如下,所以有了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);
      }
   }
}
  • 没给配置文件都是一个PropertySource name是文件名 value 是map
  • 各个顺序如下