一、回到 HttpServlet 的 service方法
Servlet 基础接口定义了用于客户端请求处理的service方法。 当请求到达Servlet容器,由Servlet容器路由到一个 Servlet实例 。
比如说 javax.servlet.http.HttpServlet 类 ,其中有一个 protected void service 方法如下:
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 LSTRING_FILE =
"javax.servlet.http.LocalStrings";
private static ResourceBundle lStrings =
ResourceBundle.getBundle(LSTRING_FILE);
/**
* HTTP状态码304
*/
public static final int SC_NOT_MODIFIED = 304;
/**
* 接收来自 public service方法的标准HTTP请求,
* 并将它们分发给此类中定义的doXXX方法。
*/
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 获取请求方法名
String method = req.getMethod();
// 如果是GET请求
if (method.equals(METHOD_GET)) {
// 上一次修改HttpServletRequest对象的时间
long lastModified = getLastModified(req);
// 没有改变
if (lastModified == -1) {
doGet(req, resp);
} else {
long ifModifiedSince;
try {
// 获取请求头中服务器修改时间
ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
} catch (IllegalArgumentException iae) {
// 获取无效
ifModifiedSince = -1;
}
// 如果请求头服务器修改时间迟
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
// 设置修改HttpServletResponse对象的时间,重新设置浏览器的参数
//maybeSetLastModified(resp, lastModified);
// 调用doGet方法
doGet(req, resp);
} else {
// 304 HTTP状态码
resp.setStatus(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);
// 501 HTTP状态码
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
代码逻辑详解如下:
1、HttpServlet的 protected void service方法 用于接受 public service接收的 标准HTTP请求 。
也就是说,HttpServlet 重写了父类 GenericServlet 的 service方法。如图显示的是该方法,将从容器获取的 ServletRequest 和 ServletResponse 对象强制转化成 用于HTTP处理的 HttpServletRequest 和 HttpServletResponse 对象。然后将两个对象路由给了 HttpServlet的 protected void service方法(图中代码选中处)
2、然后根据请求的 方法名 ,分发到此类定义的doXXX方法。如果没有被请求到的话,则返回501 HTTP 状态码。
这样子仿佛明白了什么,也就是说,如果你在 HelloServlet中重写了doGet方法,这里分发到就是HttpServlet的子类HelloServlet的doGet方法。
哦~ 还有,501 HTTP 状态码 — 未实现(Not implemented)表示服务器不支持实现请求所需要的功能 。例如,客户发出了一个服务器不支持的PUT请求。原来如此,所谓死记硬背这些HTTP 状态码有什么用?这样的记忆才是最有效的。
休息休息,小广告插一下 :(维持生计,O(∩_∩)O~)
涉及到的代码都会在开源项目 servlet-core-learning 。简介 — Servlet/JSP学习积累的例子,是Java EE初学者及Servlet/JSP核心技术巩固的最佳实践
大致就是这两步骤。这就是service的 工作流程 :
1、接受 public service接收的标准HTTP请求。
2、分发到定义的doXXX方法
二、GET 请求的处理详解
上面对于GET请求代码处理如下:
// 如果是GET请求
if (method.equals(METHOD_GET)) {
// 上一次修改HttpServletRequest对象的时间
long lastModified = getLastModified(req);
// 没有改变
if (lastModified == -1) {
doGet(req, resp);
} else {
long ifModifiedSince;
try {
// 获取请求头中服务器修改时间
ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
} catch (IllegalArgumentException iae) {
// 获取无效
ifModifiedSince = -1;
}
// 如果请求头服务器修改时间迟
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
// 设置修改HttpServletResponse对象的时间,重新设置浏览器的参数
//maybeSetLastModified(resp, lastModified);
// 调用doGet方法
doGet(req, resp);
} else {
// 304 HTTP状态码
resp.setStatus(SC_NOT_MODIFIED);
}
}
}
这里,
1、定义了 getLastModified (req) 方法。用于获取上一次修改HttpServletRequest对象的 时间 。如果lastModified为默认的 –1L ,则总是 刷新 。
这个getLastModified,是HttpServlet定义了用于支持有条件GET操作。即当客户端通过GET请求获取资源时,当资源自第一次获取那个实际点发生更改后才再次发生数据,否则将使用客户端缓存的数据。
在一些适当的场合,实现此方法可以更有效的利用网络资源,减少不必要的数据发送。
2、如果getLastModified方法的返回值是一个 正数 ,那就要分以下两种情况考虑:
(1)如果请求头**没有**包含 **If-Modified-Since头字段** (应该是**第一次访问资源**时候) 或者 其**getLastModified**返回值比**If-Modified-Since头字段**指定时间 **新** ,则调用**doGet**返回生成 **response** 和 **设置Last-Modified 消息头** 。
(2)如果其**getLastModified**返回值比**If-Modified-Since头字段**指定时间 **旧** ,则返回一个**304**状态给客户端,表示 **让客户端继续使用以前缓存的页面** 。
比如说 304 这个场景我在《JavaEE 要懂的小事:一、图解Http协议 》文章中提到,第一次访问 百度 首页时,有些资源会成功获取 返回 200 。 再次F5 ,有些资源或直接调用客户端的缓存数据,则返回 304 。
图解 & 深入浅出 JavaWeb:Servlet 再说几句
三、Servlet线程问题
Servlet容器可以并发路由多个请求到 Servlet 的 service方法。为了处理这些请求,Servlet必须在并发及线程安全问题做好处理。上一篇的《Servlet必会必知》提到定义 全局变量会造成线程安全问题 。在开发Servlet时,考虑线程安全问题提出了一下 解决 :
1、实现 SingleThreadModel 接口
Servlet2.4 已经提出不提倡使用 。实现此接口,Servlet容器为每个新的请求创建一个单独的Servlet实例。这会有 严重性能问题 。
2、同步锁
使用synchronized关键字,虽然可以保证只有一个线程可以访问被保护区段,已达到保证线程安全。但是系统性能及并发量大大降低。不可取~
3、避免使用实例变量,即Servlet中全局变量。使用局部变量 (推荐)
方法中的局部变量分配在栈空间,每个线程有私有的 栈空间 。因此访问是线程安全的。
我想到了以下一个问题:
既然Sevlet的全局变量是线程不安全的,那SpringMVC Controller 也一样。那我们在Controller定义个 XXXService 变量会不会造成线程安全呢?
答:因为这是Spring的一个Service Bean,是线程安全的,所以可以作为单例使用,不会造成线程安全。
四、总结(别忘了点赞哦)
补充文章内容要点:
HttpServlet service 方法详解
深入理解 代码 对HTTP状态码的运用
Servlet的线程安全问题