转载

图解 & 深入浅出 JavaWeb:Servlet 再说几句

Writer      :BYSocket(泥沙砖瓦浆木匠)

微         博: BYSocket

豆         瓣: BYSocket

FaceBook: BYSocket

Twitter    : BYSocket

上一篇的《 Servlet必会必知 》受到大家一致好评 — ( 感谢 读者 及 OSC 推荐 每日一’搏’) 图解 & 深入浅出 JavaWeb:Servlet 再说几句

后来觉得还有些东西没点到,这边补充补充。

一、回到 HttpServlet 的 service方法

Servlet 基础接口定义了用于客户端请求处理的service方法。 当请求到达Servlet容器,由Servlet容器路由到一个Servlet实例。

比如说javax.servlet.http.HttpServlet类 ,其中有一个protectedvoid 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的protectedvoid service方法 用于接受publicservice接收的标准HTTP请求。

也就是说,HttpServlet重写了父类GenericServlet的service方法。如图显示的是该方法,将从容器获取的ServletRequest和ServletResponse对象强制转化成 用于HTTP处理的HttpServletRequest和HttpServletResponse对象。然后将两个对象路由给了HttpServlet的protectedvoid service方法(图中代码选中处)

图解 &amp; 深入浅出 JavaWeb:Servlet 再说几句

2、然后根据请求的方法名,分发到此类定义的doXXX方法。如果没有被请求到的话,则返回501HTTP 状态码。

这样子仿佛明白了什么,也就是说,如果你在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

图解 &amp; 深入浅出 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的线程安全问题

欢迎点击我的博客及GitHub — 博客提供RSS订阅哦

———- http://www.bysocket.com/ ————- https://github.com/JeffLi1993 ———-

正文到此结束
Loading...