目录
上一篇博客已经为介绍了servelet的实现 ,这篇对上一篇博客进行补充,实现如下流程
一、什么是Servlet容器
就像上一篇博客说的动态资源映射表,Servlet容器就是一个Key—Value集合。在MyTomcat中我们获取到了注解值Key和类对象的路径。
二、ServletConfigMapping构建实现容器
- 我通过public static Map<String,Class<HttpServlet>> classMap=new HashMap<>();来定义我们的容器结构。
- 因为我想让获取key-value信息在我打开tomcat时就加载好,我这里应用了static代码块。代码块会在方法执行前初始化。
- 在最后再定义一个init方法(为空),我们只需要在Mytomcat主体文件中调用这个方法,就可以完成初始化动作
ServletConfigMapping
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(){
}
}
MyTomcat
package com.qcby.tomcat;
import com.qcby.tomcat.config.ServletConfigMapping;
public class MyTomcat {
public static void main(String[] args) {
//调用ServletConfigMapping
ServletConfigMapping.init();
}
}
调用MyTomcat的main方法
至此,流程实现了一半
三、优化server
我们在上一篇博客中单独实现了server的用法,我们现在把这个文件代码优化,并合并到MyTomcat当中
Server
我们把main方法更改为public static void serverInit(),效果和上面ServletConfigMapping是类似的的我们只需要在MyTomcat中调用serverInit()方法,即可实现server服务的打开
package com.qcby.tomcat.socket;
import com.qcby.tomcat.Request.Request;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
private static Request request=new Request();
public static void serverInit() throws IOException {
// 1.打开通信端口 tomcat:8080 3306 ---------》进行网络通信
ServerSocket serverSocket = new ServerSocket(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();
requestContext(inputStream);
}
public static void requestContext(InputStream inputStream) throws IOException {
// 创建一个StringBuilder对象,用于构建请求的第一行
StringBuilder sb = new StringBuilder();
int context; // 用于存储每次从输入流中读取的单个字节
// 读取输入流直到遇到换行符(\n)或文件结束(-1)
while ((context = inputStream.read()) != -1) {
// 如果读取到换行符,则停止读取
if (context == '\n') {
break; // 遇到换行符,退出循环
}
// 将读取到的字节转换为字符,并添加到StringBuilder中
sb.append((char) context);
}
// 从StringBuilder中获取第一行字符串,并去除首尾空格
String firstLine = sb.toString().trim();
// 检查第一行是否为空
if (firstLine.isEmpty()) {
// 如果为空,则打印提示信息
System.out.println("你输入了一个空请求");
} else {
// 如果不为空,则按空格分割第一行字符串为单词数组
String[] words = firstLine.split("\\s+");
// 打印出请求方法和请求URI(通常是数组的前两个元素)
// 注意:这里没有检查数组长度,如果数组长度小于2,将会抛出ArrayIndexOutOfBoundsException
// 在实际应用中,应该添加适当的错误处理或验证逻辑
String method=words[0];
String path=words[2];
System.out.println(words[0] + " " + words[1]);
request.setMethod(method);
request.setPath(path);
}
}
}
MyTomcat
package com.qcby.tomcat;
import com.qcby.tomcat.config.ServletConfigMapping;
import com.qcby.tomcat.socket.Server;
import java.io.IOException;
public class MyTomcat {
public static void main(String[] args) {
try {
//调用ServletConfigMapping,初始化servlet容器
ServletConfigMapping.init();
//启动server服务
Server.serverInit();
} catch (IOException e) {
e.printStackTrace();
}
}
}
至此,流程又实现了一半
四、匹配
完成上面二三步骤,我们就相当于既得到了servlet容器,又识别出来了http请求要找的对象。现在就是怎么将二者匹配起来。
在上一篇博客中,我们把所受到的http请求的method和path都封装在了request对象中,所以我们通过调取request进行连接
代码如下:
如下代码中我把server放在了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 com.qcby.tomcat.socket.Server;
import com.qcby.tomcat.webservlet.WebServlet;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
public class MyTomcat {
public static Request request=new Request();
public static Response response=new 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();
requestContext(inputStream);
}
public static void requestContext(InputStream inputStream) throws IOException, InstantiationException, IllegalAccessException {
//将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 InstantiationException, IllegalAccessException {
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请求
}
}
}
}
public static void dis(Request request) throws InstantiationException, IllegalAccessException { 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请求 } } }
注意!!HttpServlet servlet=ClassServlet.newInstance();是根据获取到的类对象,创建对应的对象 用父类去接,多态(父类的引用指向子类的对象)
方法是对象当中的一块内存空间,这是类对象,不是对象,想要得到信息就得生成对象。
这一句是通过类对象去创建对象。但是因为不确定是请求的哪个对象,需要用父类去承接(多态), 根据对象调用它的doGet或doPost方法。
整个流程这就走通了。首先当我们启动之后,我们就已经创建好了Servlet的map集合,此时用户的HTTP请求打过来。现在我们处理请求信息,把这些请求信息封装在request对象当中。获取到类对象后,根据获取到的类对象,创建对应的对象。然后再去调用方法。至此成功实现某Servlet的doGet方法。
测试如下:
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.webservlet.WebServlet;
@WebServlet(path ="/MyFirstServlet")
public class MyFirstServlet extends HttpServlet {
@Override
public void doGet(Request request, Response response) {
System.out.println("你好我是FirstServlet");
}
@Override
public void doPost(Request request, Response response) {
}
}