Springboot之整合tomcat容器过程

前言

  Tomcat容器最主要作用就是监听网络请求,并将请求封装成request和response对象最终交给Servlet去执行,而我们spring项目中用来供servlet调用的业务代码的入口一般就是Spring管理的Controller。而Springboot整合tomcat的话,就需要提供一个Servlet的标准实现,并将这个Servlet注册到Tomcat容器里,并且在Servlet处理请求时,能调用Spring所管理的Controller进而使用Spring容器的所有资源。所以这里最主要的两个问题是:
1、Springboot是怎么创建和启动Tomcat的?
2、Springboot怎么将DispatcherServlet注册到tomcat容器中?

在分析问题前,先看下Tomcat的架构图,有助于后面看代码:

这里可以看出一个Tomcat就是一个Server,一个Server下会有多个Service,Service只负责封装多个Connector和一个Container(Service本身不是容器,可以看做只是用来包装Connector和Container的壳,不负责具体功能),而Container(也叫engine)下又有多个Host,每个Host下对应多个Context,Context下才是我们的Servlet,Tomcat为了使整个架构灵活,所以抽象出这么多层,每层之间都可以根据不同的维度产生一对多个配置。对应到Tomcat类:

再看下主要容器的类图,这里所有的容器都继承自LifecycleBase抽象类,这个类抽象了控制生命周期的方法,比如init、start、stop、destory方法,而负责处理请求的四个容器又都继承了ContainerBase抽象类,这个抽象类最主要是抽象了pipeline(可以理解为一个调用链,一个接下一个的处理)来处理请求的逻辑,所以请求到达这四个容器后,实际是通过pipeline来处理请求,类图如下:

一、Springboot是怎么创建和启动Tomcat的?

  Springboot启动的主要方法是AbstractApplicationContext类的refresh方法,而springboot启动Tomcat容器的逻辑在ServletWebServerApplicationContext类的onRefresh方法里,这里从这个方法的代码开始debug:


	@Override
	protected void onRefresh() {
		super.onRefresh();
		try {
			createWebServer();
		}
		catch (Throwable ex) {
			throw new ApplicationContextException("Unable to start web server", ex);
		}
	}

private void createWebServer() {
		WebServer webServer = this.webServer;
		ServletContext servletContext = getServletContext();
		if (webServer == null && servletContext == null) {
		// 1.0
			ServletWebServerFactory factory = getWebServerFactory();
			// 2.0
			this.webServer = factory.getWebServer(getSelfInitializer());
		}
		else if (servletContext != null) {
			try {
				getSelfInitializer().onStartup(servletContext);
			}
			catch (ServletException ex) {
				throw new ApplicationContextException("Cannot initialize servlet context", ex);
			}
		}
		initPropertySources();
	}

1.0源码分析

/**
	 * Returns the {@link ServletWebServerFactory} that should be used to create the
	 * embedded {@link WebServer}. By default this method searches for a suitable bean in
	 * the context itself.
	 * @return a {@link ServletWebServerFactory} (never {@code null})
	 */
	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);
	}

这部分就是获取web容器的工厂类,这个工厂类是一个接口,先看下这个ServletWebServerFactory接口的实现类图:

可以看到有tomcat、Jetty、Undertow三种,那么这里get出来的具体是哪种?这个逻辑就在org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration这个类里,这个类是springboot容器初始化时自动转载进容器的,springboot自动加载的原理在这里,在spring-boot-autoconfigure包的spring.factories里配置了所有需要自动装载进容器的类,如下图:

这个类的作用就是告诉spring容器如何加载容器,这里先看下ServletWebServerFactoryAutoConfiguration类内容截图如下:

这三个就是对于的Tomcat、Jetty、Undertow容器的工厂配置类,这个@Import注解就是告诉spring容器,要把这几个工厂配置类加载进spring容器里,然后我们点进Tomcat的工厂配置类ServletWebServerFactoryConfiguration.EmbeddedTomcat.class里看下:
在这里插入图片描述

首先看上面的@ConditionalOnClass和@ConditionalOnMissingBean注解,@ConditionalOnClass注解就是告诉spring容器只有这些类存在的情况下才加载EmbeddedTomcat这个对象以及解析这个类里的@Bean注解,@ConditionalOnMissingBean就是告诉spring容器只有没有这些bean的条件下才加载这个类,这里就可以看见只要我们springboot项目里有引入Tomcat依赖,有Tomcat这个类,那么就会加载Tomcat容器,所以如果我们的springboot想换成jetty容器,就需要将这些依赖给干掉,spring-boot-starter-web这个依赖里会自动把tomcat相关依赖打进来:
在这里插入图片描述

