深入浅出Tomcat/4 - Tomcat容器

Container是一个Tomcat容器的接口,Tomcat有四种容器

·     Engine

·     Host

·     Context

·     Wrapper

Engine代表整个Catalina的Servlet引擎,Host则代表若干个上下文的虚拟主机。Context则代表一个Web应用,而一个Context则会用有多个Wrapper。Wrapper是一个单独的Servlet。

下图是几种容器实现的类继承图,我们可以看到最下层以Standard开头的几个类

·     StandardEngine

·     StandardHost

·     StandardContext

·     StandardWrapper

以上几个类是Tomcat对几种容器的默认实现。

以上几个类是Tomcat对几种容器的默认实现。

Engine

Engine的属性name,是Engine的名字,如果有多个Engine,Engine需要唯一。defaultHost也非常重要,如果一个Engine有多个Host时,如果匹配不到合适的Host时,则需要默认选取一个,也就是defaultHost定义的,它的值为Host的name。

<Engine name="Catalina" defaultHost="localhost">

  <RealmclassName="org.apache.catalina.realm.LockOutRealm">
    <!--This Realm uses the UserDatabase configured in the global JNDI
         resources under the key"UserDatabase".  Any edits
         that are performed against thisUserDatabase are immediately
         available for use by theRealm.  -->
    <RealmclassName="org.apache.catalina.realm.UserDatabaseRealm"
           resourceName="UserDatabase"/>
  </Realm>

  <Host name="localhost"  appBase="webapps"
        unpackWARs="true" autoDeploy="true">

    <!--SingleSignOn valve, share authentication between web applications
         Documentation at: /docs/config/valve.html-->
    <!--
    <ValveclassName="org.apache.catalina.authenticator.SingleSignOn" />
    -->

    <!-- Access log processes allexample.
         Documentation at:/docs/config/valve.html
         Note: The pattern used isequivalent to using pattern="common" -->
    <ValveclassName="org.apache.catalina.valves.AccessLogValve"directory="logs"
           prefix="localhost_access_log" suffix=".txt"
           pattern="%h %l %u %t &quot;%r&quot; %s %b" />

  </Host>
</Engine>

Engine还有另外一个非常重要的属性叫jvmRoute,它一般用在Cluster里。

假设Cluster是这么配置的,Tomcat1的 conf/server.xml

<Engine name="Catalina" defaultHost="localhost" jvmRoute="tomcat1">

Tomcat2的conf/server.xml

<Engine name="Catalina" defaultHost="localhost" jvmRoute="tomcat2">

在生成SessionID时,jvmRoute会用到的,代码如下:

public class StandardSessionIdGeneratorextends SessionIdGeneratorBase{
    @Override
    publicString generateSessionId(String route) {

        byterandom[] = newbyte[16];
        int sessionIdLength = getSessionIdLength();

        //Render the result as a String of hexadecimal digits
        // Start with enough space forsessionIdLength and medium route size
        StringBuilderbuffer = new StringBuilder(2 * sessionIdLength + 20);

        int resultLenBytes = 0;

        while (resultLenBytes < sessionIdLength) {
            getRandomBytes(random);
            for (int j = 0;
            j < random.length && resultLenBytes < sessionIdLength;
            j++) {
                byte b1 = (byte) ((random[j] & 0xf0) >> 4);
                byte b2 = (byte) (random[j] & 0x0f);
                if (b1 < 10)
                    buffer.append((char) (‘0‘ + b1));
                else
                    buffer.append((char) (‘A‘ + (b1 - 10)));
                if (b2< 10)
                    buffer.append((char) (‘0‘ + b2));
                else
                    buffer.append((char) (‘A‘ + (b2 - 10)));
                resultLenBytes++;
            }
        }

        if(route != null&& route.length() > 0) {
            buffer.append(‘.‘).append(route);
        }else {
            String jvmRoute =getJvmRoute();
            if (jvmRoute != null && jvmRoute.length() > 0) {
                buffer.append(‘.‘).append(jvmRoute);
            }
        }

        returnbuffer.toString();
    }
}

  

最后几行代码显示如果在Cluster情况下会将jvmRoute加在sessionID后面。

Host

Host是代表虚拟主机,主要设置appbase目录,例如webapps等。Host中的name代表域名,所以下面的例子中代表的localhost,可以通过localhost来访问。appBase是指该站点所在的目录,默认一般是webapps。unpackWARs这个属性也很重要,一般来说,一个webapp的发布包有格式各样,例如zip,war等,对于war包放到appBase

