仿写一个tomcat(含线程池配置)超详细!!

目录

工作原理

整体项目结构

自定义注解

创建servlet类

创建启动类

线程池配置

测试阶段


工作原理

首先看流程图,搞清楚tomcat的工作原理

工作原理如下:

  1. Tomcat使用一个叫作Catalina的核心组件来处理HTTP请求和响应。Catalina包含了一个HTTP连接器(Connector),负责监听和接收客户端请求。

  2. 当接收到一个HTTP请求时,Tomcat会解析请求头和请求体,提取出请求的URL、请求方法、请求参数等信息。

  3. Tomcat中的Servlet容器负责管理Servlet的生命周期和处理Servlet请求。Servlet是以Java类的形式编写的服务器端程序,用于处理动态内容。

  4. 在Tomcat中,Servlet类需要使用注解(如@WebServlet)进行标记,以指定Servlet的URL映射和其他配置信息。

  5. 当Tomcat启动时,它会扫描应用程序中包含的所有类,找到带有Servlet注解的类,并将它们的URL映射和全类名存储在一个映射表中。

  6. 当接收到请求时,Tomcat会根据请求的URL在映射表中查找对应的Servlet类。利用反射机制,Tomcat可以实例化Servlet类并调用其相应的方法来处理请求。

  7. 处理请求的Servlet可以通过请求对象(HttpServletRequest)获取请求的URL、参数、请求头等信息,并通过响应对象(HttpServletResponse)来生成响应内容和设置响应头。

  8. Tomcat使用Socket与浏览器进行通信,通过监听在8080端口(默认)来接收客户端的HTTP请求。

人话:它是利用注解对servlet类进行标记,之后将它标记中的请求地址和全类名存入map中,利用反射拿到类信息,调用类的方法,利用socket和浏览器交互,监听8080端口,之后将请求信息拿到,处理其中的信息拿到请求url和参数信息

仿写一个tomcat(含线程池配置)超详细!!_第1张图片

整体项目结构

仿写一个tomcat(含线程池配置)超详细!!_第2张图片

自定义注解

需要用到java中的元注解实现,如果不清楚的小伙伴可以参考这一篇:

java 元注解||自定义注解的使用_ADRU的博客-CSDN博客

import java.lang.annotation.*;

/**
 * @author 小如
 *
 * @date 2023/08/15
 */

/** 请求映射该注解可以应用于类、接口(包括注解类型)、枚举*/
@Target(ElementType.TYPE)
/**该注解标记的元素可以被Javadoc 或类似的工具文档化*/
@Documented
/**该注解的生命周期,由JVM 加载,包含在类文件中,在运行时可以被获取到*/
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMap {
    String path() default "";
}

创建servlet类

1.创建接收参数信息类

import java.util.HashMap;

/**
 * http请求参数仿写
 *
 * @author 小如
 * @date 2023/08/15
 */
public class HttpRequestDemo {

    public HashMap map = new HashMap<>();

    public String getParameter(String key) {
        return map.get(key);
    }

}
import java.io.OutputStream;

public class HttpResponseDemo {

    public OutputStream outputStream;

    public static final String responsebody="HTTP/1.1 200+\r\n"+"Content-Type:text/html+\r\n"
            +"\r\n";
    public HttpResponseDemo(OutputStream outputStream){
        this.outputStream=outputStream;
    }

}

2.自定义两个servlet类(模拟请求处理)


import Tools.HttpRequestDemo;
import Tools.HttpResponseDemo;
import annotation.RequestMap;
import java.io.IOException;

/**
 * 请求servlet
 *
 * @author 小如
 * @date 2023/08/15
 */
@RequestMap(path = "hello")
public class RequestServlet {

