(點選上方公眾號,可快速關註)
來源:hengyunabc ,
blog.csdn.net/hengyunabc/article/details/77458765
前言
對於一個簡單的Spring boot應用,它的spring context是隻會有一個。
-
非web spring boot應用,context是AnnotationConfigApplicationContext
-
web spring boot應用,context是AnnotationConfigEmbeddedWebApplicationContext
AnnotationConfigEmbeddedWebApplicationContext是spring boot裡自己實現的一個context,主要功能是啟動embedded servlet container,比如tomcat/jetty。
這個和傳統的war包應用不一樣,傳統的war包應用有兩個spring context。參考:http://hengyunabc.github.io/something-about-spring-mvc-webapplicationcontext/
但是對於一個複雜點的spring boot應用,它的spring context可能會是多個,下麵分析下各種情況。
Demo
這個Demo展示不同情況下的spring boot context的繼承情況。
https://github.com/hengyunabc/spring-boot-inside/tree/master/demo-classloader-context
配置spring boot actuator/endpoints獨立埠時
spring boot actuator預設情況下和應用共用一個tomcat,這樣子的話就會直接把應用的endpoints暴露出去,帶來很大的安全隱患。
儘管 Spring boot後面預設把這個關掉,需要配置management.security.enabled=false才可以訪問,但是這個還是太危險了。
所以通常都建議把endpoints開在另外一個獨立的埠上,比如 management.port=8081。
可以增加-Dspring.cloud.bootstrap.enabled=false,來禁止spring cloud,然後啟動Demo。比如
mvn spring-boot:run -Dspring.cloud.bootstrap.enabled=false
然後開啟 http://localhost:8080/ 可以看到應用的spring context繼承結構。
開啟 http://localhost:8081/contexttree 可以看到Management Spring Contex的繼承結構。
-
可以看到當配置management獨立埠時,management context的parent是應用的spring context
-
相關的實現程式碼在 org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration 裡
在sprig cloud環境下spring context的情況
在有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
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
> sources = new ArrayList<>();
最終會把這個ParentContextApplicationContextInitializer加到應用的spring context裡,來把自己設定為應用的context的parent。
public class ParentContextApplicationContextInitializer implements
ApplicationContextInitializer
, 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
如果應用程式碼想獲取到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();
}
};
}
如何在Endpoints程式碼裡獲取應用的Spring context
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.
*
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);
}
}
}
}
總結
-
當配置management.port 為獨立埠時,Management Spring Context也會是獨立的context,它的parent是應用的spring context
-
當啟動spring cloud時,spring cloud自己會創建出一個spring context,並置為應用的context的parent
-
ApplicationContext.setParent(ApplicationContext) 主要是把parent的environment裡的propertySources加到child裡
-
理解的spring boot context的繼承關係,能避免一些微妙的spring bean註入的問題,還有不當的spring context的問題
看完本文有收穫?請轉發分享給更多人
關註「ImportNew」,看技術乾貨