1 2 3 4 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
1. Servlet容器的使用 1.1 默认servlet容器 我们看看spring-boot-starter-web这个starter中有什么
核心就是引入了tomcat和SpringMvc,我们先来看tomcat
Spring Boot默认支持Tomcat,Jetty,和Undertow作为底层容器。如图:
而Spring Boot默认使用Tomcat,一旦引入spring-boot-starter-web模块,就默认使用Tomcat容器。
1.2 切换servlet容器 那如果我么想切换其他Servlet容器呢,只需如下两步:
将tomcat依赖移除掉
引入其他Servlet容器依赖
引入jetty:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <!--移除spring-boot-starter-web中的tomcat--> <artifactId>spring-boot-starter-tomcat</artifactId> <groupId>org.springframework.boot</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <!--引入jetty--> <artifactId>spring-boot-starter-jetty</artifactId> </dependency>
2. Servlet容器自动配置原理 2.1 EmbeddedServletContainerAutoConfiguration 其中EmbeddedServletContainerAutoConfiguration 是嵌入式Servlet容器的自动配置类,该类在spring-boot-autoconfigure.jar中的web模块 可以找到。
我们可以看到EmbeddedServletContainerAutoConfiguration被配置在spring.factories中, 看过我前面文章的朋友应该知道SpringBoot自动配置的原理,这里将EmbeddedServletContainerAutoConfiguration配置类加入到IOC容器中,接着我们来具体看看这个配置类:
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 42 43 44 45 46 47 48 49 @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) @Configuration @ConditionalOnWebApplication// 在Web环境下才会起作用 @Import(BeanPostProcessorsRegistrar.class)// 会Import一个内部类BeanPostProcessorsRegistrar public class EmbeddedServletContainerAutoConfiguration { @Configuration // Tomcat类和Servlet类必须在classloader中存在 // 文章开头我们已经导入了web的starter,其中包含tomcat和SpringMvc // 那么classPath下会存在Tomcat.class和Servlet.class @ConditionalOnClass({ Servlet.class, Tomcat.class }) // 当前Spring容器中不存在EmbeddedServletContainerFactory类型的实例 @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT) public static class EmbeddedTomcat { @Bean public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() { // 上述条件注解成立的话就会构造TomcatEmbeddedServletContainerFactory这个EmbeddedServletContainerFactory return new TomcatEmbeddedServletContainerFactory(); } } @Configuration @ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class }) @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT) public static class EmbeddedJetty { @Bean public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() { return new JettyEmbeddedServletContainerFactory(); } } @Configuration @ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class }) @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT) public static class EmbeddedUndertow { @Bean public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() { return new UndertowEmbeddedServletContainerFactory(); } } //other code... }
在这个自动配置类中配置了三个容器工厂的Bean,分别是:
TomcatEmbeddedServletContainerFactory
JettyEmbeddedServletContainerFactory
UndertowEmbeddedServletContainerFactory
这里以大家熟悉的Tomcat为例,首先Spring Boot会判断当前环境中是否引入了Servlet和Tomcat依赖,并且当前容器中没有自定义的EmbeddedServletContainerFactory 的情况下,则创建Tomcat容器工厂。其他Servlet容器工厂也是同样的道理。
2.2 EmbeddedServletContainerFactory
1 2 3 4 public interface EmbeddedServletContainerFactory { EmbeddedServletContainer getEmbeddedServletContainer( ServletContextInitializer... initializers); }
内部只有一个方法,用于获取嵌入式的Servlet容器。
该工厂接口主要有三个实现类,分别对应三种嵌入式Servlet容器的工厂类,如图所示:
2.3 TomcatEmbeddedServletContainerFactory 以Tomcat容器工厂TomcatEmbeddedServletContainerFactory类为例:
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 public class TomcatEmbeddedServletContainerFactory extends AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware { //other code... @Override public EmbeddedServletContainer getEmbeddedServletContainer( ServletContextInitializer... initializers) { //创建一个Tomcat Tomcat tomcat = new Tomcat(); //配置Tomcat的基本环节 File baseDir = (this.baseDirectory != null ? this.baseDirectory: createTempDir("tomcat")); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connector connector = new Connector(this.protocol); tomcat.getService().addConnector(connector); customizeConnector(connector); tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false); configureEngine(tomcat.getEngine()); for (Connector additionalConnector : this.additionalTomcatConnectors) { tomcat.getService().addConnector(additionalConnector); } prepareContext(tomcat.getHost(), initializers); //包装tomcat对象,返回一个嵌入式Tomcat容器,内部会启动该tomcat容器 return getTomcatEmbeddedServletContainer(tomcat); } }
首先会创建一个Tomcat的对象,并设置一些属性配置,最后调用getTomcatEmbeddedServletContainer(tomcat)方法,内部会启动tomcat, 我们来看看:
1 2 3 4 protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer( Tomcat tomcat) { return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0); }
该函数很简单,就是来创建Tomcat容器并返回。看看TomcatEmbeddedServletContainer类:
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer { public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) { Assert.notNull(tomcat, "Tomcat Server must not be null"); this.tomcat = tomcat; this.autoStart = autoStart; //初始化嵌入式Tomcat容器,并启动Tomcat initialize(); } private void initialize() throws EmbeddedServletContainerException { TomcatEmbeddedServletContainer.logger .info("Tomcat initialized with port(s): " + getPortsDescription(false)); synchronized (this.monitor) { try { addInstanceIdToEngineName(); try { final Context context = findContext(); context.addLifecycleListener(new LifecycleListener() { @Override public void lifecycleEvent(LifecycleEvent event) { if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) { // Remove service connectors so that protocol // binding doesn't happen when the service is // started. removeServiceConnectors(); } } }); // Start the server to trigger initialization listeners //启动tomcat this.tomcat.start(); // We can re-throw failure exception directly in the main thread rethrowDeferredStartupExceptions(); try { ContextBindings.bindClassLoader(context, getNamingToken(context), getClass().getClassLoader()); } catch (NamingException ex) { // Naming is not enabled. Continue } // Unlike Jetty, all Tomcat threads are daemon threads. We create a // blocking non-daemon to stop immediate shutdown startDaemonAwaitThread(); } catch (Exception ex) { containerCounter.decrementAndGet(); throw ex; } } catch (Exception ex) { stopSilently(); throw new EmbeddedServletContainerException( "Unable to start embedded Tomcat", ex); } } } }
到这里就启动了嵌入式的Servlet容器,其他容器类似。
3. Servlet容器启动原理 3.1 SpringBoot启动过程 我们回顾一下前面讲解的SpringBoot启动过程,也就是run方法:
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 42 43 44 45 46 47 48 49 50 51 52 53 public ConfigurableApplicationContext run(String... args) { // 计时工具 StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); configureHeadlessProperty(); // 第一步:获取并启动监听器 SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // 第二步:根据SpringApplicationRunListeners以及参数来准备环境 ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments); configureIgnoreBeanInfo(environment); // 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体 Banner printedBanner = printBanner(environment); // 第三步:创建Spring容器 context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); // 第四步:Spring容器前置处理 prepareContext(context, environment, listeners, applicationArguments,printedBanner); // 第五步:刷新容器 refreshContext(context); // 第六步:Spring容器后置处理 afterRefresh(context, applicationArguments); // 第七步:发出结束执行的事件 listeners.started(context); // 第八步:执行Runners this.callRunners(context, applicationArguments); stopWatch.stop(); // 返回容器 return context; } catch (Throwable ex) { handleRunFailure(context, listeners, exceptionReporters, ex); throw new IllegalStateException(ex); } }
我们回顾一下第三步:创建Spring容器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context." + "annotation.AnnotationConfigApplicationContext"; public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework." + "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext"; protected ConfigurableApplicationContext createApplicationContext() { Class<?> contextClass = this.applicationContextClass; if (contextClass == null) { try { //根据应用环境,创建不同的IOC容器 contextClass = Class.forName(this.webEnvironment ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS); } } return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass); }
创建IOC容器,如果是web应用,则创建AnnotationConfigEmbeddedWebApplicationContext的IOC容器;如果不是,则创建AnnotationConfigApplicationContext的IOC容器;很明显我们创建的容器是AnnotationConfigEmbeddedWebApplicationContext,接着我们来看看第五步,刷新容器refreshContext(context);
1 2 3 4 5 6 7 8 9 private void refreshContext(ConfigurableApplicationContext context) { refresh(context); } protected void refresh(ApplicationContext applicationContext) { Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext); //调用容器的refresh()方法刷新容器 ((AbstractApplicationContext) applicationContext).refresh(); }
3.2 容器刷新过程 调用抽象父类AbstractApplicationContext的refresh ()方法;
AbstractApplicationContext
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { /** * 刷新上下文环境 */ prepareRefresh(); /** * 初始化BeanFactory,解析XML,相当于之前的XmlBeanFactory的操作, */ ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); /** * 为上下文准备BeanFactory,即对BeanFactory的各种功能进行填充,如常用的注解@Autowired @Qualifier等 * 添加ApplicationContextAwareProcessor处理器 * 在依赖注入忽略实现*Aware的接口,如EnvironmentAware、ApplicationEventPublisherAware等 * 注册依赖,如一个bean的属性中含有ApplicationEventPublisher(beanFactory),则会将beanFactory的实例注入进去 */ prepareBeanFactory(beanFactory); try { /** * 提供子类覆盖的额外处理,即子类处理自定义的BeanFactoryPostProcess */ postProcessBeanFactory(beanFactory); /** * 激活各种BeanFactory处理器,包括BeanDefinitionRegistryBeanFactoryPostProcessor和普通的BeanFactoryPostProcessor * 执行对应的postProcessBeanDefinitionRegistry方法 和 postProcessBeanFactory方法 */ invokeBeanFactoryPostProcessors(beanFactory); /** * 注册拦截Bean创建的Bean处理器,即注册BeanPostProcessor,不是BeanFactoryPostProcessor,注意两者的区别 * 注意,这里仅仅是注册,并不会执行对应的方法,将在bean的实例化时执行对应的方法 */ registerBeanPostProcessors(beanFactory); /** * 初始化上下文中的资源文件,如国际化文件的处理等 */ initMessageSource(); /** * 初始化上下文事件广播器,并放入applicatioEventMulticaster,如ApplicationEventPublisher */ initApplicationEventMulticaster(); /** * 给子类扩展初始化其他Bean */ onRefresh(); /** * 在所有bean中查找listener bean,然后注册到广播器中 */ registerListeners(); /** * 设置转换器 * 注册一个默认的属性值解析器 * 冻结所有的bean定义,说明注册的bean定义将不能被修改或进一步的处理 * 初始化剩余的非惰性的bean,即初始化非延迟加载的bean */ finishBeanFactoryInitialization(beanFactory); /** * 通过spring的事件发布机制发布ContextRefreshedEvent事件,以保证对应的监听器做进一步的处理 * 即对那种在spring启动后需要处理的一些类,这些类实现了ApplicationListener<ContextRefreshedEvent>, * 这里就是要触发这些类的执行(执行onApplicationEvent方法) * spring的内置Event有ContextClosedEvent、ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、RequestHandleEvent * 完成初始化,通知生命周期处理器lifeCycleProcessor刷新过程,同时发出ContextRefreshEvent通知其他人 */ finishRefresh(); } finally { resetCommonCaches(); } } }
我们看第52行的方法:
1 2 3 protected void onRefresh() throws BeansException { }
很明显抽象父类AbstractApplicationContext中的onRefresh是一个空方法,并且使用protected修饰,也就是其子类可以重写onRefresh方法,那我们看看其子类AnnotationConfigEmbeddedWebApplicationContext中的onRefresh方法是如何重写的,AnnotationConfigEmbeddedWebApplicationContext又继承EmbeddedWebApplicationContext,如下:
1 public class AnnotationConfigEmbeddedWebApplicationContext extends EmbeddedWebApplicationContext {
那我们看看其父类EmbeddedWebApplicationContext 是如何重写onRefresh方法的:
EmbeddedWebApplicationContext
1 2 3 4 5 6 7 8 9 10 11 @Override protected void onRefresh() { super.onRefresh(); try { //核心方法:会获取嵌入式的Servlet容器工厂,并通过工厂来获取Servlet容器 createEmbeddedServletContainer(); } catch (Throwable ex) { throw new ApplicationContextException("Unable to start embedded container", ex); } }
在createEmbeddedServletContainer方法中会获取嵌入式的Servlet容器工厂,并通过工厂来获取Servlet容器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private void createEmbeddedServletContainer() { EmbeddedServletContainer localContainer = this.embeddedServletContainer; ServletContext localServletContext = getServletContext(); if (localContainer == null && localServletContext == null) { //先获取嵌入式Servlet容器工厂 EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory(); //根据容器工厂来获取对应的嵌入式Servlet容器 this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer()); } else if (localServletContext != null) { try { getSelfInitializer().onStartup(localServletContext); } catch (ServletException ex) { throw new ApplicationContextException("Cannot initialize servlet context",ex); } } initPropertySources(); }
关键代码在第6和第8行,先获取Servlet容器工厂,然后根据容器工厂来获取对应的嵌入式Servlet容器
3.2 获取Servlet容器工厂 1 2 3 4 5 6 protected EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() { //从Spring的IOC容器中获取EmbeddedServletContainerFactory.class类型的Bean String[] beanNames = getBeanFactory().getBeanNamesForType(EmbeddedServletContainerFactory.class); //调用getBean实例化EmbeddedServletContainerFactory.class return getBeanFactory().getBean(beanNames[0], EmbeddedServletContainerFactory.class); }
我们看到先从Spring的IOC容器中获取EmbeddedServletContainerFactory.class类型的Bean,然后调用getBean实例化EmbeddedServletContainerFactory.class,大家还记得我们第一节Servlet容器自动配置类EmbeddedServletContainerAutoConfiguration中注入Spring容器的对象是什么吗?当我们引入spring-boot-starter-web这个启动器后,会注入TomcatEmbeddedServletContainerFactory 这个对象到Spring容器中,所以这里获取到的**Servlet容器工厂是**TomcatEmbeddedServletContainerFactory****,然后调用
TomcatEmbeddedServletContainerFactory的getEmbeddedServletContainer方法获取Servlet容器,并且启动Tomcat,大家可以看看文章开头的getEmbeddedServletContainer方法。
大家看一下第8行代码获取Servlet容器方法的参数getSelfInitializer(),这是个啥?我们点进去看看
1 2 3 4 5 6 7 8 private ServletContextInitializer getSelfInitializer() { //创建一个ServletContextInitializer对象,并重写onStartup方法,很明显是一个回调方法 return new ServletContextInitializer() { public void onStartup(ServletContext servletContext) throws ServletException { EmbeddedWebApplicationContext.this.selfInitialize(servletContext); } }; }
创建一个ServletContextInitializer对象,并重写onStartup方法,很明显是一个回调方法,这里给大家留一点疑问:
ServletContextInitializer对象创建过程是怎样的?
onStartup是何时调用的?
onStartup方法的作用是什么?
ServletContextInitializer
是 Servlet 容器初始化的时候,提供的初始化接口。这里涉及到Servlet、Filter实例的注册,我们留在下一篇具体讲