Tomcat8.0.11组件的理解及在源码中的体现

Tomcat 8.0.11:

  要了解tomcat的优化,我们先看看Tomcat的官方定义:The Apache Tomcat® software is an open source implementation of the Java Servlet, JavaServer Pages, Java Expression Language and Java WebSocket technologies. The Java Servlet, JavaServer Pages, Java Expression Language and Java WebSocket specifications are developed under the Java Community Process.

  Apache Tomcat软件是一个开源的Java Servlet实现,JavaServer Pages,Java表达式语言和Java WebSocket技术。Java Servlet、JavaServer页面、Java表达式语言和Java WebSocket规范都是在Java Community Process下开发的。

Tomcat 系统架构:

  Tomcat 的缺省配置是不能稳定长期运行的,也就是不适合生产环境,它会死机,让你不断重新启动,甚至在午夜时分唤醒你。对于操作系统优化来说,是尽可能的增大可使用的内存容量、提高CPU 的频率,保证文件系统的读写速率等。经过压力测试验证,在并发连接很多的情况下,CPU 的处理能力越强,系统运行速度越快。

  从系统架构图再结合 conf/server.xml 中的标签配置来说,再结合tomcat的源码来看,每个组件都是对应Java中的一个类或者接口,先加载 server.xml 文件,解析文件中的标签组装成一个个的类,最后相互之间协同工作从而支撑起整个服务的运行,如果要对Tomcat本身进行优化的话,可以通过server.xml来改变相应组件的参数属性及行为方式来达到优化性能的目的,比如从架构图结合 server.xml我们可以得知其中比较重要的标签:Server,Services,Connector,Excutor,Engine,Host,Context等等。在官网中由如下介绍:

  • Server:在Tomcat世界中,服务器代表整个容器。Tomcat提供了一个服务器接口的默认实现,很少由用户自定义。
  • Service:Service是一个中间组件,它位于服务器内部并绑定一个或多个更多的连接器到一个引擎。服务元素很少由用户,作为默认的实现简单而充分:Service interface.Engine
  • Connector:Connector处理与客户机的通信。有多个连接器使用Tomcat。其中包括用于大多数HTTP的HTTP连接器流量,特别是在将Tomcat作为独立服务器和AJP连接器运行时哪个实现了将Tomcat连接到诸如此类的web服务器时使用的AJP协议Apache HTTPD服务器。创建一个定制的连接器是一项重要的工作。
  • Engine:Engine表示特定服务的请求处理管道。作为服务可能有多个连接器,引擎接收和处理所有请求将响应返回给相应的连接器传输到客户端。可以实现引擎接口来提供定制引擎,虽然这是不常见的。注意,该引擎可以通过jvmRoute用于Tomcat服务器集群参数。
  • Host:Host是一个网络名称(如www.yourcompany.com)与Tomcat的关联服务器。一个引擎可以包含多个Host,并且主机元素也支持网络别名,如yourcompany.com和abc.yourcompany.com。用户很少创建自定义主机,因为标准主机实现提供了重要的附加功能功能。
  • Context:Context表示web应用程序。一个主机可以包含多个上下文有唯一的路径。可以实现上下文接口来创建自定义但是这种情况很少发生,因为StandardContext提供了重要的信息额外的功能。Context中的Manager对象,查看Manager中的方法,可以发现有跟Session相关的。Session 的核心原理是通过 Filter 拦截 Servlet 请求,将标准的 ServletRequest 包装一下,换成 Spring 的Request 对象,这样当我们调用 Request 对象的 getSession 方法时,Spring 在背后为我们创建和管理Session。

