【六】SpringBoot源码分析之启动内置Tomcat(Tomcat组件、生命周期简介、一次请求)

目录

一、简介

1.1Tomcat的主要组件

1.1.1.Server:

1.1.2.Sevice:

1.1.3.Connector:

1.1.4.Container:

1.1.5.Component

1.2Lifecycle生命周期

1.2.1类图

1.2.2 状态图

 1.2.3接口Lifecycle

 1.2.4 LifecycleBase

二、Spring Boot启动内置Tomcat源码解读

2.1AbstractApplicationContext类的refresh()方法启动Tomcat

2.2EmbeddedWebApplicationContext类CreateEmbeddedServletContainer方法

2.2.1getSelfInitializer方法

2.2.2getEmbeddedServletContainer方法

2.3EmbeddedWebApplicationContext类的startEmbeddedServletContainer方法

2.3.1TomcatEmbeddedServletContainer类的start()方法


一、简介

Tomcat 就是一个 Servlet 容器, 能接收用户从浏览器发来的请求, 然后转发给 Servlet 处理, 把处理完的响应数据发回浏览器。

在实际的应用中,Apache/Nginx 作为静态资源服务器和负载均衡服务器放在最前端, 后面是 tomcat 组成的集群。

如果用户请求的是静态资源, Nginx直接处理, 如果是动态资源(如xxx.jsp), Nginx就会按照一定的算法转发的某个 Tomcat上, 达到负载均衡的目的。

示例全是使用的spring boot 1.5.8的内置tomcat。为tomcat8.5。用的http 1.1 和nio。

组件及一次请求的图

1.1Tomcat的主要组件

1.1.1.Server:

是整个Tomcat的容器,里面包含tomcat的所有组件,可以包含多个Service。

1.1.2.Sevice:

是包含Connector和Container的集合。

Service用合适的Connector接收用户的请求,再发给相应的Container来处理。

一个 Service 可以设置多个 Connector,但是只能有一个 Container 容器。

1.1.3.Connector:

负责的是底层的网络通信的实现。监听端口。

支持HTTP协议和AJP协议(用于tomcat和apache静态服务器通信)。

Connector接受请求,以命令的方式交给Container去处理请求,该过程由命令模式实现。

1.接受socket

2.从socket获取数据包,并解析成HttpServletRequest对象

3.从engine容器开始走调用流程,经过各个value,到FilterChain,最后调用Servlet完成业务逻辑

4.返回response,关闭socket

其中:

1.1.3.1Http11NioProtocol

默认使用的HTTP/1.1 ,对于tomcat8 以更高版HTTP/1.1协议会默认使用nio,即是org.apache.coyote.http11.Http11NioProtocol。

    
org.apache.coyote.http11.Http11Protocolbio使用ServerSocket处理请求JIoEndpoint
org.apache.coyote.http11.Http11NioProtocolnio使用SocketChannel处理请求NioEndpoint
org.apache.coyote.http11.Http11Nio2Protocolnio2使用AsynchronousSocketChannel处理请求Nio2Endpoint
org.apache.coyote.http11.Http11AprProtocolapr AprEndPoint

Connector在处理HTTP请求时,不同的Tomcat版本支持的protocol不同,其中最典型的protocol包括BIO、NIO和APR(Tomcat7中支持这3种,Tomcat8增加了对NIO2的支持,而到了Tomcat8.5和Tomcat9.0,则去掉了对BIO的支持)。 

如果没有指定protocol,则使用默认值HTTP/1.1,在Tomcat7中,自动选取使用BIO或APR,在Tomcat8中,自动选取使用NIO或APR(如果找到APR需要的本地库,则使用APR,否则使用NIO)。

1.1.3.2 NioEndpoint 中有。

Acceptor,初始启动1个线程,名字:http-nio-8083-Acceptor-0

Poller线程池,初始启动2个线程(单核 CPU 为 1,多核为 2),名字:http-nio-8083-ClientPoller-0

Executor线程池,初始启动核心线程数10个,最大线程数200,名字:http-nio-8083-exec-1

1.1.3.2.1 Acceptor 接受socket请求,将一个Java标准SocketChannel套接字请求通道封装成一个Tomcat的NioChannel,从Poller线程池中拿一个线程出来,进入Poller的events队列,把这个请求委托个Poller线程去处理。Acceptor--------Poller是一个生产者-消费者模式。

方法调用顺序图

1.1.3.2.2 Poller 实现Runnable接口,run()方法中会轮询去消费events队列,拿出请求,然后委托的SocketProcessor和Executor的线程池去处理。

