一、前言
前面我们从源码简单介绍了一下spring boot 的 事件机制的相关类。下面我们接着介绍一个事件相关的 监听器BootstrapApplicationListener
二、类图
三、源码分析
package org.springframework.cloud.bootstrap;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.boot.Banner.Mode;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.ParentContextApplicationContextInitializer;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.logging.LoggingApplicationListener;
import org.springframework.cloud.bootstrap.encrypt.EnvironmentDecryptApplicationInitializer;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySource.StubPropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.env.SystemEnvironmentPropertySource;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
//它是用ApplicationContextInitializer beans通过一个单独的引导环境去准备SpringApplication。
//这个引导上下文是从spring.factories源中作为BootstrapConfiguration创建的SpringApplication,
//并且用外部的文件如"bootstrap.properties"或 yml 初始化 ,而不是普通的application.properties
public class BootstrapApplicationListener
implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {
public static final String BOOTSTRAP_PROPERTY_SOURCE_NAME = "bootstrap";
public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 5;
public static final String DEFAULT_PROPERTIES = "defaultProperties";
private int order = DEFAULT_ORDER;
@Override
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);
}
apply(context, event.getSpringApplication(), environment);
}
private ConfigurableApplicationContext findBootstrapContext(
ParentContextApplicationContextInitializer initializer, String configName) {
Field field = ReflectionUtils
.findField(ParentContextApplicationContextInitializer.class, "parent");
ReflectionUtils.makeAccessible(field);
ConfigurableApplicationContext parent = safeCast(
ConfigurableApplicationContext.class,
ReflectionUtils.getField(field, initializer));
if (parent != null && !configName.equals(parent.getId())) {
parent = safeCast(ConfigurableApplicationContext.class, parent.getParent());
}
return parent;
}
private <T> T safeCast(Class<T> type, Object object) {
try {
return type.cast(object);
}
catch (ClassCastException e) {
return null;
}
}
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;
}
private Collection<? extends ApplicationListener<?>> filterListeners(
Set<ApplicationListener<?>> listeners) {
Set<ApplicationListener<?>> result = new LinkedHashSet<>();
for (ApplicationListener<?> listener : listeners) {
if (!(listener instanceof LoggingApplicationListener)
&& !(listener instanceof LoggingSystemShutdownListener)) {
result.add(listener);
}
}
return result;
}
private void mergeDefaultProperties(MutablePropertySources environment,
MutablePropertySources bootstrap) {
String name = DEFAULT_PROPERTIES;
if (bootstrap.contains(name)) {
PropertySource<?> source = bootstrap.get(name);
if (!environment.contains(name)) {
environment.addLast(source);
}
else {
PropertySource<?> target = environment.get(name);
if (target instanceof MapPropertySource) {
Map<String, Object> targetMap = ((MapPropertySource) target)
.getSource();
if (target != source) {
if (source instanceof MapPropertySource) {
Map<String, Object> map = ((MapPropertySource) source)
.getSource();
for (String key : map.keySet()) {
if (!target.containsProperty(key)) {
targetMap.put(key, map.get(key));
}
}
}
}
}
}
}
mergeAdditionalPropertySources(environment, bootstrap);
}
private void mergeAdditionalPropertySources(MutablePropertySources environment,
MutablePropertySources bootstrap) {
PropertySource<?> defaultProperties = environment.get(DEFAULT_PROPERTIES);
ExtendedDefaultPropertySource result = defaultProperties instanceof ExtendedDefaultPropertySource
? (ExtendedDefaultPropertySource) defaultProperties
: new ExtendedDefaultPropertySource(DEFAULT_PROPERTIES,
defaultProperties);
for (PropertySource<?> source : bootstrap) {
if (!environment.contains(source.getName())) {
result.add(source);
}
}
for (String name : result.getPropertySourceNames()) {
bootstrap.remove(name);
}
addOrReplace(environment, result);
addOrReplace(bootstrap, result);
}
private void addOrReplace(MutablePropertySources environment,
PropertySource<?> result) {
if (environment.contains(result.getName())) {
environment.replace(result.getName(), result);
}
else {
environment.addLast(result);
}
}
private void addAncestorInitializer(SpringApplication application,
ConfigurableApplicationContext context) {
boolean installed = false;
for (ApplicationContextInitializer<?> initializer : application
.getInitializers()) {
if (initializer instanceof AncestorInitializer) {
installed = true;
// New parent
((AncestorInitializer) initializer).setParent(context);
}
}
if (!installed) {
application.addInitializers(new AncestorInitializer(context));
}
}
private void apply(ConfigurableApplicationContext context,
SpringApplication application, ConfigurableEnvironment environment) {
@SuppressWarnings("rawtypes")
List<ApplicationContextInitializer> initializers = getOrderedBeansOfType(context,
ApplicationContextInitializer.class);
application.addInitializers(initializers
.toArray(new ApplicationContextInitializer[initializers.size()]));
addBootstrapDecryptInitializer(application);
}
private void addBootstrapDecryptInitializer(SpringApplication application) {
DelegatingEnvironmentDecryptApplicationInitializer decrypter = null;
for (ApplicationContextInitializer<?> initializer : application
.getInitializers()) {
if (initializer instanceof EnvironmentDecryptApplicationInitializer) {
@SuppressWarnings("unchecked")
ApplicationContextInitializer<ConfigurableApplicationContext> delegate = (ApplicationContextInitializer<ConfigurableApplicationContext>) initializer;
decrypter = new DelegatingEnvironmentDecryptApplicationInitializer(
delegate);
}
}
if (decrypter != null) {
application.addInitializers(decrypter);
}
}
private <T> List<T> getOrderedBeansOfType(ListableBeanFactory context,
Class<T> type) {
List<T> result = new ArrayList<T>();
for (String name : context.getBeanNamesForType(type)) {
result.add(context.getBean(name, type));
}
AnnotationAwareOrderComparator.sort(result);
return result;
}
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
private static class AncestorInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
private ConfigurableApplicationContext parent;
public AncestorInitializer(ConfigurableApplicationContext parent) {
this.parent = parent;
}
public void setParent(ConfigurableApplicationContext parent) {
this.parent = parent;
}
@Override
public int getOrder() {
// Need to run not too late (so not unordered), so that, for instance, the
// ContextIdApplicationContextInitializer runs later and picks up the merged
// Environment. Also needs to be quite early so that other initializers can
// pick up the parent (especially the Environment).
return Ordered.HIGHEST_PRECEDENCE + 5;
}
@Override
public void initialize(ConfigurableApplicationContext context) {
while (context.getParent() != null && context.getParent() != context) {
context = (ConfigurableApplicationContext) context.getParent();
}
reorderSources(context.getEnvironment());
new ParentContextApplicationContextInitializer(this.parent)
.initialize(context);
}
private void reorderSources(ConfigurableEnvironment environment) {
PropertySource<?> removed = environment.getPropertySources()
.remove(DEFAULT_PROPERTIES);
if (removed instanceof ExtendedDefaultPropertySource) {
ExtendedDefaultPropertySource defaultProperties = (ExtendedDefaultPropertySource) removed;
environment.getPropertySources().addLast(new MapPropertySource(
DEFAULT_PROPERTIES, defaultProperties.getSource()));
for (PropertySource<?> source : defaultProperties.getPropertySources()
.getPropertySources()) {
if (!environment.getPropertySources().contains(source.getName())) {
environment.getPropertySources().addBefore(DEFAULT_PROPERTIES,
source);
}
}
}
}
}
/**
* A special initializer designed to run before the property source bootstrap and
* decrypt any properties needed there (e.g. URL of config server).
*/
@Order(Ordered.HIGHEST_PRECEDENCE + 9)
private static class DelegatingEnvironmentDecryptApplicationInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private ApplicationContextInitializer<ConfigurableApplicationContext> delegate;
public DelegatingEnvironmentDecryptApplicationInitializer(
ApplicationContextInitializer<ConfigurableApplicationContext> delegate) {
this.delegate = delegate;
}
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
this.delegate.initialize(applicationContext);
}
}
private static class ExtendedDefaultPropertySource
extends SystemEnvironmentPropertySource {
private final CompositePropertySource sources;
private final List<String> names = new ArrayList<>();
public ExtendedDefaultPropertySource(String name,
PropertySource<?> propertySource) {
super(name, findMap(propertySource));
this.sources = new CompositePropertySource(name);
}
public CompositePropertySource getPropertySources() {
return this.sources;
}
public List<String> getPropertySourceNames() {
return this.names;
}
public void add(PropertySource<?> source) {
if (source instanceof EnumerablePropertySource
&& !this.names.contains(source.getName())) {
this.sources.addPropertySource(source);
this.names.add(source.getName());
}
}
@Override
public Object getProperty(String name) {
if (this.sources.containsProperty(name)) {
return this.sources.getProperty(name);
}
return super.getProperty(name);
}
@Override
public boolean containsProperty(String name) {
if (this.sources.containsProperty(name)) {
return true;
}
return super.containsProperty(name);
}
@Override
public String[] getPropertyNames() {
List<String> names = new ArrayList<>();
names.addAll(Arrays.asList(this.sources.getPropertyNames()));
names.addAll(Arrays.asList(super.getPropertyNames()));
return names.toArray(new String[0]);
}
@SuppressWarnings("unchecked")
private static Map<String, Object> findMap(PropertySource<?> propertySource) {
if (propertySource instanceof MapPropertySource) {
return (Map<String, Object>) propertySource.getSource();
}
return new LinkedHashMap<String, Object>();
}
}
}
未完待续...