两个核心组件:

  Connector:主要负责处理Socket连接,以及Request与Response的转化。设计思想 :高内聚、低耦合,其中涉及到的三个对象如下:

  • EndPoint:提供字节流给Processor,监听通信端口,是对传输层的抽象,用来实现 TCP/IP 协议的。对应的抽象类为AbstractEndPoint,有很多实现类,比如NioEndPoint,JIoEndPoint等。在其中有两个组件,一个是Acceptor,另外一个是SocketProcessor。Acceptor用于监听Socket连接请求,SocketProcessor用于处理接收到的Socket请求。
  • Processor:提供Tomcat Request对象给Adapter。Processor是用于实现HTTP协议的,也就是说Processor是针对应用层协议的抽象。Processor接受来自EndPoint的Socket,然后解析成Tomcat Request和Tomcat Response对象,最后通过Adapter提交给容器。对应的抽象类为AbstractProcessor,有很多实现类,比如AjpProcessor、Http11Processor等。
  • Adapter:提供ServletRequest给容器。ProtocolHandler接口负责解析请求并生成 Tomcat Request 类。需要把这个 Request 对象转换成 ServletRequest。Tomcat 引入CoyoteAdapter,这是适配器模式的经典运用,连接器调用 CoyoteAdapter 的 sevice 方法,传入的是Tomcat Request 对象,CoyoteAdapter 负责将 Tomcat Request 转成 ServletRequest,再调用容器的 service 方法。

  Endpoint接收Socket连接,生成一个SocketProcessor任务提交到线程池去处理。SocketProcessor的run方法会调用Processor组件去解析应用层协议,Processor通过解析生成Request对象后,会调用Adapter的service方法。

  Container:包括Engine、Host、Context和Wrapper,主要负责内部的处理以及Servlet的管理。

初始化及启动:

  接下去可以通过源码的方式来看看 tomcat 的初始化及启动的主流程。官网下载地址:https://archive.apache.org/dist/tomcat

  Tomcat 的入口类是 BootStrap 类的 main 方法:

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

    }

  启动的时候会走 bootstrap.init() :

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;

    }

  这里的主要操作是初始化自定义的ClassLoader ,然后反射调用 Catalina 的 setParentClassLoader 方法设置父加载类。值得注意的是,tomcat 定义的类加载器是WebAppClassLoader,打破了双亲委派模型:先自己尝试去加载这个类,找不到再委托给父类加载器。通过复写findClass和loadClass实现。初始化完成后会进入daemon.load(args),daemon.start(); 两个方法,也就是加载及启动,加载还是很好理解的,就是读取 Tomcat 的 server.xml 进行解析,加载成对应的类对象.然后启动。这里的 load 方法又通过反射调用 Catalina 的 load 方法,其中的一段代码如下:

InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
try {
    file = configFile();
    inputStream = new FileInputStream(file);
    inputSource = new InputSource(file.toURI().toURL().toString());
} catch (Exception e) {
    if (log.isDebugEnabled()) {
        log.debug(sm.getString("catalina.configFail", file), e);
    }
}

  可以发现这里有个文件,我们进入 configFile() 发现这个 File 就是server.xml

/**
* Pathname to the server configuration file.
*/
protected String configFile = "conf/server.xml";

protected File configFile() {

    File file = new File(configFile);
    if (!file.isAbsolute()) {
        file = new File(Bootstrap.getCatalinaBase(), configFile);
    }
    return (file);

}

  然后进行解析初始化,然后调用  getServer().init()  ,从Tomcat 系统架构图来看,各个组件的初始化理应从外圈开始,合理的流程应该是 server -> service ->Connector ->container .继续跟进到  LifecycleBase 的  init() 方法,然后调用本类的抽象方法 initInternal()。在这里所有的组件的生命周期是超级接口类 Lifecycle 来管理的。我们这里很显然会先走 Server。

  在 StandardServer 的 initInternal() 方法的最后会循环调用 Service 的初始化:

// Initialize our defined Services
for (int i = 0; i < services.length; i++) {
    services[i].init();
}

  在StandardService 的 initInternal() 方法中我们可以看到会初始化 container ,executor ,connector 三个组件,这里的 container 就是 Engine、Host、Context和 servlet。

@Override
    protected void initInternal() throws LifecycleException {

        super.initInternal();

        if (container != null) {
            container.init();
        }

        // Initialize any Executors
        for (Executor executor : findExecutors()) {
            if (executor instanceof JmxEnabled) {
                ((JmxEnabled) executor).setDomain(getDomain());
            }
            executor.init();
        }

        // Initialize mapper listener
        mapperListener.init();

        // Initialize our defined Connectors
        synchronized (connectorsLock) {
            for (Connector connector : connectors) {
                try {
                    connector.init();
                } catch (Exception e) {
                    String message = sm.getString(
                            "standardService.connector.initFailed", connector);
                    log.error(message, e);

                    if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
                        throw new LifecycleException(message);
                }
            }
        }
    }

  也就是这样,一步步的由外到内的一个初始化,daemon.start() 也是这么个流程,其中有一条线路是 Catalina#start()  -->  LifecycleBase#start() ---> Connector#startInternal() :

   这里涉及到的采用什么协议取决去 server.xml 的配置。本版本8.0.11 默认采用org.apache.coyote.http11.Http11NioProtocol,可以在 Connector 的构造函数中找到:Connector(String protocol)  --->  setProtocol(protocol):

