目录
引入
一、在Java中创建一个新的空项目(初步搭建)
问题:
要求在tomcat软件包下的MyTomcat类中编写main文件,实现在MyTomcat中扫描myweb软件包中的所有Java文件,并返回“@WebServlet(url="myFirst")”中url内填写的值:
①main函数解析:
首先,main函数用try-catch做了异常处理:
指定包名:
获取包下所有类的类对象:
扫描遍历:
二、搭建服务器端
在socket软件包的Server文件中编写:当存在客户端连接后,获取:
分析一下获取路径和请求方法的过程:
三、搭建
重写service方法:
引入
前面已经提到了,TomCat就是项目运行的环境,之前用到的Servlet文件都是通过eclipse中的tomcat容器来运行的,那么接下来在Java文件中去模拟这个过程。
在tomcat项目中创建Servlet项目。
一、在Java中创建一个新的空项目(初步搭建)
创建新项目后再去在目录下创建如下软件包和Java类。
@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;
@Retention(value= RetentionPolicy.RUNTIME)
@Target(value={ElementType.TYPE})
public @interface WebServlet {
String url() default "";
String className() default "";
}
myweb里面的MyFirstServlet等三个文件内调用@WebServlet接口,如下代码:
(以MyFirstServlet为例:)
package com.qcby.tomcat.myweb;
import com.qcby.tomcat.HttpServlet.HttpServlet;
import com.qcby.tomcat.webservlet.WebServlet;
@WebServlet(url="myFirst")
public class MyFirstServlet extends HttpServlet {
}
问题:
要求在tomcat软件包下的MyTomcat类中编写main文件,实现在MyTomcat中扫描myweb软件包中的所有Java文件,并返回“@WebServlet(url="myFirst")”中url内填写的值:
MyTomcat内:
package com.qcby.tomcat;
import com.qcby.tomcat.webservlet.WebServlet;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
public class MyTomcat {
public static void main(String[] args) {
//扫描myweb这个包下的所有文件,并获取到它的@WebServlet中的url的值
// get current dir
try {
// 1. 扫描包路径 (com.wzh.tomcat.myweb)
String packageName = "com.qcby.tomcat.myweb";
List<Class<?>> classes = getClasses(packageName); //通过getClasses()方法获取到了myweb这个包下面的所有类的类对象,并将其放到了类对象中
// 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.url());
}
}
} catch (Exception e) {
e.printStackTrace();//打印异常
}
}
/**
* 获取指定包下的所有类
*
* @param packageName 包名,例如 "com.qcby.tomcat.myweb"
* @return 类对象列表
* @throws Exception
*/
private static List<Class<?>> getClasses(String packageName) throws Exception {
List<Class<?>> classes = new ArrayList<>(); //将类文件封装进List中
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")) { //获得.class文件(.java->.class)此处获取的就是经过编译后的.class文件
// 获取类的完整类名
String className = packageName + "." + file.getName().replace(".class", "");
classes.add(Class.forName(className));
}
}
}
}
return classes;
}
}
解析:
①main函数解析:
首先,main函数用try-catch做了异常处理:
try-catch
块用于捕获并处理在扫描包和读取注解时可能发生的异常。
指定包名:
String packageName = "com.qcby.tomcat.myweb";
:定义了一个字符串变量packageName
,它存储了要扫描的包名。
获取包下所有类的类对象:
List<Class<?>> classes = getClasses(packageName);
- 这行代码调用了一个名为
getClasses
的方法(后面分析getClasses就知道这是一个获取类对象的方法
),将这些获取的类对象写入到一个泛型中,方便后续遍历。- 调用
getClasses
方法,传入包名,获取该包下所有类的Class
对象列表,并存储在classes
变量中。
扫描遍历:
对获得并写入List中的每个文件进行
isAnnotationPresent判断是否有@WebServlet注解:
【注释:isAnnotationPresent
是Java语言中的一种方法,它主要用于判断某个类、方法、变量等元素上是否存在指定类型的注解。】
- 调用
clazz.getAnnotation(WebServlet.class);
时,Java虚拟机(JVM)会做以下几件事情:
- 检查
clazz
对象所代表的类上是否存在WebServlet
注解的实例。- 如果存在,返回这个注解的实例。
- 如果不存在,返回
null
。
扫描获得后打印输出。
那么分析完成main函数后,接着来分析一下getClases方法具体是怎么实现获取包中类信息的:
(详细参见注释:)
// 定义一个静态方法,用于获取指定包名下的所有类对象列表
private static List<Class<?>> getClasses(String packageName) throws Exception {
List<Class<?>> classes = new ArrayList<>(); // 初始化一个ArrayList,用于存储找到的类对象
// 将包名中的点(.)替换为文件路径中的斜杠(/),以构造资源路径
String path = packageName.replace('.', '/');
// 获取当前线程的类加载器,用于加载资源
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// 通过类加载器获取指定路径下的所有资源(可能是目录或JAR文件中的条目)
Enumeration<URL> resources = classLoader.getResources(path);
// 遍历所有找到的资源
while (resources.hasMoreElements()) {
URL resource = resources.nextElement(); // 获取下一个资源URL
// 注意:这里假设资源是一个文件系统上的目录,但这不是总是正确的,特别是当资源在JAR中时
File directory = new File(resource.toURI()); // 将资源URL转换为File对象(这里存在潜在问题,对于JAR文件不适用)
// 检查目录是否存在(对于文件系统上的资源有效)
if (directory.exists()) {
// 遍历目录下的所有文件和子目录
for (File file : directory.listFiles()) {
// 检查文件是否以".class"结尾,即是否是类文件(.java文件经过编译后的.class文件)
if (file.getName().endsWith(".class")) {
// 构造类的完整名称(包括包名)
String className = packageName + "." + file.getName().replace(".class", "");
// 使用反射加载类,并添加到类列表中
// 注意:这里可能会抛出ClassNotFoundException,但在这个方法内部没有捕获处理
classes.add(Class.forName(className));
}
}
}
}
// 返回找到的类对象列表
return classes;
}
二、搭建服务器端
在socket软件包的Server文件中编写:
当存在客户端连接后,获取:
package com.qcby.tomcat.socket;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws Exception {
// 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 { //获取全部信息
//将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);
}
}
//获取第一个词和第二个词
// public static void requestContext(InputStream inputStream) throws IOException {
// StringBuilder sb = new StringBuilder();
// int ch;
// // 读取输入流直到遇到换行符或文件结束
// while ((ch = inputStream.read()) != -1) {
// if (ch == '\n') {
// break; // 遇到换行符,停止读取
// }
// sb.append((char) ch); // 将读取的字符添加到StringBuilder中
// }
//
// String firstLine = sb.toString().trim(); // 获取第一行并去除首尾空格
// if (firstLine.isEmpty()) {
// System.out.println("你输入了一个空请求");
// } else {
// String[] words = firstLine.split("\\s+"); // 使用正则表达式按空格分割单词
// if (words.length >= 2) {
// System.out.println("第一个词是:" + words[0]);
// System.out.println("第二个词是:" + words[1].substring(1));
// } else {
// System.out.println("第一行没有足够的词");
// }
// }
// }
}
分析一下获取路径和请求方法的过程:
String firstLine = Context.split("\\n")[0];
- 这行代码将
Context
字符串按照换行符(\n
)进行分割,并取出分割后的第一个元素(即第一行)赋值给firstLine
。String path = firstLine.split("\\s")[1];
- 接着,这行代码将
firstLine
按照空白字符(包括空格、制表符等,\s
是一个正则表达式,用于匹配任何空白字符)进行分割,并取出分割后的第二个元素(索引为1)赋值给path
。这里假设路径是firstLine
中方法名后面的第一个元素。String method = firstLine.split("\\s")[0];
- 这行代码再次对
firstLine
按照空白字符进行分割,并取出分割后的第一个元素(索引为0)赋值给method
。这里假设方法名是firstLine
中的第一个元素。System.out.println(path + " " + method);
- 最后,这行代码打印出
path
和method
的值,但顺序是先打印path
,后打印method
,中间用空格分隔。
三、搭建
【注:接口--接口就是用来定义方法的;实现接口类就必须实现接口中的方法;
抽象类中可以有抽象方法,也可以有具体方法;抽象类实现了接口,那么抽象类可以有选择性的实现接口中的方法】
重写service方法:
HTTP请求:
请求方法--Get,Post....等(主要是Get\Post方式)
而上述实现service方法,就是为了获取请求方法并且判断是Get还是Post请求(然后将请求送到doGet()/doPost()方法中)
在HttpServlet包中的方法HttpServlet抽象类中重写service方法:
首先创建request:
public class Request {
private String path;
private String method;
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;
}
}
并且修改Server来得到path和method:
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 {
//实例化Request
private static Request request = new Request();
public static void main(String[] args) throws Exception {
// 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 { //获取全部信息
//将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 method = firstLine.split("\\s")[0];
String path = firstLine.split("\\s")[1];
System.out.println(method + " " + path);
//任何请求都会被打到这个类中,随后就会被解析
//将解析后的数据(method和path放进申请的static的Request实例中--再被运输给其他需要的地方)
request.setMethod(method);
request.setPath(path);
}
}
即可通过这种方式让HttpServlet中的service方法得到Http请求的key和请求方法:
import com.qcby.tomcat.Request.Request;
import com.qcby.tomcat.socket.Server;
public abstract class HttpServlet {
/*
* 一定不能是抽象方法因为HttpServlet是要被实现的
* 根据用户的请求,来调用不用的方法去处理
* GET请求 doGet()请求
* POST请求 doPost()请求
*
* */
//重写service()方法
public void service(Request request){//一定不能是抽象方法因为HttpServlet是要被实现的
if(request.getMethod().equals("GET")){
doGet(request);
}else if(request.getMethod().equals("POST")){
doPost(request);
}
}
//去实现doGet--意味着所有继承HttpServlet抽象类的对象,都要去实现这两个方法
public abstract void doGet(Request request);
//去实现doPost
public abstract void doPost(Request request);
}
那么就意味着(继承抽象类的实例必须实现抽象类中的方法):
那么到这里,一个基础的、连贯的tomcat雏形就存在了:
四、Tomcat雏形
1.tomcat的server接收到一个请求:
发送请求:
2.被tomcat的server接收到:
3.此时server内部创建了一个static的Request实例,并被HttpServlet里面的service接收:
通过if-else if判断后,被送到对应的doGet或doPost方法:
由于HttpServlet是抽象类,所以所有继承这个抽象类的实例都要实现抽象类中的所有方法:
类似:
同时,由于doGet()和doPost()方法都是抽象方法,所以HttpServlet想要实现这些方法,就要去到各自的实例中,而这个实例究竟是哪一个,则就对应path中存的路径了。
以上就是一个基础的雏形(关于tomcat是如何接收并初步处理一个请求的)