創作緣由
平時使用 tomcat 等 web 伺服器不可謂不多,但是一直一知半解。
於是想著自己實現一個簡單版本,學習一下 tomcat 的精髓。
系列教程
從零手寫實現 apache Tomcat-01-入門介紹
從零手寫實現 apache Tomcat-02-web.xml 入門詳細介紹
從零手寫實現 tomcat-03-基本的 socket 實現
從零手寫實現 tomcat-04-請求和響應的抽象
從零手寫實現 tomcat-05-servlet 處理支援
從零手寫實現 tomcat-06-servlet bio/thread/nio/netty 池化處理
從零手寫實現 tomcat-07-war 如何解析處理三方的 war 包?
從零手寫實現 tomcat-08-tomcat 如何與 springboot 整合?
從零手寫實現 tomcat-09-servlet 處理類
從零手寫實現 tomcat-10-static resource 靜態資原始檔
從零手寫實現 tomcat-11-filter 過濾器
從零手寫實現 tomcat-12-listener 監聽器
前言
到目前為止,我們處理的都是自己的 servlet 等。
但是 tomcat 這種做一個 web 容器,坑定要能解析處理其他的 war 包。
這個要如何實現呢?
1-war 包什麼樣的?
原始碼
直接用一個 web 簡單的專案。
https://github.com/houbb/servlet-webxml
專案目錄
mvn clean
tree /f
D:.
│
└─src
└─main
├─java
│ └─com
│ └─github
│ └─houbb
│ └─servlet
│ └─webxml
│ IndexServlet.java
│
└─webapp
│ index.html
│
└─WEB-INF
web.xml
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>com.github.houbb</groupId>
<artifactId>servlet-webxml</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<plugin.tomcat.version>2.2</plugin.tomcat.version>
</properties>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-servlet-api</artifactId>
<version>9.0.0.M8</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>servlet</finalName>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>${plugin.tomcat.version}</version>
<configuration>
<port>8080</port>
<path>/</path>
<uriEncoding>${project.build.sourceEncoding}</uriEncoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<!--預設的歡迎頁面-->
<welcome-file-list>
<welcome-file>/index.html</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>index</servlet-name>
<servlet-class>com.github.houbb.servlet.webxml.IndexServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>index</servlet-name>
<url-pattern>/index</url-pattern>
</servlet-mapping>
</web-app>
index.html
<!DOCTYPE html>
<html>
<body>
Hello Servlet!
</body>
</html>
servlet
package com.github.houbb.servlet.webxml;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @author binbin.hou
* @since 0.1.0
*/
public class IndexServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("text/html");
// 實際的邏輯是在這裡
PrintWriter out = resp.getWriter();
out.println("<h1>servlet index</h1>");
}
}
目錄結構
打包成 war,然後解壓:
mvn clean install
其實比較重要的就是 web.xml 作為一切的入口。
對應的 war
D:.
│ index.html
│
├─META-INF
│ │ MANIFEST.MF
│ │
│ └─maven
│ └─com.github.houbb
│ └─servlet-webxml
│ pom.properties
│ pom.xml
│
└─WEB-INF
│ web.xml
│
└─classes
└─com
└─github
└─houbb
└─servlet
└─webxml
IndexServlet.class
如何根據類路徑載入類資訊?類不是當前專案的
JVM-09-classloader
核心實現
package com.github.houbb.minicat.support.classloader;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
/**
* https://www.liaoxuefeng.com/wiki/1545956031987744/1545956487069728
*
* 每一個 dir 的 classLoader 獨立。
*/
public class WebAppClassLoader extends URLClassLoader {
private Path classPath;
private Path[] libJars;
public WebAppClassLoader(Path classPath, Path libPath) throws IOException {
super(createUrls(classPath, libPath), ClassLoader.getSystemClassLoader());
// super("WebAppClassLoader", createUrls(classPath, libPath), ClassLoader.getSystemClassLoader());
//
this.classPath = classPath.toAbsolutePath().normalize();
if(libPath.toFile().exists()) {
this.libJars = Files.list(libPath).filter(p -> p.toString().endsWith(".jar")).map(p -> p.toAbsolutePath().normalize()).sorted().toArray(Path[]::new);
}
}
static URL[] createUrls(Path classPath, Path libPath) throws IOException {
List<URL> urls = new ArrayList<>();
urls.add(toDirURL(classPath));
//lib 可能不存在
if(libPath.toFile().exists()) {
Files.list(libPath).filter(p -> p.toString().endsWith(".jar")).sorted().forEach(p -> {
urls.add(toJarURL(p));
});
}
return urls.toArray(new URL[0]);
}
static URL toDirURL(Path p) {
try {
if (Files.isDirectory(p)) {
String abs = toAbsPath(p);
if (!abs.endsWith("/")) {
abs = abs + "/";
}
return URI.create("file://" + abs).toURL();
}
throw new IOException("Path is not a directory: " + p);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
//D:\github\minicat\src\test\webapps\servlet\WEB-INF\classes
//D:\github\minicat\src\test\webapps\WEB-INF\classes
static URL toJarURL(Path p) {
try {
if (Files.isRegularFile(p)) {
String abs = toAbsPath(p);
return URI.create("file://" + abs).toURL();
}
throw new IOException("Path is not a jar file: " + p);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
static String toAbsPath(Path p) throws IOException {
return p.toAbsolutePath().normalize().toString().replace('\\', '/');
}
}
開源地址
/\_/\
( o.o )
> ^ <
mini-cat 是簡易版本的 tomcat 實現。別稱【嗅虎】(心有猛虎,輕嗅薔薇。)
開源地址:https://github.com/houbb/minicat