public void setProtocol(String protocol) {

        if (AprLifecycleListener.isAprAvailable()) {
            if ("HTTP/1.1".equals(protocol)) {
                setProtocolHandlerClassName
                    ("org.apache.coyote.http11.Http11AprProtocol");
            } else if ("AJP/1.3".equals(protocol)) {
                setProtocolHandlerClassName
                    ("org.apache.coyote.ajp.AjpAprProtocol");
            } else if (protocol != null) {
                setProtocolHandlerClassName(protocol);
            } else {
                setProtocolHandlerClassName
                    ("org.apache.coyote.http11.Http11AprProtocol");
            }
        } else {
            if ("HTTP/1.1".equals(protocol)) {
                setProtocolHandlerClassName
                    ("org.apache.coyote.http11.Http11NioProtocol");
            } else if ("AJP/1.3".equals(protocol)) {
                setProtocolHandlerClassName
                    ("org.apache.coyote.ajp.AjpNioProtocol");
            } else if (protocol != null) {
                setProtocolHandlerClassName(protocol);
            }
        }

    }

  关注到上述图解中的ContainerBase.startInternal()方法

// Start our child containers, if any
Container children[] = findChildren();
List<Future<Void>> results = new ArrayList<>();
for (int i = 0; i < children.length; i++) {
 // 这句代码就是会调用ContainerBase下的一个个子容器的call方法
 results.add(startStopExecutor.submit(new StartChild(children[i])));
}

  查看new StartChild要执行的call方法

@Override
        public Void call() throws LifecycleException {
            child.start();
            return null;
        }

  StandardHost 将一个个web项目部署起来,  StandardHost#startInternal()  --->  ContainerBase#startInternal()  -->   ContainerBase#threadStart()  --->  ContainerBackgroundProcessor#run() --->   ContainerBase#backgroundProcess() --->HostConfig#lifecycleEvent(event)

public void lifecycleEvent(LifecycleEvent event) {

        // Identify the host we are associated with
        try {
            host = (Host) event.getLifecycle();
            if (host instanceof StandardHost) {
                setCopyXML(((StandardHost) host).isCopyXML());
                setDeployXML(((StandardHost) host).isDeployXML());
                setUnpackWARs(((StandardHost) host).isUnpackWARs());
                setContextClass(((StandardHost) host).getContextClass());
            }
        } catch (ClassCastException e) {
            log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);
            return;
        }

        // Process the event that has occurred
        if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
            check();
        } else if (event.getType().equals(Lifecycle.START_EVENT)) {
            start();
        } else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
            stop();
        }
    }

  根据事件类型去处理,这里是 check() :

protected void check() {
        if (host.getAutoDeploy()) {
            // Check for resources modification to trigger redeployment
       // 检查资源修改以触发重新部署
            DeployedApplication[] apps =
                deployed.values().toArray(new DeployedApplication[0]);
            for (int i = 0; i < apps.length; i++) {
                if (!isServiced(apps[i].name))
                    checkResources(apps[i]);
            }
            // Check for old versions of applications that can now be undeployed
        // 检查现在可以取消部署的应用程序的旧版本

            if (host.getUndeployOldVersions()) {
                checkUndeploy();
            }
            // Hotdeploy applications 部署应用
            deployApps();
        }
}

  deployApps() :

protected void deployApps() {

        File appBase = host.getAppBaseFile();
        File configBase = host.getConfigBaseFile();
        String[] filteredAppPaths = filterAppPaths(appBase.list());
        // Deploy XML descriptors from configBase
        deployDescriptors(configBase, configBase.list());
        // Deploy WARs
        deployWARs(appBase, filteredAppPaths);
        // Deploy expanded folders
        deployDirectories(appBase, filteredAppPaths);

}

  StandardContext.startInternal()解析web.xml和添加wrapper 。StandardContext#startInternal() ---> ContainerBase#startInternal()  -->   ContainerBase#threadStart()  --->  ContainerBackgroundProcessor#run() --->   ContainerBase#backgroundProcess() --->ContextConfig#lifecycleEvent(event)