下是否自动解压缩,显而易见,当为true时,自动解包。autoDeploy是指是指Tomcat在运行时应用程序是否自动部署。

<Host name="localhost"  appBase="webapps"
      unpackWARs="true" autoDeploy="true">

Context

Context可以在以下几个地方声明:

1.    Tomcat的server.xml配置文件中的<Context>节点用于配置Context,它直接在Tomcat解析server.xml的时候,就完成Context对象的创建。

2.    Web应用的/META-INF/context.xml文件可用于配置Context,此配置文件用于配置Web应用对应的Context属性。

3.    可用%CATALINA_HOME%/conf[EngineName]/[HostName]/[Web项目名].xml文件声明创建一个Context。

4.    Tomcat全局配置为conf/context.xml,此文件配置的属性会设置到所有的Context中

5.    Tomcat的Host级别配置文件为/conf[EngineName]/[HostName]/context.xml.default文件,它配置的属性会设置到某Host下面所有的Context中。

以上5种方法有些是共享的,有些是独享的。其中后面2种是被Tomcat共享的。在实际的应用中,个人非常推荐第三种方法。如果在采用第一种方法,这种方法是有侵入性的,不建议,而且该文件是在Tomcat启动时才加载。对于共享的方法我个人也是不推荐使用的,毕竟在实际的应用中还是希望自己的app配置单独出来更合理一些。

Wrapper

Wrapper 代表一个Servlet,它负责管理一个Servlet,包括Servlet 的装载、初始化、执行以及资源回收。Wrapper的父容器一般是Context,Wrapper是最底层的容器,它没有子容器了,所以调用它的addChild 将会抛illegalargumentexception。Wrapper的实现类是StandardWrapper,StandardWrapper还实现了拥有一个Servlet 初始化信息的ServletConfig,由此看出StandardWrapper 将直接和Servlet 的各种信息打交道。

Container的启动

前面的类图讲过,前面提到的容容器都实现或继承了LifeCycle,所以LifeCycle里的几个生命周期同样适用于这里。不过除了继承自LifeCycle之外,几个容器也继承ContainerBase这个类。几个Container的初始化和启动都是通过initInternal和startInternal来实现的。需要的话,各个容器可以实现自己的逻辑。

因为4大容器都继承ContainerBase,我们看看该类的initInternal和startInternal的实现。

@Override
protected void initInternal() throws LifecycleException {
   reconfigureStartStopExecutor(getStartStopThreads());
    super.initInternal();
}

/*
 * Implementation note: If there is ademand for more control than this then
 * it is likely that the best solutionwill be to reference an external
 * executor.
 */
private void reconfigureStartStopExecutor(int threads) {
    if (threads == 1) {
        //Use a fake executor
        if(!(startStopExecutorinstanceof InlineExecutorService)) {
            startStopExecutor = new InlineExecutorService();
        }
    } else{
        //Delegate utility execution to the Service
        Serverserver = Container.getService(this).getServer();
        server.setUtilityThreads(threads);
        startStopExecutor= server.getUtilityExecutor();
    }
}

我们可以看到这里并没有设置一些状态。在初始化的过程中,初始化statStopExecutor,它的类型是java.util.concurrent.ExecutorService。

下面是startInternal的代码,我们可以看出这里做的事情:

1.    如果cluster和realm都配置后,需要调用它们自己的启动方法。

2.    调用子容器的启动方法。

3.    启动管道。

4.    设置生命周期的状态。

5.    同时启动一些background的监控线程。

