从 Servlet 到 SpringMvc

从 Servlet 到 SpringMvc

下图为 SpringMvc 的 DispatcherServlet 到 Servlet 的继承体系结构,从 HttpServletBean 开始的子类,便属于 Spring 的体系结构,Spring 框架中类似这种以 XXXBean 结尾是用于和其它框架进行整合的 JavaBean 对象,类似还有和 MyBatis 框架进行整合的 SqlSessionFactoryBean。这里只需要关注 HttpServlet 到 Servlet 的这一部分。
HttpServlet 的继承体系

Servlet 接口

public interface Servlet {// 容器调用,且只调用一次public void init(ServletConfig config) throws ServletException;public void destroy();public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException; // <init-param>标签中配置的key-value键值对保存在ServletConfig中public ServletConfig getServletConfig();public String getServletInfo();
}
方法描述
init()容器启动时,被容器调用,并且只调用一次
(当 load-on-startup 为负数时懒加载,即第一次调用 Servlet 时才调用该方法,load-on-startup默认为负数)
destroy()在 Servlet 销毁(一般指关闭服务器)时,用于释放资源,和 init 一样只会调用一次
service()Servlet 对请求的具体处理逻辑
getServletConfig()ServletConfig 是重点

ServletConfig 配置

对应某个 Servlet 的配置,保存 <init-param> 标签中的值,相当于 Map<String, String>,其中一个 <init-param> 代表一个键值对(key-value)。

public interface ServletConfig {//使用@WebServlet注解方式时,默认是全类名String getServletName();// ServletContext代表应用程序,用于各个Servlet之间的参数共享ServletContext getServletContext();// 获取所有的key(迭代器)// Enumeration是遗留类,Iterator的前身Enumeration<String> getInitParameterNames();// 根据key获取valueString getInitParameter(String name);
}
方法描述
getServletName()获取 Servlet 的名字,注解方式下默认使用全类名(@WebServlet)
getServletContext()ServletContext 代表应用程序本身,在 Tomcat 中是ApplicationContextFacade。
在 ServletContext 中的参数被当前应用所有的 Servlet 共享
在 ServletContext 中还可以通过 getContext 方法来获取同主机下其它应用的 ServletContext(需要开启额外设置,默认返回 null)

ServletContext

应用程序本身,不仅包含应用程序级别的配置,还可以由用户自定义用于共享的属性(setAttribute())。
initParameter 和 attribute 包含在两个不同的 Map 中,互不干扰。

GenericServlet

Servlet 本身是一个接口,而对于 XXXServlet 的配置则保存在 ServletConfig 中,因此 GenericServlet 是实现 Servlet(业务功能)和 ServletConfig(配置属性)的一个顶级类。
从设计模式的角度来看,这种设计很经典也很实用,设计思路如下:

  1. Servlet 既是一个接口,在其基础上构建的实现类也必然是整个项目的核心。其主要体现业务逻辑,而不关注配置属性,在 Servlet 类中通过 getServletConfig() 来获取其配置类,从而实现业务(Servlet)和配置(ServletConfig)分离。
  2. 在顶层实现类 GenericServlet 中引入 ServletConfig 配置对象,重写其中的方法,但方法内容都是委托给内部的 ServletConfig 对象,从而将分离的功能重新整合为一体。(如果在 GenericServlet 中不重写,那每次获取一个 Servlet 的配置项,需要先通过该 Servlet 类来获取其配置类,然后才能获取配置项,这样给人感觉就很奇怪。)

总结:针对这种设计模式,可以认为设计思路上最先出现的是 GnericServlet,它才是真正对应一个 标签的类,然后为了进行配置管理,将其中的功能拆分到两个类中(Servlet 和 ServletConfig),并为了连接二者,在 Servlet 的接口中必须添加 getServletConfig() 方法。

public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable { // 从整合两个功能来看,符合适配器模式的设计(假设Servlet和ServletConfig都是类而不是接口的话)private transient ServletConfig config;@Overridepublic String getInitParameter(String name) {// 针对ServletConfig接口中的方法,委托给ServletConfig对象,其它方法都是一样的处理return getServletConfig().getInitParameter(name);}// 如果子类重写该带参方法,那么需要手动设置config,但是由于config是private修饰的,所以需要通过在子类中使用super.init(config)来进行设置@Overridepublic void init(ServletConfig config) throws ServletException {this.config = config;this.init();}// 一般情况下,子类只需要重写无参的init()即可,除非需要对config进行一些额外的操作public void init() throws ServletException {// NOOP by default}
}

如果子类重写 init(ServletConfig) 方法,那么需要手动设置 config,否则 config 为 null。但是由于 config 是 private 修饰的,所以需要通过在子类中使用super.init(config)来进行设置。

HttpServlet(实现 Http 协议的 Servlet)

