一:Servlet的原理
在Servlet(接口中)有:
1.init():初始化servlet
2.getServletConfig():获取当前servlet的配置信息
3.service():服务器(在HttpServlet中实现,目的是为了更好的匹配http的请求方式)
4.getServletInfo():获取servlet当前运行过程中的信息
5.destroy():销毁,回收内存
在I/O中应包含:1.请求头(f12+网络) 2.请求方式(get/post) 3.请求内容
get请求:将请求的内容放在url中(相对不安全),url长度有限(导致发送的内容不能太长),get请求做查询
post请求:请求内容放在请求体当中,无法看到(相对安全),一般用来做文件上传、下载,post请求做增删改操作
在ServletRequest中应该有:1.method:请求方式 2.编码方式 3.parmater 4.url 5.cookie
在ServletResponse中应该有:1.状态码 2.编码方式 3.字符集 4.data数据
每一个和外界进行通讯的进程(端口号0~65535)
端口号区分当前的进程
serverSocket.accept():阻塞监听(停在这里等到程序的到来)
二:tomcat原理
下面用一个图给大家展示
注解:给程序看的(@webservlet等等)
注释:给人看得(//)
实现自定义注解:需要使用jdk提供元注解(主要使用前两个)
1.@Target注解(用来描述注解的使用范围)
2.@Retention注解(表示这个注解在什么时候还有效,用于描述注解的生命周期)
3.@Documented注解
4.@Inherited注解 这四个帮助实现自定义注解
前两个的使用方法
1.
2.
利用上面两个就能够实现自定义注解(下面就是实现)
三、手写tomcat
首先先创建idea项目(名称和位置可以随意)
在创建好项目之后会有一个.java文件(可删可不删)
之后在创建下面几个软件包和MyTomcat文件(MyTomcat和刚开始有的那个java文件不一样)
之后在每个软件包中创建好java文件和注解文件、接口文件(其中zj软件包中的注解文件可用自己的姓名起,因为是自己手写的tomcat文件)
创建完成之后,首先在HttpServletRequest和HttpServletResponse中分别写
package com.qcby.request;
public class HttpServletRequest {
private String method;
private String url;
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
package com.qcby.response;
import com.qcby.request.HttpServletRequest;
import java.io.IOException;
import java.io.OutputStream;
public class HttpServletResponse {
private OutputStream outputStream;
public HttpServletResponse(OutputStream outputStream){
this.outputStream = outputStream;
}
public void writeServlet(String context) throws IOException {
outputStream.write(context.getBytes());
}
}
写好之后就可以在写servlet软件包中的内容了
这是接口
package com.qcby.servlet;
import com.qcby.request.HttpServletRequest;
import com.qcby.response.HttpServletResponse;
import java.io.IOException;
public interface servlet {
public void service(HttpServletRequest request, HttpServletResponse response) throws IOException;
}
这是java
package com.qcby.servlet;
import com.qcby.request.HttpServletRequest;
import com.qcby.response.HttpServletResponse;
import java.io.IOException;
public abstract class HttpServlet implements servlet{
public void doPost(HttpServletRequest request, HttpServletResponse response) {
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
}
@Override
public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
if(request.getMethod().equals("GET")){
doGet(request,response);
}else if(request.getMethod().equals("POST")){
doPost(request,response);
}
}
}
写关于自己的注解
package com.qcby.zj;
import java.lang.annotation.*;
@Target(ElementType.TYPE)//该注解用在类上面
@Retention(RetentionPolicy.RUNTIME)//在运行期间表达
public @interface LRHServlet {
String url();
}
之后写myweb中的
package com.qcby.myweb;
import com.qcby.request.HttpServletRequest;
import com.qcby.response.HttpServletResponse;
import com.qcby.servlet.HttpServlet;
import com.qcby.util.ResponseUtil;
import com.qcby.zj.LRHServlet;
import java.io.IOException;
@LRHServlet(url="/myservlet")
public class MyFirstServlet extends HttpServlet {
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
System.out.println("hello word");
response.writeServlet(ResponseUtil.getResponseHeader200("First hello word"));
}
@Override
public void doPost(HttpServletRequest request,HttpServletResponse response){
}
}
package com.qcby.myweb;
import com.qcby.request.HttpServletRequest;
import com.qcby.response.HttpServletResponse;
import com.qcby.servlet.HttpServlet;
import com.qcby.zj.LRHServlet;
@LRHServlet(url="/insert")
public class insertServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response){
System.out.println("I am insert");
}
@Override
public void doPost(HttpServletRequest request,HttpServletResponse response){
}
}
util中放的是工具类,可直接复制
package com.qcby.util;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* 扫描指定包,获取该包下所有的类的全路径信息
*/
public class SearchClassUtil {
/**
* 扫描指定包路径下的所有类
* @param basePack 需要扫描的包名
* @return 类的全路径列表
*/
public static List<String> searchClass(String basePack) {
//创建存储类路径的列表
List<String> classPaths = new ArrayList<>();
try {
// 获取类加载器(用于查找类路径资源)
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// 将包名转换为路径格式(如 com.qcby.test)转换为路径格式(com/qcby/test)
String path = basePack.replace('.', '/');
// 获取资源枚举通过类加载器获取该路径下的所有资源(可能来自文件系统或 JAR 包)
Enumeration<URL> resources = classLoader.getResources(path);
//resources 是通过类加载器获取的资源枚举(包含文件系统和 JAR 包中的资源)
while (resources.hasMoreElements()) {
//每次循环处理一个资源 URL
URL resource = resources.nextElement();
// 判断资源类型(文件系统或JAR包)
if (resource.getProtocol().equalsIgnoreCase("file")) {
// 处理文件系统中的类
//获取 URL 中的路径部分
String filePath = resource.getPath();
//解码特殊字符
filePath = java.net.URLDecoder.decode(filePath, "UTF-8");
//调用 findAndAddClassesInPackageByFile() 递归扫描目录
findAndAddClassesInPackageByFile(basePack, filePath, classPaths);
} else if (resource.getProtocol().equalsIgnoreCase("jar")) {
// 处理JAR包中的类
//从 JAR 资源 URL 中提取实际的 JAR 文件路径。跳过前缀 jar:file:(长度为 5)。
//indexOf("!"):找到 ! 的位置,截取到此前的部分,得到 JAR 文件的路径
String jarPath = resource.getPath().substring(5, resource.getPath().indexOf("!"));
//解码 URL 编码的特殊字符
jarPath = java.net.URLDecoder.decode(jarPath, "UTF-8");
//打开 JAR 文件,扫描指定包下的所有类文件。
findAndAddClassesInPackageByJar(basePack, jarPath, classPaths);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return classPaths;
}
/**
* 从文件系统中查找类
*/
private static void findAndAddClassesInPackageByFile(String packageName, String packagePath, List<String> classPaths) {
//创建文件对象将传入的路径字符串转换为 File 对象,用于文件系统操作。
File dir = new File(packagePath);
//确保当前路径是一个存在且有效的目录。
if (!dir.exists() || !dir.isDirectory()) {
return;
}
// 获取目录下的所有文件和子目录,并过滤出以下两类
File[] dirfiles = dir.listFiles(file ->
// 文件夹或.class文件
//使用 Lambda 表达式作为 listFiles 的过滤器,简洁地指定筛选条件。
file.isDirectory() || file.getName().endsWith(".class")
);
//目录无法访问(如权限不足)或为空时,listFiles 可能返回 null,此处直接跳过。
if (dirfiles == null) {
return;
}
// 遍历所有文件
for (File file : dirfiles) {
if (file.isDirectory()) {
// 递归处理子目录
findAndAddClassesInPackageByFile(
packageName + "." + file.getName(), //包名
file.getAbsolutePath(), //文件或目录的绝对路径
classPaths //类路径集合(用于储存结果)
);
} else {
// 处理class文件
String className = file.getName().substring(0, file.getName().length() - 6);
classPaths.add(packageName + '.' + className);
}
}
}
/**
* 从JAR包中查找类
*/
private static void findAndAddClassesInPackageByJar(String packageName, String jarPath, List<String> classPaths) {
//尝试打开 JAR 文件(资源自动关闭)
try (JarFile jar = new JarFile(jarPath)) {
//获取 JAR 文件中所有条目的枚举
Enumeration<JarEntry> entries = jar.entries();
//逐个处理 JAR 中的每个条目。
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String name = entry.getName();
// 判断是否是类文件并且在指定包路径下
//确保只提取指定包及其子包下的类文件。
if (name.endsWith(".class") && name.startsWith(packageName.replace('.', '/'))) {
//提取类全限定名去除 .class 后缀:
String className = name.substring(0, name.length() - 6).replace('/', '.');
//添加类名到结果列表
classPaths.add(className);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
// 测试示例
//测试扫描指定包
List<String> classes = searchClass("com.qcby.myweb");
//遍历并打印结果
for (String className : classes) {
System.out.println(className);
}
}
public static List<String> searchClass() {
return null;
}
}
package com.qcby.util;
public class ResponseUtil {
public static final String responseHeader200 = "HTTP/1.1 200 \r\n"+
"Content-Type:text/html; charset=utf-8 \r\n"+"\r\n";
public static String getResponseHeader404(){
return "HTTP/1.1 404 \r\n"+
"Content-Type:text/html; charset=utf-8 \r\n"+"\r\n" + "404";
}
public static String getResponseHeader200(String context){
return "HTTP/1.1 200 \r\n"+
"Content-Type:text/html; charset=utf-8 \r\n"+"\r\n" + context;
}
}
在config中写入
package com.qcby.config;
import com.qcby.servlet.HttpServlet;
import com.qcby.util.SearchClassUtil;
import com.qcby.zj.LRHServlet;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
/**
* tomcat路由
*/
public class TomcatRoute {
//HashMap<String, HttpServlet> HttpServlet向上转型(使用多态),因为myweb中的父类全是HttpServlet
//将public static HashMap<String, HttpServlet> routes = new HashMap<>();写入static{}中会导致无法调用
public static HashMap<String, HttpServlet> routes = new HashMap<>();
static{
// 传入要扫描的包名
List<String> paths = SearchClassUtil.searchClass("com.qcby.myweb");
for(String path : paths){
try{
//url
Class clazz = Class.forName(path);
LRHServlet webServlet = (LRHServlet) clazz.getDeclaredAnnotation(LRHServlet.class);
routes.put(webServlet.url(), (HttpServlet) clazz.getDeclaredConstructor().newInstance());
System.out.println(webServlet.url());
// if (webServlet != null) {
// System.out.println(webServlet.url());
// }
//对象
clazz.getDeclaredConstructor().newInstance();
}catch(ClassNotFoundException e){
e.printStackTrace();
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
}
}
最后就是直接的MyTomcat
package com.qcby;
import com.qcby.config.TomcatRoute;
import com.qcby.request.HttpServletRequest;
import com.qcby.response.HttpServletResponse;
import com.qcby.servlet.HttpServlet;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
/**
* tomcat主启动类
*/
public class MyTomcat {
static HashMap<String, HttpServlet> routes = TomcatRoute.routes;
static HttpServletRequest request = new HttpServletRequest();
/**
* 分发器
*/
public static void dispatch(HttpServletResponse response) throws IOException {
HttpServlet servlet = routes.get(request.getUrl());
System.out.println(servlet);
if (servlet != null) {
servlet.service(request, response);
}
}
public static void start() throws IOException {
System.out.append("服务器端启动...");
//1.定义ServerSocket对象进行服务器端的端口注册
ServerSocket serverSocket = new ServerSocket(8080);
while (true) {
//2.监听客户端的Socket链接程序
Socket socket = serverSocket.accept(); // 阻塞监听
//3.打开输入流,解析客户端发来的内容
InputStream inputStream = socket.getInputStream();//输入流
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));//将字节流转换成字符流
String str = reader.readLine();
request.setMethod(str.split("\\s")[0]);
request.setUrl(str.split("\\s")[1]);
//4.打开输出流
OutputStream outputStream = socket.getOutputStream();
HttpServletResponse response = new HttpServletResponse(outputStream);
dispatch(response);
}
}
public static void main(String[] args) throws IOException {
start();
}
}
这样就完成三分之二了
之后运行MyTomcat程序后,回显示com.qcby.myweb下的所有软件包名,和服务器端启动的字样
在浏览器的地址栏里输入(localhost:8080/myservlet)这里的8080要根据你实际写的端口号是多少写多少(myservlet要写自己定义的url)
在浏览器中就能够看到下面写在MyFirstServlet中的内容
在控制台能够看到MyFirstservlet中打印的内容
这样一个简单的tomcat就写好了