我这个项目里由于存在tomcat的依赖,所以会加载tomcat的工厂类TomcatServletWebServerFactory。关于@Import、@Bean等注解的解析逻辑可以看这里

2.0源码分析

由于返回的是Tomcat的工厂类TomcatServletWebServerFactoryTomcatServletWebServerFactorygetWebServer方法会传入ServletContextInitializer对象,ServletContextInitializeronStartup方法会在Context容器被初始化后调用,作用是给ServletContext配置servlet、filter、listener等属性,可以理解为Context容器被初始化后,靠这个方法填充一些必要的属性,这也是开头第三个问题的关键步骤,后面再分析


this.webServer = factory.getWebServer(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);
	}
}

public WebServer getWebServer(ServletContextInitializer... initializers) {
		// Mbean逻辑,和主逻辑无关,这里跳过,有兴趣的可以自己搜下Mbean
		if (this.disableMBeanRegistry) {
			Registry.disableRegistry();
		}
		// 生成新的Tomcat对象
		Tomcat tomcat = new Tomcat();
		File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
		tomcat.setBaseDir(baseDir.getAbsolutePath());
		// 生成connector,在connector构造器里会根据出入的protocol生成对应协议处理器,下面会分析构造器代码
		Connector connector = new Connector(this.protocol);
		connector.setThrowOnFailure(true);
		// 将connector加到Service里,这个getService会创建一个Server
		tomcat.getService().addConnector(connector);
		// 定义Connector,配置Connector一些默认参数包括端口、协议等
		customizeConnector(connector);
		tomcat.setConnector(connector);
		// getHost()方法里会生成一个Engine和一个Host对象
		tomcat.getHost().setAutoDeploy(false);
		configureEngine(tomcat.getEngine());
		for (Connector additionalConnector : this.additionalTomcatConnectors) {
			tomcat.getService().addConnector(additionalConnector);
		}
		// 生成context容器,并注册到host容器中,同时为context容器生成一个默认的servlet,包装成wrapper注册到context容器中
		prepareContext(tomcat.getHost(), initializers);
		// 生成TomcatWebServer并启动该Server
		return getTomcatWebServer(tomcat);
	}

    // 默认会生成一个StandardHost对象
    public Host getHost() {
        Engine engine = getEngine();
        if (engine.findChildren().length > 0) {
            return (Host) engine.findChildren()[0];
        }

        Host host = new StandardHost();
        host.setName(hostname);
        getEngine().addChild(host);
        return host;
    }

    // 默认会生成一个Engine对象
    public Engine getEngine() {
        Service service = getServer().findServices()[0];
        if (service.getContainer() != null) {
            return service.getContainer();
        }
        Engine engine = new StandardEngine();
        engine.setName( "Tomcat" );
        engine.setDefaultHost(hostname);
        engine.setRealm(createDefaultRealm());
        service.setContainer(engine);
        return engine;
    }

这段代码在new Connector时,会传入所能处理的协议,这个默认就是http1.1协议,会根据这个协议创建对应的处理该协议的handle,而Connector的start方法里,就会启动这个handle,而这个handle的start方法会启动一个NioEndpoint对象,在这个NioEndpoint对象的启动方法里就会启动一个Acceptor,来监听端口上的socket的accept事件,这个Acceptor就是Tomcat处理请求的总入口。
connector构造方法截图如下:
在这里插入图片描述

结合上面的Tomcat的架构图,在这个getWebServer方法里,生成了所有容器,并且声称一个默认的servlet,下面开始分析是怎么启动的,并且在启动过程中是怎么将spring自己的Servlet注册到Context容器中的。

public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
		Assert.notNull(tomcat, "Tomcat Server must not be null");
		this.tomcat = tomcat;
		this.autoStart = autoStart;
		// 初始化并启动tomcat
		initialize();
	}
	
