今天一起徒手创建了一个精简版的tomcat,相信大家对tomcat运行原理会有更加深入的理解
前置知识
-
java注解、反射
-
java网络编程
-
servlet基础
回顾tomcat
tomcat的概念
Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,属于轻量级应用,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP 程序的首选。对于一个初学者来说,可以这样认为,当在一台机器上配置好Apache 服务器,可利用它响应(下的一个应用)页面的访问请求。实际上Tomcat是Apache 服务器的扩展,但运行时它是独立运行的,所以当你运行tomcat 时,它实际上作为一个与Apache 独立的进程单独运行的。诀窍是,当配置正确时,Apache 为HTML页面服务,而Tomcat 实际上运行JSP 页面和Servlet。
tomcat正常的接收了浏览器发送的请求
tomcat根据请求映射到响应的servlet
请求原始信息如下:
GET /aaa/xxx HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: keep-alive
Cookie: JSESSIONID=6E593EFE70D7DE906671505FDBAEEAAC; Hm_lvt_d214947968792b839fd669a4decaaffc=1603700593,1603779032; Idea-661643a5=d5722ef7-628d-418c-960f-6f353393ba3b; cookie_lang=1
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
熟悉过apache的Tomcat之后,下面自己尝试一下
自建xml版本tomcatv1.0
思路分析
根据上面的思路,需要设计一下几个java类
- tomcat启动类
- 请求实体类
- 响应实体类
- servlet抽象类
- servlet的具体实现类
1. 创建maven项目,引入相关jar
<dependencies>
<!-- 引入hutool工具,解析xml文件-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.4</version>
</dependency>
<!-- 引入反射jar包-->
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.11</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
</dependencies>
2. 创建请求类
package com.aaa.tomcat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.InputStream;
/**
* @author : 尚腾飞(838449693@qq.com)
* @version : 1.0
* @createTime : 2022/9/29 19:47
* @description : 自创请求类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RequestAAA {
private String method;
private String url;
private InputStream inputStream;
public RequestAAA(InputStream inputStream) throws Exception {
byte[] buf = new byte[1024];
//定义一个请求字符串
String requestStr = "";
//处理inputStream,获取请求方式和请求地址
int count = 0;
if ((count = inputStream.read(buf)) > 0) {
requestStr = new String(buf, 0, count);
}
/**
* GET /aaa/xxx HTTP/1.1
* Host: localhost:8080
*/
//获取第一行
String head = requestStr.split("\n")[0];
this.method = head.split("\\s")[0];
this.url = head.split("\\s")[1];
}
}
3. 创建响应类
package com.aaa.tomcat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.IOException;
import java.io.OutputStream;
/**
* @author : 尚腾飞(838449693@qq.com)
* @version : 1.0
* @createTime : 2022/9/29 19:47
* @description : 自创相应类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ResponseAAA {
private OutputStream outputStream;
/**
* @param context
* @return void
* @create by: Teacher陈
* @description:将内容写回浏览器
* @create time: 2021/1/20 10:37
*/
public void write(String context) throws IOException {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("HTTP/1.1 200\r\n");
stringBuffer.append("Content-Type: text/html\r\n");
stringBuffer.append("\r\n");
stringBuffer.append("<html>");
stringBuffer.append("<body>");
stringBuffer.append(context);
stringBuffer.append("</body>");
stringBuffer.append("</html>");
outputStream.write(stringBuffer.toString().getBytes());
outputStream.flush();
outputStream.close();
}
}
4. 创建servlet映射类
package com.aaa.tomcat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author : 尚腾飞(838449693@qq.com)
* @version : 1.0
* @createTime : 2022/9/29 19:47
* @description :
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ServletMappingAAA {
/**
* 对应web.xml中的servlet url-pattern
*/
private String url;
/**
* 对应web.xml中的servlet-class
*/
private String clazz;
}
5. 创建servlet抽象类
package com.aaa.tomcat;
/**
* @author : 尚腾飞(838449693@qq.com)
* @version : 1.0
* @createTime : 2022/9/29 19:49
* @description : 我自己的servlet
*/
public abstract class ServletAAA {
/**
* 处理get请求
*
* @param req
* @param resp
*/
public abstract void doGet(RequestAAA req, ResponseAAA resp);
/**
* 处理post请求
*
* @param req
* @param resp
*/
public abstract void doPost(RequestAAA req, ResponseAAA resp);
public void service(RequestAAA req, ResponseAAA resp) {
if ("GET".equalsIgnoreCase(req.getMethod())) {
doGet(req, resp);
} else if ("POST".equalsIgnoreCase(req.getMethod())) {
doPost(req, resp);
}
}
}
6. 创建servlet实现类
package com.aaa.tomcat;
import java.io.IOException;
/**
* @author : 尚腾飞(838449693@qq.com)
* @version : 1.0
* @createTime : 2022/9/29 19:57
* @description :
*/
public class HelloServlet extends ServletAAA{
@Override
public void doGet(RequestAAA req, ResponseAAA resp) {
this.doPost(req,resp);
}
@Override
public void doPost(RequestAAA req, ResponseAAA resp) {
System.out.println("HelloServlet is called");
try {
resp.write("I Love You");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
7. 创建tomcat类
package com.aaa.tomcat;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
/**
* @author :Teacher陈
* @date :Created in 2021/1/20 11:00
* @description:AAA的tomcat
* @modified By:
* @version: 1
*/
public class TomcatAAA {
/**
* 1:通过serverSoket启动一个监听程序,监听8080端口
* 2:socket对象可以获取inputstream请求字节流,还可以获取响应outputStream
* 3:从请求中解析http报文,获取请求方式和请求地址
* 4:根据请求地址获取servlet类,然后根据请求方式执行servlet中对应的逻辑处理方法
* 5:构建响应对象,响应给浏览器
*/
private int port = 9090;
/**
* 定义一个servlet映射集合
*/
private Map<String, String> urlServletMap = new HashMap();
public TomcatAAA(int port) {
this.port = port;
}
public TomcatAAA() {
}
/**
* @return
* @create by: Teacher陈
* @description: 启动tomcat
* @create time: 2021/1/20 11:04
*/
public void start() {
ServerSocket serverSocket = null;
Socket socket = null;
try {
serverSocket = new ServerSocket(this.port);
System.out.println("tomcat is running");
while (true) {
//等待客户端请求
socket = serverSocket.accept();
//获取客户端发送的信息
InputStream inputStream = socket.getInputStream();
//响应客户端的信息
OutputStream outputStream = socket.getOutputStream();
//构建请求和响应
RequestAAA requestAAA = new RequestAAA(inputStream);
ResponseAAA responseAAA = new ResponseAAA(outputStream);
//获取servlet
ServletMappingAAA servletMappingAAA = new ServletMappingAAA("/xxx", "com.aaa.tomcat.HelloServlet");
Class<ServletAAA> servletAAAClass = (Class<ServletAAA>) Class.forName(servletMappingAAA.getClazz());
ServletAAA servletAAA = servletAAAClass.newInstance();
servletAAA.service(requestAAA, responseAAA);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
socket.close();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
TomcatAAA tomcatAAA = new TomcatAAA();
tomcatAAA.start();
}
}
8. 测试
运行main方法在浏览器中访问
只能访问一个servlet,而且是硬编码,不灵活
创建tomcatv1.1类,支持读取web.xml中单个servlet
resources目录下添加web.xml文件
<web-app>
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.aaa.tomcat.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/xxx</url-pattern>
</servlet-mapping>
</web-app>
修改TomcatAAA文件
在tomcat启动的时候加载所有的url和servlet的映射关系
package com.aaa.tomcat;
import cn.hutool.core.util.XmlUtil;
import org.w3c.dom.Document;
import javax.xml.xpath.XPathConstants;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
/**
* @author :Teacher陈
* @date :Created in 2021/1/20 11:00
* @description:AAA的tomcat
* @modified By:
* @version: 1
*/
public class TomcatAAA {
/**
* 1:通过serverSoket启动一个监听程序,监听8080端口
* 2:socket对象可以获取inputstream请求字节流,还可以获取响应outputStream
* 3:从请求中解析http报文,获取请求方式和请求地址
* 4:根据请求地址获取servlet类,然后根据请求方式执行servlet中对应的逻辑处理方法
* 5:构建响应对象,响应给浏览器
*/
private int port = 9090;
/**
* 定义一个servlet映射集合
*/
private Map<String, String> urlServletMap = new HashMap();
public TomcatAAA(int port) {
this.port = port;
}
public TomcatAAA() {
}
/**
* @author : 尚腾飞(838449693@qq.com)
* @version : 1.0
* @createTime : 2022/9/29 22:55
* @description :
*/
public void start() {
//在tomcat启动的时候加载所有的url和servlet的映射关系
Document docResult= XmlUtil.readXML("web.xml");
Object clazz = XmlUtil.getByXPath("//web-app/servlet/servlet-class", docResult, XPathConstants.STRING);
Object url = XmlUtil.getByXPath("//web-app/servlet-mapping/url-pattern", docResult, XPathConstants.STRING);
urlServletMap.put(url.toString(),clazz.toString());
ServerSocket serverSocket = null;
Socket socket = null;
try {
serverSocket = new ServerSocket(this.port);
System.out.println("tomcat is running");
while (true) {
//等待客户端请求
socket = serverSocket.accept();
//获取客户端发送的信息
InputStream inputStream = socket.getInputStream();
//响应客户端的信息
OutputStream outputStream = socket.getOutputStream();
//构建请求和响应
RequestAAA requestAAA = new RequestAAA(inputStream);
ResponseAAA responseAAA = new ResponseAAA(outputStream);
//获取servlet
for (Map.Entry<String, String> entry : urlServletMap.entrySet()) {
if (entry.getKey().equals(requestAAA.getUrl())){
Class<ServletAAA> servletAAAClass = (Class<ServletAAA>) Class.forName(entry.getValue());
ServletAAA servletAAA = servletAAAClass.newInstance();
servletAAA.service(requestAAA, responseAAA);
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
assert socket != null;
socket.close();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
TomcatAAA tomcatAAA = new TomcatAAA();
tomcatAAA.start();
}
}
创建tomcatV1.2,读取web.xml中多个servlet
上面的案例可以读取单个servlet配置信息,真实的web.xml中包含多个servlet配置。
web.xml文件
<web-app>
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.aaa.tomcat.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/xxx</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>test</servlet-name>
<servlet-class>com.aaa.tomcat.TestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>test</servlet-name>
<url-pattern>/yyy</url-pattern>
</servlet-mapping>
</web-app>
创建读取web.xml的工具类
package com.aaa.tomcat;
import cn.hutool.core.util.XmlUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import java.util.*;
/**
* @author : 尚腾飞(838449693@qq.com)
* @version : 1.0
* @createTime : 2022/9/29 23:02
* @description :
*/
public class MyXmlUtil {
/**
* 读取web.xml文件
* @return Map<String, String>
*/
public static Map<String, String> loadWebXml() {
//加载web.xml
Document docResult = XmlUtil.readXML("web.xml");
//将Document转换成Element
Element element = docResult.getDocumentElement();
//按照元素名称servlet查找所有的元素
List<Element> elementList = XmlUtil.getElements(element, "servlet");
//key servletName value clazz
Map<String, String> servletMap = new HashMap();
//遍历servlet元素
for (Element elementServlet : elementList) {
//获取单个servlet元素的所有孩子
NodeList childNodes = elementServlet.getChildNodes();
List<String> tempList = new ArrayList<String>();
//遍历所有孩子节点
for (int i = 0; i < childNodes.getLength(); i++) {
//去除多余的特殊字符和空格
String textContent = childNodes.item(i).getTextContent().trim().replace("\\n", "")
.replace("\\t", "");
if (textContent != "" && textContent.length() > 0) {
tempList.add(textContent);
}
}
//key servletName value clazz 封装map集合
servletMap.put(tempList.get(0), tempList.get(1));
}
//key servletName value url
Map<String, String> servletMappingMap = new HashMap();
List<Element> elementListMapping = XmlUtil.getElements(element, "servlet-mapping");
for (Element elementMapping : elementListMapping) {
NodeList childNodes = elementMapping.getChildNodes();
List<String> tempList = new ArrayList<String>();
for (int i = 0; i < childNodes.getLength(); i++) {
String textContent = childNodes.item(i).getTextContent().trim().replace("\\n", "")
.replace("\\t", "");
if (textContent != "" && textContent.length() > 0) {
tempList.add(textContent);
}
}
servletMappingMap.put(tempList.get(0), tempList.get(1));
}
//合并两个map
Set<String> keySet = servletMappingMap.keySet();
Map<String, String> finalMap = new HashMap();
for (String s : keySet) {
finalMap.put(servletMappingMap.get(s), servletMap.get(s));
}
System.out.println(finalMap.toString());
return finalMap;
}
}
tomcat启动类
package com.aaa.tomcat;
import cn.hutool.core.util.XmlUtil;
import org.w3c.dom.Document;
import javax.xml.xpath.XPathConstants;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
/**
* @author :Teacher陈
* @date :Created in 2021/1/20 11:00
* @description:AAA的tomcat
* @modified By:
* @version: 1
*/
public class TomcatAAA {
/**
* 1:通过serverSoket启动一个监听程序,监听8080端口
* 2:socket对象可以获取inputstream请求字节流,还可以获取响应outputStream
* 3:从请求中解析http报文,获取请求方式和请求地址
* 4:根据请求地址获取servlet类,然后根据请求方式执行servlet中对应的逻辑处理方法
* 5:构建响应对象,响应给浏览器
*/
private int port = 9090;
/**
* 定义一个servlet映射集合
*/
private Map<String, String> urlServletMap = new HashMap();
public TomcatAAA(int port) {
this.port = port;
}
public TomcatAAA() {
}
/**
* @author : 尚腾飞(838449693@qq.com)
* @version : 1.0
* @createTime : 2022/9/29 22:55
* @description :
*/
public void start() {
//在tomcat启动的时候加载所有的url和servlet的映射关系
urlServletMap = MyXmlUtil.loadWebXml();
ServerSocket serverSocket = null;
Socket socket = null;
try {
serverSocket = new ServerSocket(this.port);
System.out.println("tomcat is running");
while (true) {
//等待客户端请求
socket = serverSocket.accept();
//获取客户端发送的信息
InputStream inputStream = socket.getInputStream();
//响应客户端的信息
OutputStream outputStream = socket.getOutputStream();
//构建请求和响应
RequestAAA requestAAA = new RequestAAA(inputStream);
ResponseAAA responseAAA = new ResponseAAA(outputStream);
//获取servlet
for (Map.Entry<String, String> entry : urlServletMap.entrySet()) {
if (entry.getKey().equals(requestAAA.getUrl())){
Class<ServletAAA> servletAAAClass = (Class<ServletAAA>) Class.forName(entry.getValue());
ServletAAA servletAAA = servletAAAClass.newInstance();
servletAAA.service(requestAAA, responseAAA);
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
assert socket != null;
socket.close();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
TomcatAAA tomcatAAA = new TomcatAAA();
tomcatAAA.start();
}
}
这时HelloServlet和TestServlet两个servlet都可以访问
创建注解版本tomcatV2.0
1. 删除web.xml中的配置
2. 自建注解版本tomcatV2.0
创建servlet注解
package com.aaa.tomcat;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author 尚腾飞(838449693@qq.com)
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyWebServlet {
/**
* 需要拦截的url,可以是多个
* @return
*/
String[] urlPatterns() default {};
}
创建Servlet
package com.aaa.tomcat;
import java.io.IOException;
/**
* @author : 尚腾飞(838449693@qq.com)
* @version : 1.0
* @createTime : 2022/9/29 19:57
* @description :
*/
@MyWebServlet(urlPatterns = "/aaa")
public class AnnServlet extends ServletAAA {
@Override
public void doGet(RequestAAA req, ResponseAAA resp) {
this.doPost(req, resp);
}
@Override
public void doPost(RequestAAA req, ResponseAAA resp) {
System.out.println("AnnServlet is called");
try {
System.out.println("response is running ");
resp.write(" I miss you ");
} catch (IOException e) {
e.printStackTrace();
}
}
}
package com.aaa.servlet;
import com.aaa.tomcat.MyWebServlet;
import com.aaa.tomcat.RequestAAA;
import com.aaa.tomcat.ResponseAAA;
import com.aaa.tomcat.ServletAAA;
import java.io.IOException;
/**
* @author : 尚腾飞(838449693@qq.com)
* @version : 1.0
* @createTime : 2022/9/29 19:57
* @description :
*/
@MyWebServlet(urlPatterns = "/bbb")
public class Ann2Servlet extends ServletAAA {
@Override
public void doGet(RequestAAA req, ResponseAAA resp) {
this.doPost(req, resp);
}
@Override
public void doPost(RequestAAA req, ResponseAAA resp) {
System.out.println("AnnServlet is called");
try {
System.out.println("response is running ");
resp.write(" I miss you sister");
} catch (IOException e) {
e.printStackTrace();
}
}
}
扫描并解析注解
在MyXmlUtil类中添加方法
/**
* 扫描所有的sevlet注解
* @param packageName
* @return Map<String, String>
*/
public static Map<String, String> loadServletAnn(String packageName) {
if(packageName==null|| "".equals(packageName)){
packageName="com.aaa.servlet";
}
Reflections reflections= new Reflections(packageName);
Set<Class<?>> classSet = reflections.getTypesAnnotatedWith(MyWebServlet.class);
Map<String,String> map = new HashMap<String, String>();
for (Class<?> aClass : classSet) {
MyWebServlet annotation = aClass.getAnnotation(MyWebServlet.class);
map.put(annotation.urlPatterns()[0],aClass.getName());
}
return map;
}
tomcat启动类中运用loadServletAnn方法扫描servlet包
下的两个带MyWebServlet注解的类,启动main方法
urlServletMap = MyXmlUtil.loadServletAnn("com.aaa.servlet");
浏览器测试
六不六?
代码
https://gitee.com/c90005471/tomcat-aaa
头条
喜欢的话,可以加个关注
https://www.toutiao.com/article/6919772102732268039/?log_from=9960c1f8066e6_1664411254396
手写tomcat视频
视频链接:https://pan.baidu.com/s/1CU-n8qe3jPZNzeqtse1ORQ
提取码:32tj