SpringApplication#run 调用两次
在使用nacos作为配置中心的过程中,debug源码的过程中发现,作为启动类的SpringApplication#run
这个方法会调用两次,就很奇怪。
调用两次原理 通过跟踪调试发现,是在BootstrapApplicationListener
中再次调用了SpringApplication
,相关的代码如下:
1 2 3 4 5 6 7 8 9 10 11 SpringApplicationBuilder builder = new SpringApplicationBuilder() .profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF) .environment(bootstrapEnvironment) .registerShutdownHook(false ).logStartupInfo(false ) .web(WebApplicationType.NONE); final ConfigurableApplicationContext context = builder.run();
ps:上面代码方法调用栈:onApplicationEvent->bootstrapServiceContext
构建SpringApplicationBuilder的代码如下:
1 2 3 4 5 6 7 public SpringApplicationBuilder (Class<?>... sources) { this .application = createSpringApplication(sources); } protected SpringApplication createSpringApplication (Class<?>... sources) { return new SpringApplication(sources); }
为啥只有spring-cloud会执行两次,而一个普通的springboot没有,根本原因是spring-boot是没有BootstrapApplicationListener
这个监听器。这个监听器的配置是在spring-cloud-context
这个jar包下的spring.factories
,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 # Application Listeners org.springframework.context.ApplicationListener=\ org.springframework.cloud.bootstrap.BootstrapApplicationListener,\ org.springframework.cloud.bootstrap.LoggingSystemShutdownListener,\ org.springframework.cloud.context.restart.RestartListener # 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
这个监听器的作用是什么呢?我们可以看下这个监听器的注释:
1 2 3 4 5 6 A listener that prepares a SpringApplication (e.g. populating its Environment) by delegating to {@link ApplicationContextInitializer} beans in a separate bootstrapcontext. The bootstrap context is a SpringApplication created from sources defined in spring.factories as {@link BootstrapConfiguration}, and initialized with external config taken from "bootstrap.properties" (or yml), instead of the normal "application.properties" .
我理解的大概意思就是,创建一个Context上下文,用来解析定义在spring.factories中定义的资源信息,从bootstrap的配置文件中初始化额外的配置信息,代替正常的配置信息(application.properties)
这个监听器的出现,使我们可以在服务启动之前加载自定义的配置信息,这个配置信息可以是远程的,也可以是本地的。nacos的spring-cloud版本能够实现从远程加载配置信息,就是利用了这个监听器
使用BootstrapApplicationListener加载配置原理 通过上文,我们知道了监听器调用了bootstrapServiceContext
方法,加载配置的关键是在这个方法中实现的,下面我们继续解析这个方法,上代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 private ConfigurableApplicationContext bootstrapServiceContext ( ConfigurableEnvironment environment, final SpringApplication application, String configName) { StandardEnvironment bootstrapEnvironment = new StandardEnvironment(); MutablePropertySources bootstrapProperties = bootstrapEnvironment .getPropertySources(); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); List<String> names = new ArrayList<>(SpringFactoriesLoader .loadFactoryNames(BootstrapConfiguration.class, classLoader)); for (String name : StringUtils.commaDelimitedListToStringArray( environment.getProperty("spring.cloud.bootstrap.sources" , "" ))) { names.add(name); } SpringApplicationBuilder builder = new SpringApplicationBuilder() .profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF) .environment(bootstrapEnvironment) .registerShutdownHook(false ).logStartupInfo(false ) .web(WebApplicationType.NONE); List<Class<?>> sources = new ArrayList<>(); for (String name : names) { Class<?> cls = ClassUtils.resolveClassName(name, null ); try { cls.getDeclaredAnnotations(); } catch (Exception e) { continue ; } sources.add(cls); } builder.sources(sources.toArray(new Class[sources.size()])); final ConfigurableApplicationContext context = builder.run(); return context; }
我们继续回到onApplicationEvent
中,生成context之后,调用apply
方法将context中的ApplicationContextInitializer
类的实现添加到SpringAppliation
实例中(注意:这里的实例是第一次调用run方法生成的)
1 2 3 4 5 if (context == null ) { context = bootstrapServiceContext(environment, event.getSpringApplication(), configName); } apply(context, event.getSpringApplication(), environment);
继续看apply方法
1 2 3 4 5 6 7 8 9 10 11 12 private void apply (ConfigurableApplicationContext context, SpringApplication application, ConfigurableEnvironment environment) { @SuppressWarnings ("rawtypes" ) List<ApplicationContextInitializer> initializers = getOrderedBeansOfType(context, ApplicationContextInitializer.class); application.addInitializers(initializers .toArray(new ApplicationContextInitializer[initializers.size()])); addBootstrapDecryptInitializer(application); }
执行完毕之后,会继续执行第一次SpringApplication的run方法,
1 2 3 4 5 6 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); prepareContext(context, environment, listeners, applicationArguments, printedBanner);
可以看到,通过监听器,我们添加了额外的ApplicationContextInitializer实例(PropertySourceBootstrapConfiguration)到SpringApplication中,PropertySourceBootstrapConfiguration类用来加载额外的配置信息,实现自定义配置信息
PropertySourceBootstrapConfiguration解析 如代码所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @Configuration @EnableConfigurationProperties (PropertySourceBootstrapProperties.class)public class PropertySourceBootstrapConfiguration implements ApplicationContextInitializer <ConfigurableApplicationContext >, Ordered { @Autowired (required = false ) private List<PropertySourceLocator> propertySourceLocators = new ArrayList<>(); @Override public void initialize (ConfigurableApplicationContext applicationContext) { for (PropertySourceLocator locator : this .propertySourceLocators) { PropertySource<?> source = null ; source = locator.locate(environment); if (source == null ) { continue ; } logger.info("Located property source: " + source); composite.addPropertySource(source); empty = false ; } if (!empty) { MutablePropertySources propertySources = environment.getPropertySources(); insertPropertySources(propertySources, composite); } } }
这个类有一个属性是PropertySourceLocator
的集合,注意该属性上有Autowired
注解,所以我们只要实例化PropertySourceLocator
就会添加到这里,完成自定义配置的加载。
还记得上文中讲到,bootstrapServiceContext
会将BootstrapConfiguration
类型的配置实例化到context中,所以我们只要添加对应的配置信息就可以了。nacos的配置如下:
1 2 3 4 5 6 7 8 9 10 org.springframework.cloud.bootstrap.BootstrapConfiguration=\ com.alibaba.cloud.nacos.NacosConfigBootstrapConfiguration org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.alibaba.cloud.nacos.NacosConfigAutoConfiguration,\ com.alibaba.cloud.nacos.endpoint.NacosConfigEndpointAutoConfiguration org.springframework.boot.diagnostics.FailureAnalyzer=\ com.alibaba.cloud.nacos.diagnostics.analyzer.NacosConnectionFailureAnalyzer org.springframework.boot.env.PropertySourceLoader=\ com.alibaba.cloud.nacos.parser.NacosJsonPropertySourceLoader,\ com.alibaba.cloud.nacos.parser.NacosXmlPropertySourceLoader
ps: 该配置来自spring-cloud-starter-alibaba-nacos-config 2.0.4-release版本