1. SpringBoot中怎么启动Tomcat?
1.1 ServletWebServerFactoryAutoConfiguration
配置Servlet web容器。
@Configuration(proxyBeanMethods = false) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) @ConditionalOnClass(ServletRequest.class) @ConditionalOnWebApplication(type = Type.SERVLET) @EnableConfigurationProperties(ServerProperties.class) @Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, ServletWebServerFactoryConfiguration.EmbeddedTomcat.class, ServletWebServerFactoryConfiguration.EmbeddedJetty.class, ServletWebServerFactoryConfiguration.EmbeddedUndertow.class }) public class ServletWebServerFactoryAutoConfiguration { } 复制代码
-
ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class 导入
WebServerFactoryCustomizerBeanPostProcessor
组件,web服务工厂定制器的后置增强 -
导入了三种不同的web服务器(Tomcat、Jetty、Undertow),默认是Tomcat
我们来看一下 EmbeddedTomcat
:
@Configuration(proxyBeanMethods = false) class ServletWebServerFactoryConfiguration { @Configuration(proxyBeanMethods = false) @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class }) @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT) static class EmbeddedTomcat { @Bean TomcatServletWebServerFactory tomcatServletWebServerFactory( ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers, ObjectProvider<TomcatContextCustomizer> contextCustomizers, ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) { TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(); factory.getTomcatConnectorCustomizers() .addAll(connectorCustomizers.orderedStream().collect(Collectors.toList())); factory.getTomcatContextCustomizers() .addAll(contextCustomizers.orderedStream().collect(Collectors.toList())); factory.getTomcatProtocolHandlerCustomizers() .addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList())); return factory; } } } 复制代码
tomcatServletWebServerFactory()
方法中的参数都是从容器中获取,我们可以自定义直接放到容器中。实现 ServletWebServerFactory
接口,该接口的getWebServlet()方法就是创建 Tomcat容器。
1.2 DispatcherServletAutoConfiguration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) @Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass(DispatcherServlet.class) @AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class) public class DispatcherServletAutoConfiguration { public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet"; public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration"; @Configuration(proxyBeanMethods = false) @Conditional(DefaultDispatcherServletCondition.class) @ConditionalOnClass(ServletRegistration.class) @EnableConfigurationProperties(WebMvcProperties.class) protected static class DispatcherServletConfiguration { @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) { DispatcherServlet dispatcherServlet = new DispatcherServlet(); dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest()); dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest()); dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound()); dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents()); dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails()); return dispatcherServlet; } @Bean @ConditionalOnBean(MultipartResolver.class) @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) public MultipartResolver multipartResolver(MultipartResolver resolver) { // Detect if the user has created a MultipartResolver but named it incorrectly return resolver; } } @Configuration(proxyBeanMethods = false) @Conditional(DispatcherServletRegistrationCondition.class) @ConditionalOnClass(ServletRegistration.class) @EnableConfigurationProperties(WebMvcProperties.class) @Import(DispatcherServletConfiguration.class) protected static class DispatcherServletRegistrationConfiguration { @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME) @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) { DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, webMvcProperties.getServlet().getPath()); registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME); registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup()); multipartConfig.ifAvailable(registration::setMultipartConfig); return registration; } } } 复制代码
dispatcherServlet
方法就是往容器中注入DispatcherServlet
组件。- dispatcherServletRegistration() 方法就是将将
DispatcherServlet
组件添加到Tomcat中(tomcat.addServlet()
)
我们来看一下 DispatcherServletRegistrationBean
继承图:
在Tomcat启动的时候 会调用 ServletContextInitializer.onStartup() 方法回调,将DispatcherServlet 添加到 Tomcat中,后面的启动流程就跟SpringMVC的启动流程是一样的,参见 Spring5源码14-SpringMVC入口及启动流程 。
2. 核心方法run()
下面是一个简单的项目,我们直接开始看run方法
@SpringBootApplication public class SpringbootDemoApplication { public static void main(String[] args) { ConfigurableApplicationContext run = SpringApplication.run(SpringbootDemoApplication.class, args); Object demoService = run.getBean("demoService"); System.out.println("demoService = " + demoService); } } 复制代码
我们这里直接看 SpringApplication#run(java.lang.Class<?>[], java.lang.String[])
方法:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); } 复制代码
我们这里先看一下 SpringApplication
的构造函数流程:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); // 保存启动类信息 this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); // 初始化环境。环境分为三种 非web环境、web环境、reactive环境三种。其判断逻辑就是判断是否存在指定的类,默认是Servlet 环境,我们这也是Servlet this.webApplicationType = WebApplicationType.deduceFromClasspath(); // getSpringFactoriesInstances 方法加载了 spring.factories文件。在这里进行了首次加载spring.factoies文件。设置 ApplicationContextInitializer setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); // 获取监听器,也加载了spring.factories文件 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); // 设置启动类信息 this.mainApplicationClass = deduceMainApplicationClass(); } 复制代码
我们下面直接来看 SpringApplication#run(java.lang.String...)
方法的执行流程:
public ConfigurableApplicationContext run(String... args) { // 开启关于启动时间的信息监控 StopWatch stopWatch = new StopWatch(); stopWatch.start(); // 准备 ApplicationContext ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); //java.awt.headless是J2SE的一种模式用于在缺少显示屏、键盘或者鼠标时的系统配置,很多监控工具如jconsole 需要将该值设置为true,系统变量默认为true configureHeadlessProperty(); // 1. 获取Spring的监听器类,这里是从 spring.factories 中去获取,默认的是以 org.springframework.boot.SpringApplicationRunListener 为key,获取到的监听器类型为 EventPublishingRunListener。 SpringApplicationRunListeners listeners = getRunListeners(args); // 1.1 监听器发送启动事件 listeners.starting(); try { // 封装参数 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // 2. 构造容器环境。将容器的一些配置内容加载到 environment 中 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); // 配置BeanInfo的忽略 :“spring.beaninfo.ignore”,值为“true”表示跳过对BeanInfo类的搜索 configureIgnoreBeanInfo(environment); // 打印信息对象 Banner printedBanner = printBanner(environment); // 3. 创建上下文对象 context = createApplicationContext(); // 从 spring.factries 中获取错误报告的类。出错的时候会调用其方法通知 exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); // 4. 准备刷新上下文 prepareContext(context, environment, listeners, applicationArguments, printedBanner); // 5. 刷新上下文 refreshContext(context); // 结束刷新,留待扩展功能,并未实现什么 afterRefresh(context, applicationArguments); // 停止监听 stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } // 监听器发送启动结束时间 listeners.started(context); // 调用 ApplicationRunner 和 CommandLineRunner 对应的方法 callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { // 发送容器运行事件 listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context; } 复制代码
3. run()流程步骤详解
下面我们重点分析几个步骤
3.1 获取监听器
这一步是从 spring.factories
文件中获取监听器集合,当有事件发生时调用监听器对应事件的方法。
默认的是以 org.springframework.boot.SpringApplicationRunListener
为key,获取到的监听器类型为 EventPublishingRunListener
。
SpringApplicationRunListeners listeners = getRunListeners(args); 复制代码
其详细代码如下:
private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)); } 复制代码
这里需要注意的是 getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)
返回的是一个Collection 类型。也就是说明在 SpringApplicationRunListeners
并非代表一个监听器,而是保存了监听器集合,在默认情况下,仅有一个 EventPublishingRunListener
。在 SpringApplicationRunListeners
类中也能看到,如下:
class SpringApplicationRunListeners { private final Log log; private final List<SpringApplicationRunListener> listeners; SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) { this.log = log; this.listeners = new ArrayList<>(listeners); } ... } 复制代码
总结一下: Spring启动时,通过 spring.factories
文件中获取监听器集合。默认类型为 EventPublishingRunListener
。在事件发生时, EventPublishingRunListener
会寻找容器中 ApplicationListener
的bean,并进行事件通知。详见 Spring5源码12-监听器原理
3.2 环境变量的构造
这一步的作用就是加载一些配置文件的内容
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); 复制代码
其具体实现如下:
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { // Create and configure the environment // 获取或者创建 environment。这里获取类型是 StandardServletEnvironment ConfigurableEnvironment environment = getOrCreateEnvironment(); // 将入参配置到环境配置中 configureEnvironment(environment, applicationArguments.getSourceArgs()); ConfigurationPropertySources.attach(environment); // 发布环境准备事件。 listeners.environmentPrepared(environment); bindToSpringApplication(environment); if (!this.isCustomEnvironment) { environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } ConfigurationPropertySources.attach(environment); return environment; } private Class<? extends StandardEnvironment> deduceEnvironmentClass() { switch (this.webApplicationType) { case SERVLET: return StandardServletEnvironment.class; case REACTIVE: return StandardReactiveWebEnvironment.class; default: return StandardEnvironment.class; } } 复制代码
关于 webApplicationType
的值,在 org.springframework.boot.SpringApplication#SpringApplication(org.springframework.core.io.ResourceLoader, java.lang.Class<?>...)
构造函数中进行了赋值为 Servlet
。所以我们这里可以知道 Environment
类型为 StandardServletEnvironment
。
在 listeners.environmentPrepared(environment);
时会发送环境准备事件,环境准备事件要通知监听器如下。对于 Springboot 的配置文件 application.yml或者application.properties
文件的加载实际上是通过发布环境准备事件完成的,完成这项功能的就是 ConfigDataEnvironmentPostProcessor
。
3.2.1 application.yml 的加载
SpringBoot在启动时,读取所有的ApplicationListener下的配置类。spring-boot工程下的 spring.factories
文件:
# Application Listeners org.springframework.context.ApplicationListener=\ org.springframework.boot.ClearCachesApplicationListener,\ org.springframework.boot.builder.ParentContextCloserApplicationListener,\ org.springframework.boot.context.FileEncodingApplicationListener,\ org.springframework.boot.context.config.AnsiOutputApplicationListener,\ org.springframework.boot.context.config.DelegatingApplicationListener,\ org.springframework.boot.context.logging.LoggingApplicationListener,\ org.springframework.boot.env.EnvironmentPostProcessorApplicationListener 复制代码
当 EnvironmentPostProcessorApplicationListener
类接收到 ApplicationEnvironmentPreparedEvent
的事件时,就会调用 EnvironmentPostProcessorsFactory
类下的 getEnvironmentPostProcessors
方法来获取所有的 EnvironmentPostProcessor
下的配置类,并触发这些类的 postProcessEnvironment
方法。
public class EnvironmentPostProcessorApplicationListener implements SmartApplicationListener, Ordered { ... public EnvironmentPostProcessorApplicationListener() { this(EnvironmentPostProcessorsFactory::fromSpringFactories, new DeferredLogs()); } @Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationEnvironmentPreparedEvent) { onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event); } ... } private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) { ... for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(application.getResourceLoader(), event.getBootstrapContext())) { postProcessor.postProcessEnvironment(environment, application); } } List<EnvironmentPostProcessor> getEnvironmentPostProcessors(ResourceLoader resourceLoader, ConfigurableBootstrapContext bootstrapContext) { ... EnvironmentPostProcessorsFactory postProcessorsFactory = this.postProcessorsFactory.apply(classLoader); return postProcessorsFactory.getEnvironmentPostProcessors(this.deferredLogs, bootstrapContext); } } @FunctionalInterface public interface EnvironmentPostProcessorsFactory { List<EnvironmentPostProcessor> getEnvironmentPostProcessors(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext); static EnvironmentPostProcessorsFactory fromSpringFactories(ClassLoader classLoader) { return new ReflectionEnvironmentPostProcessorsFactory(classLoader, SpringFactoriesLoader.loadFactoryNames(EnvironmentPostProcessor.class, classLoader)); } 复制代码
spring.factories
文件中 :
# Environment Post Processors org.springframework.boot.env.EnvironmentPostProcessor=\ org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\ org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor,\ org.springframework.boot.env.RandomValuePropertySourceEnvironmentPostProcessor,\ org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\ org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor,\ org.springframework.boot.reactor.DebugAgentEnvironmentPostProcessor 复制代码
EnvironmentPostProcessor
EnvironmentPostProcessor
是处理程序的配置的,不同的子类实现不同的配置,有处理json配置文件的、有处理yaml配置文件的、有替换配置文件中的环境变量的。
- SpringBoot 2.4.0之前的版本,处理yaml配置文件的类是ConfigFileApplicationListener类
- SpringBoot 2.4.0之后的版本,处理yaml配置文件的类是ConfigDataEnvironmentPostProcessor类,我们重点看这个。
ConfigDataEnvironmentPostProcessor
SpringBoot 2.4.0之后的版本,使用ConfigDataEnvironmentPostProcessor 类来加载yaml文件中的配置。
public class ConfigDataEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered { @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { postProcessEnvironment(environment, application.getResourceLoader(), application.getAdditionalProfiles()); } void postProcessEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection<String> additionalProfiles) { ... getConfigDataEnvironment(environment, resourceLoader, additionalProfiles).processAndApply(); ... } ConfigDataEnvironment getConfigDataEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection<String> additionalProfiles) { return new ConfigDataEnvironment(this.logFactory, this.bootstrapContext, environment, resourceLoader, additionalProfiles, this.environmentUpdateListener); } } 复制代码
ConfigDataEnvironment
ConfigDataEnvironmentPostProcessor
在 postProcessEnvironment
方法中会实例化一个 ConfigDataEnvironment
对象,这个对象会加载 optional:classpath:/;optional:classpath:/config/;optional:file:./;optional:file:./config/;optional:file:./config/*/
路径下的yaml文件。
class ConfigDataEnvironment { static final String LOCATION_PROPERTY = "spring.config.location"; static final String ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location"; static final String IMPORT_PROPERTY = "spring.config.import"; static final ConfigDataLocation[] DEFAULT_SEARCH_LOCATIONS; static { List<ConfigDataLocation> locations = new ArrayList<>(); locations.add(ConfigDataLocation.of("optional:classpath:/;optional:classpath:/config/")); locations.add(ConfigDataLocation.of("optional:file:./;optional:file:./config/;optional:file:./config/*/")); DEFAULT_SEARCH_LOCATIONS = locations.toArray(new ConfigDataLocation[0]); } private ConfigDataEnvironmentContributors createContributors(Binder binder) { ... contributors.addAll(getInitialImportContributors(binder)); ... return createContributors(contributors); } protected ConfigDataEnvironmentContributors createContributors( List<ConfigDataEnvironmentContributor> contributors) { return new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapContext, contributors); } private List<ConfigDataEnvironmentContributor> getInitialImportContributors(Binder binder) { List<ConfigDataEnvironmentContributor> initialContributors = new ArrayList<>(); addInitialImportContributors(initialContributors, bindLocations(binder, IMPORT_PROPERTY, EMPTY_LOCATIONS)); addInitialImportContributors(initialContributors, bindLocations(binder, ADDITIONAL_LOCATION_PROPERTY, EMPTY_LOCATIONS)); addInitialImportContributors(initialContributors, bindLocations(binder, LOCATION_PROPERTY, DEFAULT_SEARCH_LOCATIONS)); return initialContributors; } } 复制代码
spring.factories
文件中 :
# ConfigData Location Resolvers org.springframework.boot.context.config.ConfigDataLocationResolver=\ org.springframework.boot.context.config.ConfigTreeConfigDataLocationResolver,\ org.springframework.boot.context.config.StandardConfigDataLocationResolver # ConfigData Loaders org.springframework.boot.context.config.ConfigDataLoader=\ org.springframework.boot.context.config.ConfigTreeConfigDataLoader,\ org.springframework.boot.context.config.StandardConfigDataLoader # PropertySource Loaders org.springframework.boot.env.PropertySourceLoader=\ org.springframework.boot.env.PropertiesPropertySourceLoader,\ org.springframework.boot.env.YamlPropertySourceLoader 复制代码
processAndApply()
请求路径为: processAndApply() -> ConfigDataEnvironment#processAndApply -> ConfigDataEnvironment#processWithoutProfiles -> ConfigDataEnvironmentContributors#withProcessedImports -> ConfigDataImporter#resolveAndLoad -> ConfigDataImporter#load -> ConfigDataLoaders#load -> StandardConfigDataLoader#load -> YamlPropertySourceLoader#load
我们直接看 YamlPropertySourceLoader#load
方法:
public class YamlPropertySourceLoader implements PropertySourceLoader { @Override public String[] getFileExtensions() { return new String[] { "yml", "yaml" }; } @Override public List<PropertySource<?>> load(String name, Resource resource) throws IOException { if (!ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", getClass().getClassLoader())) { throw new IllegalStateException( "Attempted to load " + name + " but snakeyaml was not found on the classpath"); } List<Map<String, Object>> loaded = new OriginTrackedYamlLoader(resource).load(); if (loaded.isEmpty()) { return Collections.emptyList(); } List<PropertySource<?>> propertySources = new ArrayList<>(loaded.size()); for (int i = 0; i < loaded.size(); i++) { String documentNumber = (loaded.size() != 1) ? " (document #" + i + ")" : ""; propertySources.add(new OriginTrackedMapPropertySource(name + documentNumber, Collections.unmodifiableMap(loaded.get(i)), true)); } return propertySources; } } 复制代码
在这里加载 application.yml文件。
EnvironmentPostProcessor
如果想处理加载的配置文件,可以在自己的java项目里添加 spring.factories
,然后实现 EnvironmentPostProcessor
类。
org.springframework.boot.env.EnvironmentPostProcessor=\ com.camellibby.springboot.component.DemoEnvironmentPostProcessor 复制代码
public class DemoEnvironmentPostProcessor implements EnvironmentPostProcessor { @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { System.out.println("DemoEnvironmentPostProcessor is starting"); } } 复制代码
3.3 创建上下文
这一步是创建上下文了:
context = createApplicationContext(); 复制代码
其详细内容如下:
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context." + "annotation.AnnotationConfigApplicationContext"; public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot." + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext"; public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework." + "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext"; protected ConfigurableApplicationContext createApplicationContext() { return this.applicationContextFactory.create(this.webApplicationType); } // org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext.Factory#create static class Factory implements ApplicationContextFactory { @Override public ConfigurableApplicationContext create(WebApplicationType webApplicationType) { return (webApplicationType != WebApplicationType.SERVLET) ? null : new AnnotationConfigServletWebServerApplicationContext(); } } public AnnotationConfigServletWebServerApplicationContext() { this.reader = new AnnotatedBeanDefinitionReader(this); this.scanner = new ClassPathBeanDefinitionScanner(this); } 复制代码
很明显,因为我们知道 webApplicationType
值是 servlet
,所以这里创建的是 AnnotationConfigServletWebServerApplicationContext
类型的上下文
这里需要注意: AnnotationConfigServletWebServerApplicationContext
构造函数中会创建 AnnotatedBeanDefinitionReader
。而在 AnnotatedBeanDefinitionReader
构造函数中会调用 AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
,该方法将一些必要Bean(如 ConfigurationClassPostProcessor、AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor
等)注入到了容器中。
3.4 上下文准备工作
上面一步,仅仅是将上下文创建出来了,并没有对上下文进行操作。这一步开始对上下文的准备操作。
prepareContext(context, environment, listeners, applicationArguments, printedBanner); 复制代码
其详细内容如下:
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { // 设置上下文的环境变量 context.setEnvironment(environment); // 执行容器后置处理 : 可以注册beanName策略生成器、设置资源加载器,设置转换服务等。但这里默认是没有做任何处理。目的是留给后续可以扩展 postProcessApplicationContext(context); // 处理所有的初始化类的初始化方法。即 spring.factories 中key 为 org.springframework.context.ApplicationContextInitializer 指向的类,调用其 initialize 方法 applyInitializers(context); // 向监听器发送容器准备事件 listeners.contextPrepared(context); if (this.logStartupInfo) { logStartupInfo(context.getParent() == null); logStartupProfileInfo(context); } // Add boot specific singleton beans // 获取上下文中的 BeanFactory。这里的BeanFactory 实际类型是 DefaultListableBeanFactory。BeanFactory 在初始化的时候,直接在构造函数里创建为 DefaultListableBeanFactory ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); // 注册 springApplicationArguments等一系列bean beanFactory.registerSingleton("springApplicationArguments", applicationArguments); if (printedBanner != null) { beanFactory.registerSingleton("springBootBanner", printedBanner); } if (beanFactory instanceof DefaultListableBeanFactory) { // 设置是否允许bean定义覆盖 ((DefaultListableBeanFactory) beanFactory) .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); } // 如果允许懒加载,则添加对应的BeanFactory后置处理器 if (this.lazyInitialization) { context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor()); } // Load the sources // 这里加载的实际上是启动类 Set<Object> sources = getAllSources(); Assert.notEmpty(sources, "Sources must not be empty"); // 这里将启动类加入到 beanDefinitionMap 中,为后续的自动化配置做好了基础 load(context, sources.toArray(new Object[0])); // 发送容器加载完成事件 listeners.contextLoaded(context); } .... // 需要注意这里的 sources参数实际上是 启动类的 Class protected void load(ApplicationContext context, Object[] sources) { if (logger.isDebugEnabled()) { logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources)); } // 从上下文中获取 BeanDefinitionRegistry并依次创建出 BeanDefinitionLoader 。这里将sources作为参数保存到了 loader 中。也就是 loader 中保存了 启动类的Class信息 BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources); if (this.beanNameGenerator != null) { loader.setBeanNameGenerator(this.beanNameGenerator); } if (this.resourceLoader != null) { loader.setResourceLoader(this.resourceLoader); } if (this.environment != null) { loader.setEnvironment(this.environment); } loader.load(); } 复制代码
我们这里比较关键的方法是 loader.load();
方法 其中 loader.load();
不管怎么跳转,最后都会跳转到 BeanDefinitionLoader#load(java.lang.Class<?>)
方法中。如下:
private int load(Class<?> source) { // 判断是否存在 groovy 加载方式 if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) { // Any GroovyLoaders added in beans{} DSL can contribute beans here GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class); load(loader); } // 判断 source 是否 需要被加载到Spring容器中。实际上是根据判断是否存在 @Component if (isComponent(source)) { // 将source 就是启动类的 class,注册到 annotatedReader 中。annotatedReader 类型是AnnotatedBeanDefinitionReader。 this.annotatedReader.register(source); return 1; } return 0; } 复制代码
this.annotatedReader.register(source);
后续会跳转到 AnnotatedBeanDefinitionReader#doRegisterBean
方法中,看名字就知道是这个方法的工作是 注册 Bean
。实际上,在这个方法中完成了对 @Qualifier
以及一些其他注解的处理。具体如下:
// 这里的 beanClass 其实就是启动类的 beanClass private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name, @Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier, @Nullable BeanDefinitionCustomizer[] customizers) { // 将Class 转换成一个 BeanDefinition 类 AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass); // 判断是否应该跳过 if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) { return; } abd.setInstanceSupplier(supplier); // 保存其作用域信息。这里默认是 singleton ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd); abd.setScope(scopeMetadata.getScopeName()); // 获取 beanName String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry)); // 处理一些通用的注解信息,包括Lazy、Primary、DependsOn、Role、Description 注解。获取其value值并保存到 abd 中 AnnotationConfigUtils.processCommonDefinitionAnnotations(abd); // 处理 @Qualifier if (qualifiers != null) { for (Class<? extends Annotation> qualifier : qualifiers) { if (Primary.class == qualifier) { abd.setPrimary(true); } else if (Lazy.class == qualifier) { abd.setLazyInit(true); } else { abd.addQualifier(new AutowireCandidateQualifier(qualifier)); } } } if (customizers != null) { for (BeanDefinitionCustomizer customizer : customizers) { customizer.customize(abd); } } BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName); // 判断是否需要创建代理,需要则创建 definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); // 将 BeanDefinitionHolder 注册到 容器中。此时的 registry 就是 AnnotationConfigServletWebServerApplicationContext。在BeanDefinitionLoader 初始化的时候保存的 BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry); } 复制代码
3.5 SpringApplication#refreshContext
对容器进行一个刷新工作。在此进行了大量的工作。这里的处理工作就由Springboot交给 Spring来处理了
refreshContext(context); 复制代码
详细如下:
private void refreshContext(ConfigurableApplicationContext context) { refresh(context); if (this.registerShutdownHook) { try { context.registerShutdownHook(); } catch (AccessControlException ex) { // Not allowed in some environments. } } } 复制代码
最终会跳转到 AbstractApplicationContext#refresh
中。而关于 AbstractApplicationContext#refresh
方法在之前的文章中有过介绍,参见 Spring5源码11-容器刷新refresh方法(注解版) 。我们重点看一下Tomcat的启动过程,着重看 onRefresh()
方法:
@Override public void refresh() throws BeansException, IllegalStateException { // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); } 复制代码
SpringBoot使用 ServletWebServerApplicationContext#onRefresh
:
@Override protected void onRefresh() { super.onRefresh(); try { createWebServer(); } catch (Throwable ex) { throw new ApplicationContextException("Unable to start web server", ex); } } 复制代码
直接看 createWebServer()
方法:
private void createWebServer() { WebServer webServer = this.webServer; ServletContext servletContext = getServletContext(); if (webServer == null && servletContext == null) { StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create"); // 本文环境获取的是tomcatServletWebServerFactory,默认的 ServletWebServerFactory factory = getWebServerFactory(); createWebServer.tag("factory", factory.getClass().toString()); this.webServer = factory.getWebServer(getSelfInitializer()); createWebServer.end(); getBeanFactory().registerSingleton("webServerGracefulShutdown", new WebServerGracefulShutdownLifecycle(this.webServer)); getBeanFactory().registerSingleton("webServerStartStop", new WebServerStartStopLifecycle(this, this.webServer)); } else if (servletContext != null) { try { getSelfInitializer().onStartup(servletContext); } catch (ServletException ex) { throw new ApplicationContextException("Cannot initialize servlet context", ex); } } initPropertySources(); } 复制代码
3.5.1 getWebServerFactory()
如下所示,从容器中获取 ServletWebServerFactory
类型的bean,唯一一个,否则抛出异常。本文环境获取的是 tomcatServletWebServerFactory
。
protected ServletWebServerFactory getWebServerFactory() { // Use bean names so that we don't consider the hierarchy String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class); if (beanNames.length == 0) { throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing " + "ServletWebServerFactory bean."); } if (beanNames.length > 1) { throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple " + "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames)); } return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class); } 复制代码
3.5.2 getSelfInitializer()
ServletWebServerApplicationContext的getSelfInitializer
方法,返回的是 ServletContextInitializer
。
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() { return this::selfInitialize; } 复制代码
看到 this::selfInitialize
是不是比较迷糊?典型的java8的 lambda
写法。我们看一下 ServletContextInitializer
可能就明白了。
如下所示,其是一个 函数式接口
,只有一个 onStartup
方法。函数式接口(有且仅有一个抽象方法的接口)可以使用lambda式的写法。
@FunctionalInterface public interface ServletContextInitializer { // 初始化过程中,使用给定的servlets、filters、listeners //context-params and attributes necessary配置ServletContext void onStartup(ServletContext servletContext) throws ServletException; } 复制代码
我们这里获取到的本质是一个 lambda
,参数则是 当前this
,如下图所示:
this::selfInitialize
中的 selfInitialize
则指的是 ServletWebServerApplicationContext的selfInitialize
方法。this指的是 AnnotationConfigServletWebServerApplicationContext
,其继承于 ServletWebServerApplicationContext
private void selfInitialize(ServletContext servletContext) throws ServletException { prepareWebApplicationContext(servletContext); registerApplicationScope(servletContext); WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext); for (ServletContextInitializer beans : getServletContextInitializerBeans()) { beans.onStartup(servletContext); } } 复制代码
其实换成匿名类的写法则是:
new ServletContextInitializer() { @Override public void onStartup(ServletContext servletContext) throws ServletException { selfInitialize(servletContext); } }; 复制代码
3.5.3 getWebServer()
本文这里是 TomcatServletWebServerFactory#getWebServer
方法。
@Override public WebServer getWebServer(ServletContextInitializer... initializers) { if (this.disableMBeanRegistry) { // registry = new NoDescriptorRegistry(); Registry.disableRegistry(); } //实例化Tomcat Tomcat tomcat = new Tomcat(); //获取临时路径 File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat"); //设置基础路径 tomcat.setBaseDir(baseDir.getAbsolutePath()); //实例化Connector 并进行配置 Connector connector = new Connector(this.protocol); connector.setThrowOnFailure(true); //这里会实例化server service tomcat.getService().addConnector(connector); customizeConnector(connector); //对Connector做配置比如Protocol、URIEncoding tomcat.setConnector(connector); //这里会实例化Engine、Host tomcat.getHost().setAutoDeploy(false); configureEngine(tomcat.getEngine()); for (Connector additionalConnector : this.additionalTomcatConnectors) { // todo tomcat.getService().addConnector(additionalConnector); } prepareContext(tomcat.getHost(), initializers); return getTomcatWebServer(tomcat); } 复制代码
getService
getService
首先会触发getServer然后获取service。getServer如下所示会实例化Server并对其进行配置。
public Service getService() { return getServer().findServices()[0]; } public Server getServer() { if (server != null) { return server; } System.setProperty("catalina.useNaming", "false"); // 实例化 server server = new StandardServer(); // 对basedir做处理 initBaseDir(); // Set configuration source ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null)); // 为server设置port和service server.setPort( -1 ); //实例化service Service service = new StandardService(); service.setName("Tomcat"); server.addService(service); return server; } 复制代码
prepareContext
这里会实例化 TomcatEmbeddedContext
并对其进行配置。
protected void prepareContext(Host host, ServletContextInitializer[] initializers) { File documentRoot = getValidDocumentRoot(); TomcatEmbeddedContext context = new TomcatEmbeddedContext(); if (documentRoot != null) { context.setResources(new LoaderHidingResourceRoot(context)); } context.setName(getContextPath()); context.setDisplayName(getDisplayName()); context.setPath(getContextPath()); File docBase = (documentRoot != null) ? documentRoot : createTempDir("tomcat-docbase"); context.setDocBase(docBase.getAbsolutePath()); context.addLifecycleListener(new FixContextListener()); context.setParentClassLoader((this.resourceLoader != null) ? this.resourceLoader.getClassLoader() : ClassUtils.getDefaultClassLoader()); resetDefaultLocaleMapping(context); addLocaleMappings(context); context.setUseRelativeRedirects(false); try { context.setCreateUploadTargets(true); } catch (NoSuchMethodError ex) { // Tomcat is < 8.5.39. Continue. } configureTldSkipPatterns(context); WebappLoader loader = new WebappLoader(context.getParentClassLoader()); loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName()); loader.setDelegate(true); context.setLoader(loader); if (isRegisterDefaultServlet()) { addDefaultServlet(context); } if (shouldRegisterJspServlet()) { addJspServlet(context); addJasperInitializer(context); } context.addLifecycleListener(new StaticResourceConfigurer(context)); ServletContextInitializer[] initializersToUse = mergeInitializers(initializers); host.addChild(context); configureContext(context, initializersToUse); postProcessContext(context); } 复制代码
getTomcatWebServer
这个方法很简单,只是直接实例化了 TomcatWebServer
返回。其构造方法触发了 initialize
,这会引起后续一系列动作,包括 tomcat.start()
。
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) { return new TomcatWebServer(tomcat, getPort() >= 0); } public TomcatWebServer(Tomcat tomcat, boolean autoStart) { Assert.notNull(tomcat, "Tomcat Server must not be null"); this.tomcat = tomcat; this.autoStart = autoStart; // todo initialize(); } 复制代码
initialize()
我们回过头来,看一下initialize()方法:
private void initialize() throws WebServerException { logger.info("Tomcat initialized with port(s): " + getPortsDescription(false)); synchronized (this.monitor) { try { ... // Start the server to trigger initialization listeners this.tomcat.start(); ... } } } 复制代码
在这里将嵌入式Tomcat启动。
selfInitialize
获取到 TomcatWebServer
后,Tomcat启动之后就触发了 selfInitialize
方法。这里 servletContext
其实是获取了 ApplicationContext
的一个门面/外观– ApplicationContextCade
。
// ServletWebServerApplicationContext private void selfInitialize(ServletContext servletContext) throws ServletException { // todo prepareWebApplicationContext(servletContext); // todo registerApplicationScope(servletContext); // todo WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext); // todo for (ServletContextInitializer beans : getServletContextInitializerBeans()) { beans.onStartup(servletContext); } } 复制代码
-
prepareWebApplicationContext
ServletWebServerApplicationContext
的prepareWebApplicationContext
方法如下所示,简单来讲就是为servletContext设置根容器属性并为当前应用上下文ApplicationContext设置servletContext引用。protected void prepareWebApplicationContext(ServletContext servletContext) { //尝试从servletContext中获取rootContext Object rootContext = servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); if (rootContext != null) { if (rootContext == this) { throw new IllegalStateException( "Cannot initialize context because there is already a root application context present - " + "check whether you have multiple ServletContextInitializers!"); } return; } Log logger = LogFactory.getLog(ContextLoader.class); // 这个日志是不是很熟悉?! servletContext.log("Initializing Spring embedded WebApplicationContext"); try { //向servletContext设置属性 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this); if (logger.isDebugEnabled()) { logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]"); } // 为ApplicationContext设置servletContext引用 setServletContext(servletContext); if (logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - getStartupDate(); logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms"); } } catch (RuntimeException | Error ex) { logger.error("Context initialization failed", ex); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex); throw ex; } } 复制代码
-
registerApplicationScope
:ServletWebServerApplicationContext
的registerApplicationScope
方法如下所示,简单来讲就是(扩展)注册scope-application。这里会实例化一个ServletContextScope (包装了servletContext),然后注册到BeanFactory中并为servletContext设置属性。private void registerApplicationScope(ServletContext servletContext) { ServletContextScope appScope = new ServletContextScope(servletContext); // application getBeanFactory().registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope); // Register as ServletContext attribute, for ContextCleanupListener to detect it. servletContext.setAttribute(ServletContextScope.class.getName(), appScope); } 复制代码
我们在Spring中refresh分析之postProcessBeanFactory方法详解提到了request-RequestScope,session–SessionScope的注册,本文这里注册了application-ServletContextScope注册。
-
registerEnvironmentBeans
:WebApplicationContextUtils的registerEnvironmentBeans
方法。public static void registerEnvironmentBeans(ConfigurableListableBeanFactory bf, @Nullable ServletContext sc) { registerEnvironmentBeans(bf, sc, null); } public static void registerEnvironmentBeans(ConfigurableListableBeanFactory bf, @Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) { //将servletContext作为单例注册容器 if (servletContext != null && !bf.containsBean(WebApplicationContext.SERVLET_CONTEXT_BEAN_NAME)) { bf.registerSingleton(WebApplicationContext.SERVLET_CONTEXT_BEAN_NAME, servletContext); } // 将servletConfig 作为单例注册容器本文这里没有触发 if (servletConfig != null && !bf.containsBean(ConfigurableWebApplicationContext.SERVLET_CONFIG_BEAN_NAME)) { bf.registerSingleton(ConfigurableWebApplicationContext.SERVLET_CONFIG_BEAN_NAME, servletConfig); } // String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters"; if (!bf.containsBean(WebApplicationContext.CONTEXT_PARAMETERS_BEAN_NAME)) { Map<String, String> parameterMap = new HashMap<>(); if (servletContext != null) { // 获取servletContextd的初始化参数 Enumeration<?> paramNameEnum = servletContext.getInitParameterNames(); while (paramNameEnum.hasMoreElements()) { String paramName = (String) paramNameEnum.nextElement(); parameterMap.put(paramName, servletContext.getInitParameter(paramName)); } } // 本文这里servletConfig 为null if (servletConfig != null) { // // 获取servletConfig的初始化参数 Enumeration<?> paramNameEnum = servletConfig.getInitParameterNames(); while (paramNameEnum.hasMoreElements()) { String paramName = (String) paramNameEnum.nextElement(); parameterMap.put(paramName, servletConfig.getInitParameter(paramName)); } } // 将contextParameters作为单例注册到容器 bf.registerSingleton(WebApplicationContext.CONTEXT_PARAMETERS_BEAN_NAME, Collections.unmodifiableMap(parameterMap)); } // String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes"; if (!bf.containsBean(WebApplicationContext.CONTEXT_ATTRIBUTES_BEAN_NAME)) { Map<String, Object> attributeMap = new HashMap<>(); if (servletContext != null) { Enumeration<?> attrNameEnum = servletContext.getAttributeNames(); while (attrNameEnum.hasMoreElements()) { String attrName = (String) attrNameEnum.nextElement(); attributeMap.put(attrName, servletContext.getAttribute(attrName)); } } // 将contextAttributes作为单例注册到容器 bf.registerSingleton(WebApplicationContext.CONTEXT_ATTRIBUTES_BEAN_NAME, Collections.unmodifiableMap(attributeMap)); } } 复制代码
-
触发
ServletContextInitializer的onStartup
:如下所示,这里会获取ServletContextInitializer
的所有实例,遍历触发其onStartup方法。for (ServletContextInitializer beans : getServletContextInitializerBeans()) { beans.onStartup(servletContext); } 复制代码
如下所示,将会挨个触发这5个的onStartup方法:
核心代码如下:
// org.springframework.boot.web.servlet.RegistrationBean#onStartup @Override public final void onStartup(ServletContext servletContext) throws ServletException { String description = getDescription(); if (!isEnabled()) { logger.info(StringUtils.capitalize(description) + " was not registered (disabled)"); return; } register(description, servletContext); } // org.springframework.boot.web.servlet.DynamicRegistrationBean#register @Override protected final void register(String description, ServletContext servletContext) { D registration = addRegistration(description, servletContext); if (registration == null) { logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)"); return; } configure(registration); } // org.springframework.boot.web.servlet.ServletRegistrationBean#addRegistration @Override protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) { String name = getServletName(); return servletContext.addServlet(name, this.servlet); } 复制代码
在这里,
会将Dispatcher加入到Tomcat中
,之后就是Tomcat的启动流程,跟之前启动SpringMVC流程一样,参见: Spring5源码14-SpringMVC入口及启动流程
3.6 afterRefresh
进入org.springframework.boot.SpringApplication#afterRefresh方法中
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) { } 复制代码
这里啥也没做,就是一个模板方法,可能使用于扩展性的吧
3.7 callRunners
进入org.springframework.boot.SpringApplication#callRunners方法中
private void callRunners(ApplicationContext context, ApplicationArguments args) { List<Object> runners = new ArrayList<>(); runners.addAll(context.getBeansOfType(ApplicationRunner.class).values()); runners.addAll(context.getBeansOfType(CommandLineRunner.class).values()); AnnotationAwareOrderComparator.sort(runners); for (Object runner : new LinkedHashSet<>(runners)) { if (runner instanceof ApplicationRunner) { callRunner((ApplicationRunner) runner, args); } if (runner instanceof CommandLineRunner) { callRunner((CommandLineRunner) runner, args); } } } private void callRunner(ApplicationRunner runner, ApplicationArguments args) { try { (runner).run(args); } catch (Exception ex) { throw new IllegalStateException("Failed to execute ApplicationRunner", ex); } } 复制代码
可以看到这里主要就是从容器中获取runner对象并调用对应的run方法。