详细介绍了Tomcat的核心组件以及server.xml配置全解,以及请求处理流程。
前面我们简单介绍了Tomcat服务器的基本概念,现在我们来简单看看它的核心配置文件server.xml。
本文将会介绍Tomcat中比较重要的一些配置和概念,而又不会太深入底层,本文并不是专业的Tomcat学习文章。如果你是Java学习者或者开发者,并且没有专门的时间和计划去详细学习Tomcat服务器,那么本文值得一看!
tomcat是组件式架构,它的server.xml配置文件中可以通过标签配置tomcat容器中的不同类型的组件,是tomcat的核心配置文件。
server.xml中的主要组件(标签)的关系如下(并没有展示全部组件的关系):
Server标签在最顶层,代表整个Tomcat容器;一个Server元素中可以有一个或多个Service元素。
Service在Connector和Engine外面包了一层,把它们组装在一起,对外提供服务。一个Service可以包含多个Connector,但是只能包含一个Engine;Connector接收请求,Engine处理请求。
Engine、Host和Context都是容器,且 Engine包含Host,Host包含Context。每个Host组件代表Engine中的一个虚拟主机;每个Context组件代表在特定Host上运行的一个Web应用。
tomcat 8.5.61的默认server.xml的配置如下:
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener"/>
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on"/>
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener"/>
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"/>
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener"/>
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml"/>
GlobalNamingResources>
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"/>
<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
Realm>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b"/>
Host>
Engine>
Service>
Server>
下面来简单的认识一下核心组件(标签)的作用以及配置。本文以tomcat 8.5.61版本为主。
Server标签是server.xml文件的根标签,代表整个tomcat的Servlet容器,时tomcat的核心组件,是Tomcat实例的顶层的org.apache.catalina.Server接口的抽象。
Server标签的属性表示 servlet 容器的一个整体的特征。常见属性如下:
属性名 | 描述 |
---|---|
className | 要使用的Java 实现类全路径名,指定的类必须实现org.apache.catalina.Server.Server 接口。如果未指定类名,则将使用标准实现,即org.apache.catalina.core.StandardServer |
address | tomcat监听shutdown命令的TCP/IP地址,如未指定,则默认为localhost |
port | tomcat监听shutdown命令的TCP/IP端口号,默认为8005 。设置为-1,表示禁止通过端口关闭Tomcat,此时shutdown.bat命令不能使用。如果一台及其启动多个tomcat实例,该端口应该不一致。 |
shutdown | 通过指定的地址(address)和端口(port)关闭tomcat时所需发送的终止字符串,默认为“SHUTDOWN”,修改shutdown的值,对shutdown.bat的使用无影响。 |
Server标签内部可以配置一个或者多个Listener标签,一个或者多个Service标签以及一个GlobalNamingResources标签。
一个Listener标签表示配置的一个tomcat的监听器组件。一个监听器负责监听特定的事件,当特定事件触发时,Listener会捕捉到该事件并做出相应处理。
Listener可嵌在Server、Engine、Host、Context标签内,不得将任何其他标签元素嵌套在监听器标签中。某些特殊类型的监听器仅用于嵌套在特定标签中,默认配置的监听器都是在Server标签内。Listener通常用于监听Tomcat的启动和关闭事件。
所有监听器都有的属性如下:
属性名 | 描述 |
---|---|
className | 要使用的Java 监听器实现类全路径名,指定的类必须实现org.apache.catalina.LifecycleListener 接口。 |
本文使用的tomcat版本中,默认开启的五个监听器配置如下:
常见的监听器介绍如下:
全局命名资源组件,定义服务器的全局JNDI资源,所有的应用程序都可以引用。通常可以包含三个子标签:Environment、Resource、ResourceEnvRef。
JNDI(Java Naming and Directory Interface)
多用于java的数据库连接中,我们常见的就是JDBC,在JDBC中,连接数据库需要用户名、用户名密码和数据库名称等参数,将来如果这三个参数中的任何一个修改,则整个程序中相关的地方都需要修改,简直是牵一发而动全身,因此出现JNDI技术。
在JNDI中,将连接数据库需要的用户、用户密码和数据库名称等JDBC引用的参数定义为一个整体(即这个整体中包含JDBC要用到类库、数据库驱动程序、用户、用户密码等),然后为这个整体设置一个名称,这个整体保存在应用程序之外的文件中,这样以后连接数据库的时候直接在程序中引用这个整体的名称即可,无论用户、用户密码怎么变换,只要这个整体的名称不变,我们仅要修改这个外部的整体的用户和用户密码,而无需修改整个程序。这里的这个整体就是JNDI数据源(JNDI资源)。对于数据库来说,JNDI数据源避免了程序与数据库之间的紧耦合,使应用更加易于配置、易于部署。
Environment标签可以用于配置全部项目的环境变量。比如:
<GlobalNamingResources ...>
...
<Environment name="maxExemptions" value="10"
type="java.lang.Integer" override="false"/>
...
GlobalNamingResources>
它表示配置一个名为maxExemptions的Integer类型的环境变量,值为10。如果想要在具体的Web应用中引用该常量,那么在具体应用的Context标签下,使用ResourceLink标签引用该Environment资源,global属性就是该Environment的name属性名字。
这等同于在web应用的/WEB-INF/web.xml文件中的如下配置:
<env-entry>
<env-entry-name>maxExemptionsenv-entry-name>
<env-entry-value>10env-entry-value>
<env-entry-type>java.lang.Integerenv-entry-type>
env-entry>
在Java代码中使用如下方式读取:
Context initCtx = null;
initCtx = new InitialContext();
Context envCtx = (Context)initCtx.lookup("java:comp/env");
Integer n = (Integer)envCtx.lookup("maxExemptions");
Resource标签可以用于配置各种全局数据源,最常见的就是用户数据库连接JNDI源。下面表示通过JNDI配置一个数据库数据源。
<GlobalNamingResources>
<Resource
name="DbSource"
username="admin"
password="123456"
maxIdle="30"
maxActive="50"
maxWait="5000"
type="javax.sql.DataSource"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:xxx" />
GlobalNamingResources>
然后在Web应用(context标签)下通过ResourceLink标签引用数据源:
<ResourceLink global="DbSource" name="DbSource" type="javax.sql.DataSource"/>
随后在Spring配置文件中可以配置引用JNDI数据源:
<beans>
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value>java:/comp/env/ DbSourcevalue>
property>
bean>
beans>
也可以使用Java代码获取:
//获得对数据源的引用:
Context ctx = new InitalContext();
DataSource ds = (DataSource)ctx.lookup("java:comp/env/DbSource");
//获得数据库连接对象:
Connection con = ds.getConnection();
//返回数据库连接到连接池:
con.close();
Tomcat manager是Tomcat自带的、使用HTML界面的、管理Tomcat自身以及部署在Tomcat上的应用的web应用。
manager需要使用用户登陆并根据角色分配权限,角色和用户在tomcat-users.xml中配置。tomcat的tomcat-users.xml文件中默认没有配置任何用户信息:
tomcat-users.xml文件中的配置信息是tomcat8在server.xml中默认配置了的一个用户数据库数据源来加载的:
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml"/>
GlobalNamingResources>
该示例表示定义了一个名为UserDataBase的全局JNDI数据源,使用容器管理该Resource,该数据源为加载tomcat-users.xml文件至内存中而定义的用于用户授权的数据库。然后在认证文件tomcat-users.xml中就可以配置可以访问Manager App和Host Manager页面的用户以及权限。在Engine标签下可以通过Realm标签来引用配置的数据源,resourceName属性的值就是Resource标签的name属性值。
上面的配置中role标签用来配置角色,user标签用来配置用户。如果我们在tomcat-users.xml中添加如下配置:
表示登陆用户名为admin,用户密码为123456,用户权限为admin-gui和admin-script,admin-gui表示允许访问 Host Manager的html接口(即URL路径为/host-manager/html/*),admin-script表示允许访问 Host Manager的纯文本接口(即URL路径为/host-manager/text/*)。此时我们就可以访问Host Manager页面。
对于Manager App页面,我们则需要配置如下几个权限:
manager-gui、manager-script、manager-jmx均具备manager-status的权限,也就是说,manager-gui、manager-script、manager-jmx三种角色权限无需再额外添加manager-status权限,即可直接访问路径/manager/status/*。
包含一个Engine和多个Connector的服务组件,表示org.apache.catalina.Service接口,用于对外提供Web服务。不同的Connector接收不同的客户端请求,多个Connector共用一个Engine来处理所有Connector接收到的请求。
Server中可以包括多个Service标签,Service标签内部不能直接定义< Value/>组件。tomcat8的默认配置中包含一个名为Catalina
的Service组件。
Service的所有实现都支持以下属性:
属性名 | 描述 |
---|---|
className | 要使用的Java实现类全路径名,指定的类必须实现org.apache.catalina.Service接口。如果未指定类名,则将使用标准实现,即org.apache.catalina.core.StandardService |
name | Service组件的名字,必须唯一 |
Executor即线程池执行器,一个Service中可以配置多个命名线程池,可以由其他组件共享使用,一般来说,使用线程池的是Connector组件,用于处理Http请求,要使用指定的线程池执行器,组件需要通过executor属性指定对应线程池的name属性。
为了使Connector能正常使用线程池,Executor元素应该放在Connector前面。
执行器的所有实现都支持以下属性:
属性名 | 描述 |
---|---|
className | 要使用的Java 实现类全路径名,指定的类必须实现org.apache.catalina.Executor 接口。如果未指定类名,则将使用标准实现,即org.apache.catalina.core.StandardThreadExecutor |
name | 执行器的名字,其他组件通过名字引用该执行器,因此该属性是必需的,并且必须是唯一的 。 |
默认执行器实现支持以下属性:
属性名 | 描述 |
---|---|
threadPriority | Executor内的线程的优先级,默认值为5(Thread.NORM_PRIORITY) |
daemon | Executor内的线程是否是后台线程,默认值true |
namePrefix | Executor创建的每个线程的名称前缀,线程池中线程名字为:namePrefix+ threadNumber |
maxThreads | Executor中最大活跃线程数,默认值200(Tomcat7和8都是) ,即处理请求的最大线程数。 |
minSpareThreads | Executor中保持的最小线程数(无论是否是空闲状态),默认值是25 |
maxIdleTime | Executor中空闲线程超时关闭的毫秒数(除非线程数小于或等于 minSpareThread)。默认值为 60000(1 分钟) |
maxQueueSize | 在Executor执行拒绝策略之前,可以排队等待执行的最大可运行任务数,即任务队列的最大长度。默认值为Integer.MAX_VALUE((2147483647) |
prestartminSpareThreads | 启动Executor时是否应立即预初始化minSpareThread线程,默认值为false |
threadRenewalDelay | 如果配置了ThreadLocalLeakPreventionListener(默认会配置),监听器,它将通知此Executor有关的以停止的Web应用(context),停止Web应用后,池中的线程将重建。threadRenewalDelay表示重建线程的时间间隔,重建线程池内的线程时,为了避免线程同时重建,每隔threadRenewalDelay(毫秒)重建一个线程,默认值为1000,设置为负数则不重建 |
一个Connector标签代表一个与客户端实际交互的组件,负责接收客户端请求和返回响应结果。Connector标签位于Service标签下,一个Service标签可以包含多个Connector标签。
在tomcat程序中,Connector将会为每个请求创建Request和Response对象,然后分配线程让Engine(Servlet实例)来处理这个请求,并把产生的Request和Response对象传给Engine,当Engine处理完请求后,也会通过Connector将响应返回给客户端。可以说,Servlet容器处理请求,是需要Connector进行调度和控制的,Connector是Tomcat处理请求的主干之一。
我们着重要掌握连接器的属性,面试的时候经常问的tomcat的并发数量之类的都可以通过连接器设置。
连接器的所有实现都支持以下常用属性:
除了上面的属性之外,标准 HTTP 连接器(NIO, NIO2 and APR/native)还支持下面的常见属性:
60000(即 60秒)
。当client与tomcat建立连接之后,在"connectionTimeout"时间之内,仍然没有得到client的请求数据,此时连接将会被断开。对于 NIO 和 NIO2,默认值为 10000,而BIO的默认值为maxThreads(如果配置了Executor,则默认值是Executor的maxThreads)。对于 APR/native,默认值为 8192,并且如果配置的值不是1024的倍数,maxConnections 的实际值将减少到1024的最大倍数。
maxConnections+acceptCount
,对于BIO来说就是300
。200
。如果该Connector绑定了Executor,这个值会被忽略,因为该Connector将使用绑定的Executor,而不是内置的线程池来执行任务。在tomcat8的默认配置中,只开放了一个Connector,其他的Connector则被注释了:
根据上面的属性,这个开放出来的Connector表示:客户端可以通过8080端口号使用HTTP/1.1协议(兼容HTTP1.0)访问tomcat,建立连接之后等待请求URI的超时时间为20000ms(即 20秒),并且当出现HTTPS请求时,该HTTPS请求将被转发至端口号为8443的Connector(从注释中可以看到端口号为8443的Connector设置了SSLEnabled=“true”,即支持处理HTTPS请求)。
http协议默认端口号为80,也就是说在URL中不给出端口号时就表示使用80端口。当然你也可以修改为其它端口号。当把端口号修改为80后,在浏览器中只需要输入:http://localhost就可以访问tomcat主页了。
Engine(引擎)表示一个与某个Service关联的请求处理组件,它接收并处理来自该Service中全部Connectors连接器接收到并封装的来自客户端的请求,并返回给连接器的已完成响应,以最终在通过连接器将响应传输回客户端。
Engine可以看作Servlet容器,即container。 由org.apahce.catalina.Engine接口定义。一个Service中有且只能有一个Engine组件。
Engine的所有实现都支持以下常见属性:
属性名 | 描述 |
---|---|
name | Engine的名称,用于日志和错误信息。在同一服务器中使用多个 Service 元素时,必须为每个Engine分配一个唯一 的名称。 |
defaultHost | 默认host主机名,当发往本机的请求指定的host名称不存在或者未指定时,一律使用defaultHost指定的host进行处理,因此此名称必须与内部的一个Host标签元素的name属性匹配。 |
jvmRoute | 必须在负载平衡方案中使用的标识符,以启用session相关性。 主要是应用于tomcat集群中的session共享,会在一次会话中添加该值,获得session sticky |
className | 指定org.apache.catalina.Engine接口的实现类全路径名,默认值为org.apache.catalina.core.StandardEngine |
在tomcat8中,默认的Engine配置为:
Host是Engine的子容器,Engine中可以包含1个或多个Host组件,Engine组件的defaultHost属性必须与其中一个Host的name属性一致。对应着org.apache.catalina.Host接口。
Host表示一个虚拟主机,可以包含(管理)一个或多个Web应用(Host内部的一个Context代表一个Web应用),并负责安装、展开、启动和结束每个Web应用。配置多个Host可以让一个tomcat支持从不同的域名访问Web应用。
Host 的所有实现都支持以下常见属性:
属性名 | 描述 |
---|---|
name | 虚拟主机名。 一般情况下,指定主机名需要是在DNS服务器中注册的网络名。localhost是DNS服务器中已经注册好的localhost对应着127.0.0.1,因此不需要我们手动注册。 如果是本地测试,Windows系统中,可以在C:\Windows\System32\drivers\etc\hosts文件中添加域名映射。 |
appBase | 此虚拟主机的应用程序基础目录 ,这里指定目录的路径名,该目录下可能包含要在此虚拟主机上部署的 Web 应用程序。可以指定绝对路径名,也可以指定相对于tomcat基础目录的路径名。如果未指定,默认值为“webapps”。该属性主要用于Web 应用程序的自动识别和部署。 |
autoDeploy | 指示 Tomcat 在 运行时应定期检查新的或更新的 Web 应用程序 。如果为true,Tomcat 会定期检查 appBase 和 xmlBase 目录,并部署找到的任何新的 Web 应用程序或上下文 XML 描述符。更新的 Web 应用程序或上下文 XML 描述符将触发 Web 应用程序的重新加载。标志的值默认为 true。有关详细信息,请参阅自动应用程序部署。 |
className | 指定org.apache.catalina.Host接口的实现类全路径名,默认值为org.apache.catalina.core.StandardHost |
startStopThreads | 此Host主机用于并行启动/发布子Context标签(Web应用)的线程数。如果开启自动部署,那么将使用同一个线程池来部署新的Web应用。指定为0 ,表示将使用Runtime.getRuntime().availableProcessors()数量的线程。负值 将导致使用Runtime.getRuntime().availableProcessors()+startStopThreads的线程数量,如果结果小于1,在这种情况下将使用 1 个线程。如果未指定 ,将使用默认1个线程,用于部署Host下的所有Web项目。如果Host下的Web项目较多,由于只有一个线程负责部署这些项目,因此这些项目将依次部署,最终导致Tomcat的启动时间较长。此时,修改startStopThreads值,增加Host部署Web项目的并行线程数,可降低Tomcat的启动时间。 |
Host的标准实现是org.apache.catalina.core.StandardHost,它还支持以下常见附加属性:
属性名 | 描述 |
---|---|
unpackWARs | 指示是否将代表Web应用的WAR文件解压;如果为true,则先解压然后运行解压的Web应用,如果为false,直接使用WAR文件运行Web应用。 |
在tomcat8中,默认的Host配置为:
它表示虚拟主机名为localhost,应用程序基础目录为一个相对路径,即tomcat目录下的webapps目录,并且支持自动解压应用的War包,支持自动发布和更新项目。
因此我们启动一个新的tomcat8,它的默认访问URL前缀应该是localhost:8080。
Context(上下文)代表一个运行在特定虚拟主机(Host)中运行的 Web 应用程序。一个Host内可以有0个或者多个Context。
对应着org.apache.catalina.Context接口。通过配置Context标签可以手动将其他路径的Web应用配置到tomcat中。
Context的所有实现都支持以下常见属性:
上下文的标准实现是org.apache.catalina.core.StandardContext。它支持以下附加常见属性:
属性名 | 描述 |
---|---|
unpackWAR | 指示是否将代表Web应用的WAR文件解压;如果指定为true,则通过所在Host的同名unpackWAR属性确定;如果为false,则所在Host的同名unpackWAR属性将被覆盖,并且不解包,直接使用WAR文件运行Web应用。如果未指定,则默认值为 true。 |
useNaming | 指示是否支持JNDI,默认值为了true |
tomcat可以自动部署Web应用,具体在哪里配置的呢?
我们需要配置Host组件的deployOnStartup或者autoDeploy属性。
如果deployOnStartup或者autoDeploy设置为true,则tomcat启动自动部署:当检测到新的Web应用或Web应用的更新时,会触发应用的部署或重新部署。二者的主要区别在于,deployOnStartup为true时,Tomcat在启动时检查Web应用,且检测到的所有Web应用视作新应用;autoDeploy为true时,Tomcat在运行时定期检查新的Web应用或Web应用的更新。除此之外,二者的处理相似。
另外,还需要配置自动部署检查的目录
,可以通过Host标签的appBase和xmlBase属性配置。appBase属性指定Web应用所在的目录,默认值是“webapps”,这是一个相对路径,代表Tomcat根目录下webapps文件夹。
xmlBase属性则是指定Web应用的XML配置文件所在的目录,默认值为conf/< engine_name>/< host_name>
,例如第一部分的例子中,主机localhost的xmlBase的默认值是$TOMCAT_HOME/conf/Catalina/localhost
。
一个Web应用可能包括以下文件:XML配置文件,WAR包,以及一个应用目录(该目录包含Web应用的文件结构);其中XML配置文件位于xmlBase指定的目录,WAR包和应用目录位于appBase指定的目录。
Tomcat按照如下的顺序进行扫描,来检查应用更新:
自动部署模式下,appBase目录中的Web应用不需要配置Context,tomcat会自动扫描appBase中的WAR包和应用目录来创建Context。
就算手动配置了Context,也不需要配置docBase和path属性。
比如,tomcat8的webapps中的默认有一下Web应用:
我们不必要单独为它们配置Context,它们的path就是对应的项目目录名,即docs、examples、host-manager、manager。对于名称为ROOT的目录,则该Web应用看作是虚拟主机默认的Web应用,此时path属性推导为””
。
因此,我们访问locahost:8080,实际上就是访问ROOT应用下的资源,而访问locahost:8080/docs,实际上就是访问docs应用下的资源。
我们的项目默认都是放到webapps下面,此时称为自动部署,我们在学习了Context之后,现在可以将Web应用放在其他地方法,通过手动配置Context标签,也能让tomcat可以找到它,部署的应用称为外部应用。通过Context部署的项目称为静态部署。
假设有一个Web应用目录为G:\work\hello
,这时我们需要通过tomcat访问,那么我们可以在Server.xml
中配置Context标签:
<Context docBase="G:\work\hello" path="" reloadable="true" />
reloadable属性的用法与自动部署时相同。静态部署时,也可以显式指定path属性,但是仍然受到了严格的限制:只有当自动部署完全关闭(deployOnStartup和autoDeploy都为false)或docBase不在appBase中时,才可以设置path属性。
还有一种方法,在conf/catalana/localhost目录下创建一个hello.xml文件,在该文件中编写< Context/>标签,在标签中指明Web目录地址。
<Context docBase="G:\work\hello"/>
此时path属性自动推导。
当请求被发送到Tomcat所在的主机时,如有多个Web应用,如何确定最终哪个Web应用来处理该请求呢?大概流程图如下:
其中index.html表示hello应用中的静态资源路径,通过该路径可以直接访问静态资源,而servlet/Aservlet则表示hi应用中的动态资源Servlet的映射路径,通过该路径可以找到对应的Servlet,随后即可处理请求。
Service中的Connector组件可以接收特定端口和特定类型的请求,因此,当Tomcat启动时,Service组件就会监听特定的端口。当请求进来时,Tomcat便可以根据协议和端口号选定处理请求的Service;Service一旦选定,Engine也就确定。
tomcat8默认的Service配置监听8080端口的HTTP请求。
通过在Service中配置多个Connector或者在Server中配置多个Service,可以实现通过不同的端口号来访问同一台机器上部署的不同应用。
Service确定后,Tomcat在Service内部的Engine中寻找name属性值与域名/IP地址匹配的Host处理该请求。如果没有找到,则使用Engine中指定的defaultHost来处理该请求。
tomcat8默认的Engine内部只有一个Host,name为locathost,并且defaultHost属性为locathost,因此该Service的所有请求都交给该Host处理。
选定Host之后,用于处理每个 HTTP 请求的 Web 应用程序由 Catalina 根据将请求 URI 的最长前缀与每个Context的上下文路径path来匹配,如果与某个Context的path一致,那么表示该请求将选择该Context对应的Web应用程序来处理,如果没有与任何一个Context的path匹配成功,那么将使用path=""的Conext。
在选择Context后,该上下文将根据对应的 Web 应用程序在部署时指定的 servlet 映射路径,选择适当的 servlet 来处理传入请求。
参考资料:
如有需要交流,或者文章有误,请直接留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!