@Override
protected synchronized void startInternal() throws LifecycleException {

    // Start our subordinate components, if any
    logger = null;
    getLogger();
    Cluster cluster = getClusterInternal();
    if (cluster instanceof Lifecycle) {
        ((Lifecycle) cluster).start();
    }
    Realm realm = getRealmInternal();
    if (realm instanceof Lifecycle) {
        ((Lifecycle) realm).start();
    }

    // Start our child containers, if any
    Container children[] = findChildren();
    List<Future<Void>> results = new ArrayList<>();
    for (int i = 0; i < children.length; i++) {
        results.add(startStopExecutor.submit(new StartChild(children[i])));
    }

    MultiThrowable multiThrowable = null;

    for (Future<Void> result : results) {
        try {
            result.get();
        } catch (Throwable e) {
            log.error(sm.getString("containerBase.threadedStartFailed"), e);
            if (multiThrowable == null) {
                multiThrowable = new MultiThrowable();
            }
            multiThrowable.add(e);
        }

    }
    if (multiThrowable != null) {
        throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"),
                multiThrowable.getThrowable());
    }

    // Start the Valves in our pipeline (including the basic), if any
    if (pipeline instanceof Lifecycle) {
        ((Lifecycle) pipeline).start();
    }

    setState(LifecycleState.STARTING);

    // Start our thread
    if (backgroundProcessorDelay > 0) {
        monitorFuture = Container.getService(ContainerBase.this).getServer()
                .getUtilityExecutor().scheduleWithFixedDelay(
                        new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS);
    }
}

这里首先根据配置启动了Cluster和Realm,启动的方法也很直观,直接调用它们的start方法。Cluster一般用于集群,Realm是Tomcat的安全域,管理资源的访问权限,例如身份认证,权限等。一个Tomcat可以拥有多个Realm的。

根据代码,子容器是使用startStopExecutor来实现的,startStopExecutor会使用新的线程来启动,这样可以使用多个线程来同时启动多个子容器,这样在性能上更胜一筹。因为可能有多个子容器,把他们存入到Future的List里,然后遍历每个Future并调用其get方法。

遍历Future的作用是什么?1,get方法是阻塞的,只有线程处理完后才能继续往下走,这样保证了Pipeline启动之前容器确保调用完成。2,可以处理启动过程中的异常,如果有容器启动失败,也不至于继续执行下去。

启动子容器调用了StartChild这么一个类似,它的实现如下:

private static class StartChild implements Callable<Void> {

    private Container child;

