【文章内容输出来源:拉勾教育Java高薪训练营】
--- 所有脑图均本人制作,未经允许请勿滥用 ---
试试来手写实现一个mini版的Tomcat"
一、 Tomcat 系统架构与原理剖析
Part1 - 准备工作
下载软件包和源代码
从表面开始分解
bin目录
conf目录
lib目录
logs目录
temp目录
webapps目录
work目录
LICENSE
Part2 - 浏览器访问服务器的流程
Part3 - Tomcat系统总体架构
身份I HTTP服务器
Connector连接器:负责对外交流
具体组件名称——Coyote
身份II Servlet容器
Container连接器:负责对内处理
具体组件名称——Catalina
Part4 - Tomcat 连接器组件 Coyote
(2)Coyote 使Catalina 容器(容器组件)与具体的请求协议及IO操作⽅式完全解耦
(3)Coyote 将Socket 输⼊转换封装为 Request 对象,进⼀步封装后交由 Catalina 容器进⾏处理,处 理请求完成后, Catalina 通过Coyote 提供的Response 对象将结果写⼊输出流
(4)Coyote 负责的是具体协议(应⽤层)和IO(传输层)相关内容
支持的IO模型与协议
应用层
- HTTP/1.1
默认--> 大部分Web应用采用的访问协议 - AJP --> 用于和WX继承(Apache)
- HTTP/2 --> 大幅提升Web性能的第二代协议,8.5+版本自适应
传输层
- NIO
默认--> 同步非阻塞I/O,采用 Java NIO 类库实现 - NIO2 --> 异步非阻塞I/O, 采用 JDK7 NIO2 类库实现
- APR --> 采用 Apache 可移植运行库实现,是C/C++编写的本地库
Coyote 内部组件及流程
EndPoint
具体Socket接收和发送处理器,是对传输层的抽象,因此EndPoint⽤来实现TCP/IP协议的
Processor
接收来自 EndPoint 的 Socket,读取字节流解析成Tomcat Request和 Response 对象
并通过 Adapter 将其提交到容器处理
ProtocolHandler
通过Endpoint 和 Processor , 实现针对具体协议的处理能力。
Tomcat 按照协议和I/O 提供了6个实现类 :
AjpNioProtocol ,AjpAprProtocol, AjpNio2Protocol , Http11NioProtocol ,Http11Nio2Protocol ,Http11AprProtocol
Adapter
由于协议不同,客户端发过来的请求信息也不尽相同
Tomcat设计者的解决方案是引入 CoyoteAdapter 转化从 ProtocolHandler 传来的非标准请求
Part4 - Tomcat Servlet容器 Catalina
Tomcat 本质上就是⼀款 Servlet 容器
可以认为整个Tomcat就是⼀个Catalina实例
1. Tomcat 启动的时候会初始化这个实例
2. Catalina实例通过加载
server.xml完成其他实例的创建并管理⼀个Server3. Server创建并管理多个服务
4. 每个服务⼜可以有多个Connector和⼀个Container
组件具体结构
- Engine
表示整个Catalina的Servlet引擎,用来管理多个虚拟站点,⼀个Service最多只能有⼀个Engine,但是⼀个引擎可包含多个Host - Host
代表⼀个虚拟主机,或者说⼀个站点,可以给Tomcat配置多个虚拟主机地址,⽽⼀个虚拟主机下可包含多个Context - Context
表示⼀个Web应⽤程序, ⼀个Web应⽤可包含多个Wrapper - Wrapper
表示⼀个Servlet,Wrapper 作为容器中的最底层,不能包含⼦容器
二、 Tomcat 服务器核心配置详解
下面着重说明 Service 标签中的内容
Executor
默认情况下,Service 并未添加共享线程池配置。 如果我们想添加⼀个线程池, 可以在
<Executor> 下添加如下配置
<!--The connectors can use a shared executor, you can define one or more named thread pools-->
<!-- name : 线程池名称 -->
<!-- namePrefix : 所创建的每个线程的名称前缀,⼀个单独的线程名称为 namePrefix + threadNumber -->
<!-- maxThreads : 池中最⼤线程数 -->
<!-- minSpareThreads : 活跃线程数,也就是核⼼池线程数,这些线程不会被销毁,会⼀直存在 -->
<!-- maxIdleTime : 线程空闲时间,超过该时间后,空闲线程会被销毁,默认值为6000(1分钟),单位毫秒 -->
<!-- maxQueueSize : 在被执⾏前最⼤线程排队数⽬,默认为Int的最⼤值,也就是⼴义的⽆限。 -->
<!-- prestartminSpareThreads : 启动线程池时是否启动 minSpareThreads部分线程。默认值为false,即不启动 -->
<!-- threadPriority : 线程池中线程优先级,默认值为5,值从1到10 -->
<!-- className : 线程池实现类,未指定情况下,
默认实现类为org.apache.catalina.core.StandardThreadExecutor。
如果想使⽤⾃定义线程池⾸先需要实现org.apache.catalina.Executor接⼝ -->
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="150" minSpareThreads="4"
maxIdleTime="60000" maxQueueSize="Integer.MAX_VALUE"
prestartminSpareThreads="false" threadPriority="5"
className="org.apache.catalina.core.StandardThreadExecutor"/>
Connector
⽤于创建链接器实例
默认情况下,server.xml 配置了两个链接器,⼀个⽀持HTTP协议,⼀个⽀持AJP协议
<!-- port : 端口号,如果设置为0,Tomcat将会随机选择⼀个可⽤的端⼝号给当前Connector 使⽤ -->
<!-- protocol : 当前Connector ⽀持的访问协议 -->
<!-- connectionTimeout : Connector 接收链接后的等待超时时间,单位为 毫秒。 -1 表示不超时。 -->
<!-- redirectPort : 当前Connector 不⽀持SSL请求,接收到了⼀个请求,并且也符合security-constraint 约束,需要SSL传输,Catalina⾃动将请求重定向到指定的端⼝。 -->
<!-- executor: : 指定共享线程池的名称, 也可以通过maxThreads、minSpareThreads 等属性配置内部线程池 -->
<!-- URIEncoding : ⽤于指定编码URI的字符编码, Tomcat8.x版本默认的编码为 UTF-8 , Tomcat7.x版本默认为ISO8859-1 -->
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
Engine
Servlet 引擎
<!-- name : ⽤于指定Engine 的名称, 默认为Catalina -->
<!-- defaultHost : 默认使⽤的虚拟主机名称 -->
<Engine name="Catalina" defaultHost="localhost">
Host
⽤于配置⼀个虚拟主机
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
Context
⽤于配置⼀个Web应⽤
<!-- docBase : Web应⽤⽬录或者War包的部署路径。可以是绝对路径,也可以是相对于 Host appBase的相对路径 -->
<!-- path : Web应⽤的Context 路径。
如果我们Host名为localhost, 则该web应⽤访问的根路径为:http://localhost:8080/web_demo。
-->
<Context docBase="/Users/yingdian/web_demo" path="/web3"></Context>
三、 手写实现迷你版 Tomcat
Part 1 简易交互 V1.0
主启动类
public class BootStrapV1 {
/* 定义socket监听的端口号 */
private int port = 8080;
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
/**
* MiniCat启动需要初始化展开的一些操作
*/
public void start() {
/*
MiniCat 1.0版本
需求:浏览器请求http://localhost:8080,返回一个固定的字符串到页面"Hello MiniCat!"
*/
try {
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("===> MiniCat start on port:" + port);
while(true) {
Socket socket = serverSocket.accept();
// 有了 socket,接收到请求,获取输出流输出内容
OutputStream os = socket.getOutputStream();
String data = "Hello MiniCat";
String responseText = HttpProtocolUtilV1.getHttpHeader200(data.getBytes().length) + data;
os.write(responseText.getBytes());
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
BootStrapV1 bootStrapV1 = new BootStrapV1();
bootStrapV1.start();
}
}
封装协议工具
public class HttpProtocolUtilV1 {
/**
* 为响应码 200 提供请求头信息
*
* @return
*/
public static String getHttpHeader200(long contentLength) {
return "HTTP/1.1 200 OK \n" +
"Content-Type: text/html;charset=utf-8 \n" +
"Content-Length: " + contentLength + "\n" +
"\r\n";
}
/**
* 为响应码 404 提供请求头信息
*
* @return
*/
public static String getHttpHeader404() {
String str404 = "<h1>404 Not Found</h1>";
return "HTTP/1.1 200 OK \n" +
"Content-Type: text/html;charset=utf-8 \n" +
"Content-Length: " + str404.getBytes().length + "\n" +
"\r\n";
}
}
Part 2 升级封装 V2.0
主启动类
public class BootStrapV2 {
/* 定义socket监听的端口号 */
private int port = 8080;
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
/**
* MiniCat启动需要初始化展开的一些操作
*/
public void start() {
/*
* 完成 MiniCat 2.0版本
* 需求:封装Request和Response对象,返回html静态资源文件
*/
try {
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("===> MiniCat start on port:" + port);
while(true) {
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
// 从输入流获取请求信息
// 封装Request对象和Response对象
Request request = new Request(inputStream);
Response response = new Response(socket.getOutputStream());
response.outputHtml(request.getUrl());
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
BootStrapV2 bootStrapV2 = new BootStrapV2();
bootStrapV2.start();
}
}
请求封装类
public class Request {
private String method; // 请求方式-[GET/POST]
private String url; // 资源定位
private InputStream inputStream; // 输入流
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public InputStream getInputStream() {
return inputStream;
}
public void setInputStream(InputStream inputStream) {
this.inputStream = inputStream;
}
public Request() {}
public Request(InputStream inputStream) throws IOException {
this.inputStream = inputStream;
/* 以下 while 针对网络间断进行判定,没有数据则一直读 */
int count = 0;
while (count == 0) {
count = inputStream.available();
}
byte[] bytes = new byte[count];
inputStream.read(bytes);
String inputStr = new String(bytes);
// 获取首行请求头信息
String firstStr = inputStr.split("\\n")[0];
String[] s = firstStr.split(" ");
this.method = s[0];
this.url = s[1];
System.out.println("===> url = " + this.url);
System.out.println("===> method = " + this.method);
}
}
响应封装类
public class Response {
private OutputStream outputStream;
public Response() {
}
public Response(OutputStream outputStream) {
this.outputStream = outputStream;
}
// 使用输出流输出指定字符串
public void output(String content) throws IOException {
outputStream.write(content.getBytes());
}
/**
* @param path 随后要根据url来获取到静态资源的绝对路径,进一步根据绝对路径读取该静态资源文件,
* 最终通过输出流输出
*/
public void outputHtml(String path) throws IOException {
// 获取静态资源文件的绝对路径
String absoluteResourcePath = StaticResourceUtil.getAbsolutePath(path);
// 输入静态资源文件
File file = new File(absoluteResourcePath);
if(file.exists() && file.isFile()) {
// 读取静态资源文件,输出静态资源
StaticResourceUtil.outputStaticResource(new FileInputStream(file),outputStream);
}else{
// 输出404
output(HttpProtocolUtil.getHttpHeader404());
}
}
}
静态资源定位工具类
public class StaticResourceUtil {
/**
* 获取静态资源文件的绝对路径
* @param path
* @return
*/
public static String getAbsolutePath(String path) {
String absolutePath = StaticResourceUtil.class.getResource("/").getPath();
return absolutePath.replaceAll("\\\\","/") + path;
}
/**
* 读取静态资源文件输入流,通过输出流输出
*/
public static void outputStaticResource(InputStream inputStream, OutputStream outputStream) throws IOException {
int count = 0;
while(count == 0) {
count = inputStream.available();
}
int resourceSize = count;
// 输出http请求头,然后再输出具体内容
outputStream.write(HttpProtocolUtil.getHttpHeader200(resourceSize).getBytes());
// 读取内容输出
long written = 0 ;// 已经读取的内容长度
int byteSize = 1024; // 计划每次缓冲的长度,一点一点读
byte[] bytes = new byte[byteSize];
/* 没写完就一直写 */
while(written < resourceSize) {
// 说明剩余未读取大小不足一个1024长度,那就按真实长度处理
if(written + byteSize > resourceSize) {
byteSize = (int) (resourceSize - written); // 剩余的文件内容长度
bytes = new byte[byteSize];
}
inputStream.read(bytes);
outputStream.write(bytes);
outputStream.flush();
written+=byteSize;
}
}
}
静态页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>static resource</title>
</head>
<body>
<h1>This is a static resource.</h1>
</body>
</html>
Part 3 结合Servlet V3.0
顶层Servlet接口
public interface Servlet {
void init() throws Exception;
void destroy() throws Exception;
void service(Request request, Response response) throws Exception;
}
顶层抽象类
public abstract class HttpServlet implements Servlet {
public abstract void doGet(Request request, Response response);
public abstract void doPost(Request request, Response response);
@Override
public void service(Request request, Response response) throws Exception {
if ("GET".equalsIgnoreCase(request.getMethod())) {
doGet(request, response);
} else {
doPost(request, response);
}
}
}
继承顶层抽象类的 自定义Servlet
public class MyServlet extends HttpServlet {
@Override
public void doGet(Request request, Response response) {
String content = "<h1>MyServlet get</h1>";
try {
response.output((HttpProtocolUtil.getHttpHeader200(content.getBytes().length) + content));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void doPost(Request request, Response response) {
String content = "<h1>MyServlet post</h1>";
try {
response.output((HttpProtocolUtil.getHttpHeader200(content.getBytes().length) + content));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void init() throws Exception { }
@Override
public void destroy() throws Exception { }
}
依赖引入
<dependencies>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
</dependencies>
web.xml
<?xml version="1.0" encoding="UTF-8" ?>
<web-app>
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>server.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MyServlet</servlet-name>
<url-pattern>/my-servlet</url-pattern>
</servlet-mapping>
</web-app>
主启动类
public class BootStrapV3 {
/* 用于存储 web.xml 中servlet的映射关系 */
private Map<String,HttpServlet> servletMap = new HashMap<String,HttpServlet>();
/* 定义socket监听的端口号 */
private int port = 8080;
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
/**
* MiniCat启动需要初始化展开的一些操作
*/
public void start() {
// 加载解析相关的配置,web.xml
loadServlet();
/*
* 完成 MiniCat 3.0版本
* 需求:可以请求动态资源(Servlet)
*/
try {
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("===> MiniCat start on port:" + port);
while(true) {
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
// 从输入流获取请求信息
// 封装Request对象和Response对象
Request request = new Request(inputStream);
Response response = new Response(socket.getOutputStream());
// 静态资源处理
if(servletMap.get(request.getUrl()) == null) {
response.outputHtml(request.getUrl());
}else{
// 动态资源servlet请求
HttpServlet httpServlet = servletMap.get(request.getUrl());
httpServlet.service(request,response);
}
socket.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 加载解析web.xml,初始化Servlet
*/
private void loadServlet() {
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml");
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(resourceAsStream);
Element rootElement = document.getRootElement();
List<Element> selectNodes = rootElement.selectNodes("//servlet");
for (int i = 0; i < selectNodes.size(); i++) {
Element element = selectNodes.get(i);
// ==> 拿到 <servlet-name>MyServlet</servlet-name>
Element servletNameElement = (Element) element.selectSingleNode("servlet-name");
String servletName = servletNameElement.getStringValue();
// ==> 拿到 <servlet-class>server.MyServlet</servlet-class>
Element servletClassElement = (Element) element.selectSingleNode("servlet-class");
String servletClass = servletClassElement.getStringValue();
// 根据 servlet-name 的值找到 url-pattern (使用XPath表达式)
Element servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
// 拿到 /my-servlet
String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();
servletMap.put(urlPattern, (HttpServlet) Class.forName(servletClass).newInstance());
}
} catch (DocumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
BootStrapV3 bootStrapV2 = new BootStrapV3();
bootStrapV2.start();
}
}
Part 4 多线程改造 V4.0
现在我们强行在doGet中添加休眠(模拟线程堵塞)
因此需要多线程来解决当前问题:
重点改造
主启动类
public class BootStrapV4 {
/* 用于存储 web.xml 中servlet的映射关系 */
private Map<String,HttpServlet> servletMap = new HashMap<String,HttpServlet>();
/* 定义socket监听的端口号 */
private int port = 8080;
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
/**
* MiniCat启动需要初始化展开的一些操作
*/
public void start() {
// 定义一个线程池
int corePoolSize = 10;
int maximumPoolSize =50;
long keepAliveTime = 100L;
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(50);
ThreadFactory threadFactory = Executors.defaultThreadFactory();
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue,
threadFactory,
handler
);
// 加载解析相关的配置,web.xml
loadServlet();
/*
* 完成 MiniCat 4.0版本
* 需求:多线程请求
*/
try {
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("===> MiniCat start on port:" + port);
System.out.println("=========>>>>>>使用线程池进行多线程改造");
// 多线程改造 (且使用线程池节约资源)
while(true) {
Socket socket = serverSocket.accept();
RequestProcessor requestProcessor = new RequestProcessor(socket,servletMap);
threadPoolExecutor.execute(requestProcessor);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 加载解析web.xml,初始化Servlet
*/
private void loadServlet() {
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml");
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(resourceAsStream);
Element rootElement = document.getRootElement();
List<Element> selectNodes = rootElement.selectNodes("//servlet");
for (int i = 0; i < selectNodes.size(); i++) {
Element element = selectNodes.get(i);
// ==> 拿到 <servlet-name>MyServlet</servlet-name>
Element servletNameElement = (Element) element.selectSingleNode("servlet-name");
String servletName = servletNameElement.getStringValue();
// ==> 拿到 <servlet-class>server.MyServlet</servlet-class>
Element servletClassElement = (Element) element.selectSingleNode("servlet-class");
String servletClass = servletClassElement.getStringValue();
// 根据 servlet-name 的值找到 url-pattern (使用XPath表达式)
Element servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
// 拿到 /my-servlet
String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();
servletMap.put(urlPattern, (HttpServlet) Class.forName(servletClass).newInstance());
}
} catch (DocumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
BootStrapV4 bootStrapV2 = new BootStrapV4();
bootStrapV2.start();
}
}
线程处理类
public class RequestProcessor extends Thread {
private Socket socket;
private Map<String, HttpServlet> servletMap;
public RequestProcessor(Socket socket, Map<String, HttpServlet> servletMap) {
this.socket = socket;
this.servletMap = servletMap;
}
@Override
public void run() {
try{
InputStream inputStream = socket.getInputStream();
// 封装Request对象和Response对象
Request request = new Request(inputStream);
Response response = new Response(socket.getOutputStream());
// 静态资源处理
if(servletMap.get(request.getUrl()) == null) {
response.outputHtml(request.getUrl());
}else{
// 动态资源servlet请求
HttpServlet httpServlet = servletMap.get(request.getUrl());
httpServlet.service(request,response);
}
socket.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
Part 5 多线程改造 V5.0
本次改造是课程结束后的大作业,作业内容如下:
开发Minicat V4.0,在已有Minicat基础上进⼀步扩展,模拟出webapps部署效果
磁盘上放置⼀个webapps目录,webapps中可以有多个项目,比如demo1,demo2,demo3...
具体的项目比如demo1中有serlvet(也即为:servlet是属于具体某⼀个项⽬的servlet),这样的话在
Minicat初始化配置加载,以及根据请求url查找对应serlvet时都需要进⼀步处理
项目结构
整体思路
- 新建两个web项目
demo01demo02(WEB-INF中含有classes/lib) - 在MiniCat项目中新建
server.xml通过<Server>等标签指定外部引入的 web 应用 - 解析
server.xml文件信息到MiniCat内存容器中 - 解析
demo01demo02中的web.xml并将对应的外部Servlet信息获取 - 通过在容器中查询 url <--> Servlet 映射关系 从而在访问对应 url时 执行外部 Servlet
代码逻辑图解
实现效果
四、 Tomcat 源码构建及核心流程源码剖析
Part 1 源代码准备工作
step A 下载源代码并整理目录
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>apache-tomcat-8.5.50-src</artifactId>
<name>Tomcat8.5</name>
<version>8.5</version>
<build>
<!--指定源⽬录-->
<finalName>Tomcat8.5</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>3.1</version>
<configuration>
<encoding>UTF-8</encoding>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</build>
<!--tomcat 依赖的基础包-->
<dependencies>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>ant</groupId>
<artifactId>ant</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>wsdl4j</groupId>
<artifactId>wsdl4j</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>javax.xml</groupId>
<artifactId>jaxrpc</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.eclipse.jdt.core.compiler</groupId>
<artifactId>ecj</artifactId>
<version>4.5.1</version>
</dependency>
<dependency>
<groupId>javax.xml.soap</groupId>
<artifactId>javax.xml.soap-api</artifactId>
<version>1.4.0</version>
</dependency>
</dependencies>
</project>
step B 导入IDEA
新建空项目
导入下载好的src项目
step C 给 tomcat 的源码程序启动类 Bootstrap 配置 VM 参数
tomcat 源码运⾏也需要加载配置⽂件
-Dcatalina.home=/Users/Administrator/Desktop/Tomcat&Nginx资料/Tomcat&Nginx资料/软件包及源码包/apache-tomcat-8.5.50-src/source
-Dcatalina.base=/Users/Administrator/Desktop/Tomcat&Nginx资料/Tomcat&Nginx资料/软件包及源码包/apache-tomcat-8.5.50-src/source
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
step D 找到BootStrap的main入口 启动
我们需要在tomcat的源码ContextConfig类中的configureStart⽅法中增加⼀⾏代码将 Jsp 引擎初始化
context.addServletContainerInitializer(new JasperInitializer(), null);
Part 2 源码剖析启动时序
时序图(来源:拉勾教育-JAVA高薪训练营)
因此设计了⽣命周期接口
Lifecycle 进⾏统⼀规范,各容器组件实现该接口
Part 3 从Main入口开始跟踪源码
待跟进
五、 Tomcat 类加载机制剖析
Java类(.java)—> 字节码⽂件(.class) —> 字节码⽂件需要被加载到jvm内存当中(这个过程就是⼀个类加载的过程)
类加载器 ClassLoader,说白了也是⼀个类,jvm启动的时候先把类加载器读取到内存当中去,其他的类
Part 1 从JVM的类加载机制开始
分类
- 引导类加载器 BootstrapClassLoader
c++编写,加载java核⼼库java.*,比如rt.jar中的类,构造ExtClassLoader和AppClassLoader - 扩展类加载器 ExtClassLoader
java编写,加载扩展库 JAVA_HOME/lib/ext⽬录下的jar中的类,如classpath中的jre,javax.*或者java.ext.dir指定位置中的类 - 系统类加载器 SystemClassLoader/AppClassLoader
默认的类加载器,搜索环境变量 classpath 中指明的路径
用户可以自定义类加载器,加载顺序如下
- 用户自己的的类加载器,把加载请求传给⽗加载器,⽗加载器再传给其⽗加载器,⼀直到加载器树的顶层
- 最顶层的类加载器⾸先针对其特定的位置加载,如果加载不到就转交给⼦类
- 如果⼀直到底层的类加载都没有加载到,那么就会抛出异常 ClassNotFoundException
Part 2 双亲委派机制
定义
递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类
作用
- 防止重复加载同⼀个.class。
通过委托去向上⾯问⼀问,加载过了,就不⽤再加载⼀遍。保证数据安全 - 保证核⼼.class不能被篡改
通过委托⽅式,不会去篡改核⼼.class,即使篡改也不会去加载,即使加载也不会是同⼀个.class对象了
Part 3 Tomcat 的类加载机制
(来源:拉勾教育-JAVA高薪训练营)
- 引导类加载器 和 扩展类加载器 的作⽤不变
- 系统类加载器正常情况下加载的是 CLASSPATH 下的类,但是 Tomcat 的启动脚本并未使⽤该变量,⽽是加载tomcat启动的类,⽐如bootstrap.jar,通常在catalina.bat或者catalina.sh中指定。位于CATALINA_HOME/bin下
- Common 通⽤类加载器加载Tomcat使⽤以及应⽤通⽤的⼀些类,位于CATALINA_HOME/lib下,⽐如servlet-api.jar
- Catalina ClassLoader ⽤于加载服务器内部可⻅类,这些类应⽤程序不能访问
- Shared ClassLoader ⽤于加载应⽤程序共享类,这些类服务器不会依赖
- Webapp ClassLoader,每个应⽤程序都会有⼀个独⼀⽆⼆的Webapp ClassLoader,他⽤来加载本应⽤程序 /WEB-INF/classes 和 /WEB-INF/lib 下的类。
tomcat 8.5 默认改变了严格的双亲委派机制
⾸先从 Bootstrap Classloader加载指定的类
---> 如果未加载到,则从 /WEB-INF/classes加载
-----> 如果未加载到,则从 /WEB-INF/lib/* .jar 加载
-------> 如果未加载到,则从 /WEB-INF/lib/* .jar 加载
六、 Tomcat 对 Https 的支持及 Tomcat 性能优化策略
Https是用来加强数据传输安全的
Part 1 HTTPS
HTTP VS HTTPS
- HTTPS协议使⽤时需要到电⼦商务认证授权机构(CA)申请SSL证书
- HTTP默认使⽤
8080端⼝,HTTPS默认使⽤8443端⼝ - HTTPS则是具有
SSL加密的安全性传输协议,对数据的传输进⾏加密,效果上相当于HTTP的升级版 - HTTP的连接是⽆状态的,不安全的;
HTTPS协议是由SSL+HTTP协议构建的可进⾏加密传输、身份认证的⽹络协议,⽐HTTP协议安全
HTTPS 工作原理
(来源:拉勾教育-JAVA高薪训练营)
Part 2 Tomcat 对 HTTPS 的支持
待跟进
Part 3 Tomcat 性能优化策略
待跟进
虚拟机运行优化
待跟进
配置优化
待跟进