每个 poller 关联了一个 Selector。

每个poller维护了一个event同步队列。

方法调用顺序图

1.1.3.2.3 Executor 线程会启动NioEndPoint中SocketProcessor的doRun方法,该方法又会去调AbstractProtocol.ConnectionHandler的process方法。

1.1.3.3 AbstractProtocol.ConnectionHandler 对象维护了一个Http11Processor对象, 这个类中就把SocketProcessor和Http11Processor 关联起来。Http11Processor的service方法会去调CoyoteAdapter。

1.1.3.4 CoyoteAdapter 将Connector组件和Engine容器连接起来,把Connector处理得到的Request和Response对象传到Engine容器,调用它的管道。

1.1.3.5 Mapper 客户端请求的路由导航组件,可以通过请求地址找到对应的Servlet

方法调用顺序图

1.1.4.Container:

负责的是上层servlet业务的实现。

Container由责任链模式实现

处理请求的容器,把处理请求的处理器包装为Valve(阀门)对象,并按一定顺序放入类型为Pipeline(管道)的管道里。

container的四个子容器组件:

1.1.4.1.Engine

一个 Engine 代表一个完整的 Servlet 引擎。

1.1.4.2.Host

一个 Host 在 Engine 中代表一个虚拟主机。

例如请求地址中的红色部分http://www.sid.com/post/index.html

1.1.4.3.Context:

就是我们所部属的具体Web应用的Context,每个请求都在是相应的Context里处理的。

例如请求地址中的红色部分http://www.sid.com/post/index.html

1.1.4.4.Wrapper:

每个Servlet都有相应的Wrapper来管理。Wrapper实际上是Tomcat容器内部对于 Servlet 的封装。

例如请求地址中的红色部分http://www.sid.com/post/index.html

wrapper主要包括三大类

1.处理静态资源的一个Wrapper:例如html,jpg.对应wrapper为org.apache.catalina.servlets.DefaultServlet

2.处理jsp文件,对应wrapper为org.apache.jasper.servlet.JspServlet

3.自定义servlet对象,在web.xml中定义的servlet

wrapper的pipeline中走到最后一个value会去调FitlerChain, FitlerChain走完了就调Servlet。

1.1.5.Component

1.1.5.1Manager管理器

被Container用来管理Session池

1.1.5.2logger日志管理

日志

1.1.5.3loader载入器

被Container用来载入各种所需的Class

1.1.5.4pipeline管道

使用责任链模式。要处理请求的话,请求会被放进容器的管道(pipeline)里面去。 管道中的各个Value(阀门)会“拦截”下管道中的需求做相应的处理。pipeline类似于FilterChain,每一个Value类似于一个Filter类。

每一层容器有自己的pipeline,不是所有容器共用一个pipeline。

1.1.5.5value管道中的阀门

每个Pipeline 都有特定的BaseValue ,而且是在Pipeline 管道的最后一个执行。

在上层容器的Pipeline 管道的最后一个特定BaseValue 中会调用下层容器的Pipeline 管道。

4个容器对应的特定BaseValue类名为:

StandardEngine---->StandardEngineValue

StandardHost---->StandardHostValue

StandardContext---->StandardContextValue

StandardWrapper---->StandardWrapperValue

 最后在StandardWrapperValue中会生成FilterChain,调filter

这里生成的Filter类有5个

name=characterEncodingFilter, filterClass=org.springframework.boot.web.filter.OrderedCharacterEncodingFilter
name=hiddenHttpMethodFilter, filterClass=org.springframework.boot.web.filter.OrderedHiddenHttpMethodFilter
name=httpPutFormContentFilter, filterClass=org.springframework.boot.web.filter.OrderedHttpPutFormContentFilter
name=requestContextFilter, filterClass=org.springframework.boot.web.filter.OrderedRequestContextFilter
name=Tomcat WebSocket (JSR356) Filter, filterClass=org.apache.tomcat.websocket.server.WsFilter

 filter调完了会调Servlet

调用的org.springframework.web.servlet.FrameworkServlet类的service方法,它的默认实现是DispatcherServlet。

这就进入spring了。

1.2Lifecycle生命周期

控制组件声明周期,由观察者模式实现。

1.2.1类图

1.2.2 状态图

 1.2.3接口Lifecycle

Tomcat通过org.apache.catalina.Lifecycle 接口来统一管理生命周期,所有有生命周期的组件都要实现 Lifecycle 接口。

