servlet_tomcat

  1. 版本情况
  2. Tomcat的顶层结构
  3. Tomcat的启动过程
    1. Catalina的启动过程
    2. Server的启动过程
    3. Service的启动过程
    4. 总结
  4. 请求处理
    1. Connector的结构
    2. Pipeline-Value管道

阅读的目的?我想要从中获得什么?

​ 对tomcat容器的分析 总结 ,实现servlet规范 web容器

版本情况

image-20190203155202660

Tomcat的顶层结构

  • Catalina 管理整个Tomcat的管理类

  • Server 最顶层容器,代表整个服务器

  • Service 提供具体服务 (多个)

  • Connector 负责网络连接、request/response的创建(可以有多个连接,从servet.xml的配置也可以看出,同时提供http和https,也可以提供相同协议不同端口的连接)

  • Container 具体处理Servlet

img

贴个图(源自这里https://my.oschina.net/masterworker/blog/844225)

Tomcat的启动过程

org.apache.catalina.startup.Bootstrap 是Tomcat的入口,作用类似一个CatalinaAdptor,具体处理还是Catalina来完成,这样做的好处是可以把启动的入口和具体的管理类分开,从而可以很方便地创建出多种启动方式。

BootStrap不在Tomcat依赖包下 ,而是在bin目录 通过反射 完全松耦合

package org.apache.catalina.startup;

import ...;

public final class Bootstrap {
    private static final Log log = LogFactory.getLog(Bootstrap.class);
    /**
     * Daemon object used by main.
     */
    private static Bootstrap daemon = null;
    /**
     * Daemon reference.
     */
    private Object catalinaDaemon = null;

    ClassLoader commonLoader = null;
    ClassLoader catalinaLoader = null;
    ClassLoader sharedLoader = null;
    private void initClassLoaders() {
        try {
            commonLoader = createClassLoader("common", null);
            if( commonLoader == null ) {
                // no config file, default to this loader - we might be in a 'single' env.
                commonLoader=this.getClass().getClassLoader();
            }
            catalinaLoader = createClassLoader("server", commonLoader);
            sharedLoader = createClassLoader("shared", commonLoader);
        } catch (Throwable t) {
            handleThrowable(t);
            log.error("Class loader creation threw exception", t);
            System.exit(1);
        }
    }

    private ClassLoader createClassLoader(String name, ClassLoader parent)
        throws Exception {

        String value = CatalinaProperties.getProperty(name + ".loader");
        if ((value == null) || (value.equals("")))
            return parent;

        value = replace(value);

        List<Repository> repositories = new ArrayList<>();

        String[] repositoryPaths = getPaths(value);

        for (String repository : repositoryPaths) {
            // Check for a JAR URL repository
            try {
                @SuppressWarnings("unused")
                URL url = new URL(repository);
                repositories.add(new Repository(repository, RepositoryType.URL));
                continue;
            } catch (MalformedURLException e) {
                // Ignore
            }

            // Local repository
            if (repository.endsWith("*.jar")) {
                repository = repository.substring
                    (0, repository.length() - "*.jar".length());
                repositories.add(
                        new Repository(repository, RepositoryType.GLOB));
            } else if (repository.endsWith(".jar")) {
                repositories.add(
                        new Repository(repository, RepositoryType.JAR));
            } else {
                repositories.add(
                        new Repository(repository, RepositoryType.DIR));
            }
        }
        return ClassLoaderFactory.createClassLoader(repositories, parent);
    }

    /**
     * Initialize daemon.
     * @throws Exception Fatal initialization error
     */
    public void init() throws Exception {
        initClassLoaders();

        Thread.currentThread().setContextClassLoader(catalinaLoader);

        SecurityClassLoad.securityClassLoad(catalinaLoader);

        // Load our startup class and call its process() method
        if (log.isDebugEnabled())
            log.debug("Loading startup class");
        Class<?> startupClass =
            catalinaLoader.loadClass
            ("org.apache.catalina.startup.Catalina");
        Object startupInstance = startupClass.newInstance();

        // Set the shared extensions class loader
        if (log.isDebugEnabled())
            log.debug("Setting startup class properties");
        String methodName = "setParentClassLoader";
        Class<?> paramTypes[] = new Class[1];
        paramTypes[0] = Class.forName("java.lang.ClassLoader");
        Object paramValues[] = new Object[1];
        paramValues[0] = sharedLoader;
        Method method =
            startupInstance.getClass().getMethod(methodName, paramTypes);
        method.invoke(startupInstance, paramValues);

        catalinaDaemon = startupInstance;
    }
    
    /**
     * Load daemon.
     */
    private void load(String[] arguments)
        throws Exception {

        // Call the load() method
        String methodName = "load";
        Object param[];
        Class<?> paramTypes[];
        if (arguments==null || arguments.length==0) {
            paramTypes = null;
            param = null;
        } else {
            paramTypes = new Class[1];
            paramTypes[0] = arguments.getClass();
            param = new Object[1];
            param[0] = arguments;
        }
        Method method =
            catalinaDaemon.getClass().getMethod(methodName, paramTypes);
        if (log.isDebugEnabled())
            log.debug("Calling startup class " + method);
        method.invoke(catalinaDaemon, param);
    }

    // ----------------------------------------------------------- Main Program

    /**
     * Load the Catalina daemon.
     * @param arguments Initialization arguments
     * @throws Exception Fatal initialization error
     */
    public void init(String[] arguments)
        throws Exception {
        init();
        load(arguments);

    }
    
    /**
     * Start the Catalina daemon.
     * @throws Exception Fatal start error
     */
    public void start()
        throws Exception {
        if( catalinaDaemon==null ) init();

        Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
        method.invoke(catalinaDaemon, (Object [])null);
    }

    /**
     * Stop the Catalina Daemon.
     * @throws Exception Fatal stop error
     */
    public void stop()
        throws Exception {
        //实现略,主要通过反射调用了catalina的stop
    }

    /**
     * Stop the standalone server.
     * @throws Exception Fatal stop error
     */
    public void stopServer()
        throws Exception {
        //实现略,主要通过反射调用了catalina的stopServer
    }
    
    /**
     * Set flag.
     * @param await <code>true</code> if the daemon should block
     * @throws Exception Reflection error
     */
    public void setAwait(boolean await)
        throws Exception {
        //实现略 ,主要通过反射调用了catalina的setAwait
    }

    public boolean getAwait()
        throws Exception{
        //实现略 ,主要通过反射调用了catalina的getAwait
    }

    /**
     * Destroy the Catalina Daemon.
     */
    public void destroy() {
        // FIXME
    }
    /**
     * Main method and entry point when starting Tomcat via the provided
     * scripts.
     *
     * @param args Command line arguments to be processed
     */
    public static void main(String args[]) {

        if (daemon == null) {
            // Don't set daemon until init() has completed
            Bootstrap bootstrap = new Bootstrap();
            try {
                bootstrap.init();
            } catch (Throwable t) {
                handleThrowable(t);
                t.printStackTrace();
                return;
            }
            daemon = bootstrap;
        } else {
            // When running as a service the call to stop will be on a new
            // thread so make sure the correct class loader is used to prevent
            // a range of class not found exceptions.
            Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
        }
        try {
            String command = "start";
            if (args.length > 0) {
                command = args[args.length - 1];
            }
            if (command.equals("startd")) {
                args[args.length - 1] = "start";
                daemon.load(args);
                daemon.start();
            } else if (command.equals("stopd")) {
                args[args.length - 1] = "stop";
                daemon.stop();
            } else if (command.equals("start")) {
                daemon.setAwait(true);
                daemon.load(args);
                daemon.start();
            } else if (command.equals("stop")) {
                daemon.stopServer(args);
            } else if (command.equals("configtest")) {
                daemon.load(args);
                if (null==daemon.getServer()) {
                    System.exit(1);
                }
                System.exit(0);
            } else {
                log.warn("Bootstrap: command \"" + command + "\" does not exist.");
            }
        } catch (Throwable t) {
            // Unwrap the Exception for clearer error reporting
            if (t instanceof InvocationTargetException &&
                    t.getCause() != null) {
                t = t.getCause();
            }
            handleThrowable(t);
            t.printStackTrace();
            System.exit(1);
        }

    }
}
Tomcat 启动脚本 startup.bat 是从main方法中开始的。其中主要做了:
  • 准备容器环境,init()初始化类加载器,

  • 初始化容器,调用load() 实际是调用catalina里的init()

  • 启动容器,通过引用catalinaDaemon 反射射调用start()方法(实际还是通过catalina操作容器)

     关于类加载,我们都知道 J2EE 默认的类加载机制是双亲委派原则(详细查看如下🔎https://www.cnblogs.com/miduos/p/9250565.html)
    

    通过debug可以发现 commonLoader、catalinaLoader 、sharedLoader 其实三个是同一个(底层都是URLClassLoader),原因是因为catalina.properties 的配置中默认是空的。

    另外在init()Thread.currentThread().setContextClassLoader(catalinaLoader);

Catalina的启动过程

Catalina的启动主要是调用setAwait()load()start()方法来完成。

  • setAwait() 方法用于设置Server启动完成后是否进入等待状态的标记
  • load() 方法主要是用来加载配置文件conf/server.xml创建Server对象 (解析是通过Digester),然后调用Server的init()
  • start() 主要是调用Server的 start()

Server的启动过程

Server的默认实现org.apache.catalina.core.StandardServer ,在其父类中org.apache.catalina.util.LifecycleBase 中的init() 实现如下

@Override
public final synchronized void init() throws LifecycleException {
    if (!state.equals(LifecycleState.NEW)) {
        invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
    }

    try {
        setStateInternal(LifecycleState.INITIALIZING, null, false);
        initInternal();
        setStateInternal(LifecycleState.INITIALIZED, null, false);
    } catch (Throwable t) {
        ExceptionUtils.handleThrowable(t);
        setStateInternal(LifecycleState.FAILED, null, false);
        throw new LifecycleException(
                sm.getString("lifecycleBase.initFail",toString()), t);
    }
}

start() 实现如下

/**
 * {@inheritDoc}
 */
@Override
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;
    }

    if (state.equals(LifecycleState.NEW)) {
        init();
    } else if (state.equals(LifecycleState.FAILED)) {
        stop();
    } else if (!state.equals(LifecycleState.INITIALIZED) &&
            !state.equals(LifecycleState.STOPPED)) {
        invalidTransition(Lifecycle.BEFORE_START_EVENT);
    }

    try {
        setStateInternal(LifecycleState.STARTING_PREP, null, false);
        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.
        ExceptionUtils.handleThrowable(t);
        setStateInternal(LifecycleState.FAILED, null, false);
        throw new LifecycleException(sm.getString("lifecycleBase.startFail", toString()), t);
    }
}

