应届生找工作、学习必看项目:java实现简易web容器

前言

因为本人是才学java不久,所以学一些基础就直接上手框架,没有用过jsp或者单独的Tomcat进行开发,所以本容器的用法更像是在框架中使用其他组件的用法。如有不足之处请指出。

项目背景以及意义

或许学框架、背面经或多或少的都会知道一些容器处理的请求响应的过程,但是相信更多的人跟博主一样,懂了,但是没完全懂。所以自己简单的写一下,算是走一遍处理流程,让自己更加理解容器或者框架请求响应部分的底层原理。本项目除了学习几乎没有其他价值,看完本项目你将学到容器处理请求响应的原理、http请求响应消息头中常见字段的作用、较为规范化的代码、java的IO流等

项目分析

前面也说过我没有直接使用过tomcat,所以我的设计更多的是参照框架。无论怎么设计,有一点很明确,那就是使用起来不能太复杂,对新手友好。所以我设计的容器只需要这几个步骤:

一、引入依赖

这一步肯定是不可少的,用别人的东西你肯定得引入jar包。

二、实例化一个启动类Server

这里框架中的注解enableXXXX,不过写注解不是本项目的重点,所以都用了其他的处理方式,启动类的构造函数需要接收端口号和ip地址。

三、编写接口

这里跟springboot类似,你可以使用Controller、Service将接口和服务分离。不一样的是没有自动注入,框架的接口只需要@Controller注解标识,一个类中所有的方法都可以处理一个或多个路径。而我的是继承Servlet类,实现doGet和doPost方法,一个类只能处理一个路径。哪个类处理哪个路径需要在web.xml中编写。

四、编写配置文件web.xml

如果不写接口,只用来做静态web服务器,配置类可以不用写,在maven项目中静态资源默认访问路径为resources下的所有文件。如果在配置类中用 /hello
com.xu.Controller.HelloController
这两个标签处理映射关系即可。

更加具体的使用看完本篇文章后相信你就都知道了

项目实现

分析完项目后我们再来设计一下容器大体的架构。

架构设计
一、启动类Server

Server用来绑定服务器ip和端口、监听客户端,启动线程处理客户端。

package com.xu.server;

import com.xu.Utils.CloseIoUtil;
import com.xu.servlet.DispatchServlet;
import com.xu.servlet.Request;
import com.xu.servlet.Response;
import com.xu.servlet.servletImp.HttpRequest;
import com.xu.servlet.servletImp.HttpResponse;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * web容器启动类
 */
@Slf4j
public class Server {

    public static void main(String[] args) {
        new Server(8080, "127.0.0.1");
    }

    /**
     * 容器的构造函数
     *
     * @param port      服务器监听端口
     * @param ipAddress 服务器监听地址
     */
    public Server(int port, String ipAddress) {
        try (ServerSocket server = new ServerSocket(port, 3, InetAddress.getByName(ipAddress))) {
            ExecutorService threadPool = Executors.newFixedThreadPool(5);
            /*
             * 不断监听客户端的请求
             * 有请求进来提交给线程池做相应的处理
             */
            while (true) {
                try {
                    Socket s = server.accept();
                    log.info("有客户端连接");
                    threadPool.execute(new Client(s));
                } catch (Throwable e) {
                    log.error(e.getMessage());
                }
            }
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }

    /**
     * 处理请求的线程类
     */
    private static class Client implements Runnable {
        private Socket s;
        private Response response;
        private Request request;

        public Client(Socket s) {
            this.s = s;
        }

        @Override
        public void run() {
            try {
                request = new HttpRequest(s.getInputStream());
                response = new HttpResponse(s.getOutputStream());
                DispatchServlet.dispatch(request, response);
            } catch (Throwable e) {
                log.error(e.getMessage());
            } finally {
                CloseIoUtil.close(s);
            }

        }
    }
}
二、Request、Response接口

用于解析请求数据和用于返回响应数据。篇幅过长,我就只贴接口的代码。

package com.xu.servlet;

import com.alibaba.fastjson.JSONObject;
import java.util.Map;

public interface Request {

    /**
     * 获取请求的消息头
     */
    Map getHeaders();

