對於一個簡單的Spring boot應用,它的spring context是隻會有一個。html
AnnotationConfigApplicationContext
AnnotationConfigEmbeddedWebApplicationContext
AnnotationConfigEmbeddedWebApplicationContext
是spring boot裏本身實現的一個context,主要功能是啓動embedded servlet container,好比tomcat/jetty。java
這個和傳統的war包應用不同,傳統的war包應用有兩個spring context。參考:hengyunabc.github.io/something-a…git
可是對於一個複雜點的spring boot應用,它的spring context可能會是多個,下面分析下各類狀況。github
這個Demo展現不一樣狀況下的spring boot context的繼承狀況。web
github.com/hengyunabc/…spring
spring boot actuator默認狀況下和應用共用一個tomcat,這樣子的話就會直接把應用的endpoints暴露出去,帶來很大的安全隱患。bootstrap
儘管 Spring boot後面默認把這個關掉,須要配置management.security.enabled=false
才能夠訪問,可是這個仍是太危險了。api
因此一般都建議把endpoints開在另一個獨立的端口上,好比 management.port=8081
。spring-mvc
能夠增長-Dspring.cloud.bootstrap.enabled=false
,來禁止spring cloud,而後啓動Demo。好比tomcat
mvn spring-boot:run -Dspring.cloud.bootstrap.enabled=false
複製代碼
而後打開 http://localhost:8080/ 能夠看到應用的spring context繼承結構。
打開 http://localhost:8081/contexttree 能夠看到Management Spring Contex的繼承結構。
org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration
裏在有spring cloud時(一般是引入 spring-cloud-starter
),由於spring cloud有本身的一套配置初始化機制,因此它其實是本身啓動了一個Spring context,並把本身置爲應用的context的parent。
spring cloud context的啓動代碼在org.springframework.cloud.bootstrap.BootstrapApplicationListener
裏。
spring cloud context其實是一個特殊的spring boot context,它只掃描BootstrapConfiguration
。
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
List<String> names = 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)
.properties("spring.application.name:" + configName)
.registerShutdownHook(false).logStartupInfo(false).web(false);
List<Class<?>> sources = new ArrayList<>();
複製代碼
最終會把這個ParentContextApplicationContextInitializer
加到應用的spring context裏,來把本身設置爲應用的context的parent。
public class ParentContextApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
private int order = Ordered.HIGHEST_PRECEDENCE;
private final ApplicationContext parent;
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
if (applicationContext != this.parent) {
applicationContext.setParent(this.parent);
applicationContext.addApplicationListener(EventPublisher.INSTANCE);
}
}
複製代碼
和上面同樣,直接啓動demo,不要配置-Dspring.cloud.bootstrap.enabled=false
,而後訪問對應的url,就能夠看到spring context的繼承狀況。
若是應用代碼想獲取到Management Spring Context,能夠經過這個bean:org.springframework.boot.actuate.autoconfigure.ManagementContextResolver
spring boot在建立Management Spring Context時,就會保存到ManagementContextResolver裏。
@Configuration
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
@ConditionalOnWebApplication
@AutoConfigureAfter({ PropertyPlaceholderAutoConfiguration.class,
EmbeddedServletContainerAutoConfiguration.class, WebMvcAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class,
RepositoryRestMvcAutoConfiguration.class, HypermediaAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class })
public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware, BeanFactoryAware, SmartInitializingSingleton {
@Bean
public ManagementContextResolver managementContextResolver() {
return new ManagementContextResolver(this.applicationContext);
}
@Bean
public ManagementServletContext managementServletContext( final ManagementServerProperties properties) {
return new ManagementServletContext() {
@Override
public String getContextPath() {
return properties.getContextPath();
}
};
}
複製代碼
spring boot自己沒有提供方法,應用能夠本身寫一個@Configuration
,保存應用的Spring context,而後在endpoints代碼裏再取出來。
ApplicationContext.setParent(ApplicationContext)
到底發生了什麼從spring的代碼就能夠看出來,主要是把parent的environment裏的propertySources加到child裏。這也就是spring cloud config能夠生效的緣由。
// org.springframework.context.support.AbstractApplicationContext.setParent(ApplicationContext)
/** * Set the parent of this application context. * <p>The parent {@linkplain ApplicationContext#getEnvironment() environment} is * {@linkplain ConfigurableEnvironment#merge(ConfigurableEnvironment) merged} with * this (child) application context environment if the parent is non-{@code null} and * its environment is an instance of {@link ConfigurableEnvironment}. * @see ConfigurableEnvironment#merge(ConfigurableEnvironment) */
@Override
public void setParent(ApplicationContext parent) {
this.parent = parent;
if (parent != null) {
Environment parentEnvironment = parent.getEnvironment();
if (parentEnvironment instanceof ConfigurableEnvironment) {
getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
}
}
}
複製代碼
// org.springframework.core.env.AbstractEnvironment.merge(ConfigurableEnvironment)
@Override
public void merge(ConfigurableEnvironment parent) {
for (PropertySource<?> ps : parent.getPropertySources()) {
if (!this.propertySources.contains(ps.getName())) {
this.propertySources.addLast(ps);
}
}
String[] parentActiveProfiles = parent.getActiveProfiles();
if (!ObjectUtils.isEmpty(parentActiveProfiles)) {
synchronized (this.activeProfiles) {
for (String profile : parentActiveProfiles) {
this.activeProfiles.add(profile);
}
}
}
String[] parentDefaultProfiles = parent.getDefaultProfiles();
if (!ObjectUtils.isEmpty(parentDefaultProfiles)) {
synchronized (this.defaultProfiles) {
this.defaultProfiles.remove(RESERVED_DEFAULT_PROFILE_NAME);
for (String profile : parentDefaultProfiles) {
this.defaultProfiles.add(profile);
}
}
}
}
複製代碼
默認狀況下,Spring Child Context會收到Parent Context的Event。若是Bean依賴某個Event來作初始化,那麼就要判斷好Event是否Bean所在的Context發出的,不然有可能提早或者屢次初始化。
正確的作法是實現ApplicationContextAware
接口,先把context
保存起來,在Event
裏判斷相等時才處理。
public class MyBean implements ApplicationListener<ContextRefreshedEvent>, ApplicationContextAware {
private ApplicationContext context;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (event.getApplicationContext().equals(context)) {
// do something
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
}
複製代碼
management.port
爲獨立端口時,Management Spring Context
也會是獨立的context,它的parent是應用的spring contextApplicationContext.setParent(ApplicationContext)
主要是把parent的environment裏的propertySources加到child裏橫雲斷嶺的專欄