手写tomcat(Ⅲ)——tomcat动态资源的获取

仿写tomcat的Servlet接口体系

之前写过一篇博客,Tomcat的Servlet-GenericServlet-HttpServlet体系的具体结构,以及Servlet的生命周期
Servlet讲解

想要模仿tomcat获取动态资源,就需要我们自己仿写一个Servlet接口体系

主要包括:注解,相关的接口+子类,搜索包名下的所有servlet的工具类

MyWebServlet注解

我们首先需要定义一个MyWebServlet注解,仿照了WebServlet注解

import java.lang.annotation.*;

@Target({ElementType.TYPE})// 作用到类
@Retention(RetentionPolicy.RUNTIME)// 运行阶段
public @interface MyWebServlet {

    // 定义url变量,并规定默认值为空字符

    String className() default "";

    String url() default "";
}

相关的接口+子类

MyServlet接口,定义了Servlet的生命周期

import java.io.IOException;

import com.qcby.config.MyServletConfig;

// 自定义的servlet接口
public interface MyServlet {

    void init();

    MyServletConfig getServletConfig();

    String getServletInfo();

    void service(Request request, Response response) throws IOException;

    void destroy();
}

自定义的MyServletConfig类

/**
 * 将获取到的url和classPath径封装到该对象当中
 */
public class MyServletConfig {
    private String url;

    private String classPath;

    public MyServletConfig(String url, String classPath) {
        this.url = url;
        this.classPath = classPath;
    }

    public MyServletConfig() {
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getClassPath() {
        return classPath;
    }

    public void setClassPath(String classPath) {
        this.classPath = classPath;
    }
}

抽象类MyGenericServlet实现了除service以外的方法

import com.qcby.config.MyServletConfig;

public abstract class MyGenericServlet implements MyServlet {


    @Override
    public void init() {

    }

    @Override
    public MyServletConfig getServletConfig() {
        return new MyServletConfig();
    }

    @Override
    public String getServletInfo() {
        return "";
    }

    @Override
    public void destroy() {

    }
}

抽象类MyHttpServlet,实现了service方法,并将其分化为doGet,doPost,doDelete等方法

import java.io.IOException;

public abstract class MyHttpServlet extends MyGenericServlet {
    // 重写service方法,将其分化为不同的方法
    // 抽象类MyHttpServlet没有抽象方法
    @Override
    public void service(Request request, Response response) throws IOException {
        // 判断request的methodType,转发到doGet/doPost当中
        if ("GET".equals(request.getMethodType())) {
            myDoGet(request, response);
        } else if ("POST".equals(request.getMethodType())) {
            myDoPost(request, response);
        }
    }

    public void myDoGet(MyHttpServletRequest req, MyHttpServletResponse resp) throws IOException {

    }

    public void myDoPost(MyHttpServletRequest req, MyHttpServletResponse resp) throws IOException {

    }

}

这里的MyHttpServletRequest还有MyHttpServletResponse,是两个接口


public interface MyHttpServletRequest {
}

public interface MyHttpServletResponse {

    void write(String context) throws IOException;
}

分别由Request类和Response类实现

public class Request implements MyHttpServletRequest{

    private String url;

    private String methodType;

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getMethodType() {
        return methodType;
    }

    public void setMethodType(String methodType) {
        this.methodType = methodType;
    }
}

/**
 * 做数据的返回
 */
public class Response implements MyHttpServletResponse {

    // 获取输出流
    private OutputStream outputStream;


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

    // 静态资源的输出

    public void writeHtml(String path) throws Exception {
        //
        // 根据路径返回资源路径地址,例如http://localhost:8666/index.html
        String resource = FileUtil.getResoucePath(path);
        File file = new File(resource);
        if (file.exists()) {
            // 静态资源存在!
            System.out.println("静态资源存在!");
            FileUtil.writeFile(file, outputStream);
        } else {
            System.out.println(path + "对应的该静态资源不存在!");
        }

    }

    // 数据写回
    public void write(String context) throws IOException {
        outputStream.write(context.getBytes());
        outputStream.flush();
    }
}

加载servlet包下的所有servlet,并将其MyWebServlet注解的url值和对应的servlet类对象组装成一个map(最重要)

上面基本都是照猫画葫芦的仿写,其实该实现的方法完全没实现,跟原本的tomcat相比差的比较多

但是这一部分,还原度比较高,tomcat本身也是扫描包,并且反射获取注解的取值,最后组成HashMap来映射servlet的

SearchClassUtil

/**
 * 扫描指定包,获取该包下所有的类的全路径信息
 */
public class SearchClassUtil {
    public static List<String> classPaths = new ArrayList<String>();

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

tomcat启动类

对静态资源和动态资源做了判断

package com.qcby.tomcatDemo;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URLDecoder;

import com.qcby.config.ServletConfigMapping;
import com.qcby.servlet.MyServlet;
import com.qcby.servlet.Request;
import com.qcby.servlet.Response;
import com.qcby.util.ResponseUtil;

/**
 * tomcat启动类
 */
public class TomcatStart {

    private static Request request = new Request();

