一、什么是Tomcat
Tomcat 是一款开源的、轻量级的 Web 服务器,它不仅能够提供 HTTP 服务,还能够运行 Java Servlet 和 JavaServer Pages(JSP)。对于许多开发者来说,理解 Tomcat 的目录结构以及如何在该结构中组织应用,往往是入门的第一步。
1、Tomcat目录结构
Tomcat 的目录结构相对简单,但每个目录和文件都有其明确的用途。
1. bin 目录
bin 目录包含启动和停止 Tomcat 所需的脚本。它的内容包括:
startup.sh/startup.bat:启动 Tomcat。
shutdown.sh/shutdown.bat:停止 Tomcat。
catalina.sh/catalina.bat:Tomcat 启动时的核心脚本,包含了一些启动参数的设置。
setenv.sh/setenv.bat:用于设置 Tomcat 环境变量,通常用于配置 Java 堆大小、日志设置等。
2. conf 目录
conf 目录包含 Tomcat 的核心配置文件,这些文件直接影响 Tomcat 的行为。常见的配置文件有:
server.xml:Tomcat 的主要配置文件,定义了服务器的端口、连接器、虚拟主机等。
web.xml:Web 应用的默认部署描述符。
context.xml:为每个 Web 应用提供额外的配置。
3. lib 目录
lib 目录存放 Tomcat 运行时所需要的 Java 类库和 JAR 包,包括:
servlet-api.jar:Java Servlet API 的实现。
jsp-api.jar:Java Server Pages(JSP)API 的实现。
其他第三方库和 Tomcat 特有的库文件。
4. logs 目录
logs 目录用于存储 Tomcat 启动、运行过程中的日志文件。常见的日志文件有:
catalina.out:Tomcat 启动和运行时的主要日志文件。
localhost_access_log.*.txt:记录每个请求的访问日志。
5. webapps 目录
webapps 目录是 Tomcat 的应用目录,所有部署的 Web 应用都应该放在此目录下。每个应用通常以一个文件夹的形式存在,目录中包含了应用的 WEB-INF 和 META-INF 等文件夹。
6. work 目录
work 目录用于存放 Tomcat 编译 JSP 文件后的中间文件。每当 Tomcat 运行一个 JSP 文件时,都会将其编译成 Java 类文件并存放在这个目录中。
7. temp 目录
temp 目录是 Tomcat 用来存放临时文件的地方。例如,Tomcat 会将文件上传的内容存放在该目录中,处理过程中生成的临时数据也会保存在这里。
2、Tomcat的工作目录
在apache的Tomcat中,工作目录可以简化成上图,即在webapps中进行资源的访问。资源的访问可以简单的理解为对网页的操作。
二、Tomcat工作原理简介
1、Tomcat中Servlet的生命周期
在一个实现具体操作的Servlet类中,具有如下图的继承关系
javax.servlet.Servlet 是所有 Servlet 的基本接口。任何自定义的 Servlet 都必须实现这个接口或者继承一个实现了它的类。该接口定义了 Servlet 的生命周期方法,例如 init(), service(), 和 destroy(),这些方法体现了Servlet的生命周期
init(..):当servlet第一次被请求时,Servlet容器就会开始调用这个方法来初始化一个Servlet对象出来
service(...):每当请求Servlet时,Servlet容器就会调用这个方法service(...)
destroy(...):当要销毁Servlet时,Servlet容器就会调用这个方法
getservletInfo(...):这个方法会返回Servlet的一段描述,可以返回一段字符串。
getServletconfig(...):这个方法会返回由Servlet容器传给init()方法的Servletconfig对象。
javax.servlet.GenericServlet 是 Servlet 接口的一个抽象实现类。它实现了 Servlet 接口,但提供了空实现的 service() 方法。通常,自定义的 Servlet 会继承 GenericServlet,并重写 service() 方法来处理客户端请求。
javax.servlet.http.HttpServlet 是处理 HTTP 请求的抽象类,继承自 GenericServlet。大多数 Web 应用中的 Servlet 会继承 HttpServlet,因为它提供了简化的 HTTP 请求处理方法,如 doGet(), doPost(), doPut() 和 doDelete(),这些方法可以根据请求类型进行重写。同时 HttpServlet 中重写了service() 方法,对于不同的请求进行判断并划分到不同的操作中,将其拆分成doGet()方法和doPost()等七种方法,其目的是为了更好的匹配http请求。
2、Tomcat对待资源
Tomcat将资源分为动态资源和静态资源。
1.静态资源
指内容在服务器上不会发生变化的资源,通常这些资源由服务器直接提供给客户端,不需要任何动态处理。常见的静态资源有:
- HTML 文件:静态的网页内容,直接通过浏览器访问即可展示。
- CSS 文件:样式表文件,用于定义网页的外观。
- JavaScript 文件:脚本文件,用于网页的交互行为。
- 图片文件:如 PNG、JPEG、GIF、SVG 等图片文件。
- 字体文件:例如 .woff, .ttf 等字体文件。
- 视频文件:如 .mp4, .avi 等文件。
Tomcat 处理静态资源的方式:
Tomcat 会直接通过文件系统(例如 /webapps/ROOT 目录)读取静态资源,然后返回给客户端。静态资源通常会被配置在 Web 应用的 webapps 目录下。
默认情况下,Tomcat 会直接将这些资源返回给客户端,除非配置了 URL 映射或者过滤器,否则不涉及任何额外的处理逻辑。
2.动态资源
动态资源 是指在客户端请求时,服务器需要根据请求的不同,动态生成或者处理后才返回的内容。这类资源的内容是可变的,通常与用户的输入、数据库等外部数据源有关。动态资源的常见形式包括:
- Servlet:通过 Java 编写的后端代码,接收客户端请求并生成响应内容。
- JSP (JavaServer Pages):与 Servlet 类似,但通过模板方式生成 HTML 内容。JSP 是动态生成 HTML 页面的一种方法。
- RESTful API:通常由 Servlet 或其他 Java 后端框架实现,用于为前端应用提供数据接口。
- WebSocket:允许在客户端与服务器之间进行双向通信的技术。
Tomcat 处理动态资源的方式:
Tomcat 会将动态请求交给相应的 Servlet 或 JSP 进行处理。Servlet/JSP 会根据请求生成动态内容。请求 URL 会映射到特定的 Servlet 或 JSP,Tomcat 通过 web.xml 或注解来配置这些映射。
动态资源通常通过 Servlet 或 JSP 来生成 HTTP 响应,这些响应可以是 HTML、JSON、XML 等格式,具体取决于请求和后端代码的逻辑。
3.两种资源的区别
资源类型 |
需要服务器处理 |
变化性 |
典型示例 |
静态资源 |
无 |
不变 |
HTML、CSS、JavaScript、图片、视频等 |
动态资源 |
需要处理 |
可变 |
Servlet、JSP、REST API、WebSocket 等 |
静态资源 直接由 Tomcat 提供,客户端请求时无需任何额外的动态处理。
动态资源 需要通过 Servlet 或 JSP 等方式在服务器端进行处理,然后生成响应内容返回给客户端。
3、Tomcat的核心——Servlet容器
Tomcat的核心是servlet容器,本质是一个hashmap,Key值是servlet的访问路径,Value值是一般为servlet对象。在启动tomcat时,动态资源加载到servlet容器中
http请求打到servlet容器中,进行key值对比,如果访问动态资源,则调用servlet对象
需要注意的一点是,在Tomcat启动时,就需要把动态资源加载到Servlet容器中。
4、Tomcat处理http请求
Tomcat 生成一个socket对象,用于接收和处理http请求,从请求中解析出请求类型和需要的资源,交给Servlet容器中的Servlet对象去处理
三、简易Tomcat实现
1、目录结构
这样设计为区分不同功能的Java文件。
需要指出的是,这里创建项目不是普通的Java项目,需要选择Maven项目,只需设置项目名称即可,其他默认即可,项目名不要有中文。
2、实现继承关系
Servlet接口
package com.tom2.servlet;
import com.tom2.servlet.Re.HttpRequest;
import com.tom2.servlet.Re.HttpResponse;
public interface Servlet {
public abstract void init();
public abstract void destory();
public abstract void service(HttpRequest request, HttpResponse response) throws Exception;
}
GenericServlet抽象类
package com.tom2.servlet;
import com.tom2.servlet.Re.HttpRequest;
import com.tom2.servlet.Re.HttpResponse;
public abstract class GenericServlet implements Servlet{
@Override
public void init() {
System.out.println("genservlet 的init");
}
@Override
public void destory() {
System.out.println("genservlet 的destory");
}
}
HttpServlet 抽象类
package com.tom2.servlet;
import com.tom2.servlet.Re.HttpRequest;
import com.tom2.servlet.Re.HttpResponse;
public abstract class HttpServlet extends GenericServlet{
@Override
public void service(HttpRequest request, HttpResponse response) throws Exception {
if(request.getMethod().equals("GET")){
doGet(request,response);
}else if(request.getMethod().equals("POST")){
doPost(request,response);
}
}
public abstract void doGet(HttpRequest request, HttpResponse response) throws Exception;
public abstract void doPost(HttpRequest request, HttpResponse response) throws Exception;
}
HttpRequest 和 HttpResponse
package com.tom2.servlet.Re;
public class HttpRequest {
private String path;
private String method;
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
@Override
public String toString() {
return "HttpRequest{" +
"path='" + path + '\'' +
", method='" + method + '\'' +
'}';
}
}
package com.tom2.servlet.Re;
import java.io.IOException;
import java.io.OutputStream;
public class HttpResponse {
private OutputStream outputStream;
public HttpResponse(OutputStream outputStream){
this.outputStream=outputStream;
}
public void writeResponse(String context) throws IOException {
outputStream.write(context.getBytes());
}
}
3、工具类
SearchClassUtil :利用反射进行类信息的查找。扫描指定的 Java 包目录,查找所有 .class 文件,并提取每个 .class 文件的全类名,将这些类名存储在一个 List 列表中。
package com.tom2.Util;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* 扫描 com.qcby.webapps 目录下的 Java 文件并获取每个类的全名
*/
public class SearchClassUtil {
public static List<String> classPaths = new ArrayList<String>();
public static List<String> searchClass() {
// 需要扫描的包名
String basePack = "com.tom2.webapps";
// 将包名转换为路径格式
String classPath = SearchClassUtil.class.getClassLoader().getResource("").getPath();
basePack = basePack.replace(".", File.separator);
if(classPath.startsWith("file:")){
classPath.substring(5);
}
String searchPath = classPath + basePack;
// 确保路径在开发环境和打包后的环境下都能正确处理
File searchDir = new File(searchPath);
if (!searchDir.exists()) {
// 如果路径不存在,尝试扫描 target/classes 目录
searchPath = "target/classes"+File.separator+basePack;
searchDir = new File(searchPath);
}
// 调用 doPath 方法递归扫描文件
doPath(searchDir, classPath);
// 返回扫描到的类路径列表
return classPaths;
}
/**
* 递归处理文件,找到 .class 文件并将其全类名添加到 classPaths 列表中
*/
private static void doPath(File file, String classpath) {
if (file.isDirectory()) {
// 如果是目录,递归处理子目录
File[] files = file.listFiles();
if (files != null) {
for (File f1 : files) {
doPath(f1, classpath);
}
}
} else {
// 如果是 .class 文件,提取类名
if (file.getName().endsWith(".class")) {
// 修正路径替换:不使用 replaceFirst,而直接使用 replace
String path = file.getPath()
.replace(classpath, "") // 直接使用 classpath,不再替换 "/" 为 File.separator
.replace(File.separator, ".")
.replace(".class", "");
if(path.startsWith("targetes.")){
path = path.substring("targetes.".length());
}
// 将类名添加到 classPaths 列表
classPaths.add(path);
}
}
}
public static void main(String[] args) {
List<String> classes = SearchClassUtil.searchClass();
for (String s : classes) {
System.out.println(s);
}
}
}
WebServlet:注解
package com.tom2.Util;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface WebServlet {
String urlMapping() default "";
}
ResponseUtil
package com.tom2.Util;
public class ResponseUtil {
public static final String responseHeader200 = "HTTP/1.1 200 \r\n"+
"Content-Type:text/html; charset=utf-8 \r\n"+"\r\n";
public static String getResponseHeader404(){
return "HTTP/1.1 404 \r\n"+
"Content-Type:text/html; charset=utf-8 \r\n"+"\r\n" + "404";
}
public static String getResponseHeader200(String context){
return "HTTP/1.1 200 \r\n"+
"Content-Type:text/html; charset=utf-8 \r\n"+"\r\n" + context;
}
}
5、模拟Servlet容器
SearchServlet
package com.tom2;
import com.tom2.Util.SearchClassUtil;
import com.tom2.Util.WebServlet;
import com.tom2.servlet.HttpServlet;
import java.util.*;
public class SearchServlet {
public static Map<String, HttpServlet> servletmap = new HashMap<>();
static {
List<String> classpath = SearchClassUtil.searchClass();
for(String path:classpath){
System.out.println(path);
}
for(String path:classpath){
try{
Class clazz = Class.forName(path);
WebServlet webServlet = (WebServlet) clazz.getDeclaredAnnotation(WebServlet.class);
HttpServlet httpServlet = (HttpServlet) clazz.getDeclaredConstructor().newInstance();
servletmap.put(webServlet.urlMapping(),httpServlet);
}catch(Exception e){
e.printStackTrace();
}
}
}
}
6、动态资源
Login
package com.tom2.webapps.myweb;
import com.tom2.Util.WebServlet;
import com.tom2.servlet.HttpServlet;
import com.tom2.servlet.Re.HttpRequest;
import com.tom2.servlet.Re.HttpResponse;
@WebServlet(urlMapping = "/login")
public class Login extends HttpServlet {
@Override
public void doGet(HttpRequest request, HttpResponse response) throws Exception {
System.out.println("俺是login的doGet");
}
@Override
public void doPost(HttpRequest request, HttpResponse response) throws Exception {
}
}
Show
package com.tom2.webapps.myweb;
import com.tom2.Util.ResponseUtil;
import com.tom2.Util.WebServlet;
import com.tom2.servlet.HttpServlet;
import com.tom2.servlet.Re.HttpRequest;
import com.tom2.servlet.Re.HttpResponse;
@WebServlet(urlMapping = "/show")
public class Show extends HttpServlet {
@Override
public void doGet(HttpRequest request, HttpResponse response) throws Exception {
response.writeResponse(ResponseUtil.getResponseHeader200("俺是Show的doGet"));
}
@Override
public void doPost(HttpRequest request, HttpResponse response) throws Exception {
}
}
7、主方法
MyTomcat
package com.tom2;
import com.tom2.servlet.HttpServlet;
import com.tom2.servlet.Re.HttpRequest;
import com.tom2.servlet.Re.HttpResponse;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class MyTomcat {
private static HttpRequest httpRequest = new HttpRequest();
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(8080);
while(true){
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
HttpResponse httpResponse = new HttpResponse(outputStream);
int len = 0;
while(len==0){
len = inputStream.available();
}
byte[] bytes = new byte[len];
inputStream.read(bytes);
String context = new String(bytes);
System.out.println(context);
if(context.equals("")){
System.out.println("空请求");
}else{
String line1 = context.split("\\n")[0];
String method = line1.split("\\s")[0];
String path = line1.split("\\s")[1];
httpRequest.setMethod(method);
httpRequest.setPath(path);
}
System.out.println(SearchServlet.servletmap.containsKey(httpRequest.getPath()));
if(SearchServlet.servletmap.containsKey(httpRequest.getPath())){
HttpServlet httpServlet = SearchServlet.servletmap.get(httpRequest.getPath());
httpServlet.service(httpRequest,httpResponse);
}
}
}
}
8、模拟效果
在浏览器地址栏中输入:localhost:8080/login
在浏览器地址栏中输入:localhost:8080/show