    /**
     * 获取请求的方法
     */
    String getMethod();

    /**
     * 获取请求的路径,不包含路径中的参数
     */
    String getPath();

    /**
     * 获取原生请求体的数据
     */
    String getBody();

    /**
     * 获取请求路径中的参数
     */
    Map<String, String> getPathVariables();

    /**
     * 如果请求类型是application/json
     * 获取请求体的json对象
     */
    JSONObject getJsonBody();

    /**
     * 如果请求类型是application/x-www-form-urlencoded
     * 获取请求体的Map对象
     */
    Map<String, String> getUrlEncodeBody();


}
package com.xu.servlet;

import java.io.IOException;

public interface Response {

    /**
     * 将数据返回给前端
     *
     * @param responseContent 返回的数据,数据必须为对象
     * @throws IOException
     */
    void send(Object responseContent) throws IOException;

    /**
     * 设置响应头
     *
     * @param key   响应头的键
     * @param value 响应头的值
     */
    void setHeader(String key, String value);

    /**
     * 设置响应编码
     */
    void setCharset(String charset);

    /**
     * 获取响应编码
     */
    String getCharset();

    /**
     * 设置状态码
     */
    void setStatus(int status);
}
三、DispatchServlet类

用于寻找能处理该请求的类或者资源

package com.xu.servlet;

import com.xu.Utils.ReadConfigFile;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;

/**
 * 请求处理中心,决定该请求由谁处理
 */
@Slf4j
public class DispatchServlet {

    private static HashMap<String, String> map;

    //静态代码块加载配置文件中路径与处理方法的映射关系
    static {
        map = ReadConfigFile.getHashmap("path", "controller");
    }

    public static void dispatch(Request request, Response response) throws Exception {
        //如果有处理方法则调用对应的方法处理,否则找静态资源,
        if (map.containsKey(request.getPath())) {
            String className = map.get(request.getPath());
            Class<Servlet> servletClass = (Class<Servlet>) Class.forName(className);
            Servlet servlet = servletClass.getDeclaredConstructor().newInstance();
            servlet.service(request, response);
        } else {
            StaticResourceController.resourceDispatch(request, response);
        }
    }
}
四、Servlet类

只要实现了该类就把实现类当做后端接口,根据请求的方法调用doGet或者doPost方法。

package com.xu.servlet;

import com.xu.exception.MethodException;
import java.io.IOException;

public abstract class Servlet {
    void service(Request request, Response response) throws IOException {
        if (request.getMethod().equalsIgnoreCase("get")) {
            response.send(doGet(request, response));
        } else if (request.getMethod().equalsIgnoreCase("post")) {
            response.send(doPost(request, response));
        } else {
            throw new MethodException("不支持该方法");
        }
    }

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

    public abstract Object doPost(Request request, Response response);
}

为了文章的篇幅,只贴出了核心代码,完整的目录结构如图

实现思路

架构就是大体的思路了,有了思路才能设计大体的架构,但是为了更便于读者理解我就先写架构在写思路。
首先用socket监听客户端的请求,请求进来后调用一个线程去处理。这个线程应该是实例化一个request用来解析请求数据,然后根据解析出来的路径、方法等通过dispatchServlet寻找能够处理的类,不存在就找静态资源,还是不存在就返回404。找到Servlet实现类后得到返回数据,根据返回类型设置相应的请求头、编码等,最后将数据通过response类将数据返回给客户端。

不足

该项目还存在很多地方尚未优化,比如不能长连接,连接又断开耗费资源。连接采用的是BIO不是NIO等。如果面试过对这些名词和他的概念应该很熟悉,时间比较充裕的大学生可以看懂这个项目并且再往这方面优化,指不定可以惊艳到面试官。毕竟做过和知道是两回事

总结

写了这个项目后发现以前在学框架时不懂的知识都弄懂了不少,果然不能光听光看,听懂看懂再做了一遍之后才是自己的。

最后完整的源码我放在我的资源里面,有需要自行下载,其中包含了一个应用该容器实现返回helloWorld的示例代码。看不懂或者发现有不好的地方欢迎评论区或者私信探讨。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值