SpringBoot源码简读——3.2 run方法-获得配置

在SpringApplication中的逻辑

配置的获取

// org/springframework/boot/SpringApplication.java
method:run
// 获得容器的Arguments
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
		args);
// 通过监听器获得并处理配置环境
ConfigurableEnvironment environment = prepareEnvironment(listeners,
		applicationArguments);
// 配置跳过搜索BeanInfo类
configureIgnoreBeanInfo(environment);

// 进一步初始化容器
prepareContext(context, environment, listeners, applicationArguments,
		printedBanner);

获取环境配置

ApplicationArguments

ApplicationArguments主要是保存了,应用启动时main方法传递过来的参数。

prepareEnvironment

	private ConfigurableEnvironment prepareEnvironment(
			SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// Create and configure the environment
		// 获得或者创建环境
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		// 进行环境配置
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		// 通知监听器,环境已经完成配置
		listeners.environmentPrepared(environment);
		// 环境绑定到SpringApplication中
		bindToSpringApplication(environment);
		// 如果不是自定义的环境,则进行类型转换
		if (!this.isCustomEnvironment) {
			environment = new EnvironmentConverter(getClassLoader())
					.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
		}
		// 如果有 attach 到 environment 上的 MutablePropertySources ,则添加到 environment 的 PropertySource 中
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

bindToSpringApplication(environment) 这个方法最初的我以为是将环境绑定到容器中,后来发现事情没有那么简单。代码里面使用了springboot2.0 的Binder工具,将指定参数绑定到目标类上,目前这个绑定有什么作用目前还不清楚。

获得配套的环境对象

getOrCreateEnvironment 和之前获得不同的web对象一样,环境对象也需要根据this.webApplicationType创建不同的环境对象。

	private ConfigurableEnvironment getOrCreateEnvironment() {
		// 如果容器存在环境,则直接返回
		if (this.environment != null) {
			return this.environment;
		}
		// 不存在则根据web容器类别返回对应的环境
		switch (this.webApplicationType) {
		case SERVLET:
			return new StandardServletEnvironment();
		case REACTIVE:
			return new StandardReactiveWebEnvironment();
		default:
			return new StandardEnvironment();
		}
	}

配置环境数据

configureEnvironment

	protected void configureEnvironment(ConfigurableEnvironment environment,
			String[] args) {
		// 增加 environment 的 PropertySource 属性源
		configurePropertySources(environment, args);
		configureProfiles(environment, args);
	}
配置属性源

configurePropertySources

	protected void configurePropertySources(ConfigurableEnvironment environment,
			String[] args) {
		// 获得Mutable属性数据源
		MutablePropertySources sources = environment.getPropertySources();
		// 假如defaultProperties不为空,则配置默认的属性源
		if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
			sources.addLast(
					new MapPropertySource("defaultProperties", this.defaultProperties));
		}
		// addCommandLineProperties 是否添加 JVM 启动参数或者main方法中参数不为空
		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));
			}
		}
	}

大概逻辑就是获得环境中用来保存属性源的对象。

  1. 假如存在默认的属性源则直接设置
  2. 否则检测JVM启动参数或者main方法参数,并解析器参数,然后配置到环境参数 springApplicationCommandLineArgs的值里面
获得激活配置文件

configureProfiles

	protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
		// 获得被激活的配置,确保配置已经配初始化
		environment.getActiveProfiles(); // ensure they are initialized
		// But these ones should go first (last wins in a property key clash)
		Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
		profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
		// 设置 activeProfiles
		environment.setActiveProfiles(StringUtils.toStringArray(profiles));
	}
获得激活的配置文件

这里就是我们常用的一种功能,当我们在开发项目的时候,存在开发、测试、生产环境。这三种环境常常是属性数量是一致的,但是参数值却不一致。这个时候我们假如在每次启动的时候修改配置,显得麻烦而且不安全,所以我们可以配置 spring.profiles.active 来指定我们启动所属环境,然后springboot就可以解析对应的配置了。而getActiveProfiles就是进行这些操作的。

// org/springframework/core/env/AbstractEnvironment.java

	public String[] getActiveProfiles() {
		return StringUtils.toStringArray(doGetActiveProfiles());
	}
	protected Set<String> doGetActiveProfiles() {
		synchronized (this.activeProfiles) {
			if (this.activeProfiles.isEmpty()) {
				String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
				if (StringUtils.hasText(profiles)) {
				// 将参数设置到activeProfiles中
					setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
							StringUtils.trimAllWhitespace(profiles)));
				}
			}
			return this.activeProfiles;
		}
	}

代码中的ACTIVE_PROFILES_PROPERTY_NAME的内容为ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active"。根据上面逻辑可以看到最后系统读取到spring.profiles.active中后被添加到this.activeProfiles.add(profile)这里面了。

