SpringBoot中的Web容器

1.写在前面

上一篇博客中介绍了SpringBoot的事件管理机制,当时留下了一个坑,就是在事件管理器中要配置线程池,不然在执行监听器中逻辑代码的时候如果报错了,这个时候会影响事件管理器主线程中的代码,这样对我们来说是不允许的,这儿最好用异步执行,所以我们这儿要配置一个线程池,但是怎么配置,上篇博客没有讲,这篇博客会讲,而今天博客最主要的内容还是SpringBoot中Web容器。

2.SpringBoot中配置事件监听器的线程池

要知道怎么在事件管理器中使用线程池的方法,首先我们需要看怎么获取线程池的,然后才知道怎么配置线程池,具体的代码如下:

@Override
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    //获取线程池
		Executor executor = getTaskExecutor();
		for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
				invokeListener(listener, event);
			}
		}
	}
@Nullable
	protected Executor getTaskExecutor() {
		return this.taskExecutor;
	}

从上面可以知道是通过一个成员变量taskExecutor来获取的,这个是SimpleApplicationEventMulticaster的成员变量,那么我们怎么去改变这个成员变量的值呢?这个时候我们需要看下这个类是怎么注入到初始化的,看看有没有什么方法?让这个类在初始化的时候,我们来改变一下这个成员变量?具体的代码如下:

protected void initApplicationEventMulticaster() {
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
			this.applicationEventMulticaster =
					beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
			if (logger.isTraceEnabled()) {
				logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
			}
		}
		else {
			this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
			beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
			if (logger.isTraceEnabled()) {
				logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
						"[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
			}
		}
	}

可以看到上面的代码只要容器中有applicationEventMulticaster类名的Bean,Spring就不会创建SimpleApplicationEventMulticaster那么我们这儿就可以注入一个名称为applicationEventMulticasterSimpleApplicationEventMulticaster的Bean,然后修改其中的属性,就可以配置线程池了。具体代码如下:

package com.example.springbootevent.config;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

@Configuration
public class AppConfig {

    @Bean("applicationEventMulticaster")
    public SimpleApplicationEventMulticaster applicationEventMulticaster(BeanFactory beanFactory,
                                                                         ThreadPoolTaskExecutor threadPoolExecutor) {
        SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = new
                SimpleApplicationEventMulticaster(beanFactory);
        simpleApplicationEventMulticaster.setTaskExecutor(threadPoolExecutor);
        return simpleApplicationEventMulticaster;
    }

    @Bean
    public ThreadPoolTaskExecutor threadPoolExecutor() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setMaxPoolSize(15);
        threadPoolTaskExecutor.setCorePoolSize(10);
        threadPoolTaskExecutor.setQueueCapacity(30);
        threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        threadPoolTaskExecutor.initialize();
        return threadPoolTaskExecutor;
    }
}

这样我们就注入了线程池了,我们来验证一下,运行的结果如下:

在这里插入图片描述

通过debug的方式,我们已经发现我们刚才设置的线程池,已经加入到对应的事件管理器中去了。算是我把上篇博客的一个坑给填了吧。继续我们今天的内容。

3.SpringBoot中Web容器的参数如何配置

主要有三种方式。

第一种通过配置文件来配置对应的参数,具体的内容如下:

server.port=8081

主要是通过修改application.properties或者application.yml配置文件来修改对应Web容器中参数,运行的结果如下:

在这里插入图片描述

可以发现我们的端口已经映射到8081上去了。

第二种就是通过修改TomcatServletWebServerFactory Bean的属性来修改对应的参数,具体的如下:

package com.example.springbootweb.config;

import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
        TomcatServletWebServerFactory tomcatServletWebServerFactory = new TomcatServletWebServerFactory();
        tomcatServletWebServerFactory.setPort(8082);
        return tomcatServletWebServerFactory;
    }
}

运行结果如下:

在这里插入图片描述

可以发现我们的端口已经映射到8082上去了。

第三种就是通过修改WebServerFactoryCustomizer Bean的属性来修改对应的参数,具体的如下:

package com.example.springbootweb.config;

import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public WebServerFactoryCustomizer<TomcatServletWebServerFactory> webServerFactoryCustomizer() {
        return factory -> factory.setPort(8083);
    }
}

运行结果如下:

在这里插入图片描述

可以发现我们的端口已经映射到8083上去了。

那么现在大家可能有一个疑问了,就是这三种方式那个优先级最高呢?我们来测试一下看看,先看运行结果:

在这里插入图片描述

可以发现是我们的8083映射上了,所以第三种方式,WebServerFactoryCustomizer的方式是最高的,然后我们再来看次之的,先注释掉WebServerFactoryCustomizer部分,再来看运行结果,如下:

