tomcat中HttpServlet继承关系与方法实现原理分析
HttpServlet的继承/实现关系
有点乱,但应该能看得懂吧.
这个呢?
service方法实现
HttpServlet类中有两个service方法,先看第一个------>
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取请求的方式----
String method = req.getMethod();
long lastModified;//定义最后一次修改的序列号
//判断请求方式是什么类型,get/post.....
if (method.equals("GET")) { //是get
//获得最后一次修改的序列号
lastModified = this.getLastModified(req);//这个要请求的资源的序列号
//第一次请求会返回 服务器中最后一次修改的序列号 为-1L,
if (lastModified == -1L) {
//处理请求
this.doGet(req, resp);
} else {//否则的话就会判断序列号是否为-1L
long ifModifiedSince;//
try {
//即返回内容有修改才会将资源返回,如果没修改,则不返回资源而是用的浏览器缓存,只返回304响应,可以在浏览器上实验一下
ifModifiedSince = req.getDateHeader("If-Modified-Since");
/* If-Modified-Since 是一个条件式请求首部,
服务器只在所请求的资源在给定的日期时间之后对内容进行过修改的情况下才会将资源返回,状态码为 200 。
如果请求的资源从那时起未经修改,那么返回一个不带有消息主体的 304 响应,
而在 Last-Modified 首部中会带有上次修改时间。
不同于 If-Unmodified-Since, If-Modified-Since 只可以用在 GET 或 HEAD 请求中。*/
} catch (IllegalArgumentException var9) {//有异常处理异常
ifModifiedSince = -1L;
}
/* Last-Modified 是一个响应首部,其中包含源头服务器认定的资源做出修改的日期及时间。
它通常被用作一个验证器来判断接收到的或者存储的资源是否彼此一致。
由于精确度比 ETag 要低,所以这是一个备用机制。
包含有 If-Modified-Since 或 If-Unmodified-Since 首部的条件请求会使用这个字段。
ETag HTTP响应头是资源的特定版本的标识符。这可以让缓存更高效,并节省带宽,因为如果内容没有改变,Web服务器不需要发送完整的响应。
而如果内容发生了变化,使用ETag有助于防止资源的同时更新相互覆盖(“空中碰撞”)。
如果给定URL中的资源更改,则一定要生成新的Etag值。 因此Etags类似于指纹,
也可能被某些服务器用于跟踪。 比较etags能快速确定此资源是否变化,但也可能被跟踪服务器永久存留。*/
//将获得的最后一次修改时间与服务器中修改序列号相比较,小于则返回新的请求资源并设置修改
//如果在获得If-Modified-Since时产生了异常,也会返回新的请求资源
if (ifModifiedSince < lastModified / 1000L * 1000L) {//如果之前请求的资源(iflastModified ) 在服务器中(lastModified)做出了修改,那么处理请求返回新的资源
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);//如果是get方法,则会调用doGet()方法,那么doGet方法会做一些什么呢?--
} else {//否则只返回状态码----304
resp.setStatus(304);
}
}
//处理其他请求方式
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {//如果没有以上的请求方式,则返回501错误提示----不支持这种请求方式即没有此类型的方法被实现
String errMsg = lStrings.getString("http.method_not_implemented");//获取国际上通用的提示解析信息
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
}
再看第二个----->>>>
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
// 将请求和响应强制包装成HttpServletRequest和HttpServletResponse对象
request = (HttpServletRequest)req;
response = (HttpServletResponse)res;
} catch (ClassCastException var6) {
throw new ServletException(lStrings.getString("http.non_http"));
}
//然后调用上面的service方法
this.service(request, response);
}
为什么会有两个呢?
因为第一个是protect 方法,外部不能直接调用,只能在同包内访问,而第二个是public,这样由同类中的 public service调用;
为了便于理解ifModifiedSince 和lastModified,画了一张图
浏览器中的状态
doGet方法
private void sendMethodNotAllowed(HttpServletRequest req, HttpServletResponse resp, String msg) throws IOException {
//获得协议类型
String protocol = req.getProtocol();
//如果协议的长度不为0且协议不是以0.9结尾且协议不是以1.0结尾
//即如果协议为空或者为http/0.9或者http/1.0返回4开头的响应----->>>这三种基本都不用了,现在以http/1.1为主要要应用,也有http/2,0
//他们之间的区别----可以查看下面连接
//https://www.cnblogs.com/wupeixuan/p/8642100.html
if (protocol.length() != 0 && !protocol.endsWith("0.9") && !protocol.endsWith("1.0")) {
resp.sendError(405, msg);//发送响应----405对于请求所标识的资源,不允许使用请求行中所指定的方法
} else {
resp.sendError(400, msg);//无法找到该网页”HTTP
}
}
整体的运行逻辑------
doPost
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取国际上通用的提示解析信息
String msg = lStrings.getString("http.method_post_not_supported");
this.sendMethodNotAllowed(req, resp, msg);
}
private void sendMethodNotAllowed(HttpServletRequest req, HttpServletResponse resp, String msg) throws IOException {
//跟get方式类似
String protocol = req.getProtocol();//获得协议类型
if (protocol.length() != 0 && !protocol.endsWith("0.9") && !protocol.endsWith("1.0")) {
resp.sendError(405, msg);//返回响应405信息
} else {
resp.sendError(400, msg);//返回响应400信息
}
}
其他的请求方式
HEAD、DELETE、OPTIONS、TRACE
不太常用,也不做分析了;
可以发现源码中的doGet和doPost方法只负责发送一些4**信息,其他的活啥也不干;
在我们自定义的Servlet中,如果想区分请求方式,不同的请求方式使用不同的代码处理,那么我么重写 doGet doPost 即可
如果我们没有必要区分请求方式的差异,那么我们直接重写service方法即可 要么重写doGet doPost 要么重写 service,必须二选一,而且必须进行重写;