我是靠谱客的博主 高挑柠檬,最近开发中收集的这篇文章主要介绍tomcat到底是啥?手写一个tomcat带你理解它的机制和原理!手写 minitomcat1.0版本2.0版本3.0多线程改造如何实现多项目部署效果,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

手写 minitomcat

目标:可以通过浏览器客户端发送 http 请求,minicat 可以接受请求并进行处理,处理后的结果可以返回给客户端。

  • 接受请求,socket 通信
  • 请求信息封装成 Request 对象,返回的信息封装成 Response 对象
  • 客户端请求资源,分为静态资源和动态资源
  • 资源返回给浏览器

好吧稍微有点标题党,不过对于理解tomcat的原理机制,这样一个mini版的tomcat自己写出来,还是很有帮助的,话不多说,进入正题。

1.0版本

需求:请求 8080 端口,固定返回字符串。

创建 BootStrap 类
端口号暂时写死 8080,编写 start 方法,监听端口,获取请求,返回资源。
通过 main 方法启动,代码如下:

public class BootStrap {
    //定义 socket 监听端口号(写死)
    private int port = 8080;

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    /**
     * 初始化操作
     */
    public void start() throws IOException {
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("-------->mini-tomcat start on port: " + port);
        while (true){
            Socket accept = serverSocket.accept();
            OutputStream outputStream = accept.getOutputStream();
            outputStream.write("Hello mini-tomcat!".getBytes());
            accept.close();
        }
    }

