歡迎光臨
每天分享高質量文章

深入Spring Boot:Spring Context 的繼承關係和影響

(點選上方公眾號,可快速關註)


來源: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」,看技術乾貨

贊(0)

分享創造快樂