后续在configureProfiles中将springboot中this.additionalProfiles的参数也添加至环境中

Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
// 设置 activeProfiles
environment.setActiveProfiles(StringUtils.toStringArray(profiles));

监听器参与、事件发布

    // org/springframework/boot/SpringApplication.java
	// 通知监听器,环境已经完成配置
	listeners.environmentPrepared(environment);
		
		
	// org/springframework/boot/context/event/EventPublishingRunListener.java
	public void environmentPrepared(ConfigurableEnvironment environment) {
		this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
				this.application, this.args, environment));
	}

根据上面的代码可以知道最终会被封装成ApplicationEnvironmentPreparedEvent事件进行发布。而根据上一章内容说的几个监听器中可以发现这个事情会被ConfigFileApplicationListener处理

    // org/springframework/boot/context/config/ConfigFileApplicationListener.java

	public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
		return ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(eventType)
			   || ApplicationPreparedEvent.class.isAssignableFrom(eventType);
	}
事件处理
    // 事件处理
	public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof ApplicationEnvironmentPreparedEvent) {
			onApplicationEnvironmentPreparedEvent(
					(ApplicationEnvironmentPreparedEvent) event);
		}
		if (event instanceof ApplicationPreparedEvent) {
			onApplicationPreparedEvent(event);
		}
	}
	
	
	private void onApplicationEnvironmentPreparedEvent(
			ApplicationEnvironmentPreparedEvent event) {
		// 加载在`META-INF/spring.factories` 定义的EnvironmentPostProcessor对应的类名
		List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
		// 把自己加入其中,因为其也实现了EnvironmentPostProcessor
		postProcessors.add(this);
		// 排序
		AnnotationAwareOrderComparator.sort(postProcessors);
		// 遍历处理器(EnvironmentPostProcessor)并依次执行其业务逻辑
		for (EnvironmentPostProcessor postProcessor : postProcessors) {
			postProcessor.postProcessEnvironment(event.getEnvironment(),
					event.getSpringApplication());
		}
	}

这里会使用spring.factories中配置的处理类。

去spring.factories中可以发现下面的内容

# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor
处理类 - CloudFoundryVcapEnvironmentPostProcessor

实现对 Cloud Foundry 的支持,这一块因为接触不多,就暂时跳过了。

Cloud Foundry是业界第一个开源PaaS云平台,它支持多种框架、语言、运行时环境、云平台及应用服务,使开发人员能够在几秒钟内进行应用程序的部署和扩展,无需担心任何基础架构的问题。(百度百科)

处理类 - SpringApplicationJsonEnvironmentPostProcessor

解析JSON属性值

	public void postProcessEnvironment(ConfigurableEnvironment environment,
			SpringApplication application) {
		// 获得环境中的属性内容
		MutablePropertySources propertySources = environment.getPropertySources();
		// 将根据属性对象创建JsonPropertyValue对象,然后筛选出不为空的第一个值
		StreamSupport.stream(propertySources.spliterator(), false)
				.map(JsonPropertyValue::get).filter(Objects::nonNull).findFirst()
				.ifPresent((v) -> processJson(environment, v));
	}

提醒一下,springboot的代码里面包含了大量的函数式方法调用,所以不了解这一块的可以看看我之前的学习笔记。

JsonPropertyValue::get

JsonPropertyValue是SpringApplicationJsonEnvironmentPostProcessor.java的内部类

		public static JsonPropertyValue get(PropertySource<?> propertySource) {
			// 遍历CANDIDATES内容  spring.application.json;SPRING_APPLICATION_JSON
			for (String candidate : CANDIDATES) {
				// 获得值,并封装成JsonPropertyValue对象
				Object value = propertySource.getProperty(candidate);
				if (value != null && value instanceof String
						&& StringUtils.hasLength((String) value)) {
					return new JsonPropertyValue(propertySource, candidate,
							(String) value);
				}
			}
			return null;
		}

主要是处理配置中spspring.application.json的值,假如配置了spring.application.json的值则返回包装后的JsonPropertyValue

processJson

处理json字符串,解析成map对象,然后添加到环境中

    private void processJson(ConfigurableEnvironment environment,
			JsonPropertyValue propertyValue) {
		try {
			// 获得json处理器,将json字符串解析成map对象,然后封装成JsonPropertySource
			// 保存到容器环境中
			JsonParser parser = JsonParserFactory.getJsonParser();
			Map<String, Object> map = parser.parseMap(propertyValue.getJson());
			if (!map.isEmpty()) {
				addJsonPropertySource(environment,
						new JsonPropertySource(propertyValue, flatten(map)));
			}
		}
		catch (Exception ex) {
			logger.warn("Cannot parse JSON for spring.application.json: "
					+ propertyValue.getJson(), ex);
		}
	}
处理类 - SystemEnvironmentPropertySourceEnvironmentPostProcessor