public void lifecycleEvent(LifecycleEvent event) {

        // Identify the context we are associated with
        try {
            context = (Context) event.getLifecycle();
        } catch (ClassCastException e) {
            log.error(sm.getString("contextConfig.cce", event.getLifecycle()), e);
            return;
        }

        // Process the event that has occurred
        if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
            configureStart();
        } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
            beforeStart();
        } else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
            // Restore docBase for management tools
            if (originalDocBase != null) {
                context.setDocBase(originalDocBase);
            }
        } else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {
            configureStop();
        } else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
            init();
        } else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
            destroy();
        }

    }

  configureStart()  --->   ContextConfig#webConfig()  --->  ContextConfig#getContextWebXmlSource() 进行解析对应应用的 web.xml:

protected InputSource getContextWebXmlSource() {
        InputStream stream = null;
        InputSource source = null;
        URL url = null;

        String altDDName = null;

        // Open the application web.xml file, if it exists
        ServletContext servletContext = context.getServletContext();
        if (servletContext != null) {
            altDDName = (String)servletContext.getAttribute(Globals.ALT_DD_ATTR);
            if (altDDName != null) {
                try {
                    stream = new FileInputStream(altDDName);
                    url = new File(altDDName).toURI().toURL();
                } catch (FileNotFoundException e) {
                    log.error(sm.getString("contextConfig.altDDNotFound",
                                           altDDName));
                } catch (MalformedURLException e) {
                    log.error(sm.getString("contextConfig.applicationUrl"));
                }
            }
            else {//public static final String ApplicationWebXml = "/WEB-INF/web.xml";
                stream = servletContext.getResourceAsStream
                    (Constants.ApplicationWebXml);
                try {
                    url = servletContext.getResource(
                            Constants.ApplicationWebXml);
                } catch (MalformedURLException e) {
                    log.error(sm.getString("contextConfig.applicationUrl"));
                }
            }
        }
        if (stream == null || url == null) {
            if (log.isDebugEnabled()) {
                log.debug(sm.getString("contextConfig.applicationMissing") + " " + context);
            }
            if (stream != null) {
                try {
                    stream.close();
                } catch (IOException e) {
                    // Ignore
                }
            }
        } else {
            source = new InputSource(url.toExternalForm());
            source.setByteStream(stream);
        }

        return source;
    }

  ContextConfig.webConfig()的step9解析到servlets包装成wrapper对象:ContextConfig.webConfig()—>configureContext(webXml) :

for (ServletDef servlet : webxml.getServlets().values()) {
            Wrapper wrapper = context.createWrapper();
            // Description is ignored
            // Display name is ignored
            // Icons are ignored

            // jsp-file gets passed to the JSP Servlet as an init-param

            if (servlet.getLoadOnStartup() != null) {
                wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
            }
            if (servlet.getEnabled() != null) {
                wrapper.setEnabled(servlet.getEnabled().booleanValue());
            }
            wrapper.setName(servlet.getServletName());
            Map<String,String> params = servlet.getParameterMap();
            for (Entry<String, String> entry : params.entrySet()) {
                wrapper.addInitParameter(entry.getKey(), entry.getValue());
            }
            wrapper.setRunAs(servlet.getRunAs());
            Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs();
            for (SecurityRoleRef roleRef : roleRefs) {
                wrapper.addSecurityReference(
                        roleRef.getName(), roleRef.getLink());
            }
            wrapper.setServletClass(servlet.getServletClass());
            MultipartDef multipartdef = servlet.getMultipartDef();
            if (multipartdef != null) {
                if (multipartdef.getMaxFileSize() != null &&
                        multipartdef.getMaxRequestSize()!= null &&
                        multipartdef.getFileSizeThreshold() != null) {
                    wrapper.setMultipartConfigElement(new MultipartConfigElement(
                            multipartdef.getLocation(),
                            Long.parseLong(multipartdef.getMaxFileSize()),
                            Long.parseLong(multipartdef.getMaxRequestSize()),
                            Integer.parseInt(
                                    multipartdef.getFileSizeThreshold())));
                } else {
                    wrapper.setMultipartConfigElement(new MultipartConfigElement(
                            multipartdef.getLocation()));
                }
            }
            if (servlet.getAsyncSupported() != null) {
                wrapper.setAsyncSupported(
                        servlet.getAsyncSupported().booleanValue());
            }
            wrapper.setOverridable(servlet.isOverridable());
            context.addChild(wrapper);
        }

  StandardContext.startInternal()->最终会调用 StandardContext#loadOnStartup(findChildren()))  ---->  wrapper.load()

  详细流程请查阅官网:https://tomcat.apache.org/tomcat-8.0-doc/architecture/startup/serverStartup.pdf

 

posted @ 2019-02-10 13:25  吴振照  阅读(503)  评论(0编辑  收藏  举报