该接口定义了13个 String 类型常量,用于 LifecycleEvent 时间的 type 属性中,作用是区分组件发出的 LifecycleEvent 事件时的状态。


    public static final String BEFORE_INIT_EVENT = "before_init";

    public static final String AFTER_INIT_EVENT = "after_init";

    public static final String START_EVENT = "start";

    public static final String BEFORE_START_EVENT = "before_start";

    public static final String AFTER_START_EVENT = "after_start";

    public static final String STOP_EVENT = "stop";

    public static final String BEFORE_STOP_EVENT = "before_stop";

    public static final String AFTER_STOP_EVENT = "after_stop";

    public static final String AFTER_DESTROY_EVENT = "after_destroy";

    public static final String BEFORE_DESTROY_EVENT = "before_destroy";

    public static final String PERIODIC_EVENT = "periodic";

    public static final String CONFIGURE_START_EVENT = "configure_start";

    public static final String CONFIGURE_STOP_EVENT = "configure_stop";

定义了3个监听器方法,用来添加、查找和删除 LifecycleListener 类型的监听器。

    //添加监听器
    public void addLifecycleListener(LifecycleListener listener);

    //获取所有监听器
    public LifecycleListener[] findLifecycleListeners();

    //移除某个监听器
    public void removeLifecycleListener(LifecycleListener listener);

定义了4个生命周期的方法

    //初始化
    public void init() throws LifecycleException;

    //开始
    public void start() throws LifecycleException;

    //结束
    public void stop() throws LifecycleException;

    //摧毁
    public void destroy() throws LifecycleException;

定义获取当前状态的方法 getState 和 getStateName,用来获取当前的状态。

    //获取声明周期状态
    public LifecycleState getState();

    //获取状态名字(字符串类型的声明周期状态)
    public String getStateName();

 1.2.4 LifecycleBase

Lifecycle接口的默认实现类是 org.apache.catalina.LifecycleBase,所有有生命周期的组件都直接或间接的继承自 LifecycleBase

LifecycleBase对Lifecycle接口的方法有基本的实现

但LifecycleBase还有几个未实现的模板方法,将由其子类实现

protected abstract void destroyInternal() throws LifecycleException;

protected abstract void initInternal() throws LifecycleException;

protected abstract void startInternal() throws LifecycleException;

protected abstract void stopInternal() throws LifecycleException;

二、Spring Boot启动内置Tomcat源码解读

环境SpringBoot 1.5.8

         <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

SpringBoot在启动的时候会去启动Tomcat。在如下方法中:

2.1AbstractApplicationContext类的refresh()方法启动Tomcat

其中

2.1.1 onRefresh()方法中会调用EmbeddedWebApplicationContext类的CreateEmbeddedServletContainer方法,

创建及初始化Tomcat类,并且启动,启动了Tomcat的Server、Service、Container(Engine、Host、Context、Wrapper、Realm、Pipeline、Value)、MapperListerner。

2.1.2 finishRefresh()方法会调用EmbeddedWebApplicationContext类的startEmbeddedServletContainer方法,

启动内置的ServletContainer,这里即是启动Tomcat的Connector。

发布内置ServletContainer已初始化的事件。

	protected void finishRefresh() {

        //调用父类的finishRefresh方法
		super.finishRefresh();

        //启动内置的ServletContainer,这里即是启动Tomcat余下的所有组件。
		EmbeddedServletContainer localContainer = startEmbeddedServletContainer();
		if (localContainer != null) {

            //发布内置ServletContainer已初始化的事件。
			publishEvent(
					new EmbeddedServletContainerInitializedEvent(this, localContainer));
		}
	}

2.2EmbeddedWebApplicationContext类CreateEmbeddedServletContainer方法

创建及初始化Tomcat类,并且启动,启动了Tomcat的Server、Service、Container(Engine、Host、Context、Wrapper、Realm、Pipeline、Value)、MapperListerner。

	private void createEmbeddedServletContainer() {
		EmbeddedServletContainer localContainer = this.embeddedServletContainer;
		ServletContext localServletContext = getServletContext();
        
        //第一次启动这两个都为空
		if (localContainer == null && localServletContext == null) {

            //得到Servlet容器工厂
			EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();

            //通过Servlet容器工厂创建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();
	}

重点在这句话。

this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer());

2.2.1getSelfInitializer方法

 先是getSlfInitializer返回一个ServletContextInitializer初始化器,用来初始化Tomcat。这个new出来的ServletContextInitializer实例的onStartup方法会在之后tomcat StandardContext启动中被调用

	private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
		return new ServletContextInitializer() {
			@Override
			public void onStartup(ServletContext servletContext) throws ServletException {
				selfInitialize(servletContext);
			}
		};
	}
	private void selfInitialize(ServletContext servletContext) throws ServletException {
		prepareEmbeddedWebApplicationContext(servletContext);
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(
				beanFactory);
		WebApplicationContextUtils.registerWebApplicationScopes(beanFactory,
				getServletContext());
		existingScopes.restore();
		WebApplicationContextUtils.registerEnvironmentBeans(beanFactory,
				getServletContext());
		for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
			beans.onStartup(servletContext);
		}
	}

