JAVA Servlet工作原理

导读

本文来自stackoverflow的问答,讨论了Java
Servlet的工作机制,如何进行实例化、共享变量和多线程处理。

servlet容器加载顺序

  1. 当 Servlet 容器(比如 Apache Tomcat )启动后,会部署和加载所有 web
    应用。当web 应用被加载,Servlet 容器会创建一次
    ServletContext,然后将其保存在服务器的内存中。
  2. web 应用的 web.xml 被解析,找到其中所有 servlet 、 filter 和
    Listener 或 @WebServlet 、 @WebFilter 和 @WebListener
    注解的内容,创建一次并保存到服务器的内存中。
  3. 对于所有过滤器会立即调用init() 。
  4. 当Servlet配置的 load-on-startup 或者
    @WebServlet(loadOnStartup) 设置了一个大于 0
    的值,则同样会在启动的时候立即调用 init()
    方法。“load-on-startup”中的值表示那些 Servlet
    会以相同顺序初始化。如果配置的值相同,会遵循 web.xml 中指定的顺序或
    @WebServlet 类加载的顺序。另外,如果不设置 “load-on-startup
    值, init() 方法只在第一次 HTTP 请求命中问题中的 Servlet
    时才被调用。
  5. 当 Servlet 容器停止,将卸载所有 web 应用,调用所有初始化的 Servlet
    和过滤器的 destroy() 方法,最后回收 ServletContext和所有
    Servlet 、Filter 与 Listener 实例。

一、基础知识
学习Servlet技术,首先需要有一个Servlet运行环境,也就是需要有一个Servlet容器,本书采用的是Tomcat。
Tomcat是一个免费的开放源代码的Servlet容器,它是Apache软件基金会(Apache
Software
Foundation)的一个顶级项目,由Apache、Sun和其他一些公司及个人共同开发而成。由于有了Sun的参与和支持,最新的Servlet和JSP规范总是能在Tomcat中得到体现,Tomcat
6支持最新的Servlet 2.5和JSP
2.1规范。因为Tomcat技术先进、性能稳定,而且免费,因而深受Java爱好者的喜爱,并得到了部分软件开发商的认可,成为目前比较流行的Web服务器。
Tomcat和IIS、Apache等Web服务器一样,具有处理HTML页面的功能,另外它还是一个Servlet和JSP容器,独立的Servlet容器是Tomcat的默认模式。不过,Tomcat处理静态HTML的能力不如Apache,我们可以将Apache和Tomcat集成在一起使用,Apache作为HTTP
Web服务器,Tomcat作为Web容器。
下面给出Tomcat服务器接受客户请求并做出响应的图例,如图1-4所示。

问题:Servlet是如何工作的?Servlet 如何实例化、共享变量、并进行多线程处理?

假设我有一个运行了大量 Servlet 的 web 服务器。通过 Servlet
之间传输信息得到 Servlet 上下文,并设置 session 变量。

现在,如果有两名或更多使用者向这个服务发送请求,接下来 session
变量会发生什么变化?究竟是所有用户都是用共同的变量?还是不同的用户使用的变量都不一样?如果是后者,服务器如何区分不同用户?

另一个相似的问题,如果有 *n* 名用户访问一个特定的 Servlet,那么该
Servlet
是仅在第一个用户首次访问的时候实例化,还是分别为每个用户实例化?

HttpServletRequest 与 HttpServletResponse

Servlet 容器附加在一个 web 服务上,这个 web 服务会在某个端口号上监听
HTTP 请求,在开发环境中这个端口通常为 8080,生产环境中通常为
80。当客户端(web 浏览器)发送了一个 HTTP 请求,Servlet 容器会创建新的
HttpServletRequestHttpServletResponse
对象,传递给已创建好并且请求的 URL 匹配 url-pattern 的 Filter 和 Servlet
实例中的方法,所有工作都在同一个线程中处理。