其中 startInternal() 和 initInternal() 为模版方法 ,查看其实现类 可以发现是循环调用了每个servicestart()init()

Service的启动过程

类似于Server , StandardServiceinitInternal()startInternal()的方法主要调用containerexecutorsmapperListenerconnectorsinit()start()方法。

mapperListener是Mapper的监听器,可以监听container容器的变化

executors是用在connectors中管理线程的线程池,在server.xml配置文件中tomcatThreadPool

总结

下图为整个启动流程

image-20181218123224591

请求处理

​ Connector用于接收请求并将请求封装成RequestResponse来处理,最底层是使用Socket来进行连接的,封装完后交给Container进行处理(通过pipeline-Value管道来处理),Container处理完之后返回给Connector,最后Connector使用Socket将处理结果返回给客户端,整个请求就处理完了。

Connector的结构

Connector中具体是用ProtocolHandler来处理请求的,有多种不同的连接类型(Ajp、HTTP和Spdy)。

其中有3个非常重要的组件。(Connector的创建过程主要是初始化ProtocolHandler,serverx.xml中可配置)

  • Endpoint 用于处理底层的Socket的网络连接,用来实现TCP/IP协议
  • Processor 用于将Endpoint接收到的Socket封装成Request,用来实现HTTP协议(BIO、NIO、APR)
  • Adapter 用于将封装好的Request交给Container进行具体处理,将适配到Servlet容器(转换org.apache.coyote.Requestorg.apache.catalina.connector.Request

Ajp :连接器监听8009端口,负责和其他的HTTP服务器建立连接。在把Tomcat与其他HTTP服务器集成时,就需要用到这个连接器。

Apr : 是从操作系统级别解决异步IO问题,大幅度提高服务器的并发处理性能 http://tomcat.apache.org/tomcat-8.5-doc/apr.html

协议升级:在servlet 3.1之后新增 WebSocket协议,如果Processor处理之后Socket的状态是UPGRADING,则EndPoint中的handler回接着创建并调用upgrade包中的processor进行处理

request转换:Adapter转换后的request,其实就是封装了一层, public class Request implements org.apache.catalina.servlet4preview.http.HttpServletRequest

Pipeline-Value管道

​ Piepeline的管道模型和普通的责任链模式稍微有点不同,区别主要如下

  • 每个pipeline都有特定的value,而且是在管道的最后一个执行,这个Value叫做BaseValue
  • 上层的BaseValue(类似配货车到中转站的感觉)会调用下层容器的管道

每个BaseValue都有一个StandarValue的实现,例如WrapperValue的标准实现是StanderWrpperValue

​ 经过所有管道(EnginePipeline、HostPipeline、ContextPipeline、WrapperPieline)后,在最后的WrapperPieline的BaseValue中会创建FilterChain并调用其doFilter来处理请求,FilterChain包含着我们配置的与请求相匹配的FilterServlet


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 951488791@qq.com

文章标题:servlet_tomcat

字数:2.4k

本文作者:zhengyumin

发布时间:2019-02-03, 15:49:08

最后更新:2020-02-23, 18:42:14

原始链接:http://zyumin.github.io/2019/02/03/Overview_Tomcat/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。