    public void doGet(HttpRequestDemo request, HttpResponseDemo response) throws IOException {
        System.out.println("hello GET响应:");
        System.out.println("a="+request.getParameter("a"));
        String resp= HttpResponseDemo.responsebody+"\n" +
                "\n" +
                "\n" +
                "    \n" +
                "\n"+
                "\n" +
                "\n" +
                " \n" +
                "    
\n" + " \n" + "
\n" + " \n" + "\n" + ""; response.outputStream.write(resp.getBytes()); response.outputStream.flush(); response.outputStream.close(); } public void doPost(HttpRequestDemo request,HttpResponseDemo response) throws IOException { System.out.println("\n响应的http如下:"); String resp= HttpResponseDemo.responsebody+ "{\"sorry\":\"we only respond to method GET now\"},\r\n"+ ""; System.out.println(resp); response.outputStream.write(resp.getBytes()); response.outputStream.flush(); response.outputStream.close(); } }
import Tools.HttpRequestDemo;
import Tools.HttpResponseDemo;
import annotation.RequestMap;

import java.io.IOException;

/**
 * 请求servlet
 *
 * @author 小如
 * @date 2023/08/15
 */
@RequestMap(path = "nihao")
public class RequestServlet2 {

    public void doGet(HttpRequestDemo request, HttpResponseDemo response) throws IOException {
        System.out.println("nihao GET响应:");
        System.out.println("a="+request.getParameter("a"));

        String resp= HttpResponseDemo.responsebody+
                "{\"success\":\"200\"},\r\n"+
                "";
        response.outputStream.write(resp.getBytes());
        response.outputStream.flush();
        response.outputStream.close();

    }

    public void doPost(HttpRequestDemo request,HttpResponseDemo response) throws IOException {
        System.out.println("\n响应的http如下:");
        String resp= HttpResponseDemo.responsebody+
                "{\"sorry\":\"we only respond to method GET now\"},\r\n"+
                "";
        System.out.println(resp);
        response.outputStream.write(resp.getBytes());
        response.outputStream.flush();
        response.outputStream.close();
    }
}

3.定义一个异常servlet,当请求异常时,打到这个servlet

import Tools.HttpRequestDemo;
import Tools.HttpResponseDemo;
import annotation.RequestMap;

import java.io.IOException;

/**
 * 未找到返回的页面
 *
 * @author 小如
 * @date 2023/08/17
 */
@RequestMap(path = "404")
public class Html404 {
    public void err(HttpRequestDemo request, HttpResponseDemo response) throws IOException {

        String resp= HttpResponseDemo.responsebody+"\n" +
                "\n" +
                "\n" +
                "\n"+
                "    \n" +
                "    404 - Not Found\n" +
                "\n" +
                "\n" +
                "    

404 - Not Found

\n" + "

Sorry, the page you are looking for does not exist.

\n" + "\n" + ""; response.outputStream.write(resp.getBytes()); response.outputStream.flush(); response.outputStream.close(); } }

创建启动类

1.启动类框架


public class start {
    private static ArrayList arr = new ArrayList<>();
    public static HashMap map = new HashMap<>();

2.在启动类中定义方法遍历文件,找到所有的java文件

   /**
     * 寻找带有RequestMap的注解,拿到相应的数据
     *
     * @param file 文件
     */
    private static void func(File file) {
        File[] fs = file.listFiles();
        for (File f : fs) {
            if (f.isDirectory()){    //若是目录,则递归打印该目录下的文件
                func(f);
            }
            if (f.isFile()) {        //若是文件,直接打印
                String filepath = f.toString();
                filepath = filepath.split("src")[1];
                filepath = filepath.substring(1,filepath.length());
                if( filepath.endsWith(".java")) {
                    arr.add(filepath.replace("\\", ".").replace(".java", "").replace("main.",""));
                }

            }
        }
    }

3.在找到的java文件中筛选出含有自定义注解的类,并将path和全类名存入map中

   /**
     * 找到servlet,将信息放入map
     *
     * @throws ClassNotFoundException 类没有发现异常
     */
    public static void choseServlet() throws ClassNotFoundException {
        for(int i = 0; i < arr.size(); i++) {
            String path = arr.get(i);
            Class servletClass = Class.forName(path);
            //System.out.println(servletClass);
            //判断Class对象上是否有RequestMap的注解
            if (servletClass.isAnnotationPresent(RequestMap.class)) {
                //System.out.println("找到了一个Servlet,路径是:" + path);
                //获取SystemConfig注解
                RequestMap config = servletClass.getAnnotation(RequestMap.class);
                //System.out.println("它的请求路径是:" + config.path() );
                map.put( config.path(), servletClass);
            }
        }
    }

以上扫描文件的操作应该在项目启动时就开始处理,当我们发送请求的时候可以直接调用。因此我们将以上两个方法调用放入static块中


    static {
        String inputPath = "F:\\javaDemo\\simple-tomcat2\\src\\servlet";		//要遍历的路径,改成自己的
        File file = new File(inputPath);		//获取其file对象
        func(file);//遍历指定目录下的所有子文件以及子目录下的文件名字
        try {
            choseServlet();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }//  根据注解筛选出servlet并存储到hashmap中
    }

4.socket监听8080端口

 //注册端口
            InetAddress localHost = InetAddress.getLocalHost();
            System.out.println("localhost:" + localHost);
            ServerSocket serverSocket = new ServerSocket(8080, 10, localHost);
            startUp(serverSocket);

5.建立连接,解析http请求

 private static void startUp(ServerSocket serverSocket) throws IOException, InterruptedException, InvocationTargetException, NoSuchMethodException, IllegalAccessException, InstantiationException {
        ExecutorService executorService = new ThreadPoolConfigPlus().executorService();
        System.out.println("等待建立连接");
        for(int i =0;i strings) throws IOException, InterruptedException, InvocationTargetException, NoSuchMethodException, IllegalAccessException, InstantiationException {
                                //处理请求
                                if(!strings.get(0).equals("GET /favicon.ico HTTP/1.1")){
                                    requestHttp(server,strings.get(0));
                                }
                            }
                        });
                        httpAcceptThreadPlus.start();
                        break;
                    }
                }catch (Exception e){
                    // 加锁失败,线程睡眠两秒
                    Thread.sleep(2000);
                }
            }
        }
    }

