Apache Tomcat - Java Web应用服务器

项目简介

Apache Tomcat是Apache软件基金会开发的一个开源的Java Servlet容器,实现了Java EE的Servlet、JSP(JavaServer Pages)和WebSocket规范。Tomcat为Java Web应用程序提供了一个”纯Java”的HTTP Web服务器环境,使得Java代码能够在其中运行。

Tomcat最初由Sun Microsystems的James Duncan Davidson开发,后来捐赠给Apache软件基金会。它是目前最受欢迎的Java Web应用服务器之一,被广泛用于开发和部署Java Web应用程序。

主要特性

  • Servlet容器:完整实现Java Servlet 4.0规范
  • JSP引擎:支持JSP 2.3规范,提供动态网页生成
  • WebSocket支持:实现WebSocket 1.1规范
  • 管理界面:提供Web界面管理应用程序和服务器
  • 集群支持:支持会话复制和负载均衡
  • 安全性:内置安全域和访问控制机制
  • 轻量级:相比其他Java EE服务器,占用资源较少

项目原理

核心架构

Tomcat采用分层的组件架构:

1
2
3
4
5
6
7
8
9
10
11
12
Server (服务器)
└── Service (服务)
├── Connector (连接器)
│ ├── HTTP/1.1 Connector
│ ├── HTTP/2 Connector
│ └── AJP Connector
└── Engine (引擎)
└── Host (虚拟主机)
└── Context (Web应用上下文)
├── Servlet
├── Filter
└── Listener

请求处理流程

  1. 连接接收:Connector接收HTTP请求
  2. 请求解析:解析HTTP请求头和参数
  3. 容器调用:将请求传递给Engine
  4. 主机匹配:根据Host匹配虚拟主机
  5. 上下文路由:根据URL路径匹配Web应用
  6. Servlet处理:调用相应的Servlet处理请求
  7. 响应生成:生成HTTP响应并返回

核心组件

Catalina(Servlet容器)

  • 管理Servlet生命周期
  • 处理请求分发和会话管理
  • 实现安全和过滤机制

Coyote(HTTP连接器)

  • 处理HTTP协议
  • 管理网络连接
  • 支持HTTP/1.1、HTTP/2和AJP协议

Jasper(JSP引擎)

  • 编译JSP页面为Servlet
  • 管理JSP页面的生命周期
  • 提供表达式语言(EL)支持

使用场景

1. Java Web应用部署

Tomcat是部署Java Web应用程序的标准平台,支持WAR文件的热部署和管理。

2. 微服务架构

在微服务架构中,Tomcat作为嵌入式服务器,为每个微服务提供HTTP服务能力。

3. 开发和测试环境

开发者使用Tomcat搭建本地开发环境,进行Java Web应用的开发和调试。

4. 企业级Web服务

中小型企业使用Tomcat部署内部管理系统、OA系统等企业应用。

5. 反向代理后端

Tomcat与Apache HTTP Server或Nginx配合,作为处理动态内容的后端服务器。

具体案例

案例1:Spring Boot应用部署

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- pom.xml 配置 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
</parent>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
</dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Spring Boot应用主类
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

// REST控制器
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(@RequestParam(defaultValue = "World") String name) {
return String.format("Hello, %s!", name);
}
}

案例2:传统Servlet应用部署

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!-- web.xml 配置 -->
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
version="3.1">
<display-name>Web Application</display-name>

<!-- Servlet配置 -->
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.example.HelloServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>

<!-- 过滤器配置 -->
<filter>
<filter-name>EncodingFilter</filter-name>
<filter-class>com.example.EncodingFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
1
2
3
4
5
6
7
8
9
10
11
12
// Servlet实现
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<h1>Hello from Servlet!</h1>");
}
}

案例3:集群配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!-- server.xml 集群配置 -->
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
channelSendOptions="8">

<Manager className="org.apache.catalina.ha.session.DeltaManager"
expireSessionsOnShutdown="false"
notifyListenersOnReplication="true"/>

<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<Membership className="org.apache.catalina.tribes.membership.McastService"
address="228.0.0.4"
port="45564"
frequency="500"
dropTime="3000"/>

<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="auto"
port="4000"
autoBind="100"
selectorTimeout="5000"
maxThreads="6"/>

<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
</Sender>
</Channel>

<Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
filter=""/>
<Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>

<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>

案例4:数据源配置

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- context.xml 数据源配置 -->
<Context>
<Resource name="jdbc/MyDB"
auth="Container"
type="javax.sql.DataSource"
maxTotal="20"
maxIdle="10"
maxWaitMillis="-1"
username="dbuser"
password="dbpass"
driverClassName="com.mysql.cj.jdbc.Driver"
url="jdbc:mysql://localhost:3306/mydb?useUnicode=true&amp;characterEncoding=UTF-8"/>
</Context>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 使用数据源
@WebServlet("/users")
public class UserServlet extends HttpServlet {
private DataSource dataSource;

@Override
public void init() throws ServletException {
try {
Context initContext = new InitialContext();
Context envContext = (Context) initContext.lookup("java:/comp/env");
dataSource = (DataSource) envContext.lookup("jdbc/MyDB");
} catch (NamingException e) {
throw new ServletException("数据源初始化失败", e);
}
}

@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
try (Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users");
ResultSet rs = stmt.executeQuery()) {

// 处理查询结果
response.setContentType("application/json;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("[");

boolean first = true;
while (rs.next()) {
if (!first) out.println(",");
out.printf("{\"id\":%d,\"name\":\"%s\"}",
rs.getInt("id"), rs.getString("name"));
first = false;
}
out.println("]");

} catch (SQLException e) {
throw new ServletException("数据库查询失败", e);
}
}
}

性能优化建议

1. JVM调优

1
2
# 启动脚本优化
export JAVA_OPTS="-Xms2048m -Xmx4096m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+HeapDumpOnOutOfMemoryError"

2. 连接器调优

1
2
3
4
5
6
7
8
9
10
11
<!-- server.xml 连接器优化 -->
<Connector port="8080"
protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="200"
minSpareThreads="10"
connectionTimeout="20000"
acceptorThreadCount="2"
enableLookups="false"
compression="on"
compressionMinSize="2048"
compressableMimeType="text/html,text/xml,text/plain,application/javascript,text/css"/>

3. 会话管理优化

1
2
3
4
5
6
7
8
9
10
<!-- context.xml 会话管理 -->
<Manager className="org.apache.catalina.session.PersistentManager"
saveOnRestart="true"
maxActiveSessions="-1"
minIdleSwap="1800"
maxIdleSwap="3600"
maxIdleBackup="1800">
<Store className="org.apache.catalina.session.FileStore"
directory="sessions"/>
</Manager>

4. 日志配置优化

1
2
3
4
5
6
7
8
9
# logging.properties
handlers = 1catalina.org.apache.juli.AsyncFileHandler, java.util.logging.ConsoleHandler

.handlers = 1catalina.org.apache.juli.AsyncFileHandler, java.util.logging.ConsoleHandler

1catalina.org.apache.juli.AsyncFileHandler.level = INFO
1catalina.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs
1catalina.org.apache.juli.AsyncFileHandler.prefix = catalina.
1catalina.org.apache.juli.AsyncFileHandler.maxDays = 7

Apache Tomcat作为Java Web应用服务器的标杆,其简单易用、高性能的特点使其成为Java开发者的首选。通过合理的配置和优化,Tomcat可以支撑高并发的Web应用,满足从小型项目到企业级应用的各种需求。

版权所有,如有侵权请联系我