最后的for循环做的事情对应console中打印的日志:

映射了dispatcherServlet、characterEncodingFilter、hiddenHttpMethodFilter、httpPutFormContentFilter、requestContextFilter的URL

2.2.2getEmbeddedServletContainer方法

2.2.2.1getSlfInitializer方法

产生的初始化器会作为getEmbeddedServletContainer方法的入参。

这个方法前面的都是生成各种组件,配置各种组件,最后的getTomcatEmbeddedServletContainer(tomcat)创建tomcat。

	public EmbeddedServletContainer getEmbeddedServletContainer(
			ServletContextInitializer... initializers) {
        
        //生成tomcat类
		Tomcat tomcat = new Tomcat();

        //创建临时目录
		File baseDir = (this.baseDirectory != null ? this.baseDirectory
				: createTempDir("tomcat"));

        //设置基础路径
		tomcat.setBaseDir(baseDir.getAbsolutePath());

        //创建Connector 协议用的 Http11NioProtocol
		Connector connector = new Connector(this.protocol);

        //创建StandardServer,初始化基础路径。
        //创建StandardService,设置名字
        //把service注册到server中
        //把Connector注册到service中
		tomcat.getService().addConnector(connector);

        //设置connector的一些属性,端口,协议地址,URI编码,SSL,压缩
		customizeConnector(connector);

        //设置connector
		tomcat.setConnector(connector);

        //创建并且配置Engine,注册到service中。
        //创建StandradHost,把engine.addChild(host)
        //host关闭自动部署
		tomcat.getHost().setAutoDeploy(false);

        //配置engine,设置后台线程延迟时间
		configureEngine(tomcat.getEngine());

        //第一次启动size=0 后面再来看它做了啥
		for (Connector additionalConnector : this.additionalTomcatConnectors) {
			tomcat.getService().addConnector(additionalConnector);
		}

        //准备context,这里的initializers就是前面外层getSlfInitializer方法生成传进来的
        //创建Context,这里用的TomcatEmbeddedContext,设置属性(名字,显示名字,路径,doc路径,生命周期监听器,类加载器)
        //创建StandardWrapper,配置属性,servlet设置成默认的org.apache.catalina.servlets.DefaultServlet
        //把servlet/Wrapper注册到context中
        //把servletmapping注册到context中
        //给context添加声明周期监听器
        //合并初始化器
        //配置context,设置starter,添加初始化器,配置session
        //把context加入到host中
		prepareContext(tomcat.getHost(), initializers);
    
        //创建tomcat内置servlet容器并且初始化,启动tomcat
		return getTomcatEmbeddedServletContainer(tomcat);
	}

跟到2.2.2.2 getTomcatEmbeddedServletContainer(tomcat)方法中

	protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
			Tomcat tomcat) {
		return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0);
	}
	public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) {
		Assert.notNull(tomcat, "Tomcat Server must not be null");
		this.tomcat = tomcat;
		this.autoStart = autoStart;
		initialize();
	}

在创建该类的时候会执行一个2.2.2.3 initialize方法,该方法中正儿八经开始启动tomcat

private void initialize() throws EmbeddedServletContainerException {
		TomcatEmbeddedServletContainer.logger
				.info("Tomcat initialized with port(s): " + getPortsDescription(false));
		synchronized (this.monitor) {
			try {
                //全局原子变量containerCounter+1,由于初始值是-1
				addInstanceIdToEngineName();
				try {

					//将上面new的connection以service(这里是StandardService[Tomcat])做key保存到private final Map<Service, Connector[]> serviceConnectors中
                    //并将StandardService中的protected Connector[] connectors与service解绑               
                    //解绑后下面利用LifecycleBase启动容器就不会启动到Connector了。
					removeServiceConnectors();


					//启动tomcat服务,触发监听器
					this.tomcat.start();

					// 检查初始化过程中的异常,如果有直接在主线程抛出
					rethrowDeferredStartupExceptions();

					Context context = findContext();
					try {

                        //绑定context和classloader
						ContextBindings.bindClassLoader(context, getNamingToken(context),
								getClass().getClassLoader());
					}
					catch (NamingException ex) {
						// Naming is not enabled. Continue
					}

					// Tomcat所有的线程都是守护线程
                    //所以创建一个非守护线程来避免服务到这就shutdown了
					startDaemonAwaitThread();
				}
				catch (Exception ex) {
					containerCounter.decrementAndGet();
					throw ex;
				}
			}
			catch (Exception ex) {
				throw new EmbeddedServletContainerException(
						"Unable to start embedded Tomcat", ex);
			}
		}
	}

