浅析Java源码之HttpServlet
纯粹是闲的,在慕课网看了几集的Servlet入门,刚写了1个小demo,就想看看源码,好在也不难
主要是介绍一下里面的主要方法,真的没什么内容啊~
源码来源于apache-tomcat-7.0.52,servlet-api.jar包
继承树
首先来看一下HttpServlet类的继承关系:
// javax.servlet.http
public abstract class HttpServlet extends GenericServlet implements java.io.Serializable {
//...
} // javax.servlet
public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable {
//...
}
先不看HttpServlet本身,它的父类是GenericServlet,该类主要是对Servlet、ServletConfig两个接口中的部分方法做了简单实现,并没有多少东西。
这里列举一下ServletConfig与Servlet接口中的方法:
ServletConfig
public interface ServletConfig {
// 获取servlet名字
public String getServletName();
// 获取servlet上下文
public ServletContext getServletContext();
// 获取初始化参数列表
public String getInitParameter(String name);
// 获取初始化参数名
public Enumeration getInitParameterNames();
}
Servlet
public interface Servlet {
// servlet初始化方法
public void init(ServletConfig config) throws ServletException;
// 获取配置信息
public ServletConfig getServletConfig();
// 处理请求
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
public String getServletInfo();
// 销毁
public void destroy();
}
从Servlet接口中可以简单看到Servlet的生命周期:constructor、init、service、destroy =>构造、 初始化、处理请求、销毁。
值得注意的是,在GenericServlet中,init、destroy方法都未实现:
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void destroy() {
}
也就是在实际运行中,会根据定义方法来进行初始化与销毁。
接下来就看看HttpServlet本身,这里就不一个一个过,挑一些方法来看:
1、为什么继承类需要重写doGet/doPost
在看视频的时候,讲课老师提到了我们需要override这两个方法,看了源码就明白原因了:
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求协议 => HTTP/1.1
String protocol = req.getProtocol();
// 默认响应返回信息
String msg = lStrings.getString("http.method_get_not_supported");
// 直接返回错误
if (protocol.endsWith("1.1")) {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
} else {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
}
}
其余诸如doPost、doPut方法类似,这里就不贴出来了。
未重写的方法只是简单的获取了请求协议,并根据协议返回一个错误提示信息,所以所有继承的方法都有必要重写对应的响应方法。
2、通用方法service
在请求处理中,内置了一个通用的方法,名为service。
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求方式
String method = req.getMethod();
// 开始匹配响应方法
if (method.equals(METHOD_GET)) {
// 获取请求里lastModified值 默认为-1
long lastModified = getLastModified(req);
if (lastModified == -1) {
// 未处理缓存相关
// 直接响应
doGet(req, resp);
} else {
// 获取请求头中的If-Modified-Since值
// private static final String HEADER_IFMODSINCE = "If-Modified-Since";
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
// 过了缓存期 返回最新资源内容
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
}
// 告知浏览器可以直接从缓存获取
else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_POST)) {
// doPost
}
// 其余请求方法处理
// 报错
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);
}
}
这个方法总的来说只有两部:
1、获取的方法,get?post?
2、匹配对应的响应处理方法,doGet or doPost
这里唯有get响应有一些复杂,主要原因在于所有页面的请求默认是get请求,这涉及到协商缓存问题,详细的可以自己去网上查。
中间有一个maybeSetLastModified是一个检测方法,判断响应头中是否有设置Last-Modified,如下:
private void maybeSetLastModified(HttpServletResponse resp, long lastModified) {
// 如果已有直接返回
if (resp.containsHeader(HEADER_LASTMOD))
return;
// 大于0说明有做处理 设置响应头的Last-Modified
if (lastModified >= 0)
resp.setDateHeader(HEADER_LASTMOD, lastModified);
}
这个比较简单,就不解释了。
另外,注意到上面的service方法权限是protected,其实还有看起来一样的public版本提供了外部访问途径,参数不太一样:
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("non-HTTP request or response");
}
service(request, response);
}
看一下就行了。
3、doTrace
类中还内置了一个特殊方法,可以详细展示了请求的头部信息。
protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
int responseLength;
// 换行
String CRLF = "\r\n";
// 地址 + 协议名
String responseString = "TRACE " + req.getRequestURI() + " " + req.getProtocol();
// 获取所有请求头
Enumeration reqHeaderEnum = req.getHeaderNames();
// 遍历拼接key: value
while (reqHeaderEnum.hasMoreElements()) {
String headerName = (String) reqHeaderEnum.nextElement();
responseString += CRLF + headerName + ": " + req.getHeader(headerName);
}
responseString += CRLF;
responseLength = responseString.length();
// 这个响应类型查都查不到
// 表现形式为下载一个文件 内容为拼接的字符串
resp.setContentType("message/http");
resp.setContentLength(responseLength);
// 内置的输出流 与PrintWriter类似
ServletOutputStream out = resp.getOutputStream();
out.print(responseString);
out.close();
return;
}
这个方法调用后,就不能继续用视频里的out.print输出内容了,如果在doGet中调用此方法,例如:
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doTrace(req,resp);
}
将会下载一个名为servlet的文件:
里面的内容如下:
TRACE /Myservlet/MyServlet/servlet HTTP/1.1
host: localhost:8080
connection: keep-alive
cache-control: max-age=0
upgrade-insecure-requests: 1
user-agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
referer: http://localhost:8080/Myservlet/
accept-encoding: gzip, deflate, br
accept-language: zh-CN,zh;q=0.9
cookie: JSESSIONID=46E569152C4D155D266B790E09604F30
很明显,就是请求头的键值对打印信息。
4、响应头Allow
有一个方法专门设置Allow的响应头,该字段表明可以处理的请求方式。
不过在此之前,需要看一下getAllDeclaredMethods方法,该方法获取继承链(除了根类javax.servlet.http.HttpServlet)上所有类方法:
private static Method[] getAllDeclaredMethods(Class c) {
// 该类为终点
if (c.equals(javax.servlet.http.HttpServlet.class)) {
return null;
}
// 递归获取父类方法
Method[] parentMethods = getAllDeclaredMethods(c.getSuperclass());
// 通过反射获取本类中的方法
Method[] thisMethods = c.getDeclaredMethods();
// 如果父类存在方法 拷贝到数组中
if ((parentMethods != null) && (parentMethods.length > 0)) {
Method[] allMethods = new Method[parentMethods.length + thisMethods.length];
System.arraycopy(parentMethods, 0, allMethods, 0, parentMethods.length);
System.arraycopy(thisMethods, 0, allMethods, parentMethods.length, thisMethods.length);
thisMethods = allMethods;
}
return thisMethods;
}
该方法通过反射机制,获取到本类向上直到HttpServlet类中间的所有方法,用一个Method数组保存起来。
接下来就可以看这个doOptions是如何设置这个头信息的:
protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取methods
Method[] methods = getAllDeclaredMethods(this.getClass());
// 假设请求方法均为false
// trace、options默认为true
boolean ALLOW_GET = false;
boolean ALLOW_HEAD = false;
boolean ALLOW_POST = false;
boolean ALLOW_PUT = false;
boolean ALLOW_DELETE = false;
boolean ALLOW_TRACE = true;
boolean ALLOW_OPTIONS = true;
// 遍历methods
// 如果存在对应的方法名 说明有重写方法处理对应的请求
// getName方法获取对应的字符串
for (int i = 0; i < methods.length; i++) {
Method m = methods[i];
if (m.getName().equals("doGet")) {
ALLOW_GET = true;
ALLOW_HEAD = true;
}
if (m.getName().equals("doPost"))
ALLOW_POST = true;
if (m.getName().equals("doPut"))
ALLOW_PUT = true;
if (m.getName().equals("doDelete"))
ALLOW_DELETE = true;
}
// 进行字符串拼接
String allow = null;
if (ALLOW_GET)
if (allow == null)
allow = METHOD_GET;
// 很多if
// ...
if (ALLOW_TRACE)
if (allow == null)
allow = METHOD_TRACE;
else
allow += ", " + METHOD_TRACE;
if (ALLOW_OPTIONS)
// 这个分支不可能达到的吧……
if (allow == null)
allow = METHOD_OPTIONS;
else
allow += ", " + METHOD_OPTIONS;
// 设置头
resp.setHeader("Allow", allow);
}
}
很简单,遍历methods,有对应的方法,就将对应的控制变量设为true,最后进行拼接,设置响应头Allow。
测试代码如下:
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
PrintWriter out = resp.getWriter();
this.doOptions(req,resp);
out.println("123");
}
打开网页,查看Network中的Headers,可以看到:
基本上讲完了,里面还有两个内部类:NoBodyResponse、NoBodyOutputStream,看起来没什么营养就不看了。
浅析Java源码之HttpServlet的更多相关文章
- 浅析Java源码之ArrayList
面试题经常会问到LinkedList与ArrayList的区别,与其背网上的废话,不如直接撸源码! 文章源码来源于JRE1.8,java.util.ArrayList 既然是浅析,就主要针对该数据结构 ...
- 浅析Java源码之LinkedList
可以骂人吗???辛辛苦苦写了2个多小时搞到凌晨2点,点击保存草稿退回到了登录页面???登录成功草稿没了???喵喵喵???智障!!气! 很厉害,隔了30分钟,我的登录又失效了,草稿再次回滚,不客气了,* ...
- 浅析Java源码之HashMap
写这篇文章还是下了一定决心的,因为这个源码看的头疼得很. 老规矩,源码来源于JRE1.8,java.util.HashMap,不讨论I/O及序列化相关内容. 该数据结构简介:使用了散列码来进行快速搜索 ...
- 浅析Java源码之Math.random()
从零自学java消遣一下,看书有点脑阔疼,不如看看源码!(๑╹◡╹)ノ""" JS中Math调用的都是本地方法,底层全是用C++写的,所以完全无法观察实现过程,Jav ...
- 浅析Java源码之HashMap外传-红黑树Treenode(已鸽)
(这篇文章暂时鸽了,有点理解不能,点进来的小伙伴可以撤了) 刚开始准备在HashMap中直接把红黑树也过了的,结果发现这个类不是一般的麻烦,所以单独开一篇. 由于红黑树之前完全没接触过,所以这篇博客相 ...
- 如何阅读Java源码 阅读java的真实体会
刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心. 说到技术基础,我打个比 ...
- Android反编译(一)之反编译JAVA源码
Android反编译(一) 之反编译JAVA源码 [目录] 1.工具 2.反编译步骤 3.实例 4.装X技巧 1.工具 1).dex反编译JAR工具 dex2jar http://code.go ...
- 如何阅读Java源码
刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动.源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心. 说到技术基础,我打个比方吧, ...
- Java 源码学习线路————_先JDK工具包集合_再core包,也就是String、StringBuffer等_Java IO类库
http://www.iteye.com/topic/1113732 原则网址 Java源码初接触 如果你进行过一年左右的开发,喜欢用eclipse的debug功能.好了,你现在就有阅读源码的技术基础 ...
随机推荐
- day07_雷神_面向对象进阶
day07 1.接口类(抽象类) 接口类和抽象类是一种规范,写代码时的规范. 两个思想: 一个是统一接口,一个是定义规则. 最终版本:接口类,抽象类,是一种规范,写代码时的规范 强制性的规定. fro ...
- poj2828 线段树单点更新
Buy Tickets Time Limit: 4000 MS Memory Limit: 65536 KB 64-bit integer IO format: %I64d , %I64u Java ...
- WinForm企业级框架实战项目演练
一.课程介绍 我们都知道在软件架构方式分为:C/S和B/S两类.这里阿笨不谈论两种软件架构的优劣之分,因为它们各有千秋,用于不同场合.一位伟大的讲师曾经说过一句话:事物存在即合理!录制这堂课程的目的就 ...
- javascript实现OOP编程
1.首先通过一个函数来实现JS的单继承.使用原型方式来实现继承 (function () { $.extend({ oop: { extend: function (child, father) { ...
- 【转】「Chris Richardson 微服务系列」微服务架构的优势与不足
Posted on 2016年5月4日 编者的话|本文来自 Nginx 官方博客,是微服务系列文章的第一篇,主要探讨了传统的单体式应用的不足,以及微服务架构的优势与挑战. 作者介绍:Chris Ric ...
- 【计算机网络】 网络体系结构分类: 客户机/服务器体系和P2P
网络体系结构的分类 现代网络应用程序有两种主流的体系结构: 客户机/服务器体系结构和P2P体系结构(peer to peer “对等”) 一 . 客户机/服务器体系结构 客户机/服务器体系 ...
- react项目开发中遇到的问题
前言 作为一个前端爱好者来说,都想在react上一试生手,那么在搭建react项目开发时,肯定会有这样或者那样的问题,尤其是对初学者来说,下面就个人在开发过程中遇到的问题总结一下,好在有google帮 ...
- 多用户在线人数监听(基于TomCat)
服务器Servlet端 package com.sxt.mvcpro.servlet; import java.io.IOException; import java.util.HashSet; im ...
- linux服务器ssh免密码登录
假设有三台服务器的ip分别是10.9.1.101.10.9.1.102.10.9.1.103 修改hosts文件 分别修改三台服务器的hosts的文件(vi /etc/hosts), 在hosts文件 ...
- iOS-iOS9系统SEGV_ACCERR问题处理【v3.6.3的一些bug修复】
前言 最近APP不断地更新版本,却发现一些未知的错误导致崩溃,我把能测出来的错误,全部修复了,因为项目里集成了腾讯Bugly,看了下后台的崩溃,依旧千篇一律啊,然后就纠结了,很多SEGV_ACCERR ...