将 环境(environment) 中的 systemEnvironment 对应的 PropertySource属性源对象替换成 OriginAwareSystemEnvironmentPropertySource 对象

	public void postProcessEnvironment(ConfigurableEnvironment environment,
			SpringApplication application) {
		// 系统环境属性源名称 systemEnvironment
		String sourceName = StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME;
		// 从应用环境中获得对应的属性源
		PropertySource<?> propertySource = environment.getPropertySources()
				.get(sourceName);
		// 假如应用环境中存在系统环境值进行值替换
		if (propertySource != null) {
			// 进行替换
			replacePropertySource(environment, sourceName, propertySource);
		}
	}

主要是将SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME的值进行替换。

数据源替换

	/**
	 * 
	 * @param environment	应用环境配置
	 * @param sourceName	系统环境属性源名称
	 * @param propertySource	应用环境中属性源名称对应的值
	 */
	@SuppressWarnings("unchecked")
	private void replacePropertySource(ConfigurableEnvironment environment,
			String sourceName, PropertySource<?> propertySource) {
		// 获得原始源数据
		Map<String, Object> originalSource = (Map<String, Object>) propertySource
				.getSource();
		// 创建OriginAwareSystemEnvironmentPropertySource对象,然后进行环境中参数替换
		// 将应用属性源包装成系统属性源
		SystemEnvironmentPropertySource source = new OriginAwareSystemEnvironmentPropertySource(
				sourceName, originalSource);
		// 将应用属性中的数据替换从系统属性中的数据
		environment.getPropertySources().replace(sourceName, source);
	}

环境配置的绑定

// org/springframework/boot/SpringApplication.java

// 环境绑定到容器中
bindToSpringApplication(environment);

/**
 * Bind the environment to the {@link SpringApplication}.
 * 将环境对象绑定到this 就是SpringApplication
 * @param environment the environment to bind
 */
protected void bindToSpringApplication(ConfigurableEnvironment environment) {
	try {
		Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
	}
	catch (Exception ex) {
		throw new IllegalStateException("Cannot bind to SpringApplication", ex);
	}
}

其他操作

主要是类型转换和部分参数值替换

类型判断

    // org/springframework/boot/SpringApplication.java
    // 如果不是自定义的环境,则进行类型转换
	if (!this.isCustomEnvironment) {
		environment = new EnvironmentConverter(getClassLoader())
				.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
	}

deduceEnvironmentClass方法之前介绍过了,就是根据web类型返回不同的环境类型

特殊参数处理

// 如果有 attach 到 environment 上的 MutablePropertySources ,则添加到 environment 的 PropertySource 中
		ConfigurationPropertySources.attach(environment);

	public static void attach(Environment environment) {
		Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
		MutablePropertySources sources = ((ConfigurableEnvironment) environment)
				.getPropertySources();
		// 获得configurationProperties的值
		PropertySource<?> attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME);
		// 如果存在则移除 configurationProperties 内容
		if (attached != null && attached.getSource() != sources) {
			sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);
			attached = null;
		}
		// 然后新建一个ConfigurationPropertySourcesPropertySource添加到列表中
		if (attached == null) {
			sources.addFirst(new ConfigurationPropertySourcesPropertySource(
					ATTACHED_PROPERTY_SOURCE_NAME,
					new SpringConfigurationPropertySources(sources)));
		}
	}
	

主要是针对configurationProperties参数的特殊处理,其实就是org.springframework.core.env.PropertySource对应在属性源中的名字

配置跳过搜索BeanInfo类

	/**
	 * 跳过搜索BeanInfo类
	 * @param environment
	 */
	private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
		// spring.beaninfo.ignore的值
		if (System.getProperty(
				CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {
			Boolean ignore = environment.getProperty("spring.beaninfo.ignore",
					Boolean.class, Boolean.TRUE);
			System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME,
					ignore.toString());
		}
	}

主要将应用环境中的spring.beaninfo.ignore参数配置到系统中。
至于这个参数的用途。我只在网上找到了一个回答:

“spring.beaninfo.ignore”, with a value of “true” means skipping the search for BeanInfo classes (typically for scenarios where no such classes are being defined for beans in the application in the first place). The default is “false”, considering all BeanInfo metadata classes, like for standard Introspector.getBeanInfo(Class) calls.

主要意思:跳过对BeanInfo类的搜索(通常用于首先没有为应用程序中的bean定义此类的情况)。
中间提到的Introspector.getBeanInfo(Class)作用是这样的

  // 在 Java Bean 上进行内省,了解其所有属性、公开的方法和事件
  BeanInfo beanInfo = Introspector.getBeanInfo(type);

个人理解是,是springboot在加载的时候会java会通过内省读取一些额外数据。这个操作报装数据读取的完整,但也可能带来多余的类读取。

总结:
截止到目前springboot已经完成

  • 应用环境的创建
  • 应用环境获取属性源以及激活的属性源配置
  • 特殊参数的处理
  • 将环境绑定至应用上
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大·风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值