對於一個簡單的Spring boot應用,它的spring context是隻會有一個。html
AnnotationConfigApplicationContext
AnnotationConfigEmbeddedWebApplicationContext
AnnotationConfigEmbeddedWebApplicationContext
是spring boot裏本身實現的一個context,主要功能是啓動embedded servlet container,好比tomcat/jetty。java
這個和傳統的war包應用不同,傳統的war包應用有兩個spring context。參考:http://hengyunabc.github.io/something-about-spring-mvc-webapplicationcontext/git
可是對於一個複雜點的spring boot應用,它的spring context可能會是多個,下面分析下各類狀況。github
這個Demo展現不一樣狀況下的spring boot context的繼承狀況。web
https://github.com/hengyunabc/spring-boot-inside/tree/master/demo-classloader-contextspring
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裏橫雲斷嶺的專欄