如果开发一个应用,客户端和服务端需要使用自定义协议,也可以继承 GenericServlet 来实现一个 XXXServlet 用来表示一个实现自定义协议的 Servlet。

public abstract class HttpServlet extends GenericServlet {private static final long serialVersionUID = 1L;private static final String METHOD_DELETE = "DELETE";private static final String METHOD_HEAD = "HEAD";private static final String METHOD_GET = "GET";private static final String METHOD_OPTIONS = "OPTIONS";private static final String METHOD_POST = "POST";private static final String METHOD_PUT = "PUT";private static final String METHOD_TRACE = "TRACE";private static final String HEADER_IFMODSINCE = "If-Modified-Since";private static final String HEADER_LASTMOD = "Last-Modified";private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings";private static final ResourceBundle lStrings = ResourceBundle.getBundle(LSTRING_FILE);private static final List<String> SENSITIVE_HTTP_HEADERS =Arrays.asList("authorization", "cookie", "x-forwarded", "forwarded", "proxy-authorization");// 这个service真正体现出这个类(HttpServlet)是针对HTTP协议而设计的protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String method = req.getMethod();if (method.equals(METHOD_GET)) {long lastModified = getLastModified(req);if (lastModified == -1) {// servlet doesn't support if-modified-since, no reason// to go through further expensive logicdoGet(req, resp);} else {long ifModifiedSince;try {ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); } catch (IllegalArgumentException iae) {// Invalid date header - proceed as if none was setifModifiedSince = -1;}if (ifModifiedSince < (lastModified / 1000 * 1000)) {// If the servlet mod time is later, call doGet()// Round down to the nearest second for a proper compare// A ifModifiedSince of -1 will always be lessmaybeSetLastModified(resp, lastModified);doGet(req, resp);} else {resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);}}} else if (method.equals(METHOD_HEAD)) {long lastModified = getLastModified(req);maybeSetLastModified(resp, lastModified);doHead(req, resp);} else if (method.equals(METHOD_POST)) {doPost(req, resp);} else if (method.equals(METHOD_PUT)) {doPut(req, resp);} else if (method.equals(METHOD_DELETE)) {doDelete(req, resp);} else if (method.equals(METHOD_OPTIONS)) {doOptions(req, resp);} else if (method.equals(METHOD_TRACE)) {doTrace(req, resp);} else {String errMsg = lStrings.getString("http.method_not_implemented");Object[] errArgs = new Object[1];errArgs[0] = method;errMsg = MessageFormat.format(errMsg, errArgs);resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);}}// Servlet不是只能用来实现HTTP协议,而这个service()是Servlet和HTTP的桥梁,是个桥接方法public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {HttpServletRequest request;HttpServletResponse response;try {request = (HttpServletRequest) req;response = (HttpServletResponse) res;} catch (ClassCastException e) {throw new ServletException(lStrings.getString("http.non_http"));}service(request, response);}}

Get、Post、Put、Delete 请求

具体业务逻辑每个 Servlet 都不相同,所以交给子类根据自己的场景去实现,HttpServlet 中直接抛出异常。

//get、post、put、delete这四种请求方式的处理逻辑交给子类实现,这里只是抛出异常(子类使用这些方法的业务逻辑都不相同)
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String msg = lStrings.getString("http.method_get_not_supported");sendMethodNotAllowed(req, resp, msg);
}

Head 请求

本质上还是 Get 请求,只是客户端只需要服务端返回响应消息(Response)的响应头(Response-Head),不需要响应体。

protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if (DispatcherType.INCLUDE.equals(req.getDispatcherType())) { doGet(req, resp);} else {//服务端对response进行处理,相当于response.setBody(null);NoBodyResponse response = new NoBodyResponse(resp);doGet(req, response);// ...}
}

Options 请求

在响应消息中设置了一个 Allow 响应头,表示允许的请求方式。Options 和 Trace 正常情况下不需要使用,主要用于进行一些调试工作,可能存在安全漏洞被黑客利用,最好禁用。

protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Method[] methods = getAllDeclaredMethods(this.getClass());// get、head、post、put、delete、trace、optionsboolean[] ALLOW_METHODS = {false, false, false, false, false, true, true};String[] METHODS = {"GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "OPTIONS"};Class<?> clazz = null;try {clazz = Class.forName("org.apache.catalina.connector.RequestFacade");Method getAllowTrace = clazz.getMethod("getAllowTrace", (Class<?>[]) null);ALLOW_METHODS[5] = (Boolean) getAllowTrace.invoke(req, (Object[]) null);} catch (ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException |IllegalArgumentException | InvocationTargetException ignored) {}for (Method m : methods) {if (m.getName().equals("doGet")) {ALLOW_METHODS[0] = true;ALLOW_METHODS[1] = true;}if (m.getName().equals("doPost")) {ALLOW_METHODS[2] = true;}if (m.getName().equals("doPut")) {ALLOW_METHODS[3] = true;}if (m.getName().equals("doDelete")) {ALLOW_METHODS[4] = true;}}// 源码写得比较恶心,简单改进一下StringBuilder allowBuilder = new StringBuilder();for (int i = 0; i < ALLOW_METHODS.length; i++) {if (ALLOW_METHODS[i]) {allowBuilder.append(", ").append(METHODS[i]);}}resp.setHeader("Allow", allowBuilder.substring(2));
}