    public StartChild(Container child) {
        this.child = child;
    }

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

这个类也是定义在ContainerBase里的,所以所有容器的启动过程都对调用容器的start方法。

我们可以看到StartChild实现了Callable接口。我们知道启动线程,有Runnable和Callable等方式,那么Runnable和Callable的区别在哪里呢?我认为的区别是:

1.    对于实现Runnable,run方法并不会返回任何东西,但是对于Callable,真是可以实现当执行完成后返回结果的。但需要注意,一个线程并不能和Callable创建,尽可以和Runnable一起创建。

2.    另外一个区别就是Callable的Call方式可以抛出Exception,但是Runnable的run方法这不可以。

根据以上,我们可以看出为什么要用Callable,前面说捕获到异常也正是这个原理。

在这里我们也看到了Future这个东西。有必要在这里详细解释一下Future的概念。Future用来表示异步计算的结果,它提供了一些方法用来检查计算是否已经完成,或等待计算的完成以及获取计算的结果。计算结束后的结果只能通过get方法来获取。当然,也可以使用Cancel方法来取消计算。在回到我们这里的代码,如下,我们可以看到结果已经存在result里,通过get方法来获取,前面我们分析Callable可以抛出异常,这里我们可以看到有捕获到这些异常的代码。

for (Future<Void> result : results) {
    try {
        result.get();
    } catch (Throwable e) {
        log.error(sm.getString("containerBase.threadedStartFailed"), e);
        if (multiThrowable == null) {
            multiThrowable = new MultiThrowable();
        }
        multiThrowable.add(e);
    }

}

Engine

Engine的默认实现类是StandardEngine,它的初始化和启动会调用initInternal和startInternal。下面是StandardEngine的结构图

初始化和启动的代码分别如下:

@Override
protected void initInternal() throws LifecycleException {
    // Ensure that a Realm is present before any attempt is made to start
    // one. This will create the default NullRealm if necessary.
    getRealm();
    super.initInternal();
}

/**
 * Start this component and implement the requirements
 * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
 *
 * @exception LifecycleException if this component detects a fatal error
 *  that prevents this component from being used
 */
@Override
protected synchronized void startInternal() throws LifecycleException {

    // Log our server identification information
    if (log.isInfoEnabled()) {
        log.info(sm.getString("standardEngine.start", ServerInfo.getServerInfo()));
    }

    // Standard container startup
    super.startInternal();
}

初始化和启动还是分别调用了ContainerBase的initInternal·和startInternal。特别要注意的是initInternal额外调用了getRealm获取Realm的信息。那么getRealm的实现如下:

@Overridepublic Realm getRealm() {    Realm configured = super.getRealm();    // If no set realm has been called - default to NullRealm    // This can be overridden at engine, context and host level    if (configured == null) {        configured = new NullRealm();        this.setRealm(configured);    }    return configured;}

我们可以看出,如果没有realm配置,直接返回默认的NullRealm。

Host

Host的默认实现类是StandardHost,继承图如下。

下面代码只有startInternal,并没有initInternal,那是因为StandardHost并没有重写initInternal。

代码比较简单,除了调用ContainerBase的startInternal,前面还需要查询Pipeline里的Valve有没有和ErrorReport相关的。如果没有创建Valve一下,然后加到Pipeline里。

protected synchronized void startInternal() throws LifecycleException {

    // Set error report valve
    String errorValve = getErrorReportValveClass();
    if ((errorValve != null) && (!errorValve.equals(""))) {
        try {
            boolean found = false;
            Valve[] valves = getPipeline().getValves();
            for (Valve valve : valves) {
                if (errorValve.equals(valve.getClass().getName())) {
                    found = true;
                    break;
                }
            }
            if(!found) {
                Valve valve =
                    (Valve) Class.forName(errorValve).getConstructor().newInstance();
                getPipeline().addValve(valve);
            }
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            log.error(sm.getString(
                    "standardHost.invalidErrorReportValveClass",
                    errorValve), t);
        }
    }
    super.startInternal();
}

其中默认的ErrorReport Valve是

/**
 * The Java class name of the default error reporter implementation class
 * for deployed web applications.
 */
private String errorReportValveClass =
    "org.apache.catalina.valves.ErrorReportValve"

Context

下面是Context的初始化代码,后面调用了NamingResource相关信息。

@Override
protected void initInternal() throws LifecycleException {
    super.initInternal();

    // Register the naming resources
    if (namingResources != null) {
        namingResources.init();
    }

    // Send j2ee.object.created notification
    if (this.getObjectName() != null) {
        Notification notification = new Notification("j2ee.object.created",
                this.getObjectName(), sequenceNumber.getAndIncrement());
        broadcaster.sendNotification(notification);
    }
}

接下来看看startInternal,这个方法非常长,节选重要代码.

如果resouce没有启动,需要调用resource的启动,接下来是调用web.xml中定义的Listener,另外还需要初始化该配置文件定义的Filter以及load-on-startup的Servlet。

protected synchronized void startInternal() throws LifecycleException {
//… …
if (ok) {
        resourcesStart();
    }
//… …

        // Configure and call application event listeners
        if (ok) {
            if (!listenerStart()) {
                log.error(sm.getString("standardContext.listenerFail"));
                ok = false;
            }
        }

        //……

        // Configure and call application filters
        if (ok) {
            if (!filterStart()) {
                log.error(sm.getString("standardContext.filterFail"));
                ok = false;
            }
        }

        // Load and initialize all "load on startup" servlets
        if (ok) {
            if (!loadOnStartup(findChildren())){
                log.error(sm.getString("standardContext.servletFail"));
                ok = false;
            }
        }

原文地址:https://www.cnblogs.com/confach/p/10348037.html

时间: 2024-10-28 20:09:24

深入浅出Tomcat/4 - Tomcat容器的相关文章

Tomcat就是个容器,一种软件

1.tomcat就是一个容器而已,一个软件,运行在java虚拟机. 2.tomcat是一种能接收http协议的软件,java程序猿自己也可以写出http解析的服务器啊. 3.tomcat支持servlet,tomcat有自己写好的java jar包,java.servlet.*jar包,只需要咱们编写的程序import 这个包就好. 4.

JSP学习 —— 开篇:JSP,servlet容器,Tomcat,servlet容器之间的关系

JSP(JAVA SERVER PAGE)的缩写,其本身就是servlet的简化,是一种动态网页标准,其特点是在HTML代码中嵌入JAVA代码,JSP标签或用户标签来生成网页.至于它为什么会出现,主要原因在于早期的servlet技术在编写代码时经常通篇性的写一大堆HTML标签,静态文本及文本格式等表现逻辑,其开发效率非常之低下:为了解决这种情况,便随之出现了JSP,其静态部分(包括表现逻辑,如图片,文字等等)全用HTML语言来编写,只有需要动态生成的逻辑才由嵌入的JAVA代码来实现. 说到最后,

tomcat(5)servlet容器

[0]README 0.0)本文部分文字描述转自:“深入剖析tomcat”,旨在学习 tomcat(5)servlet容器 的基础知识: 0.1)intro to servlet容器:servlet容器是用来处理请求servlet资源,并为web客户端填充response 对象的模块: 0.1.1)servlet容器:是 org.apache.catalina.Container接口的实例: 0.1.2)在Tomcat中,共有四种容器(types): t1)Engine: t2)Host: t3

