仿写tomcat的Servlet接口体系
之前写过一篇博客,Tomcat的Servlet-GenericServlet-HttpServlet体系的具体结构,以及Servlet的生命周期
Servlet讲解
想要模仿tomcat获取动态资源,就需要我们自己仿写一个Servlet接口体系
主要包括:注解,相关的接口+子类,搜索包名下的所有servlet的工具类
MyWebServlet注解
我们首先需要定义一个MyWebServlet注解,仿照了WebServlet注解
import java.lang.annotation.*;
@Target({ElementType.TYPE})// 作用到类
@Retention(RetentionPolicy.RUNTIME)// 运行阶段
public @interface MyWebServlet {
// 定义url变量,并规定默认值为空字符
String className() default "";
String url() default "";
}
相关的接口+子类
MyServlet接口,定义了Servlet的生命周期
import java.io.IOException;
import com.qcby.config.MyServletConfig;
// 自定义的servlet接口
public interface MyServlet {
void init();
MyServletConfig getServletConfig();
String getServletInfo();
void service(Request request, Response response) throws IOException;
void destroy();
}
自定义的MyServletConfig类
/**
* 将获取到的url和classPath径封装到该对象当中
*/
public class MyServletConfig {
private String url;
private String classPath;
public MyServletConfig(String url, String classPath) {
this.url = url;
this.classPath = classPath;
}
public MyServletConfig() {
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getClassPath() {
return classPath;
}
public void setClassPath(String classPath) {
this.classPath = classPath;
}
}
抽象类MyGenericServlet实现了除service以外的方法
import com.qcby.config.MyServletConfig;
public abstract class MyGenericServlet implements MyServlet {
@Override
public void init() {
}
@Override
public MyServletConfig getServletConfig() {
return new MyServletConfig();
}
@Override
public String getServletInfo() {
return "";
}
@Override
public void destroy() {
}
}
抽象类MyHttpServlet,实现了service方法,并将其分化为doGet,doPost,doDelete等方法
import java.io.IOException;
public abstract class MyHttpServlet extends MyGenericServlet {
// 重写service方法,将其分化为不同的方法
// 抽象类MyHttpServlet没有抽象方法
@Override
public void service(Request request, Response response) throws IOException {
// 判断request的methodType,转发到doGet/doPost当中
if ("GET".equals(request.getMethodType())) {
myDoGet(request, response);
} else if ("POST".equals(request.getMethodType())) {
myDoPost(request, response);
}
}
public void myDoGet(MyHttpServletRequest req, MyHttpServletResponse resp) throws IOException {
}
public void myDoPost(MyHttpServletRequest req, MyHttpServletResponse resp) throws IOException {
}
}
这里的MyHttpServletRequest还有MyHttpServletResponse,是两个接口
public interface MyHttpServletRequest {
}
public interface MyHttpServletResponse {
void write(String context) throws IOException;
}
分别由Request类和Response类实现
public class Request implements MyHttpServletRequest{
private String url;
private String methodType;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getMethodType() {
return methodType;
}
public void setMethodType(String methodType) {
this.methodType = methodType;
}
}
/**
* 做数据的返回
*/
public class Response implements MyHttpServletResponse {
// 获取输出流
private OutputStream outputStream;
public Response(OutputStream outputStream) {
this.outputStream = outputStream;
}
// 静态资源的输出
public void writeHtml(String path) throws Exception {
//
// 根据路径返回资源路径地址,例如http://localhost:8666/index.html
String resource = FileUtil.getResoucePath(path);
File file = new File(resource);
if (file.exists()) {
// 静态资源存在!
System.out.println("静态资源存在!");
FileUtil.writeFile(file, outputStream);
} else {
System.out.println(path + "对应的该静态资源不存在!");
}
}
// 数据写回
public void write(String context) throws IOException {
outputStream.write(context.getBytes());
outputStream.flush();
}
}
加载servlet包下的所有servlet,并将其MyWebServlet注解的url值和对应的servlet类对象组装成一个map(最重要)
上面基本都是照猫画葫芦的仿写,其实该实现的方法完全没实现,跟原本的tomcat相比差的比较多
但是这一部分,还原度比较高,tomcat本身也是扫描包,并且反射获取注解的取值,最后组成HashMap来映射servlet的
SearchClassUtil
/**
* 扫描指定包,获取该包下所有的类的全路径信息
*/
public class SearchClassUtil {
public static List<String> classPaths = new ArrayList<String>();
public static List<String> searchClass(){
//需要扫描的包名
String basePack = "com.qcby.webapp";
//将获取到的包名转换为路径
String classPath = SearchClassUtil.class.getResource("/").getPath();
basePack = basePack.replace(".", File.separator);
String searchPath = classPath + basePack;
doPath(new File(searchPath),classPath);
//这个时候我们已经得到了指定包下所有的类的绝对路径了。我们现在利用这些绝对路径和java的反射机制得到他们的类对象
return classPaths;
}
/**
* 该方法会得到所有的类,将类的绝对路径写入到classPaths中
* @param file
*/
private static void doPath(File file,String classpath) {
if (file.isDirectory()) {//文件夹
//文件夹我们就递归
File[] files = file.listFiles();
for (File f1 : files) {
doPath(f1,classpath);
}
} else {//标准文件
//标准文件我们就判断是否是class文件
if (file.getName().endsWith(".class")) {
String path = file.getPath().replace(classpath.replace("/","\\").
replaceFirst("\\\\",""),"").replace("\\",".").
replace(".class","");
//如果是class文件我们就放入我们的集合中。
classPaths.add(path);
}
}
}
public static void main(String[] args) {
List<String> classes = SearchClassUtil.searchClass();
for (String s: classes
) {
System.out.println(s);
}
}
}
tomcat启动类
对静态资源和动态资源做了判断
package com.qcby.tomcatDemo;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URLDecoder;
import com.qcby.config.ServletConfigMapping;
import com.qcby.servlet.MyServlet;
import com.qcby.servlet.Request;
import com.qcby.servlet.Response;
import com.qcby.util.ResponseUtil;
/**
* tomcat启动类
*/
public class TomcatStart {
private static Request request = new Request();
public static void main(String[] args) throws IOException, ClassNotFoundException {
System.out.println("socket服务器启动!!!");
// 0. 加载servlet类信息
ServletConfigMapping.init();
// 1. 打开相关通信端口
// tomcat:8080,mysql:3306,应用软件独占一个端口的全部信息
ServerSocket serverSocket = new ServerSocket(8666);
// 线程持续扫描当前网卡xxxx端口(死循环),如果有数据就拿过来,交给端口对应的程序处理
// 2. 监听并接收请求数据
while (true) {
// 一旦发现有数据,就打开socket通信
// 这里没有创建新的线程,所以这里是main线程监听数据
Socket socket = serverSocket.accept();
System.out.println(socket.getInetAddress().getCanonicalHostName() + "进行了连接!");
// 第二步监听并接收到了数据,处理数据可以用主线程,但是没必要,创建子线程处理
// 每接收一次数据,创建一个子线程
Thread t1 = new Thread(() -> {
// 处理数据包括两部分:读和写
try {
dataHandle(socket);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
t1.start();
}
}
// 处理数据的方法,读+写
public static void dataHandle(Socket socket) throws Exception {
// 1. 读取请求的数据
// 1.1打开输入流对象,读取socket对象中的数据,这里的数据都是0101010的二进制数据
InputStream inputStream = socket.getInputStream();
requestContext(inputStream);
// 数据的输出
Response response = new Response(socket.getOutputStream());
if ("".equals(request.getUrl())) {
response.write(ResponseUtil._404ResponseHeader);
} else if (ServletConfigMapping.classMap.get(request.getUrl()) != null) {
// 动态web资源
dispatch(request, response);
} else {
// 访问静态资源
response.writeHtml(request.getUrl());
}
}
public static void dispatch(Request request, Response response) throws InstantiationException, IllegalAccessException, IOException {
Class<MyServlet> servletClass = ServletConfigMapping.classMap.get(request.getUrl());
if (servletClass != null) {
MyServlet myServlet = servletClass.newInstance();
myServlet.service(request, response);
}
}
public static void requestContext(InputStream inputStream) throws IOException {
// 1.2 二进制数据的翻译并读取
int count = 0;
while (count == 0) {
// 返回此输入流下一个方法调用可以不受阻塞地从此输入流读取(或跳过)的估计字节数。
// 下一个调用可能是同一个线程,也可能是另一个线程。
// 一次读取或跳过此估计数个字节不会受阻塞,但读取或跳过的字节数可能小于该数。
// 可以不受阻塞地从此输入流读取(或跳过)的估计字节数;如果到达输入流末尾,则返回 0
count = inputStream.available();
}
byte[] bytes = new byte[count];
inputStream.read(bytes);
// 这里用URLDecoder是为了防止路径中出现特殊符号,经过get请求之后会被URLEncode为乱码
String context = URLDecoder.decode(new String(bytes, "utf-8"));// Base64.getDecoder().decode(bytes)
System.out.println("===context:" + context);
if ("".equals(context)) {
System.out.println("null request!");
request.setUrl("");
request.setMethodType("");
} else {
//根据换行来获取第一行数据
String firstLine = context.split("\\n")[0];
// 第一行数据的第2个字符串
System.out.println("===url:" + firstLine.split("\\s")[1]);
request.setUrl(firstLine.split("\\s")[1]);
// 第一行数据的第1个字符串
System.out.println("===methodType:" + firstLine.split("\\s")[0]);
request.setMethodType(firstLine.split("\\s")[0]);
}
}
}
webapp包下有两个servlet
@MyWebServlet(url = "/first")
public class FirstServlet extends MyHttpServlet {
@Override
public void myDoGet(MyHttpServletRequest req, MyHttpServletResponse resp) throws IOException {
String context = "<h1>这是FirstServlet的myDoGet方法</h1>";
resp.write(ResponseUtil.makeResponse(context, ResponseUtil.htmlResponseHeader));
}
@Override
public void myDoPost(MyHttpServletRequest req, MyHttpServletResponse resp) throws IOException {
String context = "<h1>这是FirstServlet的myDoPost方法</h1>";
resp.write(ResponseUtil.makeResponse(context, ResponseUtil.htmlResponseHeader));
}
}
@MyWebServlet(url = "/hello")
public class HelloServlet extends MyHttpServlet {
@Override
public void myDoGet(MyHttpServletRequest req, MyHttpServletResponse resp) throws IOException {
String context = "<h1>这是HelloServlet的myDoGet方法</h1>";
resp.write(ResponseUtil.makeResponse(context, ResponseUtil.htmlResponseHeader));
}
@Override
public void myDoPost(MyHttpServletRequest req, MyHttpServletResponse resp) throws IOException {
String context = "<h1>这是HelloServlet的myDoPost方法</h1>";
resp.write(ResponseUtil.makeResponse(context, ResponseUtil.htmlResponseHeader));
}
}
项目下还有一个静态资源index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>Hello TomcatDemo!!!</p>
</body>
</html>
我们在浏览器分别测试静态和动态资源的获取