在SpringCloud应用启动时,使用的还是Spring Boot,下面这个run()方法,createBootstrapContext()这个是Spring Boot的引导上下文,每个Spring Boot应用都会有,而在prepareEnvironment()方法中,会创建一个另外一个cloud 的上下文。
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
在org.springframework.context.ApplicationListener众多实现者有一个非常重要的实现就是
org.springframework.cloud.bootstrap.BootstrapApplicationListener
可以看到,ApplicationListener是定义在spring-context包中,属于经典上下文范畴,BootstrapApplicationListener是在spring-cloud-context中,属于cloud上下文范畴。
在classpath中出现spring-cloud-context.jar包时,它在spring.factories文件里写了BootstrapApplicationListener,所以在构建SpringApplication对象时就扫描出这个监听器了。然后在run()方法中发送事件给监听器,监听器执行回调。具体流程是:
org.springframework.boot.SpringApplication#run(java.lang.String...)
org.springframework.boot.SpringApplication#prepareEnvironment()
prepareEnvironment中listeners.environmentPrepared(bootstrapContext, environment);
org.springframework.cloud.bootstrap.BootstrapApplicationListener#onApplicationEvent()
org.springframework.cloud.bootstrap.BootstrapApplicationListener#bootstrapServiceContext()
bootstrapServiceContext中final ConfigurableApplicationContext context = builder.run();
org.springframework.boot.builder.SpringApplicationBuilder#run()
这里又到了org.springframework.boot.SpringApplication#run(java.lang.String...)
这就是很多人在调试Spring cloud源码时,发现创建多个application context的原因。
官方解释:
这个listener会prepare SpringApplication(例如填充他的Environment等),通过代理给ApplicationContextInitializer beans,这些beans在独立的bootstrap context里。bootstrap context 是SpringApplication从spring.factories中BootstrapConfiguration中创建的,从bootstrap.properties(or yml)而不是普通的application.properties(or yml)初始化的。
这里有几个判断条件,只有在开启了bootstrap的情况下才会创建。
BootstrapApplicationListener执行后会创建一个id=bootstrap的上下文,这个上下文不是一个独立的父上下文,不是装载应用bean的上下文。
public class BootstrapApplicationListener
implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
// 没有启用bootstrap且没有使用老配置方式,老配置方式需要创建bootstrap context
if (!bootstrapEnabled(environment) && !useLegacyProcessing(environment)) {
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);
event.getSpringApplication().addListeners(new CloseContextOnFailureApplicationListener(context));
}
apply(context, event.getSpringApplication(), environment);
}
}
public static final String BOOTSTRAP_ENABLED_PROPERTY = "spring.cloud.bootstrap.enabled";
public static final boolean MARKER_CLASS_EXISTS = ClassUtils.isPresent(MARKER_CLASS, null);
public static final String MARKER_CLASS = "org.springframework.cloud.bootstrap.marker.Marker";
public static boolean bootstrapEnabled(Environment environment) {
return environment.getProperty(BOOTSTRAP_ENABLED_PROPERTY, Boolean.class, false) || MARKER_CLASS_EXISTS;
}
整个回调方法通过开关控制
spring.cloud.bootstrap.enabled
org.springframework.cloud.bootstrap.BootstrapApplicationListener#onApplicationEvent()方法做的事情是
其中核心的方法是:
org.springframework.cloud.bootstrap.BootstrapApplicationListener#bootstrapServiceContext()
这个方法会创建一个bootstrapEnvironment,一个bootstrapApplicationContext,这个applicationContext可以认为是一个父上下文。
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:}");
String configAdditionalLocation = environment
.resolvePlaceholders("${spring.cloud.bootstrap.additional-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);
}
if (StringUtils.hasText(configAdditionalLocation)) {
bootstrapMap.put("spring.config.additional-location",
configAdditionalLocation);
}
bootstrapProperties.addFirst(
new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
for (PropertySource<?> source : environment.getPropertySources()) {
if (source instanceof StubPropertySource) {
continue;
}
bootstrapProperties.addLast(source);
}
// 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);
final SpringApplication builderApplication = builder.application();
if (builderApplication.getMainApplicationClass() == null) {
// gh_425:
// SpringApplication cannot deduce the MainApplicationClass here
// if it is booted from SpringBootServletInitializer due to the
// absense of the "main" method in stackTraces.
// But luckily this method's second parameter "application" here
// carries the real MainApplicationClass which has been explicitly
// set by SpringBootServletInitializer itself already.
builder.main(application.getMainApplicationClass());
}
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.
builderApplication
.setListeners(filterListeners(builderApplication.getListeners()));
}
builder.sources(BootstrapImportSelectorConfiguration.class);
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;
}
比较关键的点是这里手动引入了一个导入配置类:
org.springframework.cloud.bootstrap.BootstrapImportSelectorConfiguration
@Configuration(proxyBeanMethods = false)
@Import(BootstrapImportSelector.class)
public class BootstrapImportSelectorConfiguration {
}
进而导入:
org.springframework.cloud.bootstrap.BootstrapImportSelector
BootstrapImportSelector的主要作用就是从spring.factories文件中读取:
org.springframework.cloud.bootstrap.BootstrapConfiguration配置的类
public class BootstrapImportSelector implements EnvironmentAware, DeferredImportSelector {
private Environment environment;
private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory();
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
List<String> names = new ArrayList<>(SpringFactoriesLoader
.loadFactoryNames(BootstrapConfiguration.class, classLoader));
names.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(
this.environment.getProperty("spring.cloud.bootstrap.sources", ""))));
List<OrderedAnnotatedElement> elements = new ArrayList<>();
for (String name : names) {
try {
elements.add(
new OrderedAnnotatedElement(this.metadataReaderFactory, name));
}
catch (IOException e) {
continue;
}
}
AnnotationAwareOrderComparator.sort(elements);
String[] classNames = elements.stream().map(e -> e.name).toArray(String[]::new);
return classNames;
}
class OrderedAnnotatedElement implements AnnotatedElement {
private final String name;
private Order order = null;
private Integer value;
OrderedAnnotatedElement(MetadataReaderFactory metadataReaderFactory, String name)
throws IOException {
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(name);
AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
Map<String, Object> attributes = metadata
.getAnnotationAttributes(Order.class.getName());
this.name = name;
if (attributes != null && attributes.containsKey("value")) {
this.value = (Integer) attributes.get("value");
this.order = new Order() {
@Override
public Class<? extends Annotation> annotationType() {
return Order.class;
}
@Override
public int value() {
return OrderedAnnotatedElement.this.value;
}
};
}
}
@Override
@SuppressWarnings("unchecked")
public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
if (annotationClass == Order.class) {
return (T) this.order;
}
return null;
}
@Override
public Annotation[] getAnnotations() {
return this.order == null ? new Annotation[0]
: new Annotation[] { this.order };
}
@Override
public Annotation[] getDeclaredAnnotations() {
return getAnnotations();
}
@Override
public String toString() {
return new ToStringCreator(this).append("name", this.name)
.append("value", this.value).toString();
}
}
}
# Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,\
org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration,\
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.cloud.util.random.CachedRandomPropertySourceAutoConfiguration