Trace 请求

protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {int responseLength;String CRLF = "\r\n";StringBuilder buffer = new StringBuilder("TRACE ").append(req.getRequestURI()).append(' ').append(req.getProtocol());Enumeration<String> reqHeaderNames = req.getHeaderNames();while (reqHeaderNames.hasMoreElements()) {String headerName = reqHeaderNames.nextElement();// RFC 7231, 4.3.8 - skip 'sensitive' headersif (!isSensitiveHeader(headerName)) {Enumeration<String> headerValues = req.getHeaders(headerName);while (headerValues.hasMoreElements()) {String headerValue = headerValues.nextElement();buffer.append(CRLF).append(headerName).append(": ").append(headerValue);}}}buffer.append(CRLF);responseLength = buffer.length();resp.setContentType("message/http");resp.setContentLength(responseLength);ServletOutputStream out = resp.getOutputStream();out.print(buffer.toString());out.close(); 
}

总结

从 HttpServlet 学习构建自定义协议的 Servlet

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/3016784.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

Unity技术学习:渲染大量物体的解决方案,外加RenderMesh、RenderMeshInstanced、RenderMeshIndirect的简单使用

叠甲&#xff1a;本人比较菜&#xff0c;如果哪里不对或者有认知不到的地方&#xff0c;欢迎锐评&#xff08;不玻璃心&#xff09;&#xff01; 导师留了个任务&#xff0c;渲染大量的、移动的物体。 寻找解决方案&#xff1a; 当时找了几个解决方案&#xff1a; 静态批处…

硬件工程师必读:10条职业发展黄金法则!

在快速发展的科技时代&#xff0c;硬件工程师作为推动技术创新和产业升级的重要力量&#xff0c;其职业发展之路既充满挑战也蕴含无限机遇。为了在这条道路上稳步前行&#xff0c;我们首先需要了解硬件产品的研发流程。 在这个过程中&#xff0c;公司内的每个岗位都发挥着不可或…

【Linux】基础命令

常用命令及参数&#xff1a;dir表示文件夹&#xff0c;file表示文件&#xff08;file可表示其他目录下的文件&#xff09; pwd命令&#xff1b;查看当前所属文件夹&#xff08;print working directory&#xff09; ls [选项] dir&#xff1b;查看当前、指定文件夹目录内容&am…

6.移除元素

文章目录 题目简介题目解答解法一&#xff1a;双指针代码&#xff1a;复杂度分析&#xff1a; 解法二&#xff1a;双指针优化代码&#xff1a;复杂度分析&#xff1a; 题目链接 大家好&#xff0c;我是晓星航。今天为大家带来的是 相关的讲解&#xff01;&#x1f600; 题目简…

无卤素产品是什么?有什么作用?

无卤素产品&#xff0c;即在生产过程中完全不使用卤素元素——氟、氯、溴、碘等——的产品。 卤素元素&#xff0c;虽然在电子设备、材料等领域应用广泛&#xff0c;却也可能潜藏危害。其阻燃剂&#xff0c;一旦在产品生命周期结束后释放&#xff0c;将对土壤和水体造成污染&a…

pxe远程安装

PXE 规模化&#xff1a;可以同时装配多台服务器 自动化&#xff1a;自动安装操作系统和各种配置 不需要光盘U盘 前置需要一台PXE服务器 pxe是预启动执行环境&#xff0c;再操作系统之前运行 实验&#xff1a; 首先先关闭防火墙等操作 [rootlocalhost ~]# systemc…

普洱茶泡多少茶叶才算淡茶?

普洱茶淡茶一般放几克茶叶&#xff0c;品深茶官网根据多年专业研究与实践结果&#xff0c;制定了淡茶冲泡标准。在冲泡普洱茶淡茶时&#xff0c;茶叶的投放量是关键因素之一。淡茶冲泡标准旨在保持茶汤的清爽口感&#xff0c;同时充分展现普洱茶的独特风味。 根据《品深淡茶冲…

手动配置dns后网速变慢

之前因为自动的dns能上qq但打不开网页&#xff0c;就手动设置了一个&#xff0c;结果近些天时不时出现网页图片加载慢的问题&#xff0c;影响到我看美女图片了&#xff0c;是可忍熟不可忍 测了下网速&#xff0c;很快&#xff0c;下载上传都是三位数的&#xff0c;那显然不是网…

交易复盘-20240507

仅用于记录当天的市场情况&#xff0c;用于统计交易策略的适用情况&#xff0c;以便程序回测 短线核心&#xff1a;不参与任何级别的调整&#xff0c;采用龙空龙模式 一支股票 10%的时候可以操作&#xff0c; 90%的时间适合空仓等待 蔚蓝生物 (5)|[9:25]|[36187万]|4.86 百合花…

【Qt 学习笔记】Qt常用控件 | 输入类控件 | Date/Time Edit的使用及说明

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt常用控件 | 输入类控件 | Spin Box的使用及说明 文章编号&#xff1…

Quora 首席执行官亚当·德安杰洛 (Adam D’Angelo) 谈论了 AI、聊天机器人平台 Poe,以及 OpenAI 为什么不是竞争对手

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

回归预测 | Matlab实现基于CNN-SE-Attention-ITCN多特征输入回归组合预测算法

回归预测 | Matlab实现基于CNN-SE-Attention-ITCN多特征输入回归组合预测算法 目录 回归预测 | Matlab实现基于CNN-SE-Attention-ITCN多特征输入回归组合预测算法预测效果基本介绍程序设计参考资料 预测效果 基本介绍 【模型简介】CNN-SE_Attention结合了卷积神经网络&#xff…

代码随想录day19day20打卡

二叉树 1 二叉树的最大深度和最小深度 最大深度已经学习过了&#xff0c;实质就是递归的去判断左右子节点的深度&#xff0c;然后对其进行返回。 附加两个学习的部分&#xff1a; &#xff08;1&#xff09;使用前序遍历的方法求解 int result; void getdepth(TreeNode* nod…

Linux\_c输出

第一条Linux_c输出 初界面 : ls # 显示目录下的文件cd # 进入到某个目录 # 比如 我进入了Codels # 发现没有显示, 说明为文件下为空vim cpucdoe.c # 创建一个 .c的源码文件进入到了vim的编辑界面: i # 按i 就可以进行编辑 , 下面显示插入标识在编辑模式下, 可以通…

快速找出存(不存在)在某个(或多个)文件的文件夹

首先&#xff0c;需要用到的这个工具&#xff1a; 度娘网盘 提取码&#xff1a;qwu2 蓝奏云 提取码&#xff1a;2r1z 想要找出有下面这个文件存在的文件夹 切换到批量文件复制版块&#xff0c;快捷键Ctrl5 右侧&#xff0c;搜索添加 选定范围&#xff0c;勾选搜索文件夹、包…

UE5自动生成地形二:自动生成插件

UE5自动生成地形二&#xff1a;自动生成插件 Polycam使用步骤 本篇主要讲解UE5的一些自动生成地形的插件 Polycam 此插件是通过现实的多角度照片自动建模生成地形数据&#xff0c;也是免费的。这里感谢B站up主古道兮峰的分享 Polycam网站 插件下载地址 插件网盘下载 提取码&a…

automa警惕通过点击元素打开新的标签页,因为你可能会被他蒙蔽!

大家好&#xff0c;我是大胡子&#xff0c;专注于研究RPA实战与解决方案。 我们经常用到automa里面的【点击元素】组件&#xff0c;但要警惕通过点击元素打开新的标签页&#xff0c;例如下面这个场景&#xff0c;点击公众号的图文消息&#xff0c;之后&#xff0c;要自动输入标…

QGraphicsView实现简易地图10『自适应窗口大小』

前文链接&#xff1a;QGraphicsView实现简易地图9『层级缩放显示底图』 自适应窗口大小 当地图窗口放大或缩小的时候&#xff0c;需要地图能够动态覆盖整个视口。 1、动态演示效果 2、核心代码 注&#xff1a;WHMapView继承自MapViewvoid WHMapView::resize() {if (m_curLev…

C++变量的作用域与存储类型

一 变量的作用域和存储类型 1 变量的作用域(Scope) 指在源程序中定义变量的位置及其能被读写访问的范围分为局部变量(Local Variable)和全局变量(Global Variable) 1&#xff09;局部变量(Local Variable) 在语句块内定义的变量 形参也是局部变量 特点&#xff1a; 生存期是…

如何从Windows 10电脑远程登录Ubuntu系统

要从Windows 10电脑远程登录Ubuntu系统&#xff0c;您可以使用以下步骤&#xff1a; 在Ubuntu上安装xRDP: 首先&#xff0c;在Ubuntu电脑上打开终端&#xff0c;然后输入以下命令来安装xRDP服务&#xff1a; sudo apt update sudo apt install xrdpxRDP是一个开源的远程桌面协议…