private void initialize() throws WebServerException {
		logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
		synchronized (this.monitor) {
			try {
				addInstanceIdToEngineName();

				Context context = findContext();
				context.addLifecycleListener((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
				this.tomcat.start();

				// We can re-throw failure exception directly in the main thread
				rethrowDeferredStartupExceptions();

				try {
					ContextBindings.bindClassLoader(context, context.getNamingToken(), 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) {
				stopSilently();
				destroySilently();
				throw new WebServerException("Unable to start embedded Tomcat", ex);
			}
		}
	}	

这里debug进tomcat.start()方法里,会发现是启动tomcat对象里的server对象:

public void start() throws LifecycleException {
        getServer();
        server.start();
    }

这个server对象是在上面的getWebServer方法里的,tomcat.getService()方法里生成的StandardServer类,可以看下这个类图,继承自LifecycleBase抽象类,tomcat里的server、service、connector、engine、host、context、wrapper容器都会继承自这个抽象类,该类的start方法抽象了事件的通知逻辑
在这里插入图片描述

LifecycleBase类start方法代码如下:

public final synchronized void start() throws LifecycleException {

        if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
                LifecycleState.STARTED.equals(state)) {

            if (log.isDebugEnabled()) {
                Exception e = new LifecycleException();
                log.debug(sm.getString("lifecycleBase.alreadyStarted", toString()), e);
            } else if (log.isInfoEnabled()) {
                log.info(sm.getString("lifecycleBase.alreadyStarted", toString()));
            }

            return;
        }
        // 如果容器的当前状态为新建状态,则进行init方法
        if (state.equals(LifecycleState.NEW)) {
            init();
        } else if (state.equals(LifecycleState.FAILED)) {
        // 如果容器的当前状态为失败状态,则进行stop方法
            stop();
        } else if (!state.equals(LifecycleState.INITIALIZED) &&
                !state.equals(LifecycleState.STOPPED)) {
        // 如果容器的当前状态为失败状态,则进行stop方法
            invalidTransition(Lifecycle.BEFORE_START_EVENT);
        }

        try {
        // 设置容器状态,并发布事件
            setStateInternal(LifecycleState.STARTING_PREP, null, false);
            // 调用子类实现的start方法
            startInternal();
            if (state.equals(LifecycleState.FAILED)) {
                // This is a 'controlled' failure. The component put itself into the
                // FAILED state so call stop() to complete the clean-up.
                stop();
            } else if (!state.equals(LifecycleState.STARTING)) {
                // Shouldn't be necessary but acts as a check that sub-classes are
                // doing what they are supposed to.
                invalidTransition(Lifecycle.AFTER_START_EVENT);
            } else {
                setStateInternal(LifecycleState.STARTED, null, false);
            }
        } catch (Throwable t) {
            // This is an 'uncontrolled' failure so put the component into the
            // FAILED state and throw an exception.
            handleSubClassException(t, "lifecycleBase.startFail", toString());
        }
    }

Tomcat整个start方法的启动及初始化链如下:
在这里插入图片描述

springboot在构建并启动tomcat过程分为两个阶段:

  • 第一阶段是初始化每一级的容器,但是并不启动Connector,原因是开启了connector就会开始监听端口上的socket链接,而此时Spring的bean都没有初始化完成,无法处理请求。
  • 第二阶段就是当spring的所有bean都初始化完成后,启动connector。
    以下截图是AbstractApplicationContext的refresh方法,可以看出这个逻辑:
    在这里插入图片描述

二、Springboot怎么将DispatcherServlet注册到tomcat容器中?

Springboot用来实现Servlet的类是DispatcherServlet,在spring-boot-autoconfigure包的spring.factories里配置了自动装载类DispatcherServletAutoConfiguration,在这个类里加载了DispatcherServlet这个bean:


		@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
		public DispatcherServlet dispatcherServlet(HttpProperties httpProperties, WebMvcProperties webMvcProperties) {
			DispatcherServlet dispatcherServlet = new DispatcherServlet();
			dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
			dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
			dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
			dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
			dispatcherServlet.setEnableLoggingRequestDetails(httpProperties.isLogRequestDetails());
			return dispatcherServlet;
		}

同样在这个类里加载了DispatcherServletRegistrationBean,这个bean负责将servlet注册到tomcat中,可以看到在构造这个bean时,会将spring生成的DispatcherServlet通过构造器传入到DispatcherServletRegistrationBean

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

这个类实现了ServletContextInitializer接口,这个接口的作用就是在context容器初始化完成后通过onStartup方法为context添加servlet、filter等逻辑,类图如下:
在这里插入图片描述

那么这个类的触发流程图如下:
在这里插入图片描述

触发入口是在context容器的start方法里,在context容器的pipeline的start方法后,开始调用所有ServletContainerInitializer的onStartup方法,而在ServletContainerInitializer的实现类TomcatStarter的onStartup方法里,则是调用spring容器管理的ServletContextInitializer的onStartup方法来实现DispatcherServlet的注册,这里有课扩展点是我们也可以扩展自己的ServletContextInitializer类.
下面是实现了ServletContainerInitializer接口的TomcatStarter类的onStartup方法debug截图:
在这里插入图片描述

下面是负责注册DispatcherServelt的实现了ServletContextInitializer接口的lambda表达式的代码debug截图,这段lambda表达式添加到TomcatStarter里的代码在上面
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值