Session or Cookie?是否需要用Tomcat等Web容器的Session

Cookie是HTTP协议标准下的存储用户信息的工具,浏览器把用户信息存放到本地的文本文件中. Session是基于Cookie实现的. 2011年4月,武汉群硕面试的时候(实习生),面试官也问过这个问题. 当时只知道Session是基于Cookie的,但是没有想到"不使用Tomcat等Web容器的Session,只使用Cookie也可以实现自己的Session,完成会话管理,而且据说性能更好." 以前的做法: 使用HttpRequestSession保存用户信息,非常方便. 配置一个

[solr] - 环境搭建 - 使用tomcat做为容器

这里忽略solr其他依赖环境的搭建,这里搭建solr5.2.1.使用Java1.7.0_17,tomcat使用6.0.36版本的. 1.下载solr压缩文件 Solr是Apache基金组织在lucene之上建立的一个独立的企业级搜索应用服务器,它对外提供类似于Web-service的API接口.Lucene是一个全文检索引擎工具包,不是一个完整的应用服务器.下载Solr的地址为:http://mirrors.hust.edu.cn/apache/lucene/solr/,这个链接中只有常用的版本

大话tomcat之tomcat连接mysql

研究主题:tomcat连接mysql 一.tomcat连接mysql的两种连接方式:简单连接(不使用连接池)使用tomcat连接池 二.简单分析:(简介部分摘自一篇博客,觉得写得非常赞,读了非常有快感,就引用来与大家分享) 对于一个简单的数据库应用,由于对于数据库的访问不是很频繁.这时可以简单地在需要访问数据库时,就创建一个连接,用完后就关闭它这样也不会带来什么明显的性能上的开销.但是对于一个复杂的数据库应用,QPS和TPS都并发很大的时候情况就完全不同了.频繁的建立,关闭连接,会极大的降低系统

Tomcat学习—Tomcat的简介和目录以及配置文件介绍(Windows环境)

今天学习TOMCAT,主要学习的是Tomcat的目录结构,配置文件!1:Tomcat简介Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP 程序的首选.对于一个初学者来说,可以这样认为,当在一台机器上配置好Apache 服务器,可利用它响应HTML页面的访问请求.实际上Tomcat 部分是Apache 服务器的扩展,但它是独立运行的,所以当你运行tomcat 时,它实际上作为一个与Apac

linux下安装配置tomcat以及tomcat开机自启配置

Linux下Tomcat安装配置以及Windows不能连接服务器Tomcat解决方案 一.从官方网站上下载tomcat软件包. 官网地址: http://tomcat.apache.org/ 点击左侧的 download的一个版本,我选择的是 tomcat7.0,选择一个后缀名为.tar.gz文件直接下载到本地. 二.通过工具SSH Secure 上传至linux服务器中,进行解压 解压tomcat压缩文件: #tar zxvf apache-tomcat-7.0.53.tar.gz 将解压后的

【Tomcat】Tomcat + Memcached 实现session共享

概述 web项目中,Tomcat的访问量总是有限的,这时候就需要用到Tomcat集群,多个Tomcat的时候就要考虑Session共享的问题,这里介绍一种使用Memcached做Session共享的解决方案 环境 操作系统:Linux( centOS 6..5 版) 软件:Tomcat7    Memcached 实现原理 Tomcat + Memcached 实现session共享流程图 配置 安装Tomcat 2个和Memcached 1个,参考[Linux]Tomcat安装及一个服务器配置

Linux下部署tomcat及tomcat war包应用程序

1, 通过winscp将tomcat包(6和7版本都是一样的安装方法)和jdk-6u27-linux-x64.bin安装文件传送到linux 系统/opt里面.(这里没有固定要传送到/opt/hn,可以自己定义.) 2, 安装首先解压tomcat和安装jdk(假设tomcat包是tomcat-6.0.35.tar,jdk是jdk-7u17-linux-i586.tar.gz) 解压tomcat命令如下: [[email protected]lentim hn]# tar -xvf tomcat-