    public static void main(String[] args) throws IOException, ClassNotFoundException {

        System.out.println("socket服务器启动!!!");
        // 0. 加载servlet类信息
        ServletConfigMapping.init();

        // 1. 打开相关通信端口
        // tomcat:8080,mysql:3306,应用软件独占一个端口的全部信息

        ServerSocket serverSocket = new ServerSocket(8666);
        // 线程持续扫描当前网卡xxxx端口(死循环),如果有数据就拿过来,交给端口对应的程序处理
        // 2. 监听并接收请求数据
        while (true) {
            // 一旦发现有数据,就打开socket通信
            // 这里没有创建新的线程,所以这里是main线程监听数据
            Socket socket = serverSocket.accept();
            System.out.println(socket.getInetAddress().getCanonicalHostName() + "进行了连接!");

            // 第二步监听并接收到了数据,处理数据可以用主线程,但是没必要,创建子线程处理
            // 每接收一次数据,创建一个子线程
            Thread t1 = new Thread(() -> {
                // 处理数据包括两部分:读和写
                try {
                    dataHandle(socket);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }

            });
            t1.start();
        }
    }

    // 处理数据的方法,读+写
    public static void dataHandle(Socket socket) throws Exception {
        // 1. 读取请求的数据
        // 1.1打开输入流对象,读取socket对象中的数据,这里的数据都是0101010的二进制数据
        InputStream inputStream = socket.getInputStream();
        requestContext(inputStream);

        // 数据的输出
        Response response = new Response(socket.getOutputStream());
        if ("".equals(request.getUrl())) {
            response.write(ResponseUtil._404ResponseHeader);
        } else if (ServletConfigMapping.classMap.get(request.getUrl()) != null) {
        	// 动态web资源
            dispatch(request, response);
        } else {
            // 访问静态资源
            response.writeHtml(request.getUrl());
        }
    }

    public static void dispatch(Request request, Response response) throws InstantiationException, IllegalAccessException, IOException {
        Class<MyServlet> servletClass = ServletConfigMapping.classMap.get(request.getUrl());
        if (servletClass != null) {
            MyServlet myServlet = servletClass.newInstance();
            myServlet.service(request, response);
        }
    }

    public static void requestContext(InputStream inputStream) throws IOException {

        //  1.2 二进制数据的翻译并读取
        int count = 0;
        while (count == 0) {
            // 返回此输入流下一个方法调用可以不受阻塞地从此输入流读取(或跳过)的估计字节数。
            // 下一个调用可能是同一个线程,也可能是另一个线程。
            // 一次读取或跳过此估计数个字节不会受阻塞,但读取或跳过的字节数可能小于该数。


            // 可以不受阻塞地从此输入流读取(或跳过)的估计字节数;如果到达输入流末尾,则返回 0
            count = inputStream.available();
        }
        byte[] bytes = new byte[count];

        inputStream.read(bytes);

        // 这里用URLDecoder是为了防止路径中出现特殊符号,经过get请求之后会被URLEncode为乱码
        String context = URLDecoder.decode(new String(bytes, "utf-8"));// Base64.getDecoder().decode(bytes)
        System.out.println("===context:" + context);

        if ("".equals(context)) {
            System.out.println("null request!");
            request.setUrl("");
            request.setMethodType("");
        } else {
            //根据换行来获取第一行数据
            String firstLine = context.split("\\n")[0];
            // 第一行数据的第2个字符串
            System.out.println("===url:" + firstLine.split("\\s")[1]);
            request.setUrl(firstLine.split("\\s")[1]);
            // 第一行数据的第1个字符串
            System.out.println("===methodType:" + firstLine.split("\\s")[0]);
            request.setMethodType(firstLine.split("\\s")[0]);
        }
    }
}

webapp包下有两个servlet
在这里插入图片描述

@MyWebServlet(url = "/first")
public class FirstServlet extends MyHttpServlet {

    @Override
    public void myDoGet(MyHttpServletRequest req, MyHttpServletResponse resp) throws IOException {
        String context = "<h1>这是FirstServlet的myDoGet方法</h1>";

        resp.write(ResponseUtil.makeResponse(context, ResponseUtil.htmlResponseHeader));
    }

    @Override
    public void myDoPost(MyHttpServletRequest req, MyHttpServletResponse resp) throws IOException {
        String context = "<h1>这是FirstServlet的myDoPost方法</h1>";
        resp.write(ResponseUtil.makeResponse(context, ResponseUtil.htmlResponseHeader));
    }
}
@MyWebServlet(url = "/hello")
public class HelloServlet extends MyHttpServlet {
    @Override
    public void myDoGet(MyHttpServletRequest req, MyHttpServletResponse resp) throws IOException {
        String context = "<h1>这是HelloServlet的myDoGet方法</h1>";
        resp.write(ResponseUtil.makeResponse(context, ResponseUtil.htmlResponseHeader));
    }

    @Override
    public void myDoPost(MyHttpServletRequest req, MyHttpServletResponse resp) throws IOException {
        String context = "<h1>这是HelloServlet的myDoPost方法</h1>";
        resp.write(ResponseUtil.makeResponse(context, ResponseUtil.htmlResponseHeader));
    }
}

项目下还有一个静态资源index.html
在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <p>Hello TomcatDemo!!!</p>
</body>
</html>

我们在浏览器分别测试静态和动态资源的获取

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值