在这里插入图片描述

可以发现是我们的8081映射上了,所以是第一种方式,配置文件的方式的优先级次之,所以我们得出以下的结果:

优先级最高的是WebServerFactoryCustomizer次之配置文件,最末TomcatServletWebServerFactory

4.SpringBoot如何使用别的Web容器

我们这儿就使用Jetty服务器,那么如何使用呢?先修改我们的pom文件,具体的如下:

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
              <!--剔除Tomcat依赖-->
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

  			<!--引入jetty依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

运行结果如下:

在这里插入图片描述

可以看到我们这儿已经使用了Jetty服务器了。那我们这儿为什么要剔除Tomcat的依赖呢?这个时候需要简单看下源码吧!具体的如下:

在这里插入图片描述

可以看到这儿是先Import Tomcat的,所以要剔除Tomcat的依赖,才能使用Jetty服务器。

5.SpringBoot启动Web容器的原理

这里SpringBoot的启动方式主要有两种,一种是基于jar的内嵌的web服务器,一种是基于war包的外部服务器。两种方式这儿,我都会讲一下,其实区别很小。首先我们先看基于jar的内嵌的web服务器。

5.1基于jar启动方式的web的服务器启动原理

我们就直接看对应的调用链,大家可以根据对应的调用链,来看对应的代码,具体的如下:

org.springframework.boot.SpringApplication#run(java.lang.Class<?>, java.lang.String...)
--->org.springframework.boot.SpringApplication#run(java.lang.Class<?>[], java.lang.String[])
--->org.springframework.boot.SpringApplication#run(java.lang.String...)
--->org.springframework.boot.SpringApplication#refreshContext
--->org.springframework.boot.SpringApplication#refresh(org.springframework.context.ApplicationContext)
--->org.springframework.boot.SpringApplication#refresh
(org.springframework.context.ConfigurableApplicationContext)
--->org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#refresh
--->org.springframework.context.support.AbstractApplicationContext#refresh
--->org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#onRefresh
--->org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#createWebServer

经过一系列的方法的调用,最终调用的我们的核心的代码,具体的如下:

private void createWebServer() {
  WebServer webServer = this.webServer;
  //获取servlet上下文对象
	ServletContext servletContext = getServletContext();
	//由于我们这儿是基于jar启动的,所以这儿这儿两个的值都是空
  if (webServer == null && servletContext == null) {	
    StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
    //获取servlet工厂,这儿的代码下面会带着大家看一遍
    ServletWebServerFactory factory = getWebServerFactory();
		createWebServer.tag("factory", factory.getClass().toString());
    //创建web容器
    this.webServer = factory.getWebServer(getSelfInitializer());
    createWebServer.end();
    //注册两个生命周期的回调函数
    getBeanFactory().registerSingleton("webServerGracefulShutdown",
                                       new WebServerGracefulShutdownLifecycle(this.webServer));
    getBeanFactory().registerSingleton("webServerStartStop",
					new WebServerStartStopLifecycle(this, this.webServer));
	}
  //这儿是基于war包启动的,这儿下一节再讲
	else if (servletContext != null) {
		try {
			getSelfInitializer().onStartup(servletContext);
		}
		catch (ServletException ex) {
			throw new ApplicationContextException("Cannot initialize servlet context", ex);
		}
	}
	initPropertySources();
}

经过上面的简单的分析,我们先看看创建工厂的方法getWebServerFactory(),具体的代码如下:

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);
	}

这儿可以看见这儿是从容器中获取ServletWebServerFactory的实现类,并且这儿实现类只允许有一个,如果出现多个,springboot这儿直接报错,那么先看看这儿类ServletWebServerFactory有几个实现类吧。具体的如下:

在这里插入图片描述

可以看到,就是springBoot自己适配的几个Web服务器,所以这儿配置的话,就只能配置一个服务器,不能配置多,配置多,springBoot会直接报错。我们再来看factory.getWebServer(getSelfInitializer());方法,由于这儿是调用接口的getWebServer方法,所以每个工厂都有着对应的实现,我们今天还是只看看Tomcat怎么做的吧。于是我们打开TomcatServletWebServerFactory类中getWebServer方法,具体的代码如下:

public WebServer getWebServer(ServletContextInitializer... initializers) {
		if (this.disableMBeanRegistry) {
			Registry.disableRegistry();
		}
		Tomcat tomcat = new Tomcat();
		File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
		tomcat.setBaseDir(baseDir.getAbsolutePath());
		Connector connector = new Connector(this.protocol);
		connector.setThrowOnFailure(true);
		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);
		return getTomcatWebServer(tomcat);
	}

