手写Tomcat:实现基本功能

首先,Tomcat是一个软件,所有的项目都能在Tomcat上加载运行,Tomcat最核心的就是Servlet集合,本身就是HashMap。Tomcat需要支持Servlet,所以有servlet底层的资源:HttpServlet抽象类、HttpRequest和HttpResponse,否则我们无法新建Servlet。

这样我们就可以在webapps写项目了,一个项目有两大资源:servlet资源和静态资源,servlet本身是java类,我们让它调用doGet和doPost方法,必须继承HttpServlet,同时也需要@WebServlet注解,那么在Tomcat中就必须要有@WebServlet注解的实现,如果没有@WebServlet,我们就无法拿到相应的注解。这样我们就能成功搭建起Servlet资源。

如果servlet集合在Tomcat启动之前实现,也就是main()方法之前,需要使用static代码块去实现;如果servlet集合在Tomcat启动之后实现,也就是main()方法之后,再去加载servlet容器。

当Http请求打过来之后,先打到socket上,处理Http请求,其本身就是连接器,从请求上能获取到请求路径和请求方法。先获取到请求路径,判断servlet容器中是否含有相同路径,如果有,再获取请求方法,判断是doGet还是doPost。

一、Servlet资源准备

1、Servlet接口:

  • Servlet接口的作用:实现Servlet生命周期,定义init()、service(request,response)和destroy()方法。
  • 接口中的方法全都是抽象方法,只定义不实现,方法头默认为public abstract。
java 复制代码
import com.qcby.Servlet.req.HttpServletRequest;
import com.qcby.Servlet.req.HttpServletResponse;

public interface Servlet {

    void init();

    void service(HttpServletRequest request, HttpServletResponse response) throws Exception;

    void destroy();
}

2、GenericServlet抽象类

  • GenericServlet抽象类实现Servlet接口中的init()方法destroy()方法

为什么GenericServlet类是抽象类?

我们知道当一个普通类继承一个接口时,需要对接口中的所有方法进行实现,但这里我们不实现Servlet接口中的service(request,response)方法,因此我们需要把GenericServlet类变成抽象类,因为抽象类不必实现接口中的全部方法。

java 复制代码
public abstract class GenericServlet implements Servlet {
   
    public void init(){
        System.out.println("初始化成功");

    }

    public void destroy(){
        System.out.println("销毁成功");

    }
}

3、HttpServlet抽象类

  • HttpServlet抽象类实现Servlet接口中的**service(request,response)**方法

为什么HttpServlet类也是抽象类?

之所以HttpServlet类也是抽象类,是因为普通类中必须对方法进行实现,而在HttpServlet类中,实现service(request,response)方法的逻辑为:如果接收到GET请求,那么执行doGet方法;如果接收到POST请求,那么执行doPost方法。在此我们只需要对doGet和doPost方法进行定义不需要实现,所以HttpServlet只有成为抽象类,才不必实现doGet和doPost方法。

java 复制代码
import com.qcby.Servlet.req.HttpServletRequest;
import com.qcby.Servlet.req.HttpServletResponse;

public abstract class HttpServlet  extends GenericServlet {
    //实现Servlet生命周期的service方法
    public void service(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //如果接收到GET请求,那么执行doGet方法
        if(request.getMethod().equals("GET")){
            doGet(request,response);
        }
        //如果接收到POST请求,那么执行doPost方法
        else if(request.getMethod().equals("POST")){
            doPost(request,response);
        }
    }

    protected abstract void doGet(HttpServletRequest request,HttpServletResponse response) throws Exception;

    protected abstract void doPost(HttpServletRequest request,HttpServletResponse response)throws Exception;
}

4、HttpRequest类

  • HttpRequest类实现请求路径请求方法的获取与输出。
java 复制代码
public class HttpServletRequest {

    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;
    }
}

5、HttpResponse类

java 复制代码
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;

public class HttpServletResponse {

    private OutputStream outputStream;

    public HttpServletResponse(OutputStream outputStream){
        this.outputStream = outputStream;
    }

    public void writeServlet(String context) throws IOException{
        outputStream.write(context.getBytes());
    }
}

二、注解

  • @Retention():表示该注解的作用阶段。阶段分三种:源代码阶段、类对象阶段和运行时阶段。
  • @Target():表示该注解的作用范围。
java 复制代码
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

//作用阶段
@Retention(RetentionPolicy.RUNTIME)//作用在运行阶段

//作用范围:类、方法......
@Target(ElementType.TYPE)//作用在类上

public @interface WebServlet {
    String urlMapping() default "";//自定义的servlet路径
}

三、工具类

