仿写一个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和参数信息

整体项目结构

自定义注解

需要用到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<String, String> 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+"<!DOCTYPE html>\n" +
                "<html>\n" +
                "<head>\n" +
                "    <meta charset=\"utf-8\" />\n" +
                "<link rel=\"icon\" href=\"data:image/ico;base64,aWNv\">\n"+
                "</head>\n" +
                "<body>\n" +
                " \n" +
                "    <form name=\"my_form\" method=\"POST\">\n" +
                "        <input type=\"button\" value=\"按下\" onclick=\"alert('你按下了按钮')\">\n" +
                "    </form>\n" +
                " \n" +
                "</body>\n" +
                "</html>";
        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+"<!DOCTYPE html>\n" +
                "<html>\n" +
                "<head>\n" +
                "<link rel=\"icon\" href=\"data:image/ico;base64,aWNv\">\n"+
                "    <meta charset=\"utf-8\" />\n" +
                "    <title>404 - Not Found</title>\n" +
                "</head>\n" +
                "<body>\n" +
                "    <h1>404 - Not Found</h1>\n" +
                "    <p>Sorry, the page you are looking for does not exist.</p>\n" +
                "</body>\n" +
                "</html>";
        response.outputStream.write(resp.getBytes());
        response.outputStream.flush();
        response.outputStream.close();
    }
}

创建启动类

1.启动类框架


public class start {
    private static ArrayList<String> arr = new ArrayList<>();
    public static HashMap<String, Class> 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<Max_Request_Count;i++){
            Socket server = serverSocket.accept();
            while (true) {
                // 尝试加锁
                try {
                    synchronized (server) {
                        // 获取到锁
                        HttpAcceptThreadPlus httpAcceptThreadPlus = new HttpAcceptThreadPlus(server, executorService,new HttpAcceptThreadPlus.OnFinishListener() {
                            @Override
                            public void onFinish(List<String> 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<parameters.length;i++){
                    httpRequestDemo.map.put(parameters[i].split("=")[0],parameters[i].split("=")[1]);
                }
            }else{
                httpPath=httpPathAndParameter.substring(1);
            }

            //创建HttpResponse对象
            OutputStream outputStream=socket.getOutputStream();
            HttpResponseDemo httpResponseDemo=new HttpResponseDemo(outputStream);

            //反射调用doGet
            if(map.containsKey(httpPath)){
                Class<?> 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<String> arr = new ArrayList<>();
    public static HashMap<String, Class> 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<Max_Request_Count;i++){
            Socket server = serverSocket.accept();
            while (true) {
                // 尝试加锁
                try {
                    synchronized (server) {
                        // 获取到锁
                        HttpAcceptThreadPlus httpAcceptThreadPlus = new HttpAcceptThreadPlus(server, executorService,new HttpAcceptThreadPlus.OnFinishListener() {
                            @Override
                            public void onFinish(List<String> 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<parameters.length;i++){
                    httpRequestDemo.map.put(parameters[i].split("=")[0],parameters[i].split("=")[1]);
                }
            }else{
                httpPath=httpPathAndParameter.substring(1);
            }

            //创建HttpResponse对象
            OutputStream outputStream=socket.getOutputStream();
            HttpResponseDemo httpResponseDemo=new HttpResponseDemo(outputStream);

            //反射调用doGet
            if(map.containsKey(httpPath)){
                Class<?> 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<String> 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<String> 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,开始测试

 浏览器输入请求

 测试多次请求,观察后台

 

 发送错误的请求:

 后台提示,抛出异常

 我已将项目开源至Github

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ADRU

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值