6.处理http请求,判断是GET还是POST请求,调用相应的servlet方法

 private static void requestHttp(Socket socket,String http) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, IOException, InterruptedException {

        //获取请求方式
        String requestStyle=http.split(" ")[0];

        if(requestStyle.equals("GET")){

            String httpPathAndParameter=http.split(" ")[1];
            String httpPath;

            //创建HttpRequest对象
            HttpRequestDemo httpRequestDemo=new HttpRequestDemo();
            if(httpPathAndParameter.indexOf("?")!=-1){
                httpPath=httpPathAndParameter.substring(1);
                httpPath=httpPath.split("\\?")[0];
                String parameterString=httpPathAndParameter.split("\\?")[1];
                String[] parameters=parameterString.split("&");
                for (int i=0;i servletClass=map.get(httpPath);
                Method method=servletClass.getMethod("doGet",HttpRequestDemo.class,HttpResponseDemo.class);
                method.invoke(servletClass.newInstance(),httpRequestDemo,httpResponseDemo);
            }else {
               httpPath="404";
                Class servletClass=map.get(httpPath);
                Method method=servletClass.getMethod("err",HttpRequestDemo.class,HttpResponseDemo.class);
                method.invoke(servletClass.newInstance(),httpRequestDemo,httpResponseDemo);
                throw new RuntimeException("无效的请求路径!");
            }
        }else{
            String httpPath=http.split(" ")[1];
            httpPath=httpPath.substring(1);

            HttpRequestDemo httpRequestDemo=new HttpRequestDemo();

            OutputStream outputStream=socket.getOutputStream();
            HttpResponseDemo httpResponseDemo=new HttpResponseDemo(outputStream);

            Class servletClass=map.get(httpPath);
            Method method=servletClass.getMethod("doPost",HttpRequestDemo.class,HttpResponseDemo.class);
            method.invoke(servletClass.newInstance(),httpRequestDemo,httpResponseDemo);
        }

    }

完整的启动类如下:


//省略n个包

public class start {
    private static ArrayList arr = new ArrayList<>();
    public static HashMap map = new HashMap<>();


    public static void main(String[] args) throws Exception {
        //启动Socket --->  获取字符串协议数据(HTTP协议 --> requestHttp()  )
        try {
            //注册端口
            InetAddress localHost = InetAddress.getLocalHost();
            System.out.println("localhost:" + localHost);
            ServerSocket serverSocket = new ServerSocket(8080, 10, localHost);
            startUp(serverSocket);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private static void startUp(ServerSocket serverSocket) throws IOException, InterruptedException, InvocationTargetException, NoSuchMethodException, IllegalAccessException, InstantiationException {
        ExecutorService executorService = new ThreadPoolConfigPlus().executorService();
        System.out.println("等待建立连接");
        for(int i =0;i strings) throws IOException, InterruptedException, InvocationTargetException, NoSuchMethodException, IllegalAccessException, InstantiationException {
                                //处理请求
                                if(!strings.get(0).equals("GET /favicon.ico HTTP/1.1")){
                                    requestHttp(server,strings.get(0));
                                }
                            }
                        });
                        httpAcceptThreadPlus.start();
                        break;
                    }
                }catch (Exception e){
                    // 加锁失败,线程睡眠两秒
                    Thread.sleep(2000);
                }
            }
        }
    }


    private static void requestHttp(Socket socket,String http) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, IOException, InterruptedException {

        //获取请求方式
        String requestStyle=http.split(" ")[0];

        if(requestStyle.equals("GET")){

            String httpPathAndParameter=http.split(" ")[1];
            String httpPath;

            //创建HttpRequest对象
            HttpRequestDemo httpRequestDemo=new HttpRequestDemo();
            if(httpPathAndParameter.indexOf("?")!=-1){
                httpPath=httpPathAndParameter.substring(1);
                httpPath=httpPath.split("\\?")[0];
                String parameterString=httpPathAndParameter.split("\\?")[1];
                String[] parameters=parameterString.split("&");
                for (int i=0;i servletClass=map.get(httpPath);
                Method method=servletClass.getMethod("doGet",HttpRequestDemo.class,HttpResponseDemo.class);
                method.invoke(servletClass.newInstance(),httpRequestDemo,httpResponseDemo);
            }else {
               httpPath="404";
                Class servletClass=map.get(httpPath);
                Method method=servletClass.getMethod("err",HttpRequestDemo.class,HttpResponseDemo.class);
                method.invoke(servletClass.newInstance(),httpRequestDemo,httpResponseDemo);
                throw new RuntimeException("无效的请求路径!");
            }
        }else{
            String httpPath=http.split(" ")[1];
            httpPath=httpPath.substring(1);

            HttpRequestDemo httpRequestDemo=new HttpRequestDemo();

            OutputStream outputStream=socket.getOutputStream();
            HttpResponseDemo httpResponseDemo=new HttpResponseDemo(outputStream);

            Class servletClass=map.get(httpPath);
            Method method=servletClass.getMethod("doPost",HttpRequestDemo.class,HttpResponseDemo.class);
            method.invoke(servletClass.newInstance(),httpRequestDemo,httpResponseDemo);
        }

    }

    /**
     * 找到servlet,将信息放入map
     *
     * @throws ClassNotFoundException 类没有发现异常
     */
    public static void choseServlet() throws ClassNotFoundException {
        for(int i = 0; i < arr.size(); i++) {
            String path = arr.get(i);
            Class servletClass = Class.forName(path);
            //System.out.println(servletClass);
            //判断Class对象上是否有RequestMap的注解
            if (servletClass.isAnnotationPresent(RequestMap.class)) {
                //System.out.println("找到了一个Servlet,路径是:" + path);
                //获取SystemConfig注解
                RequestMap config = servletClass.getAnnotation(RequestMap.class);
                //System.out.println("它的请求路径是:" + config.path() );
                map.put( config.path(), servletClass);
            }
        }
    }
    /**
     * 寻找带有RequestMap的注解,拿到相应的数据
     *
     * @param file 文件
     */
    private static void func(File file) {
        File[] fs = file.listFiles();
        for (File f : fs) {
            if (f.isDirectory()){    //若是目录,则递归打印该目录下的文件
                func(f);
            }
            if (f.isFile()) {        //若是文件,直接打印
                String filepath = f.toString();
                filepath = filepath.split("src")[1];
                filepath = filepath.substring(1,filepath.length());
                if( filepath.endsWith(".java")) {
                    arr.add(filepath.replace("\\", ".").replace(".java", "").replace("main.",""));
                }

            }
        }
    }

    static {
        String inputPath = "F:\\javaDemo\\simple-tomcat2\\src\\servlet";		//要遍历的路径,改成自己的
        File file = new File(inputPath);		//获取其file对象
        func(file);//遍历指定目录下的所有子文件以及子目录下的文件名字
        try {
            choseServlet();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }//  根据注解筛选出servlet并存储到hashmap中
    }

}