1、SearchClassUtil类

SearchUtil类的功能:扫描com.qcby.webapps包下的文件,获取全路径名

java 复制代码
import java.io.File;
import java.util.ArrayList;
import java.util.List;

/*
* 扫描com.qcby.webapps包下的文件,获取全路径名
* */
public class SearchClassUtil {
    public static List<String> classPaths = new ArrayList<String>();

    public static List<String> searchClass(){
        //需要扫描的包名
        String basePack = "com.qcby.webapps";
        //将获取到的包名转换为路径
        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);
        }
    }
}

2、ResponseUtil类

java 复制代码
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;
    }
}

四、Servlet类

1、LoginServlet类

java 复制代码
import com.qcby.Servlet.HttpServlet;
import com.qcby.Servlet.req.HttpServletRequest;
import com.qcby.Servlet.req.HttpServletResponse;
import com.qcby.Util.ResponseUtil;
import com.qcby.Util.WebServlet;

import java.io.IOException;

@WebServlet(urlMapping = "/login")
public class LoginServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        System.out.println("我是LoginServlet下的doGet方法");
        response.writeServlet(ResponseUtil.getResponseHeader200("hello"));
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) {
        System.out.println("我是LoginServlet下的doPost方法");
    }
}

五、ServletConfigMapping类

ServletConfigMapping类是Servlet容器,是Tomcat中最核心的部分,其本身是一个HashMap,其功能为:将路径和对象写入Servlet容器中。

java 复制代码
import com.qcby.Servlet.HttpServlet;
import com.qcby.Util.SearchClassUtil;
import com.qcby.Util.WebServlet;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ServletConfigMapping {

    //创建servlet容器
    public static Map<String, HttpServlet> servletMap = new HashMap<>();

    //将Path和对象写入servlet容器当中
    //采用static代码块 在启动tomcat之前实现
    static{
        List<String> classesPath = SearchClassUtil.searchClass();
        for(String path:classesPath){
            try{
                //加载类
                Class clazz = Class.forName(path);
                //获取注解
                WebServlet webServlet = (WebServlet) clazz.getDeclaredAnnotation(WebServlet.class);
                HttpServlet servlet = (HttpServlet) clazz.newInstance();
                servletMap.put(webServlet.urlMapping(),servlet);
            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
}

六、MyTomcat

java 复制代码
import com.qcby.Servlet.HttpServlet;
import com.qcby.Servlet.req.HttpServletRequest;
import com.qcby.Servlet.req.HttpServletResponse;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class MyTomcat {

    static HttpServletRequest request = new HttpServletRequest();


    public static void main(String[] args) throws Exception {
        //ServletConfigMapping.init(); // tomcat启动,但是还没有监听之前,将信息加载入servlet容器当中


        //1.创建serversocket对象,持续监听8080端口
        ServerSocket serverSocket = new ServerSocket(8888);
        while (true){
            //accept():阻塞监听  ,当代码执行到这一样,如果没有数据到来,那么循环阻塞在这里。如果有数据到来,就继续向下执行
            Socket socket = serverSocket.accept();
            InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream();
            HttpServletResponse response = new HttpServletResponse(outputStream);

            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];
                request.setMethod(firstLine.split("\\s")[0]);
                request.setPath(firstLine.split("\\s")[1]);
            }

            //如果我们访问的路径在servlet容器当中存在
            if(ServletConfigMapping.servletMap.containsKey(request.getPath())){
                HttpServlet servlet = ServletConfigMapping.servletMap.get(request.getPath());
                servlet.service(request,response);
            }
        }
    }
}
相关推荐
兩尛4 分钟前
Spring面试
java·spring·面试
Java中文社群11 分钟前
服务器被攻击!原因竟然是他?真没想到...
java·后端
Full Stack Developme22 分钟前
java.nio 包详解
java·python·nio
零千叶39 分钟前
【面试】Java JVM 调优面试手册
java·开发语言·jvm
代码充电宝1 小时前
LeetCode 算法题【简单】290. 单词规律
java·算法·leetcode·职场和发展·哈希表
li3714908901 小时前
nginx报400bad request 请求头过大异常处理
java·运维·nginx
摇滚侠1 小时前
Spring Boot 项目, idea 控制台日志设置彩色
java·spring boot·intellij-idea
Aevget2 小时前
「Java EE开发指南」用MyEclipse开发的EJB开发工具(二)
java·ide·java-ee·eclipse·myeclipse
黄昏晓x2 小时前
C++----多态
java·jvm·c++
Brookty2 小时前
【算法】前缀和
java·学习·算法·前缀和·动态规划