手写web server: 4-ServletContext

75 阅读2分钟

【声明】文章为本人学习时记录的笔记。 原课程地址:www.liaoxuefeng.com/wiki/154595…

一、前言

ServletContext在Server中代表程序的运行环境,一个web应用,对应一个唯一的ServletContext实例。 主要作用:

1、提供初始化和全局配置

获取Web App配置的初始化参数、资源路径等信息;

2、共享全局数据

存储的数据可以被整个Web App的所有组件读写;

二、目标

实现ServletContext功能,并让ServletContext管理Servlet,处理请求。

三、设计

1、为了让SevletContext对Servlet进行管理,这里对ServletContextImpl增加3个成员变量:servletRegistrationsnameToServletsservletMappings 用于实现对Servlet的管理。

成员作用
Map<String, ServletRegistrationImpl> servletRegistrations注册Servlet
Map<String, Servlet> nameToServlets缓存名称和Servlet的映射关系
List servletMappings将注册过的Servlet进行Mapping缓存

2、增加process()方法来处理请求。 具体的处理逻辑,需要能通过请求的path过滤出来目标Servlet,然后执行该Servlet。

四、实现

整体UML类图如下:

image.png

1、SevletContext中依赖的ServletRegistrationServletMappinguml如下:

ServletRegistration image.png

ServletMapping(继承自AbstractMapping)

image.png

2、实现ServletContext接口,构建自己的ServletContext

image.png

3、ServletContextImpl新增initialize方法,实现对Servlet的注册,和路径映射。

        public void initialize(List<Class<?>> servletClasses) {
        // 注册Servlet
        for (Class<?> c : servletClasses) {
            WebServlet ws = c.getAnnotation(WebServlet.class);
            if (ws != null) {
                logger.info("auto register @WebServlet: {}", c.getName());
                @SuppressWarnings("unchecked")
                Class<? extends Servlet> clazz = (Class<? extends Servlet>) c;
                // 注册到 servletRegistrations 中
                // ServletRegistrationImpl 实现了 ServletRegistration.Dynamic接口
                ServletRegistration.Dynamic registration = this.addServlet(AnnoUtils.getServletName(clazz), clazz);
                // 这里读取@WebServlet(urlPatterns = "/xxx")上的urlPatterns信息
                // 并添加到registration的urlPatterns中去
                registration.addMapping(AnnoUtils.getServletUrlPatterns(clazz));
                registration.setInitParameters(AnnoUtils.getServletInitParams(clazz));
            }
        }

        // 初始化servlets:
        for (String name : this.servletRegistrations.keySet()) {
            var registration = this.servletRegistrations.get(name);
            try {
                registration.servlet.init(registration.getServletConfig());
                this.nameToServlets.put(name, registration.servlet);
                // getMappings() 返回 registration中的 urlPatterns
                // 这里完成对 urlPattern和Servlet的映射保存在ServletContext中
                for (String urlPattern : registration.getMappings()) {
                    this.servletMappings.add(new ServletMapping(urlPattern, registration.servlet));
                }
                registration.initialized = true;
            } catch (ServletException e) {
                logger.error("init servlet failed: " + name + " / " + registration.servlet.getClass().getName(), e);
            }
        }
        // important: sort mappings:
        Collections.sort(this.servletMappings);
    }

在Connector构造函数中,创建ServletContext,调用该initialize()方法来对ServletContext进行初始化操作。将Servlet注册到ServletContext中来,具体实现为遍历应用中的Sevlets,将有@WebServlet注解的进行注册和完成urlPattern的映射。 addServlet()方法:

    @Override
    public ServletRegistration.Dynamic addServlet(String name, Servlet servlet) {
        if (name == null) {
            throw new IllegalArgumentException("name is null.");
        }
        if (servlet == null) {
            throw new IllegalArgumentException("servlet is null.");
        }
        var registration = new ServletRegistrationImpl(this, name, servlet);
        this.servletRegistrations.put(name, registration);
        return registration;
    }

【例】完成注册之后servletRegistrations的内容

image.png

然后对注册进来的Servlet进行 urlParttern进行映射

                for (String urlPattern : registration.getMappings()) {
                    this.servletMappings.add(new ServletMapping(urlPattern, registration.servlet));
                }
                
其中 registration.getMappings()
@Override  
public Collection<String> getMappings() {  
    return this.urlPatterns;  
}
返回了registration成员变量urlPatterns。
// field
final List<String> urlPatterns

最后,对完成映射的ServletMapping进行排序。ServletMapping实现了Comparable接口。

4、实现process()方法,完成对目标Servlet的查找,和执行

    public void process(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        String path = request.getRequestURI();
        // search servlet:
        Servlet servlet = null;
        for (ServletMapping mapping : this.servletMappings) {
            if (mapping.matches(path)) {
                servlet = mapping.servlet;
                break;
            }
        }
        if (servlet == null) {
            // 404 Not Found:
            PrintWriter pw = response.getWriter();
            pw.write("<h1>404 Not Found</h1><p>No mapping for URL: " + path + "</p>");
            pw.close();
            return;
        }
        servlet.service(request, response);
    }