【声明】文章为本人学习时记录的笔记。 原课程地址:www.liaoxuefeng.com/wiki/154595…
一、前言
ServletContext在Server中代表程序的运行环境,一个web应用,对应一个唯一的ServletContext实例。 主要作用:
1、提供初始化和全局配置
获取Web App配置的初始化参数、资源路径等信息;
2、共享全局数据
存储的数据可以被整个Web App的所有组件读写;
二、目标
实现ServletContext功能,并让ServletContext管理Servlet,处理请求。
三、设计
1、为了让SevletContext对Servlet进行管理,这里对ServletContextImpl增加3个成员变量:servletRegistrations、nameToServlets、servletMappings
用于实现对Servlet的管理。
| 成员 | 作用 |
|---|---|
| Map<String, ServletRegistrationImpl> servletRegistrations | 注册Servlet |
| Map<String, Servlet> nameToServlets | 缓存名称和Servlet的映射关系 |
| List servletMappings | 将注册过的Servlet进行Mapping缓存 |
2、增加process()方法来处理请求。 具体的处理逻辑,需要能通过请求的path过滤出来目标Servlet,然后执行该Servlet。
四、实现
整体UML类图如下:
1、SevletContext中依赖的ServletRegistration和ServletMappinguml如下:
ServletRegistration
ServletMapping(继承自AbstractMapping)
2、实现ServletContext接口,构建自己的ServletContext。
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的内容
然后对注册进来的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);
}