概述
Tomcat是在web开发过程中最常用的一个Servlet容器,尤其在SpringBoot开始流行后更加简化了Tomcat的使用流程。深入理解Tomcat能够学习到这个流行了很长时间的中间件的设计精华,同时还能够为更好的使用Tomcat提供技术支撑。
总体架构
组件说明
Server | 代表一个tomcat实例 |
Service | 一个Service代表一组Connector和Engine的组合 |
Connector | 连接器,负责接受客户端的TCP连接 |
Engine | 引擎代表了处理器请求的处理机的实体,它负责接受Connector的请求并返回Connector响应 |
Host | 虚拟主机,对应不同的host name |
Context | 代表一个web服务实例 |
Servlet | 代表一个处理业务逻辑的处理器 |
Jsper | 用来处理jsp的组件 |
Java EL | 用来处理表达式的组件 |
Naming | 命名服务 |
Juli | 用来处理服务器日志的组件 |
层次结构
<Server>
<Service>
<Connector/>
<Connector/>
<Engine>
<Host>
<Context/>
<Host/>
<Engine/>
<Service/>
</Server>
目录结构
bin |
脚本目录 |
conf |
配置文件目录 |
lib | 存放依赖包的目录 |
logs | 默认的日志目录 |
webapps | web应用默认的部署目录 |
work | 存放jsp生成的编译文件的目录 |
配置
服务器配置
配置文件是conf/server.xml
//port: 接受关闭服务器的指令的端口
//shutdown: 关闭服务器的指令
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener className="org.apache.catalina.security.SecurityListener" />
<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">
<Executor
#线程池名称
name="tomcatThreadPool"
#线程中的线程前缀
namePrefix="catalina‐exec‐"
#最大线程数
maxThreads="200"
#核心线程数
minSpareThreads="100"
#非核心线程的空闲时间
maxIdleTime="60000"
#线程池排队队列的大小
maxQueueSize="Integer.MAX_VALUE"
#启动线程时是否初始化minSpareThreads这部分线程
prestartminSpareThreads="false"
#线程池中线程的优先级
threadPriority="5"
#线程池实现类
className="org.apache.catalina.core.StandardThreadExecutor"/>
<Connector port="8080"
protocol="HTTP/1.1"
executor="tomcatThreadPool"
maxThreads="1000"
minSpareThreads="100"
acceptCount="1000"
maxConnections="1000"
connectionTimeout="20000"
compression="on"
compressionMinSize="2048"
disableUploadTimeout="true"
redirectPort="8443"
URIEncoding="UTF‐8" />
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="150" SSLEnabled="true">
<SSLHostConfig>
<Certificate certificateKeystoreFile="conf/localhost-rsa.jks"
type="RSA" />
</SSLHostConfig>
</Connector>
<Connector port="8443" protocol="org.apache.coyote.http11.Http11AprProtocol"
maxThreads="150" SSLEnabled="true" >
<UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
<SSLHostConfig>
<Certificate certificateKeyFile="conf/localhost-rsa-key.pem"
certificateFile="conf/localhost-rsa-cert.pem"
certificateChainFile="conf/localhost-rsa-chain.pem"
type="RSA" />
</SSLHostConfig>
</Connector>
<Connector protocol="AJP/1.3"
address="::1"
port="8009"
redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost">
<Host
#虚拟主机名称
name="localhost"
#应用的部署目录,默认时webapps
appBase="webapps"
#是否自动解压war文件
unpackWARs="true"
#是否自动检查项目变更,并重新部署
autoDeploy="true">
<!-- Access log processes all example.
Documentation at: /docs/config/valve.html
Note: The pattern used is equivalent to using pattern="common" -->
<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>
应用配置
应用级别的配置文件是web.xml
<context‐param>
<param‐name>contextConfigLocation</param‐name>
<param‐value>classpath:applicationContext‐*.xml</param‐value>
<description>Spring Config File Location</description>
</context‐param>
<session‐config>
<session‐timeout>30</session‐timeout>
<cookie‐config>
<name>JESSIONID</name>
<domain>www.itcast.cn</domain>
<path>/</path>
<comment>Session Cookie</comment>
<http‐only>true</http‐only>
<secure>false</secure>
<max‐age>3600</max‐age>
</cookie‐config>
<tracking‐mode>COOKIE</tracking‐mode>
</session‐config>
<servlet>
<servlet‐name>myServlet</servlet‐name>
<servlet‐class>cn.itcast.web.MyServlet</servlet‐class>
<init‐param>
<param‐name>fileName</param‐name>
<param‐value>init.conf</param‐value>
</init‐param>
<load‐on‐startup>1</load‐on‐startup>
<enabled>true</enabled>
<multipart‐config>
#文件存放路径
<location>C://path</location>
#文件大小的最大值
<max‐file‐size>10485760</max‐file‐size>
<max‐request‐size>10485760</max‐request‐size>
<file‐size‐threshold>0</file‐size‐threshold>
</multipart‐config>
</servlet>
<servlet‐mapping>
<servlet‐name>myServlet</servlet‐name>
<url‐pattern>*.do</url‐pattern>
<url‐pattern>/myservet/*</url‐pattern>
</servlet‐mapping>
<filter>
<filter‐name>myFilter</filter‐name>
<filter‐class>cn.itcast.web.MyFilter</filter‐class>
<async‐supported>true</async‐supported>
<init‐param>
<param‐name>language</param‐name>
<param‐value>CN</param‐value>
</init‐param>
</filter>
<filter‐mapping>
<filter‐name>myFilter</filter‐name>
<url‐pattern>/*</url‐pattern>
</filter‐mapping>
<listener>
<listener‐class>org.springframework.web.context.ContextLoaderListener</listener‐ class>
</listener>
<welcome‐file‐list>
<welcome‐file>index.html</welcome‐file>
<welcome‐file>index.htm</welcome‐file>
<welcome‐file>index.jsp</welcome‐file>
</welcome‐file‐list>
<error‐page>
<error‐code>404</error‐code>
<location>/404.html</location>
</error‐page>
<error‐page>
<error‐code>500</error‐code>
<location>/500.html</location>
</error‐page>
<error‐page>
<exception‐type>java.lang.Exception</exception‐type>
<location>/error.jsp</location>
</error‐page>
管理配置
tomcat管理配置主要在tomcat-user.xml(但在生产环境下一般不会对外开放tomcat管理系统)。
<role rolename="manager-gui"/>
<role rolename="manager-script"/>
<role rolename="admin-gui"/>
<role rolename="admin-script"/>
<user username="tomcat" password="tomcat" roles="admin-gui,admin-script,manager-gui,manager-script"/>
安全
配置安全
- 删除webapps目录下的所有文件,禁用tomcat管理界面;
- 注释或删除tomcat-users.xml文件内的所有用户权限;
- 更改关闭tomcat指令或禁用(server标签的port和shutdown);
- 定义错误页面,避免内部异常直接暴露给用户;
应用安全
应用程序自身对访问进行认证和授权。
传输安全
使用https代替http。
dongzhang@DongdeMacBook-Air conf % keytool -genkey -alias tomcat -keyalg RSA -keystore tomcatkey.keystore
输入密钥库口令:
再次输入新口令:
您的名字与姓氏是什么?
[Unknown]: zhang
您的组织单位名称是什么?
[Unknown]: local
您的组织名称是什么?
[Unknown]: local
您所在的城市或区域名称是什么?
[Unknown]: guangzhou
您所在的省/市/自治区名称是什么?
[Unknown]: guangzhou
该单位的双字母国家/地区代码是什么?
[Unknown]: CN
CN=zhang, OU=local, O=local, L=guangzhou, ST=guangzhou, C=CN是否正确?
[否]: y
输入 <tomcat> 的密钥口令
(如果和密钥库口令相同, 按回车):
再次输入新口令:
Warning:
JKS 密钥库使用专用格式。建议使用 "keytool -importkeystore -srckeystore tomcatkey.keystore -destkeystore tomcatkey.keystore -deststoretype pkcs12" 迁移到行业标准格式 PKCS12。
#配置ssl Connector
<Connector port="8443" protocol="org.apache.coyote.http11.Http11AprProtocol"
maxThreads="150" SSLEnabled="true" >
<UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
<SSLHostConfig>
<Certificate certificateKeyFile="conf/localhost-rsa-key.pem"
certificateFile="conf/localhost-rsa-cert.pem"
certificateChainFile="conf/localhost-rsa-chain.pem"
type="RSA" />
</SSLHostConfig>
</Connector>
集群
WebSocket
嵌入式Tomcat
源码
环境准备
- 下载源码包并解压 https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.65/src/apache-tomcat-9.0.65-src.tar.gz
- 在源码包目录下创建home文件夹,并把 conf/和 webapps/ 复制到home目录下
- 在源码包目录下创建一个pom.xml,并添加如下内容
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.apache.tomcat</groupId> <artifactId>Tomcat9.0</artifactId> <name>Tomcat9</name> <version>9.0.20</version> <build> <finalName>Tomcat9</finalName> <sourceDirectory>java</sourceDirectory> <resources> <resource> <directory>java</directory> </resource> </resources> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3</version> <configuration> <encoding>UTF-8</encoding> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>org.apache.ant</groupId> <artifactId>ant</artifactId> <version>1.10.1</version> </dependency> <dependency> <groupId>org.apache.ant</groupId> <artifactId>ant-apache-log4j</artifactId> <version>1.9.5</version> </dependency> <dependency> <groupId>org.apache.ant</groupId> <artifactId>ant-commons-logging</artifactId> <version>1.9.5</version> </dependency> <dependency> <groupId>javax.xml.rpc</groupId> <artifactId>javax.xml.rpc-api</artifactId> <version>1.1</version> </dependency> <dependency> <groupId>wsdl4j</groupId> <artifactId>wsdl4j</artifactId> <version>1.6.2</version> </dependency> <!-- https://mvnrepository.com/artifact/org.eclipse.jdt/org.eclipse.jdt.core --> <dependency> <groupId>org.eclipse.jdt</groupId> <artifactId>core</artifactId> <version>3.26.0</version> <scope>system</scope> <systemPath>${basedir}/lib/org.eclipse.jdt.core-3.26.0.jar</systemPath> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.easymock</groupId> <artifactId>easymock</artifactId> <version>4.0.2</version> <scope>test</scope> </dependency> <dependency> <groupId>biz.aQute.bnd</groupId> <artifactId>biz.aQute.bndlib</artifactId> <version>5.2.0</version> <scope>provided</scope> </dependency> </dependencies> </project>
- 在idea中创建一个空项目,并把tomcat源码项目导入到这个idea项目中
- 创建一个Application启动向,并指向 org.apache.catalina.startup.Bootstrap
- 在启动项的jvm参数中添加如下内容
-Dcatalina.home=/Users/dongzhang/develop/workspace/echo20222022/open_src/apache-tomcat-9.0.65-src/home -Dcatalina.base=/Users/dongzhang/develop/workspace/echo20222022/open_src/apache-tomcat-9.0.65-src/home -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djava.util.logging.config.file=/Users/dongzhang/develop/workspace/echo20222022/open_src/apache-tomcat-9.0.65-src/home/conf/logging.properties -Dfile.encoding=UTF-8
- 在ContextConfig中加入如下代码
核心类
上图中的核心类都是Tomcat中的核心组件的抽象,在LifeCycle这个体系下,整体上采用了模板方法设计模式,以init方法为入口来实现对各个组件的初始化工作。
初始化流程
BootStrap.java
public static void main(String args[]) {
Bootstrap bootstrap = new Bootstrap();
bootstrap.init();
daemon = bootstrap;
//初始化
daemon.load(args);
//启动
daemon.start();
}
private void load(String[] arguments) throws Exception {
String methodName = "load";
Method method = catalinaDaemon.getClass().getMethod(methodName, paramTypes);
//调用Catalina.load方法
method.invoke(catalinaDaemon, param);
}
Catalina.java
public void load() {
// 解析server.xml
parseServerXml(true);
//获取Server实例
Server s = getServer();
// 将System.out重定向到日志输出
initStreams();
getServer().init();
}
StandardServer.java
从这里开始,大部分的组件初始都采用了基于LifeCycle的模板方法设计模式。
LifeCyclie.java
public interface Lifecycle {
public void init() throws LifecycleException ;
}
@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) {
handleSubClassException(t, "lifecycleBase.initFail", toString());
}
}
@Override
protected void initInternal() throws LifecycleException {
// 初始化公共线程连接池
reconfigureUtilityExecutor(getUtilityThreadsInternal(utilityThreads));
register(utilityExecutor, "type=UtilityExecutor");
//初始化通用字符串缓存
onameStringCache = register(new StringCache(), "type=StringCache");
// 初始化命名服务中的资源
globalNamingResources.init();
//初始化Service
for (Service service : services) {
service.init();
}
StandardService.java
@Override
protected void initInternal() throws LifecycleException {
if (engine != null) {
//初始化Engine
engine.init();
}
// Initialize any Executors
for (Executor executor : findExecutors()) {
if (executor instanceof JmxEnabled) {
((JmxEnabled) executor).setDomain(getDomain());
}
//初始化线程池组件
executor.init();
}
//初始化Listener
mapperListener.init();
// Initialize our defined Connectors
synchronized (connectorsLock) {
for (Connector connector : connectors) {
//初始化Connector
connector.init();
}
}
}
Connector.java
public Connector(String protocol) {
boolean apr = AprStatus.getUseAprConnector() && AprStatus.isInstanceCreated()
&& AprLifecycleListener.isAprAvailable();
//初始化ProtocolHandler
ProtocolHandler p = null;
try {
p = ProtocolHandler.create(protocol, apr);
} catch (Exception e) {
log.error(sm.getString(
"coyoteConnector.protocolHandlerInstantiationFailed"), e);
}
if (p != null) {
protocolHandler = p;
protocolHandlerClassName = protocolHandler.getClass().getName();
} else {
protocolHandler = null;
protocolHandlerClassName = protocol;
}
// Default for Connector depends on this system property
setThrowOnFailure(Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"));
}
protected void initInternal() throws LifecycleException {
// 初始化CoyoteAdapter
adapter = new CoyoteAdapter(this);
protocolHandler.setAdapter(adapter);
if (service != null) {
//获取连接池,并设置给ProtocolHander,用来实现Reactor I/O
protocolHandler.setUtilityExecutor(service.getServer().getUtilityExecutor());
}
//执行初始化
protocolHandler.init();
}
AbstractHttp11Protocol.java
public void init() throws Exception {
//初始化EndPoint
String endpointName = getName();
endpoint.setName(endpointName.substring(1, endpointName.length()-1));
endpoint.setDomain(domain);
endpoint.init();
}
AbstractEndpoint.java
public final void init() throws Exception {
if (bindOnInit) {
//绑定Socket端口
bindWithCleanup();
bindState = BindState.BOUND_ON_INIT;
}
}
private void bindWithCleanup() throws Exception {
bind();
}
NioEndPoint.java
@Override
public void bind() throws Exception {
initServerSocket();
setStopLatch(new CountDownLatch(1));
// Initialize SSL if needed
initialiseSsl();
}
protected void initServerSocket() throws Exception {
//在指定端口上绑定socket监听
serverSock = ServerSocketChannel.open();
socketProperties.setProperties(serverSock.socket());
InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
serverSock.bind(addr, getAcceptCount());
serverSock.configureBlocking(true);
}
到此初始化工作就结束了,Host、Context和Wrapper的初始是在启动流程中进行的,总体上来说,初始工作处理做一些属性、参数、配置的初始,最主要的工作就是启动Tomcat在指定端口上的监听,每一个Connector配置都对应一个Socket。
启动流程
Tomcat的启动时在初始化工作完成后进行的,入口在Bootstrap.start()方法。
Bootstrap.java
public void start() throws Exception {
//调用Catalina的start方法
Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
method.invoke(catalinaDaemon, (Object [])null);
}
Catalina.java
public void start() {
//启动Server
getServer().start();
Runtime.getRuntime().addShutdownHook(shutdownHook);
//如果配置了shutdown端口,则会启动一个socket来监听设置端口,并处理shutdown命令
await();
}
StandardServer.java
LifeCycleBase.java
@Override
public final synchronized void start() throws LifecycleException {
startInternal();
}
StandardServer.java
@Override
protected void startInternal() throws LifecycleException {
globalNamingResources.start();
//初始Service
for (Service service : services) {
service.start();
}
}
StandardService.java
@Override
protected void startInternal() throws LifecycleException {
//启动Engin
engine.start();
//启动Executor
for (Executor executor: executors) {
executor.start();
}
mapperListener.start();
//启动Connector
for (Connector connector: connectors) {
// If it has already failed, don't try and start it
if (connector.getState() != LifecycleState.FAILED) {
connector.start();
}
}
}
StandardEngine.java
StandardEngin.java
@Override
protected synchronized void startInternal() throws LifecycleException {
// Start our child containers, if any
Container children[] = findChildren();
List<Future<Void>> results = new ArrayList<>();
for (Container child : children) {
results.add(startStopExecutor.submit(new StartChild(child)));
}
}
Connector.java
@Override
protected void startInternal() throws LifecycleException {
protocolHandler.start();
}
AbstractProtocol.java
@Override
public void start() throws Exception {
endpoint.start();
}
AbstractEndpoint.java
public final void start() throws Exception {
if (bindState == BindState.UNBOUND) {
bindWithCleanup();
bindState = BindState.BOUND_ON_START;
}
startInternal();
}
NioEndpoint.java
@Override
public void startInternal() throws Exception {
// 启动一个从Channel中拉取事件的线程,这是一个单线程
//Poller执行的是从缓存里拿到一个Socket交给线程池
poller = new Poller();
Thread pollerThread = new Thread(poller, getName() + "-Poller");
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();
//这里启动的Acceptor接受客户端的连接
startAcceptorThread();
}
protected void startAcceptorThread() {
acceptor = new Acceptor<>(this);
String threadName = getName() + "-Acceptor";
acceptor.setThreadName(threadName);
Thread t = new Thread(acceptor, threadName);
t.setPriority(getAcceptorThreadPriority());
t.setDaemon(getDaemon());
t.start();
}
Acceptor.java
public void run() {
socket = endpoint.serverSocketAccept();
}
@Override
protected SocketChannel serverSocketAccept() throws Exception {
//这里拿到一个真正的客户端连接
SocketChannel result = serverSock.accept();
// Bug does not affect Windows. Skip the check on that platform.
if (!JrePlatform.IS_WINDOWS) {
SocketAddress currentRemoteAddress = result.getRemoteAddress();
long currentNanoTime = System.nanoTime();
if (currentRemoteAddress.equals(previousAcceptedSocketRemoteAddress) &&
currentNanoTime - previousAcceptedSocketNanoTime < 1000) {
throw new IOException(sm.getString("endpoint.err.duplicateAccept"));
}
previousAcceptedSocketRemoteAddress = currentRemoteAddress;
previousAcceptedSocketNanoTime = currentNanoTime;
}
return result;
}
请求处理流程
Tomcat请求的第一步是接受客户端连接,然后将连接交给容器处理,在8.5版本之后,tomcat已经移除了BIO模型,支持NIO、NIO2、APR三种类型的IO模型,以NIO模型为了来看一下Tomcat的线程模型:
下面从源码的角度来看一下具体的请求处理流程。又有请求接收的入口是Endpoint,所以开始出在NioEndPoint中。
Acceptor.java
@SuppressWarnings("deprecation")
@Override
public void run() {
U socket = null;
// Accept the next incoming connection from the server
// socket
//这里调用NioEndPoint的方法,拿到一个客户端连接SocketChanal
socket = endpoint.serverSocketAccept();
// Successful accept, reset the error delay
errorDelay = 0;
// Configure the socket
if (!stopCalled && !endpoint.isPaused()) {
// setSocketOptions() will hand the socket off to
// an appropriate processor if successful
//这里通过调用setSocketOptions,把这个客户端连接socket加入到
//Pollor对应的缓存中
if (!endpoint.setSocketOptions(socket)) {
endpoint.closeSocket(socket);
}
} else {
endpoint.destroySocket(socket);
}
}
NioEndpoint.java
//获取客户端连接SocketChanal
@Override
protected SocketChannel serverSocketAccept() throws Exception {
SocketChannel result = serverSock.accept();
// Bug does not affect Windows. Skip the check on that platform.
if (!JrePlatform.IS_WINDOWS) {
SocketAddress currentRemoteAddress = result.getRemoteAddress();
long currentNanoTime = System.nanoTime();
if (currentRemoteAddress.equals(previousAcceptedSocketRemoteAddress) &&
currentNanoTime - previousAcceptedSocketNanoTime < 1000) {
throw new IOException(sm.getString("endpoint.err.duplicateAccept"));
}
previousAcceptedSocketRemoteAddress = currentRemoteAddress;
previousAcceptedSocketNanoTime = currentNanoTime;
}
return result;
}
//将客户端的SocketChanal加入到Poller缓存
@Override
protected boolean setSocketOptions(SocketChannel socket) {
//创建了一个包装类
NioSocketWrapper newWrapper = new NioSocketWrapper(channel, this);
channel.reset(socket, newWrapper);
connections.put(socket, newWrapper);
socketWrapper = newWrapper;
// Set socket properties
// Disable blocking, polling will be used
socket.configureBlocking(false);
if (getUnixDomainSocketPath() == null) {
socketProperties.setProperties(socket.socket());
}
socketWrapper.setReadTimeout(getConnectionTimeout());
socketWrapper.setWriteTimeout(getConnectionTimeout());
socketWrapper.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());
//调用Pollor的register方法,加入到Poller的缓存中
poller.register(socketWrapper);
}
public void register(final NioSocketWrapper socketWrapper) {
socketWrapper.interestOps(SelectionKey.OP_READ);//this is what OP_REGISTER turns into.
PollerEvent pollerEvent = createPollerEvent(socketWrapper, OP_REGISTER);
//构造一个Event对象,加入到缓存
addEvent(pollerEvent);
}
private void addEvent(PollerEvent event) {
events.offer(event);
if (wakeupCounter.incrementAndGet() == 0) {
selector.wakeup();
}
}
Poller.java
@Override
public void run() {
将Chanal注册到Selector上
hasEvents = events();
//遍历事件
Iterator<SelectionKey> iterator =
keyCount > 0 ? selector.selectedKeys().iterator() : null;
while (iterator != null && iterator.hasNext()) {
SelectionKey sk = iterator.next();
iterator.remove();
NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();
// Attachment may be null if another thread has called
// cancelledKey()
if (socketWrapper != null) {
//开始处理
processKey(sk, socketWrapper);
}
}
}
protected void processKey(SelectionKey sk, NioSocketWrapper socketWrapper) {
processSocket(socketWrapper, SocketEvent.OPEN_READ, true)
}
public boolean processSocket(SocketWrapperBase<S> socketWrapper,
SocketEvent event, boolean dispatch) {
//这里的SocketProcessorBase 是一个Runnable,最终线程池会执行它的run方法
SocketProcessorBase<S> sc = processorCache.pop();
Executor executor = getExecutor();
if (dispatch && executor != null) {
//交给线程池处理
executor.execute(sc);
} else {
sc.run();
}
}
SocketProcessorBase.java
@Override
public final void run() {
doRun();
}
NioEndPoint.SocketProcessor.java
@Override
protected void doRun() {
//吧socketWrapper交给Processer处理
SocketState state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);
}
AbstractProtocol.java
@SuppressWarnings("deprecation")
@Override
public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) {
//拿到Processor
Processor processor = (Processor) wrapper.takeCurrentProcessor();
//通过Processor来处理
SocketState state = processor.process(wrapper, status);
}
AbstractProcessorLight.java
@Override
public SocketState process(SocketWrapperBase<?> socketWrapper, SocketEvent status) {
state = service(socketWrapper);
}
Http11Processor.java
//构建Request、Response,调用Adapter
@Override
public SocketState service(SocketWrapperBase<?> socketWrapper) {
getAdapter().service(request, response);
}
CyoteAdapter.java
@Override
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
throws Exception {
// Calling the container
//这里拿到Service实例,把请求叫给Engin进行处理
connector.getService().getContainer().getPipeline().getFirst().invoke(
request, response);
}
这样,处理流程就从Connector来到了Engine,也就来到了真正的Servlet容器层,
由于Engine、Host和Context是Container,所以都采用了Pipline模式,增加了自定义Valve的入口(在server.xml中这三个标签中都可以配置Valve)。调用从Adapter来到Engine后,首先会来到StandardEngineValue。
StandardEngineValve.java
@Override
public final void invoke(Request request, Response response) {
Host host = request.getHost();
//如果通过Engine没有找到对应的host,就返回404
if (host == null) {
if (!response.isError()) {
response.sendError(404);
}
return;
}
//这里拿到Host对应的Pipline的第一个Valve,也就是StandardHostValve
host.getPipeline().getFirst().invoke(request, response);
}
StandardHostValve.java
@Override
public final void invoke(Request request, Response response)
throws IOException, ServletException {
//尝试找到对应的Context,也就是项目,如果没有,还是会返回404
Context context = request.getContext();
if (context == null) {
// Don't overwrite an existing error
if (!response.isError()) {
response.sendError(404);
}
return;
}
向下找,找到Context Pipline的第一个Valve,也就是StandardContextValve
context.getPipeline().getFirst().invoke(request, response);
}
StandardContextValve.java
@Override
public final void invoke(Request request, Response response)
throws IOException, ServletException {
// Select the Wrapper to be used for this Request
Wrapper wrapper = request.getWrapper();
//继续向下,调用Wrapper的Pipline,最终拿到StandardWrapperValve
wrapper.getPipeline().getFirst().invoke(request, response);
}
StandardWrapperValve.java
在这里就能够找到对应的Servlet实例了
@Override
public final void invoke(Request request, Response response)
throws IOException, ServletException {
StandardWrapper wrapper = (StandardWrapper) getContainer();
//从Wrapper中拿到Servlet实例
servlet = wrapper.allocate();
//构建FilterChain,包含了Filter和Servlet
ApplicationFilterChain filterChain =
ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
//这里调用filterChain,即调用Filter和Servlet
filterChain.doFilter(request.getRequest(),response.getResponse());
}
ApplicationFilterChain.java
@Override
public void doFilter(ServletRequest request, ServletResponse response){
final ServletRequest req = request;
final ServletResponse res = response;
internalDoFilter(request,response);
}
private void internalDoFilter(ServletRequest request,
ServletResponse response)
throws IOException, ServletException {
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = filterConfig.getFilter();
//调用Filter
filter.doFilter(request, response, this);
//调用Servlet
servlet.service(request, response);
}
再往下走,就来到了Servlet规范中的HttpServlet
HttpServlet.java
@Override
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
} catch (ClassCastException e) {
throw new ServletException(lStrings.getString("http.non_http"));
}
service(request, response);
}
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//这里根据请求方式调用Servlet中的doGet或doPost等
}
类加载机制
由于在Tomcat中会部署多个项目,而且在每个项目中很有可能存在同样的依赖包或自定义类,为了将每个项目对应的类对象隔离开来,Tomcat并没有直接采用Java的双亲委派加载机制,因为双亲委派模型无法同时加载相同的类对象,所以Tomcat自己实现了一个WebappClassLoader,在使用上,每个web项目都对应一个WebAppClassloader的实例,这个类加载器负责加载web应用目录下的lib包和业务类,而java本身的类还是会由java自带的类加载器进行加载。
Tomcat优化
- 禁用ajp Connector;
- 为Connector配置线程池(具体参数根据性能要求和硬件条件进行调整);
- 现在合适的I/O模型(BIO、NIO、NIO2、APR);
- 选择合适的jvm 垃圾收集器;
- 调整新生代的大小(减少gc发生的次数,减少对象gc年龄);
- 大内存使用G1等高级垃圾收集器;