Tomcat原理(5)——tomcat最终实现-CSDN博客文章浏览阅读610次,点赞7次,收藏17次。就像上一篇博客说的动态资源映射表,Servlet容器就是一个Key—Value集合。在MyTomcat中我们获取到了注解值Key和类对象的路径。https://blog.csdn.net/2301_78566776/article/details/144515542?spm=1001.2014.3001.5501 之前的博客介绍到使用我们 自己做的tomcat访问动态资源,这篇博客介绍如何实现动静态资源一同访问,完善之前的结构。
目录
一个完整的交互过程如上图,这篇博客为总结,将MyTomcat进行一个完整实现
一、创建项目
我们首先选择maven创建一个maven项目
创建完成!
二、放置静态资源
静态资源如html等,我们把它放在是src.main.resources下
我这里新建一个Demo.html界面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<span style="font-size:40px">"Hello,it is Demo"</span>
</body>
</html>
三、逐步配置tomcat
上一篇博客里有详细讲解,下面我们简单的逐步配置一下
1.创建webservlet注解类
package com.qcby.tomcat.webservlet;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value = ElementType.TYPE)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface WebServlet {
String path() default "";
}
2.创建request类
package com.qcby.tomcat.Request;
public class Request {
private String path;
private String method;
public Request(String path, String method) {
this.path = path;
this.method = method;
}
public Request() {
}
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;
}
}
3.创建response类
放入后报错先不要急,之后会导入工具包
package com.qcby.tomcat.Response;
import com.qcby.tomcat.util.FileUtil;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
public class Response {
private OutputStream outputStream;
//打开输出流
/*
* 输入输出流都来源于socket,那么这里初始化的outputStream究竟是什么
* 就取决于MyTomcat中传进来的socket.getOutputStream是什么了
* */
public Response(OutputStream outputStream){
this.outputStream=outputStream;
}
//静态资源进行输出
public void wirthHtml(String path) throws Exception {
/*
getResoucePath()是工具类FileUtil中获取完整路径的方法
其内部主要用了getResource()方法--根据当前类的位置来定位资源文件
*/
String resourcePath=FileUtil.getResoucePath(path);
System.out.println("resourcePath:"+resourcePath);
File file=new File(resourcePath);
if(file.exists()){ //访问的静态资源文件是存在的
System.out.println("静态资源存在");
FileUtil.writeFile(file,outputStream);
}else {
System.out.println("404");
}
}
public void wirth(String context) throws IOException {
outputStream.write(context.getBytes(StandardCharsets.UTF_8));
}
}
解释
response是做输出,所以首先打开输出流。
* 需要一个工具类FileUtil,与I/O相关(每一行代码去读取,是能够帮助数据转换成二进制进行传输 ---> 当前html代码是一个文件形式,在网络上传输文件形式非常麻烦。于是用输入输出的方式把文件转换成相应的二进制形式,通过二进制形式对数据进行传输。)
* 想要输出静态资源,少不了FileUtil的支持,调用它的getResourcePath()方法,把访问路径放进去。---->获取当前访问路径。
4.导入工具包
FileUtil
package com.qcby.tomcat.util;
import java.io.*;
/**
* 该类的主要作用是进行读取文件
*/
public class FileUtil {
public static boolean witeFile(InputStream inputStream, OutputStream outputStream){
boolean success = false ;
BufferedInputStream bufferedInputStream ;
BufferedOutputStream bufferedOutputStream;
try {
bufferedInputStream = new BufferedInputStream(inputStream);
bufferedOutputStream = new BufferedOutputStream(outputStream);
bufferedOutputStream.write(ResponseUtil.responseHeader200.getBytes());
int count = 0;
while (count == 0){
count = inputStream.available();
}
int fileSize = inputStream.available();
long written = 0;
int beteSize = 1024;
byte[] bytes = new byte[beteSize];
while (written < fileSize){
if(written + beteSize > fileSize){
beteSize = (int)(fileSize - written);
bytes = new byte[beteSize];
}
bufferedInputStream.read(bytes);
bufferedOutputStream.write(bytes);
bufferedOutputStream.flush();
written += beteSize;
}
success = true;
} catch (IOException e) {
e.printStackTrace();
}
return success;
}
public static boolean writeFile(File file,OutputStream outputStream) throws Exception{
return witeFile(new FileInputStream(file),outputStream);
}
/**
* 获取资源地址
* @param path
* @return
*/
public static String getResoucePath(String path){
String resource = FileUtil.class.getResource("/").getPath();
return resource + "\\" + path;
}
}
ResponseUtil
package com.qcby.tomcat.util;
public class ResponseUtil {
public static final String responseHeader200 ="HTTP/1.1 200 \r\n"+
"Content-Type:text/html \r\n"+"\r\n";;
public static String getResponseHeader200(String context){
//响应头的添加
return "HTTP/1.1 200 \r\n"+
"Content-Type:text/html \r\n"+"\r\n" +context;
}
}
这要解释一下,ResponseUtil为添加响应头
每个响应和请求是类似的,完整的相应包括响应头和响应内容
我们用这个工具包手动添加响应头
5.创建HttpServlet抽象类
用于定位到动态资源的请求方式。
package com.qcby.tomcat.HttpServlet;
import com.qcby.tomcat.Request.Request;
import com.qcby.tomcat.Response.Response;
import java.io.IOException;
public abstract class HttpServlet {
public void service(Request request, Response response) throws IOException {
if(request.getMethod().equals("GET")){
doGet(request,response);
}else if (request.getMethod().equals("POST")){
doPost(request,response);
}else if (request.getMethod().equals("")){
System.out.println("未获取到方法类型");
}
}
public abstract void doGet(Request request,Response response) throws IOException;
public abstract void doPost(Request request,Response response)throws IOException;
}
6.创建Servlet容器
package com.qcby.tomcat.config;
import com.qcby.tomcat.HttpServlet.HttpServlet;
import com.qcby.tomcat.webservlet.WebServlet;
import java.io.File;
import java.net.URL;
import java.util.*;
/*
* servlet容器
* */
public class ServletConfigMapping {
//注意这写HttpServlet,父类对象,因为它要封装一系列子类对象
public static Map<String,Class<HttpServlet>> classMap=new HashMap<>();
//static代码块在main方法之前执行
static {
try {
// 1. 扫描包路径 (com.wzh.tomcat.myweb)
String packageName = "com.qcby.tomcat.MyWeb";
List<Class<?>> classes = getClasses(packageName);
// 2. 遍历所有类,检查是否有@WebServlet注解
for (Class<?> clazz : classes) {
if (clazz.isAnnotationPresent(WebServlet.class)) {
// 3. 获取@WebServlet注解的值
WebServlet webServlet = clazz.getAnnotation(WebServlet.class);
//System.out.println("类名: " + clazz.getName() + " | URL路径: " + webServlet.path());
System.out.println(webServlet.path());
classMap.put(webServlet.path(), (Class<HttpServlet>) clazz);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static List<Class<?>> getClasses(String packageName) throws Exception {
List<Class<?>> classes = new ArrayList<>();
String path = packageName.replace('.', '/'); // 将包名转换为文件路径
// 通过类加载器获取包的资源路径
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Enumeration<URL> resources = classLoader.getResources(path);
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
File directory = new File(resource.toURI());
// 扫描文件夹下的所有类文件
if (directory.exists()) {
for (File file : directory.listFiles()) {
if (file.getName().endsWith(".class")) {
// 获取类的完整类名
String className = packageName + "." + file.getName().replace(".class", "");
classes.add(Class.forName(className));
}
}
}
}
return classes;
}
public static void init(){
}
}
这串代码是对MyWeb包里的servlet进行管理。
这段代码是一个简单的Servlet配置映射工具,用于在自定义的Tomcat服务器中加载和映射Servlet。它扫描指定包下的所有类,查找带有
@WebServlet
注解的类,并将这些类的路径(URL路径)与类本身映射起来,存储在一个静态的HashMap
中。
7.MyWeb放置动态资源
里面三个Servlet
package com.qcby.tomcat.MyWeb;
import com.qcby.tomcat.HttpServlet.HttpServlet;
import com.qcby.tomcat.Request.Request;
import com.qcby.tomcat.Response.Response;
import com.qcby.tomcat.util.ResponseUtil;
import com.qcby.tomcat.webservlet.WebServlet;
import java.io.IOException;
@WebServlet(path ="/MyFirstServlet")
public class MyFirstServlet extends HttpServlet {
@Override
public void doGet(Request request, Response response) throws IOException {
System.out.println("你好我是FirstServlet");
String context="<h1>你好我是FirstServlet</h1>";
response.wirth(ResponseUtil.getResponseHeader200(context));
}
@Override
public void doPost(Request request, Response response) throws IOException {
}
}
其余两个Servlet类似
8.MyTomcat核心类
package com.qcby.tomcat;
import com.qcby.tomcat.HttpServlet.HttpServlet;
import com.qcby.tomcat.Request.Request;
import com.qcby.tomcat.Response.Response;
import com.qcby.tomcat.config.ServletConfigMapping;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class MyTomcat {
public static Request request=new Request();
public static Response response;
public static void main(String[] args) throws Exception {
ServletConfigMapping.init();
serverInit();
}
public static void serverInit() throws Exception{
// 1.打开通信端口 tomcat:8080 3306 ---------》进行网络通信
ServerSocket serverSocket = new ServerSocket(8080);//监听8080端口号
System.out.println("****************server start.....");
//2.接受请求数据
while (true){
Socket socket = serverSocket.accept(); //--------------------->注意:此时监听网卡的是:主线程
System.out.println("有客户进行了链接");
new Thread(()->{ //利用子线程方式处理数据
//处理数据---------》数据的处理在于读和写
try {
handler(socket);
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
public static void handler(Socket socket) throws Exception {
//读取请求的数据
InputStream inputStream = socket.getInputStream();
response=new Response(socket.getOutputStream());
requestContext(inputStream);
}
public static void requestContext(InputStream inputStream) throws Exception {
//将bit流转为文字信息
int count = 0;
while (count == 0){
count = inputStream.available();
}
byte[] bytes = new byte[count];
inputStream.read(bytes);
String Context = new String(bytes);
// System.out.println(Context);
//解析数据
if(Context.equals("")){
System.out.println("你输入了一个空请求");
}else {
//获得第一行的前两个
String firstLine=Context.split("\\n")[0];//根据换行来获取第一行数据
String path=firstLine.split("\\s")[1];
String method=firstLine.split("\\s")[0];
System.out.println(path+" "+method);
request.setMethod(method);
request.setPath(path);
}
dis(request);
}
public static void dis(Request request) throws Exception{
if(!request.getPath().equals("")){//不是空请求
if(ServletConfigMapping.classMap.get(request.getPath())!=null){//当前请求地址能否从classMap中查找到
Class<HttpServlet> ClassServlet=ServletConfigMapping.classMap.get(request.getPath());//获取类对象
HttpServlet servlet=ClassServlet.newInstance();//根据获取到的类对象,创建对应的对象 用父类去接,多态(父类的引用指向子类的对象)
servlet.service(request,response);//调用HttpServlet的service方法,判断是get请求还是post请求
}else { //静态资源
response.wirthHtml(request.getPath());
}
}
}
}
dis方法是处理http请求
在非空请求的基础下判断是动态资源还是静态资源
四、结果展示:
首先我们对动态资源——MyFirstServlet进行访问
接下来我们对静态资源——Demo.html进行访问