线程池配置

socket有个弊端,当我们建立一次连接处理完http请求之后,连接断开就会导致项目停止运行,因此我们加入线程池配置,开多个线程去接收建立连接

import java.util.concurrent.*;

import static Tools.config.*;

/**
 * 自定义的线程池配置
 *
 * @author 小如
 * @date 2023/08/17
 */
public class ThreadPoolConfigPlus {
    /**
     * 核心线程数量
     */
    private int corePoolSize=Core_PoolSize;
    /**
     * 最大线程数量
     */
    private int maxPoolSize=Max_PoolSize;
    /**
     * 队列容量
     */
    private int queueCapacity=Queue_Capacity;


    public ExecutorService executorService(){
        //线程工厂
        ThreadFactory factory=new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread=new Thread(r);
                return thread;
            }
        };
        //手动创建线程池
        ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(corePoolSize, maxPoolSize, 10, TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(queueCapacity), factory,
                new ThreadPoolExecutor.CallerRunsPolicy()
        );
        return threadPoolExecutor;
    }
}

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;

/**
 * @author 小如
 */
public class HttpAcceptThreadPlus {
    private Socket socket;
    private ExecutorService executorService;
    private OnFinishListener onFinishListener; // 回调接口

    public interface OnFinishListener {
        void onFinish(List strings) throws IOException, InterruptedException, InvocationTargetException, NoSuchMethodException, IllegalAccessException, InstantiationException;
    }
    public HttpAcceptThreadPlus(Socket server, ExecutorService executorService, OnFinishListener onFinishListener) {
        this.socket = server;
        this.executorService = executorService;
        this.onFinishListener = onFinishListener;
    }
    public void start(){
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    String s = null;
                    ArrayList strings = new ArrayList<>();
                    BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));

                    while ((s = reader.readLine()).length() != 0) {
                        //每次循环接收一行的Http数据
                        try {
                            strings.add(s);
                        } catch (Exception e) {
                            System.out.println("接收Http进程结束");
                            break;
                        }
                    }
                    if(!strings.get(0).equals("GET /favicon.ico HTTP/1.1")){
                        System.out.println(Thread.currentThread().getName()+"连接成功");
                        System.out.println(Thread.currentThread().getName()+"接收Http进程结束,获取的请求为:"+strings.get(0));
                    }
                    onFinishListener.onFinish(strings);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

线程池的配置文件

public class config {
    /**
     * 核心线程数量
     */
    public static final int Core_PoolSize = 8;
    /**
     * 最大线程数量
     */
    public static final int Max_PoolSize = 16;
    /**
     * 队列容量
     */
    public static final int Queue_Capacity =2000;

    public static final int Max_Request_Count = 2000;
}

测试阶段

至此,项目已经完成,启动start,开始测试

仿写一个tomcat(含线程池配置)超详细!!_第3张图片

 浏览器输入请求仿写一个tomcat(含线程池配置)超详细!!_第4张图片

 测试多次请求,观察后台

仿写一个tomcat(含线程池配置)超详细!!_第5张图片

 仿写一个tomcat(含线程池配置)超详细!!_第6张图片仿写一个tomcat(含线程池配置)超详细!!_第7张图片

 发送错误的请求:

仿写一个tomcat(含线程池配置)超详细!!_第8张图片

 后台提示,抛出异常

仿写一个tomcat(含线程池配置)超详细!!_第9张图片

 我已将项目开源至Github

https://github.com/adpanru/easy-tomcat

你可能感兴趣的:(源码仿写,tomcat,java)