可以看到上面的代码就是我们Tomcat的启动的代码,至此,基于jar的启动的springboot的启动web容器的原理,我这儿已经讲清楚了。这儿有个需要注意点,我们先看下官网,如下:

在这里插入图片描述

上面的意思大概就是springBoot在使用内嵌的web容器,servlet3.0的spi特性就会失效,这儿时候需要实现ServletContextInitializer并且实现这个接口的对应的方法,这儿才会在Tomcat启动的时候调用,具体的测试如下:

package com.example.springbootweb.config;

import org.springframework.stereotype.Component;
import org.springframework.web.WebApplicationInitializer;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;

@Component
public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        System.out.println("MyWebApplicationInitializer.onStartup");
    }
}

package com.example.springbootweb.config;

import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.stereotype.Component;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;

@Component
public class MyServletContextInitializer implements ServletContextInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        System.out.println("MyServletContextInitializer.onStartup");
    }
}

我们写出了两种方式,看看在springboot启动的时候,会调用哪个方法,具体的测试如下:

在这里插入图片描述

可以发现实现ServletContextInitializer接口的方法调用了对应的启动的方法,也验证了官网所说的那句话。那么原理又是什么呢?

这个时候我们还需要看下原来创建容器的方法createWebServer()方法,具体的代码如下:

private void createWebServer() {
  WebServer webServer = this.webServer;
  //获取servlet上下文对象
	ServletContext servletContext = getServletContext();
	//由于我们这儿是基于jar启动的,所以这儿这儿两个的值都是空
  if (webServer == null && servletContext == null) {	
    StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
    //获取servlet工厂,这儿的代码下面会带着大家看一遍
    ServletWebServerFactory factory = getWebServerFactory();
		createWebServer.tag("factory", factory.getClass().toString());
    //创建web容器
    this.webServer = factory.getWebServer(getSelfInitializer());
    createWebServer.end();
    //注册两个生命周期的回调函数
    getBeanFactory().registerSingleton("webServerGracefulShutdown",
                                       new WebServerGracefulShutdownLifecycle(this.webServer));
    getBeanFactory().registerSingleton("webServerStartStop",
					new WebServerStartStopLifecycle(this, this.webServer));
	}
  //这儿是基于war包启动的,这儿下一节再讲
	else if (servletContext != null) {
		try {
			getSelfInitializer().onStartup(servletContext);
		}
		catch (ServletException ex) {
			throw new ApplicationContextException("Cannot initialize servlet context", ex);
		}
	}
	initPropertySources();
}

在调用getWebServer(getSelfInitializer());方法,往其中传入一个参数,这个参数是getSelfInitializer()的返回值,具体的代码如下:

private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
		return this::selfInitialize;
	}

private void selfInitialize(ServletContext servletContext) throws ServletException {
	prepareWebApplicationContext(servletContext);
	registerApplicationScope(servletContext);
	WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
	for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
		beans.onStartup(servletContext);
	}
}

这儿利用了java8的方法引用,就是在调用onStartup()方法的时候,会用下面的方法来替代。同时这个类还传给了Tomcat,后面Tomcat在启动的时候就会调用上面的selfInitialize方法。而这儿的逻辑就是找到我们项目中所有实现了ServletContextInitializer接口的Bean。我为什么这么说呢?可以看看下图:

在这里插入图片描述

可以发现调用getServletContextInitializerBeans()方法,这儿获取到了我们自己实现的ServletContextInitializer接口的类MyServletContextInitializer类,然后调用其中的onStartup()方法,这儿的原理就讲清楚了,这个时候我们需要看基于war包的启动方式的web的容器启动原理。

5.2基于war包的启动方式的web的容器启动原理

我们先看打成war包的时候,要加的一条关键的代码,具体的如下:

package com.example.springbootweb;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

@SpringBootApplication
public class SpringBootWebApplication extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return super.configure(builder).sources(SpringBootWebApplication.class);
    }

    public static void main(String[] args) {
        SpringApplication.run(SpringBootWebApplication.class, args);
    }

}

我们可以发现我们是继承了一个SpringBootServletInitializer这个类,而这个类实现了WebApplicationInitializer,这个类大家都很熟悉吧。Tomcat在启动的时候,都会调用这个类的实现类中的onStartup()方法,所以这儿Tomcat启动的时候,就会调用SpringBootServletInitializer类中的onStartup()方法,具体的代码如下:

public void onStartup(ServletContext servletContext) throws ServletException {
		// Logger initialization is deferred in case an ordered
		// LogServletContextInitializer is being used
		this.logger = LogFactory.getLog(getClass());
		WebApplicationContext rootApplicationContext = createRootApplicationContext(servletContext);
		if (rootApplicationContext != null) {
			servletContext.addListener(new SpringBootContextLoaderListener(rootApplicationContext, servletContext));
		}
		else {
			this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not "
					+ "return an application context");
		}
	}

上面的代码就是在调用createRootApplicationContext(servletContext)方法中启动Web容器的,具体的代码如下:

protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
		SpringApplicationBuilder builder = createSpringApplicationBuilder();
		builder.main(getClass());
		ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
		if (parent != null) {
			this.logger.info("Root context already created (using as parent).");
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
			builder.initializers(new ParentContextApplicationContextInitializer(parent));
		}
		builder.initializers(new ServletContextApplicationContextInitializer(servletContext));
		builder.contextFactory((webApplicationType) -> new AnnotationConfigServletWebServerApplicationContext());
		builder = configure(builder);
		builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
		SpringApplication application = builder.build();
		if (application.getAllSources().isEmpty()
				&& MergedAnnotations.from(getClass(), SearchStrategy.TYPE_HIERARCHY).isPresent(Configuration.class)) {
			application.addPrimarySources(Collections.singleton(getClass()));
		}
		Assert.state(!application.getAllSources().isEmpty(),
				"No SpringApplication sources have been defined. Either override the "
						+ "configure method or add an @Configuration annotation");
		// Ensure error pages are registered
		if (this.registerErrorPageFilter) {
			application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
		}
		application.setRegisterShutdownHook(false);
		return run(application);
	}

前面的代码都不用在乎,看到最后的代码run(application);点开的代码内容如下:

protected WebApplicationContext run(SpringApplication application) {
		return (WebApplicationContext) application.run();
	}

再点开,具体的代码如下:

public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		DefaultBootstrapContext bootstrapContext = createBootstrapContext();
		ConfigurableApplicationContext context = null;
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting(bootstrapContext, this.mainApplicationClass);
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			context.setApplicationStartup(this.applicationStartup);
			prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
			//比较重要的代码
      refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

你会发现一个比较重要的方法调用refreshContext(context);这个方法是不是很熟悉?如果你有认真阅读前面的内容,你会发现后面的调用逻辑和基于jar方法调用的逻辑是一样的了。于是最终会走到下面的方法,具体的如下:

private void createWebServer() {
  WebServer webServer = this.webServer;
  //获取servlet上下文对象
	ServletContext servletContext = getServletContext();
	//由于我们这儿是基于jar启动的,所以这儿这儿两个的值都是空
  if (webServer == null && servletContext == null) {	
    StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
    //获取servlet工厂,这儿的代码下面会带着大家看一遍
    ServletWebServerFactory factory = getWebServerFactory();
		createWebServer.tag("factory", factory.getClass().toString());
    //创建web容器
    this.webServer = factory.getWebServer(getSelfInitializer());
    createWebServer.end();
    //注册两个生命周期的回调函数
    getBeanFactory().registerSingleton("webServerGracefulShutdown",
                                       new WebServerGracefulShutdownLifecycle(this.webServer));
    getBeanFactory().registerSingleton("webServerStartStop",
					new WebServerStartStopLifecycle(this, this.webServer));
	}
  //这儿是基于war包启动的,这儿下一节再讲
	else if (servletContext != null) {
		try {
			getSelfInitializer().onStartup(servletContext);
		}
		catch (ServletException ex) {
			throw new ApplicationContextException("Cannot initialize servlet context", ex);
		}
	}
	initPropertySources();
}

这儿会走下面的else if 的分支,然后调用实现的ServletContextInitializer接口中的onStartup方法,也就是说这儿实现其他的两个接口类中onStartup方法会调用,实现这个ServletContextInitializer接口中的onStartup方法也是会调用的,只不过是最后调用的。最终我们得到:基于jar包启动方式的springBoot,内嵌式的Tomcat,在启动的时候,会调用org.springframework.boot.web.servlet.ServletContextInitializer接口的实现类中的onStartup方法。而基于war包启动方式的springBoot,外部的Tomcat,在启动的Tomcat的时候,会调用实现javax.servlet.ServletContainerInitializer接口和实现org.springframework.web.WebApplicationInitializer接口以及实现org.springframework.boot.web.servlet.ServletContextInitializer接口的实现类中的onStartup方法。

6.写在最后

这篇博客,我大概的介绍了SpringBoot中如何使用其他的web容器,同时也介绍了SpringBoot如何启动web容器。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值