request 对象可以访问所有该 HTTP 请求中的信息,例如 request header 和
request body。response 对象为你提供需要的控制和发送 HTTP
响应方法,例如设置 header 和 body(通常会带有 JSP 文件中的 HTML
内容)。提交并完成HTTP 响应后,将回收 request 和 response 对象。

![EN092XC$MLWR`KF7)76UR4.png

回答(BalusC):

HttpSession

当用户第一次访问该 web 应用时,会通过 request.getSession() 第一次获得
HttpSession澳门新葡萄京官网首页, 。之后 Servlet 容器将会创建 HttpSession
,生成一个唯一的 ID(可以通过 session.getId()
获取)并储存在服务器内存中。然后 Servlet 容器在该次 HTTP 响应的
Set-Cookie 头部设置一个 Cookie ,以 JSESSIONID 作为 Cookie
名字,那个唯一的 session ID 作为 Cookie 的值。

按照 HTTP cookie 规则 (正常 web 浏览器和 web 服务端必须遵循的标准),当
cookie 有效时,要求客户端(浏览器)在后续请求的 Cookie 头中返回这个
cookie。使用浏览器内置的 HTTP 流量监控器可以查看它们(在
Chrome、Firefox23+、IE9+ 中按 F12,然后查看 Net/Network 标签)。Servlet
容器将会确定每个进入的 HTTP 请求的 Cookie 头中是否存在名为 JSESSIONID 的
cookie,然后用它的值(session ID)从服务端内存中找到关联的 HttpSession

在 web.xml 中设置 session-timeout ,默认值为 30 分钟。超时到达之前
HttpSession 会一直存活。所以当客户端不再访问该 web 应用超过 30
分钟后,Servlet 容器就会回收这个 session。后续每个请求,即使指定 cookie
名称也不能再访问到相同的 session。Servlet 容器会创建一个新的 Cookie 。

另一方面,客户端上的 session cookie
有一个默认存活时间,该事件和该浏览器实例运行时间一样长。所以,当客户端关闭该浏览器实例(所有标签和窗口)后,这个
session 就会被客户端回收。新浏览器实例不再发送与该 session 关联的
cookie。一个新的 request.getSession() 将会返回新的 HttpSession
并设置一个拥有新 session ID 的 cookie。

  • 客户端(通常都是浏览器)访问Web服务器,发送HTTP请求。
  • Web服务器接收到请求后,传递给Servlet容器。
  • Servlet容器加载Servlet,产生Servlet实例后,向其传递表示请求和响应的对象。
  • Servlet实例使用请求对象得到客户端的请求信息,然后进行相应的处理。
  • Servlet实例将处理结果通过响应对象发送回客户端,容器负责确保响应正确送出,同时将控制返回给Web服务器。

ServletContext

当 Servlet 容器(比如 Apache Tomcat)启动后,会部署和加载所有 web
应用。当web 应用被加载,Servlet 容器会创建一次
ServletContext,然后将其保存在服务器的内存中。web 应用的 web.xml
被解析,找到其中所有 servletfilterListener
@WebServlet@WebFilter@WebListener
注解的内容,创建一次并保存到服务器的内存中。对于所有过滤器会立即调用
init()。当 Servlet 容器停止,将卸载所有 web 应用,调用所有初始化的
Servlet 和过滤器的 destroy() 方法,最后回收 ServletContext 和所有
Servlet、Filter 与 Listener 实例。

当问题中的 Servlet 配置的 load-on-startup 或者
@WebServlet(loadOnStartup) 设置了一个大于 0
的值,则同样会在启动的时候立即调用 init()
方法。“load-on-startup”中的值表示那些 Servlet
会以相同顺序初始化。如果配置的值相同,会遵循 web.xml 中指定的顺序或
@WebServlet 类加载的顺序。另外,如果不设置 “load-on-startup”
值,init() 方法只在第一次 HTTP 请求命中问题中的 Servlet 时才被调用。

概述

  1. ServletContext 与 web 应用存活时间一样长。它被所有 session
    中的所有请求共享。
  2. 只要客户端一直与相同浏览器实例的web应用交互并且没有超时,HttpSession就会存在。
  3. HttpServletRequestHttpServletResponse
    的存活时间为客户端发送完成到完整的响应(web
    页面)到达的这段时间。不会被其他地方共享。
  4. 所有 Servlet 、 Filter 和 Listener 对象在 web
    应用运行时都是活跃的。它们被所有 session 中的请求共享。
  5. 设置在 HttpServletRequestHttpServletResponse
    HttpSession 中的所有属性在Servlet存活时都会一直保持存活。

二、扩展

HttpServletRequest 与 HttpServletResponse

Servlet 容器附加在一个 web 服务上,这个 web 服务会在某个端口号上监听
HTTP 请求,在开发环境中这个端口通常为 8080,生产环境中通常为
80。当客户端(web 浏览器)发送了一个 HTTP 请求,Servlet 容器会创建新的
HttpServletRequest

HttpServletResponse
对象,传递给已创建好并且请求的 URL 匹配 url-patternFilter
Servlet 实例中的方法,所有工作都在同一个线程中处理。

request 对象可以访问所有该 HTTP 请求中的信息,例如 request header 和
request body。response 对象为你提供需要的控制和发送 HTTP
响应方法,例如设置 header 和 body(通常会带有 JSP 文件中的 HTML
内容)。提交并完成HTTP 响应后,将回收 request 和 response 对象。

线程安全

Servlet 和 Filter 被所有请求共享。那是 Java
的一个优点,使得多个不同线程(读取 HTTP
请求)可以使用同一个实例。否则为每个请求重新创建线程的开销实在过于昂贵。

但永远不要将任何 request 或 session 域中的数据赋值给 servlet 或 filter
的实例变量。它将会被所有其他 session
中的所有请求共享。那是非线程安全的!下面的示例对这种情况进行了展示:

public class ExampleServlet extends HttpServlet {

    private Object thisIsNOTThreadSafe;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Object thisIsThreadSafe;

        thisIsNOTThreadSafe = request.setParameter("foo"); // 不安全,为所有请求所共享
        thisIsThreadSafe = request.getParameter("foo"); // 线程安全
    }
}

出处:http://www.importnew.com/17025.html
本文来自stackoverflow的问答,讨论了Java
Servlet的工作机制,如何进行实例化、共享变量和多线程处理。

HttpSession

当用户第一次访问该 web 应用时,会通过 request.getSession() 第一次获得
HttpSession。之后 Servlet 容器将会创建 HttpSession,生成一个唯一的
ID(可以通过 session.getId() 获取)并储存在服务器内存中。然后 Servlet
容器在该次 HTTP 响应的 Set-Cookie 头部设置一个 Cookie,以
JSESSIONID 作为 Cookie 名字,那个唯一的 session ID 作为 Cookie
的值。

按照 HTTP cookie 规则(正常 web 浏览器和 web 服务端必须遵循的标准),当
cookie 有效时,要求客户端(浏览器)在后续请求的 Cookie 头中返回这个
cookie。使用浏览器内置的 HTTP 流量监控器,你可以查看它们(在
Chrome、Firefox23+、IE9+ 中按 F12,然后查看 Net/Network 标签)。Servlet
容器将会确定每个进入的 HTTP 请求的 Cookie 头中是否存在名为JSESSIONID
的 cookie,然后用它的值(session ID)从服务端内存中找到关联的
HttpSession

你可以在 web.xml 中设置 session-timeout ,默认值为 30
分钟。超时到达之前 HttpSession 会一直存活。所以当客户端不再访问该 web
应用超过 30 分钟后,Servlet 容器就会回收这个
session。后续每个请求,即使指定 cookie 名称也不能再访问到相同的
session。Servlet 容器会创建一个新的 Cookie

另一方面,客户端上的 session cookie
有一个默认存活时间,该事件和该浏览器实例运行时间一样长。所以,当客户端关闭该浏览器实例(所有标签和窗口)后,这个
session 就会被客户端回收。新浏览器实例不再发送与该 session 关联的
cookie。一个新的 request.getSession() 将会返回新的 HttpSession
并设置一个拥有新 session ID 的 cookie。

问题:Servlet是如何工作的?Servlet
如何实例化、共享变量、并进行多线程处理?

概述

  • ServletContext 与 web 应用存活时间一样长。它被所有 session
    中的所有请求共享。
  • 只要客户端一直与相同浏览器实例的web应用交互并且没有超时,HttpSession就会存在。
  • HttpServletRequestHttpServletResponse
    的存活时间为客户端发送完成到完整的响应(web
    页面)到达的这段时间。不会被其他地方共享。
  • 所有 ServletFilterListener 对象在 web
    应用运行时都是活跃的。它们被所有 session 中的请求共享。
  • 你设置在 HttpServletRequestHttpServletResponse
    HttpSession 中的所有属性在问题中的对象存活时都会一直保持存活。

假设我有一个运行了大量 Servlet 的 web 服务器。通过 Servlet
之间传输信息得到 Servlet 上下文,并设置 session 变量。

线程安全

即便如此,你最关心的可能是线程安全。你现在应该学习到 Servlet 和 filter
被所有请求共享。那是 Java 的一个优点,使得多个不同线程(读取 HTTP
请求)可以使用同一个实例。否则为每个请求重新创建线程的开销实在过于昂贵。

但你应该也意识到永远不要将任何 request 或 session 域中的数据赋值给
servlet 或 filter 的实例变量。它将会被所有其他 session
中的所有请求共享。那是非线程安全的!下面的示例对这种情况进行了展示:

public class ExampleServlet extends HttpServlet {

    private Object thisIsNOTThreadSafe;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Object thisIsThreadSafe;

        thisIsNOTThreadSafe = request.getParameter("foo"); // BAD!! Shared among all requests!
        thisIsThreadSafe = request.getParameter("foo"); // OK, this is thread safe.
    } 
}

现在,如果有两名或更多使用者向这个服务发送请求,接下来 session
变量会发生什么变化?究竟是所有用户都是用共同的变量?还是不同的用户使用的变量都不一样?如果是后者,服务器如何区分不同用户?

请参考:

  • JSF、Servlet 和 JSP
    之间有什么不同?
  • Java 中管理 Session
    的最佳选择
  • Servlet 中的 doGet 与
    doPost
  • Servlet
    似乎会同步处理多并发请求

另一个相似的问题,如果有 n 名用户访问一个特定的 Servlet,那么该
Servlet 是仅在第一个用户首次访问的时候实例化,还是分别为每个用户实例化?

回答(BalusC):

ServletContext

当 Servlet 容器(比如 Apache Tomcat)启动后,会部署和加载所有 web
应用。当web 应用被加载,Servlet 容器会创建一次
ServletContext,然后将其保存在服务器的内存中。web 应用的 web.xml
被解析,找到其中所有 servlet、filter 和 Listener 或
@WebServlet、@WebFilter 和 @WebListener
注解的内容,创建一次并保存到服务器的内存中。对于所有过滤器会立即调用
init()。当 Servlet 容器停止,将卸载所有 web 应用,调用所有初始化的
Servlet 和过滤器的 destroy() 方法,最后回收 ServletContext 和所有
Servlet、Filter 与 Listener 实例。

当问题中的 Servlet 配置的 load-on-startup 或者
@WebServlet(loadOnStartup) 设置了一个大于 0
的值,则同样会在启动的时候立即调用 init()
方法。“load-on-startup”中的值表示那些 Servlet
会以相同顺序初始化。如果配置的值相同,会遵循 web.xml 中指定的顺序或
@WebServlet 类加载的顺序。另外,如果不设置 “load-on-startup” 值,init()
方法只在第一次 HTTP 请求命中问题中的 Servlet 时才被调用。

HttpServletRequest 与 HttpServletResponse

Servlet 容器附加在一个 web 服务上,这个 web 服务会在某个端口号上监听
HTTP 请求,在开发环境中这个端口通常为 8080,生产环境中通常为
80。当客户端(web 浏览器)发送了一个 HTTP 请求,Servlet 容器会创建新的
HttpServletRequest 和 HttpServletResponse 对象,传递给已创建好并且请求的
URL 匹配 url-pattern 的 Filter 和 Servlet
实例中的方法,所有工作都在同一个线程中处理。

request 对象可以访问所有该 HTTP 请求中的信息,例如 request header 和
request body。response 对象为你提供需要的控制和发送 HTTP
响应方法,例如设置 header 和 body(通常会带有 JSP 文件中的 HTML
内容)。提交并完成HTTP 响应后,将回收 request 和 response 对象。

HttpSession

当用户第一次访问该 web 应用时,会通过 request.getSession() 第一次获得
HttpSession。之后 Servlet 容器将会创建 HttpSession,生成一个唯一的
ID(可以通过 session.getId() 获取)并储存在服务器内存中。然后 Servlet
容器在该次 HTTP 响应的 Set-Cookie 头部设置一个 Cookie,以 JSESSIONID
作为 Cookie 名字,那个唯一的 session ID 作为 Cookie 的值。

按照 HTTP cookie 规则(正常 web 浏览器和 web 服务端必须遵循的标准),当
cookie 有效时,要求客户端(浏览器)在后续请求的 Cookie 头中返回这个
cookie。使用浏览器内置的 HTTP 流量监控器,你可以查看它们(在
Chrome、Firefox23+、IE9+ 中按 F12,然后查看 Net/Network 标签)。Servlet
容器将会确定每个进入的 HTTP 请求的 Cookie 头中是否存在名为JSESSIONID 的
cookie,然后用它的值(session ID)从服务端内存中找到关联的 HttpSession。

你可以在 web.xml 中设置 session-timeout ,默认值为 30 分钟。超时到达之前
HttpSession 会一直存活。所以当客户端不再访问该 web 应用超过 30
分钟后,Servlet 容器就会回收这个 session。后续每个请求,即使指定 cookie
名称也不能再访问到相同的 session。Servlet 容器会创建一个新的 Cookie。

另一方面,客户端上的 session cookie
有一个默认存活时间,该事件和该浏览器实例运行时间一样长。所以,当客户端关闭该浏览器实例(所有标签和窗口)后,这个
session 就会被客户端回收。新浏览器实例不再发送与该 session 关联的
cookie。一个新的 request.getSession() 将会返回新的 HttpSession
并设置一个拥有新 session ID 的 cookie。

概述

ServletContext 与 web 应用存活时间一样长。它被所有 session
中的所有请求共享。
只要客户端一直与相同浏览器实例的web应用交互并且没有超时,HttpSession就会存在。
HttpServletRequest 和 HttpServletResponse
的存活时间为客户端发送完成到完整的响应(web
页面)到达的这段时间。不会被其他地方共享。
所有 Servlet、Filter 和 Listener 对象在 web
应用运行时都是活跃的。它们被所有 session 中的请求共享。
你设置在 HttpServletRequest、HttpServletResponse 和 HttpSession
中的所有属性在问题中的对象存活时都会一直保持存活。
线程安全
即便如此,你最关心的可能是线程安全。你现在应该学习到 Servlet 和 filter
被所有请求共享。那是 Java 的一个优点,使得多个不同线程(读取 HTTP
请求)可以使用同一个实例。否则为每个请求重新创建线程的开销实在过于昂贵。

但你应该也意识到永远不要将任何 request 或 session 域中的数据赋值给
servlet 或 filter 的实例变量。它将会被所有其他 session
中的所有请求共享。那是非线程安全的!下面的示例对这种情况进行了展示:

public class ExampleServlet extends HttpServlet {

    private Object thisIsNOTThreadSafe;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Object thisIsThreadSafe;

        thisIsNOTThreadSafe = request.getParameter("foo"); // BAD!! Shared among all requests!
        thisIsThreadSafe = request.getParameter("foo"); // OK, this is thread safe.
    } 
}

发表评论

电子邮件地址不会被公开。 必填项已用*标注