重点看一下方法

this.tomcat.start();

跟进去到Tomcat类

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

这里可以看到2.2.2.4 server.start();

调用该方法后会依次调用:

StandardServer类的startInternal()

    StandardService类的startInternal()

        StandardEngine类的startInternal()

            ContainerBase类的startInternal()

                RealmBase类的startInternal()

                StandardPipeline类的startInternal()

                    ValueBase类的startInternal()

        MapperListener类的startInternal()

即在这里启动Server的时候就把Service、Container(Engine 、Realm、Pipeline、Value)、 Mapperlistener一起启动了。

给Container添加监听器(ContainerListener、LifecycleListener

2.2.2.4  ContainerBase类的startInternal方法

这里要注意ContainerBase类的startInternal方法。

StandardEngine[Tomcat].StandardHost[localhost]的启动与StandardEngine不在同一个线程中。

StandardEngine[Tomcat].StandardHost[localhost]的启动在ContainerBase类的startInternal方法中另外起了一个线程来启动

protected synchronized void startInternal() throws LifecycleException {
        //省略...

        List<Future<Void>> results = new ArrayList<>();
        for (int i = 0; i < children.length; i++) {
            results.add(startStopExecutor.submit(new StartChild(children[i])));
        }

        boolean fail = false;
        for (Future<Void> result : results) {
            try {
                result.get();
            } catch (Exception e) {
                log.error(sm.getString("containerBase.threadedStartFailed"), e);
                fail = true;
            }

        }
        if (fail) {
            throw new LifecycleException(
                    sm.getString("containerBase.threadedStartFailed"));
        }

         //省略...

    }

这句话就是另外启动一个线程来启动 StandardEngine[Tomcat].StandardHost[localhost]

这里该线程的名字叫Tomcat-startStop-1,5,main

results.add(startStopExecutor.submit(new StartChild(children[i]))); 

这个线程会启动StandardHost、没有ErrorReportValue则在PipeLine中添加

再启动一个线程localhost-startStop-1,5,main,该线程启动StandardContext、启动StandardWrapper

2.3EmbeddedWebApplicationContext类的startEmbeddedServletContainer方法

启动内置的ServletContainer,这里即是启动Tomcat的Connector。

发布内置ServletContainer已初始化的事件。

	private EmbeddedServletContainer startEmbeddedServletContainer() {
		EmbeddedServletContainer localContainer = this.embeddedServletContainer;
		if (localContainer != null) {
            //启动内置的tomcat余下所有组件
			localContainer.start();
		}
		return localContainer;
	}

跟到localContainer.start()方法中可以看到是调用了TomcatEmbeddedServletContainer类的start()方法

2.3.1TomcatEmbeddedServletContainer类的start()方法

public void start() throws EmbeddedServletContainerException {
		synchronized (this.monitor) {
			if (this.started) {
				return;
			}
			try {

                //启动Service对应的每个Connector
                    //创建CoyoteAdapter
                    //初始化Http11protocol
                    //初始化NioEndPoint
                    //启动Connector
                    //启动Http11protocol
                    //启动NioEndPoint,绑定监听端口启动,启动selector线程(1个),创建Executor线程池(核心线程数10,最大线程数200),初始化最大连接数connectionLatch默认10000,开始poller线程池(2个线程)、开始Acceptor线程(1个)
				addPreviouslyRemovedConnectors();

				Connector connector = this.tomcat.getConnector();

				if (connector != null && this.autoStart) {
					startConnector(connector);
				}
        
                //检查connector是否已经启动
				checkThatConnectorsHaveStarted();
				this.started = true;
				TomcatEmbeddedServletContainer.logger
						.info("Tomcat started on port(s): " + getPortsDescription(true));
			}
			catch (ConnectorStartFailedException ex) {
				stopSilently();
				throw ex;
			}
			catch (Exception ex) {
				throw new EmbeddedServletContainerException(
						"Unable to start embedded Tomcat servlet container", ex);
			}
			finally {
				Context context = findContext();
				ContextBindings.unbindClassLoader(context, getNamingToken(context),
						getClass().getClassLoader());
			}
		}
	}

主要是启动Tomcat的Connector

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值