    //程序入口
    public static void main(String[] args) {
        BootStrap bootStrap = new BootStrap();
        try {
            bootStrap.start();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

什么?太简单了?
但你会发现,此时浏览器访问,并不能实现这个功能,因为返回信息的时候,我们没有按照 http 的规范,浏览器无法识别。
增加一个 httpProtocolUtil 类
封装返回信息

/**
 * http 协议工具类
 * 提供响应头信息。只提供 200 和 404 即可
 */
public class HttpProtocolUtil {
    public static String getHttpHeader200(long contentLength) {
        return "HTTP/1.1 200 OK n" +
                "Content-Type: text/html n" +
                "Content-Lenght: " + contentLength + " n" +
                "rn";
    }

    public static String getHttpHeader404() {
        String str404 = "<h1>404 not found</h1>";
        return "HTTP/1.1 404 NOT Found n" +
                "Content-Type: text/html n" +
                "Content-Lenght: " + str404.getBytes().length + " n" +
                "rn" + str404;
    }
}

再改造之前的 start 方法

while (true){
    Socket accept = serverSocket.accept();
    OutputStream outputStream = accept.getOutputStream();
    String responseText = HttpProtocolUtil.getHttpHeader200("Hello mini-tomcat!".getBytes().length)+"Hello mini-tomcat!";
    outputStream.write(responseText.getBytes());
    accept.close();
}

再次访问 localhost:8080
bingo~成功
在这里插入图片描述
容易吧,不过这只是入门版本而已,太low了,返回的内容都是固定的,看上去好像也没什么技术含量,不着急,2.0版本马上来~

2.0版本

需求:通过发送请求,返回静态资源。
首先。我们先弄清楚,浏览器发请求过来,到底发了一些什么东西?
在后台的逻辑中,先获取请求内容进行控制台打印,看看请求的信息都有是什么,代码如下:

//2.0 版本
while (true){
    Socket accept = serverSocket.accept();
    InputStream inputStream = accept.getInputStream();
    int count = 0;
    while (count == 0){
        count = inputStream.available();
    }
    byte[] bytes = new byte[count];
    inputStream.read(bytes);
    System.out.println("------>请求信息:"+ new String(bytes));

    OutputStream outputStream = accept.getOutputStream();
    accept.close();
}

执行,发送请求 localhost:8080
发现后台打印的是这样:
在这里插入图片描述
这么复杂?其实我们要关注的就只是第一行而已:
在这里插入图片描述
没错,这里的GET代表请求方式是GET请求,第二个斜杠“/”代表的就是访问路径了,因为我们请求地址后边什么都没有,所以这里就只有一个“/”,不信,你可以试试请求localhost:8080/index.html 看看后台打印出来的是不是 /index.html 请自行实验~
所以,接下来,我们对请求信息进行解析,封装成一个我们需要的对象:

Request request = new Request(inputStream);

这样把请求url封装到request对象里,需要的时候取就可以了。
Request类的定义如下

/**
 * 把请求信息封装为Request对象(根据InputSteam输入流封装)
 */
public class Request {

    private String method; // 请求方式,比如GET/POST
    private String url;  // 例如 /,/index.html

    private InputStream inputStream;  // 输入流,其他属性从输入流中解析出来

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

    public InputStream getInputStream() {
        return inputStream;
    }

    public void setInputStream(InputStream inputStream) {
        this.inputStream = inputStream;
    }

    public Request() {
    }


    // 构造器,输入流传入
    public Request(InputStream inputStream) throws IOException {
        this.inputStream = inputStream;

        // 从输入流中获取请求信息
        int count = 0;
        while (count == 0) {
            count = inputStream.available();
        }

        byte[] bytes = new byte[count];
        inputStream.read(bytes);

        String inputStr = new String(bytes);
        // 获取第一行请求头信息
        String firstLineStr = inputStr.split("\n")[0];  // GET / HTTP/1.1

        String[] strings = firstLineStr.split(" ");

        this.method = strings[0];
        this.url = strings[1];

        System.out.println("=====>>method:" + method);
        System.out.println("=====>>url:" + url);

    }
}

其实很简单,就是简单的获取然后进行字符串分割,得到method信息和url信息封装。
获得了要请求的静态资源路径,我们就可以找到这个资源,然后以输出流的方式进行输出。
所以在封装一个response对象,可以根据url进行流的输出:

/**
 * 封装Response对象,需要依赖于OutputStream
 *
 * 该对象需要提供核心方法,输出html
 */
public class Response {

    private OutputStream outputStream;

    public Response() {
    }

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


    // 使用输出流输出指定字符串
    public void output(String content) throws IOException {
        outputStream.write(content.getBytes());
    }


    /**
     *
     * @param path  url,随后要根据url来获取到静态资源的绝对路径,进一步根据绝对路径读取该静态资源文件,最终通过输出流输出
     *              /-----> classes
     */
    public void outputHtml(String path) throws IOException {
        // 获取静态资源文件的绝对路径
        String absoluteResourcePath = StaticResourceUtil.getAbsolutePath(path);

        // 输入静态资源文件
        File file = new File(absoluteResourcePath);
        if(file.exists() && file.isFile()) {
            // 读取静态资源文件,输出静态资源
            StaticResourceUtil.outputStaticResource(new FileInputStream(file),outputStream);
        }else{
            // 输出404
            output(HttpProtocolUtil.getHttpHeader404());
        }

    }

}

这里引入了一个工具类,用来进行静态资源查找,和输出流转换:

public class StaticResourceUtil {

    /**
     * 获取静态资源文件的绝对路径
     * @param path
     * @return
     */
    public static String getAbsolutePath(String path) {
        String absolutePath = StaticResourceUtil.class.getResource("/").getPath();
        return absolutePath.replaceAll("\\","/") + path;
    }


    /**
     * 读取静态资源文件输入流,通过输出流输出
     */
    public static void outputStaticResource(InputStream inputStream, OutputStream outputStream) throws IOException {

        int count = 0;
        while(count == 0) {
            count = inputStream.available();
        }

        int resourceSize = count;
        // 输出http请求头,然后再输出具体内容
        outputStream.write(HttpProtocolUtil.getHttpHeader200(resourceSize).getBytes());

        // 读取内容输出
        long written = 0 ;// 已经读取的内容长度
        int byteSize = 1024; // 计划每次缓冲的长度
        byte[] bytes = new byte[byteSize];

        while(written < resourceSize) {
            if(written  + byteSize > resourceSize) {  // 说明剩余未读取大小不足一个1024长度,那就按真实长度处理
                byteSize = (int) (resourceSize - written);  // 剩余的文件内容长度
                bytes = new byte[byteSize];
            }

            inputStream.read(bytes);
            outputStream.write(bytes);

            outputStream.flush();
            written+=byteSize;
        }
    }
}

到这,2.0版本基本上就完成了,完善一下启动类:

/**
 * 完成Minicat 2.0版本
 * 需求:封装Request和Response对象,返回html静态资源文件
 */
while(true) {
    Socket socket = serverSocket.accept();
    InputStream inputStream = socket.getInputStream();

    // 封装Request对象和Response对象
    Request request = new Request(inputStream);
    Response response = new Response(socket.getOutputStream());

    response.outputHtml(request.getUrl());
    socket.close();

}

这时访问localhost:8080/index.html就会得到resource目录中放的html文件了。
静态资源访问成功了,距离tomcat完全版不远了,差什么?差一个servlet,没错。
如果我们的tomcat可以处理servlet,那么基本上就是完全版的mini-tomcat了。也就是接下来的3.0版本要做的事情。

3.0

需求:增加 servlet,在web.xml里面配置servlet信息,加载servlet类,通过请求servlet来执行servlet里的逻辑。
首先进行分析,在web.xml里面可以自行定义servlet,所以必须要有一个servlet规范,所有的servlet必须满足规范才可以。所以需要编写一个servlet规范。
因为配置信息都写在web.xml里,所以tomcat启动的时候,就应该加载这个配置文件,并且获取到url和servlet对象的关系,并且进行封装存储,当请求到来时,进行判断这个请求是否是servlet请求,如果不是,还需要进行静态页面的处理,也就是还需要走2.0版本的逻辑。
首先定义servlet规范:

public interface Servlet {

    void init() throws Exception;

    void destory() throws Exception;

    void service(Request request,Response response) throws Exception;
}

这个规范和官方的tomcat一样,我们主要用到的是service方法,由于请求分为post和get两种,我们再创建一个抽象类对规范进行完善:

public abstract class HttpServlet implements Servlet{


    public abstract void doGet(Request request,Response response);

    public abstract void doPost(Request request,Response response);


    @Override
    public void service(Request request, Response response) throws Exception {
        if("GET".equalsIgnoreCase(request.getMethod())) {
            doGet(request,response);
        }else{
            doPost(request,response);
        }
    }
}

这样自定义的servlet通过继承HttpServlet ,实现init、destory、doGet、doPost方法,请求到来时,我们就可以通过反射直接调用service方法,根据请求类型不同走不同的方法逻辑。
接下来就是web.xml了,首先看一下这个文件长什么样子:

<?xml version="1.0" encoding="UTF-8" ?>
<web-app>
    <servlet>
        <servlet-name>bys</servlet-name>
        <servlet-class>server.BysServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>bys</servlet-name>
        <url-pattern>/bys</url-pattern>
    </servlet-mapping>
</web-app>

和官方的tomcat配置文件也基本是一样的。我们要从中获取什么样的信息?
首先要获取servlet-name标签中的内容,根据这个name去定位servlet-mapping标签组,然后取出相应的servlet-class和url-pattern,为了还原度高,我特意把配置文件弄成这样,其实在我们自定义的配置文件里,只需要url和class能对应就好了,就算写成这样也是可以的

<web-app>
  <servlet>
    <servlet-url>/bys</servlet-url>
    <servlet-class>server.BysServlet</servlet-class>
  </servlet>
</web-app>

回归正题,有了web.xml文件,在项目启动时就需要加载,解析,还需要保存url和servlet的信息,所以我们完善一下启动类


private Map<String,HttpServlet> servletMap = new HashMap<String,HttpServlet>();

/**
 * 加载解析web.xml,初始化Servlet
 */
private void loadServlet() {
    InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml");
    SAXReader saxReader = new SAXReader();

    try {
        Document document = saxReader.read(resourceAsStream);
        Element rootElement = document.getRootElement();

        List<Element> selectNodes = rootElement.selectNodes("//servlet");
        for (int i = 0; i < selectNodes.size(); i++) {
            Element element =  selectNodes.get(i);
            // <servlet-name>lagou</servlet-name>
            Element servletnameElement = (Element) element.selectSingleNode("servlet-name");
            String servletName = servletnameElement.getStringValue();
            // <servlet-class>server.LagouServlet</servlet-class>
            Element servletclassElement = (Element) element.selectSingleNode("servlet-class");
            String servletClass = servletclassElement.getStringValue();


            // 根据servlet-name的值找到url-pattern
            Element servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
            // /lagou
            String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();
            servletMap.put(urlPattern, (HttpServlet) Class.forName(servletClass).newInstance());

        }

    } catch (DocumentException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }

}

现在就有了维护url和class对象的map,当请求到来时,我们根据url去map中查找,如果查到对应的servlet对象,就执行对应的service逻辑,如果没有找到,则按照静态资源去处理:

while(true) {
    Socket socket = serverSocket.accept();
    InputStream inputStream = socket.getInputStream();

    // 封装Request对象和Response对象
    Request request = new Request(inputStream);
    Response response = new Response(socket.getOutputStream());

    // 静态资源处理
    if(servletMap.get(request.getUrl()) == null) {
        response.outputHtml(request.getUrl());
    }else{
        // 动态资源servlet请求
        HttpServlet httpServlet = servletMap.get(request.getUrl());
        httpServlet.service(request,response);
    }

    socket.close();

}

到这里就完成了3.0版本。
目前的tomcat只是实现了最基本的功能,是经不起折腾的,仅供理解tomcat原理和机制。
比如它还存在着这样的问题,如果第一次请求时间比较长,卡住了,这时来了另一个请求,另一个请求也会卡住,只有第一个请求处理完成,第二个请求才会接收。
所以这里可以引入多线程的概念,让每一次的处理都交给线程,不会阻塞下边的请求。

多线程改造

创建一个线程类,用来处理请求

public class RequestProcessor extends Thread {

    private Socket socket;
    private Map<String,HttpServlet> servletMap;

    public RequestProcessor(Socket socket, Map<String, HttpServlet> servletMap) {
        this.socket = socket;
        this.servletMap = servletMap;
    }

    @Override
    public void run() {
        try{
            InputStream inputStream = socket.getInputStream();

            // 封装Request对象和Response对象
            Request request = new Request(inputStream);
            Response response = new Response(socket.getOutputStream());

            // 静态资源处理
            if(servletMap.get(request.getUrl()) == null) {
                response.outputHtml(request.getUrl());
            }else{
                // 动态资源servlet请求
                HttpServlet httpServlet = servletMap.get(request.getUrl());
                httpServlet.service(request,response);
            }

            socket.close();

        }catch (Exception e) {
            e.printStackTrace();
        }

    }
}

在项目启动类中改用线程

while(true) {
    Socket socket = serverSocket.accept();
    RequestProcessor requestProcessor = new RequestProcessor(socket,servletMap);
    requestProcessor.start();
}

这样就实现了线程池处理请求。每个请求之间不会互相影响。
当然,如果你想更优雅,还可以使用线程池,更好的提升性能:

// 定义一个线程池
int corePoolSize = 10;
int maximumPoolSize =50;
long keepAliveTime = 100L;
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(50);
ThreadFactory threadFactory = Executors.defaultThreadFactory();
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();


ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
        corePoolSize,
        maximumPoolSize,
        keepAliveTime,
        unit,
        workQueue,
        threadFactory,
        handler
);

while(true) {
    Socket socket = serverSocket.accept();
    RequestProcessor requestProcessor = new RequestProcessor(socket,servletMap);
    //requestProcessor.start();
    threadPoolExecutor.execute(requestProcessor);
}

这样就又解决了线程问题。
继续思考,我们目前的tomcat,如果要处理servlet,我们是把servlet类直接写到了项目里,跟随项目代码一起编译和运行,实际上使用tomcat,是把项目打包,放到指定的目录下,启动tomcat会自动去加载我们部署的项目,那么如何实现这种部署项目的效果呢?

如何实现多项目部署效果

想要实现多项目部署,我们需要模拟一个webapp目录,假设我们在tomcat的配置文件中指定,并且把这个配置文件叫做server.xml,在这个配置文件中可以指定tomcat的端口号,以及部署项目的绝对路径。
接下来就是从这个路径下去寻找项目,为了好理解,我们不对项目进行打包,直接以文件夹形式放到webapp目录下,每个项目是一个文件夹,文件夹名称就是项目名,文件中放入class文件、对应项目的web.xml文件以及对应的静态资源文件。
所以下一步就是从webapp目录中解析项目,找到每个项目的web.xml,对每个项目的class文件进行加载,对静态文件的路径也进行解析并存储保存。
当请求到来时,分析请求路径,第一级路径就是项目名,找到对应的项目,后边的路径就是servlet活着静态资源的路径,我们直接去找到对应的资源,进行处理和返回即可。
思路分析完毕,接下来就是实战了:
方便演示和简单理解,静态资源就不再进行演示,我只模拟了两个项目,每个项目分别又不同的web,xml和class文件,如果请求能找到对应的servlet,说明整个流程是通的就可以了。静态资源部分可自行补充完善。
首先是简化版的server.xml,指定端口号,和webapps的绝对路径

<?xml version="1.0" encoding="utf-8" ?>
<server>
    <service>
        <port>8080</port>
        <engine>
            <host>
                <appBase>D:\webapps\</appBase>
            </host>
        </engine>
    </service>
</server>

端口号也可以不写,默认8080,这个在读取配置文件的时候可以很简单进行处理,所以重点就是这个appBase路径,我把server.xml配置文件放到了resource目录,方便解析。
顺便说一下我的模拟部署的项目:我创建了demo1和demo2两个项目
在这里插入图片描述
两个项目结构完全一样:
图片: https://uploader.shimo.im/f/8sXbzMfFsoWUmFPg.png

图片: https://uploader.shimo.im/f/w89hWYTi5JqPH8nL.png

不同的是每个项目中web.xml的配置,这是项目demo1的:
图片: https://uploader.shimo.im/f/3mTLnIL0qMrnrCvc.png

这是demo2的
图片: https://uploader.shimo.im/f/uy2Ak8GM6XYWP9Jh.png

我指定了两个servlet,都是在所在项目的文件夹中,互相之间无影响。因为servlet类在编译之前我指定了包名,所以为了jvm能够正常加载,我在每个项目中根据包名创建了文件夹
图片: https://uploader.shimo.im/f/xuQOrGn77IOSCpBQ.png

这是demo2的
图片: https://uploader.shimo.im/f/hvQW458uDjWH7xUh.png

然后通过反编译看一下这两个servlet的内容:
图片: https://uploader.shimo.im/f/gIsGKONwygLQ7Evs.png

图片: https://uploader.shimo.im/f/XJBNhxLkmyDuvHly.png

为了结果简单易见,两个servlet的返回内容分别代表了不同的项目。
有了这些,下一步做的就是,当tomcat启动,我在浏览器访问 localhost:8080/demo1/bys
返回的应该是demo1 servlet get
访问localhost:8080/demo2/bys 返回的应该是demo2 servlet get。
好了,现在正式开始。
首先对于之前版本中,用于存放url和servlet对象映射关系的map,在这里要进行修改,因为这里存放的不只是一个项目的servlet,而是多个项目,每个项目的url和servlet可能相同,也可能不同,互相之间也没有任何关系,所以单个项目的url和servlet对象的映射关系仍可以用servletMap表示,只不过对于每个不同的项目,都要有一个servletMap,所以这里定一个嵌套的map用来表示不同项目的url-servlet映射关系。所以不妨以项目名作为key,servletMap作为value进行嵌套一层map存储。

private Map<String, Map<String, HttpServlet>> servletMap = new HashMap<String, Map<String, HttpServlet>>();

然后改写是解析配置文件,之前解析web.xml,现在解析server.xml

private void loadServlet() {

    //先读取server.xml 读取端口号,和servlet的根路径。
    InputStream serverStream = this.getClass().getClassLoader().getResourceAsStream("server.xml");
    try {
        Document document = saxReader.read(serverStream);
        Element rootElement = document.getRootElement();
        //获取端口号,如果没设置就按默认的8080
        String portStr = rootElement.selectSingleNode("/server/service/port").getStringValue();
        if (portStr != null && !portStr.equals("")) {
            port = Integer.parseInt(portStr);
        }
        //获取项目绝对根路径
        String appBase = rootElement.selectSingleNode("/server/service/engine/host/appBase").getStringValue();
        // 设置class文件所在根路径
        File clazzPath = new File(appBase);
        File[] classFiles = clazzPath.listFiles();
        for (File classFile : classFiles) {
            if (classFile.isDirectory()) {
                //String name = classFile.getName();
                loadClass(classFile);
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

获取根路径后,把该路径下的所有项目初始化为file对象,通过loadClass方法解析。这里需要注意此处传入的file,是每个项目单独的路径,通过for循环遍历,依次对每个项目文件夹进行处理。此时file已经是具体项目的路径了。
接下来具体看一下loadClass方法:
首先获取file的名称,这里也就是项目名,因为我们需要项目名作为map中的key存放,所以进入方法首先进行获取name,同时定义该项目的servletMap

String name = file.getName();
Map<String, HttpServlet> map = new HashMap<>();

接下来需要引入一个知识点,就是用类加载器加载外部的class文件,java中有一个类叫URLClassLoader,指定目录后,这个类加载器就可以对这个目录下的class文件进行加载,可以得到class对象,从而可以得到class的实例。
因为每个项目的servlet可能相同也可能不同,所以对于每个项目,通过不同的路径构建不同的类加载器,这样可以直接通过每个项目web.xml配置文件中的全限定类名直接找到对应的class对象,所以每个项目都单独使用不同的类加载器进行加载。

URLClassLoader classLoader = new URLClassLoader(new URL[]{file.toURI().toURL()});

接下来就是寻找项目中的web.xml配置文件了,使用递归或者栈的方式都可以,获取到配置文件后,就和之前的解析一样,解析后封装到对应的servletMap,然后同项目名作为key一起存入成员变量map中。
这里直接放入完整的classLoad方法:

//根据根目录去查找所有的servlet并加载
private void loadClass(File file) throws Exception {
    //name = demo1
    String name = file.getName();
    Map<String, HttpServlet> map = new HashMap<>();

    if (file.exists() && file.isDirectory()) {
        // 设置类加载器  D:\webapps\demo1
        URLClassLoader classLoader = new URLClassLoader(new URL[]{file.toURI().toURL()});

        File[] classFiles = file.listFiles(new FileFilter() {
            public boolean accept(File pathname) {
                //只加载xml文件
                return pathname.isDirectory() || pathname.getName().equals("web.xml");
            }
        });
        if (classFiles == null) {
            return;
        }
        for (File subFile : classFiles) {
            if(subFile.isDirectory()){
                continue;
            }
            try {
                //解析web.xml文件
                FileInputStream fis = new FileInputStream(subFile);

                Document document = saxReader.read(fis);
                Element rootElement = document.getRootElement();

                List<Element> selectNodes = rootElement.selectNodes("//servlet");
                for (int i = 0; i < selectNodes.size(); i++) {
                    Element element = selectNodes.get(i);

                    Element servletnameElement = (Element) element.selectSingleNode("servlet-name");
                    String servletName = servletnameElement.getStringValue();

                    Element servletclassElement = (Element) element.selectSingleNode("servlet-class");
                    String servletClass = servletclassElement.getStringValue();

                    // 根据servlet-name的值找到url-pattern
                    Element servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");

                    String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();
                    //servletMap.put(urlPattern, (HttpServlet) Class.forName(servletClass).newInstance());
                    //   /bys    servet1
                    map.put(urlPattern, (HttpServlet) classLoader.loadClass(servletClass).newInstance());
                }

                //路径和servlet进行映射  嵌套了一层map
                servletMap.put(name, map);
            } catch (Exception e) {
                e.printStackTrace();
            }

        }

    }

}

这样就可以把所有项目的servlet分类加载到map中。
接下来需要修改的地方就是解析url了,因为url中会携带项目名,我们还要根据项目名去查找不同的servletMap和静态资源,所以在线程的run方法中,还要做一些修改,就是对request的url进行处理:

String url = request.getUrl();
//   /demo1/bys
//    /demo2/bys
String context = url.split("/")[1];
//  /bys
String substring = url.substring(context.length() + 1);

Map<String, HttpServlet> map = this.servletMap.get(context);

这样就得到了之前的servletMap。
完整的run方法改造后如下:

InputStream inputStream = socket.getInputStream();
Request request = new Request(inputStream);
Response response = new Response(socket.getOutputStream());
String url = request.getUrl();
//   /demo1/bys
//    /demo2/bys
String context = url.split("/")[1];
//  /bys
String substring = url.substring(context.length() + 1);

Map<String, HttpServlet> map = this.servletMap.get(context);
if (map != null) {
    if (map.get(substring) == null) {
        //静态资源
        response.outputHtml(request.getUrl());
    } else {
        HttpServlet httpServlet = map.get(substring);
        httpServlet.service(request, response);
    }
}
socket.close();

到这里,手写的tomcat终极版就大功告成了,自己手动写,对于tomcat的机制理解直接提高不止一个档次,谢谢大家的捧场~

最后

以上就是高挑柠檬为你收集整理的tomcat到底是啥?手写一个tomcat带你理解它的机制和原理!手写 minitomcat1.0版本2.0版本3.0多线程改造如何实现多项目部署效果的全部内容,希望文章能够帮你解决tomcat到底是啥?手写一个tomcat带你理解它的机制和原理!手写 minitomcat1.0版本2.0版本3.0多线程改造如何实现多项目部署效果所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(31)

评论列表共有 0 条评论

立即
投稿
返回
顶部