Servlet 3.1规范

概览

1.1 什么是Servlet

Servlet是基于Java的web组件,用于生成动态内容。像其他基于Java的组件技术一样,Servlet也是基于平台无关的Java类格式,被编译为平台无关的字节码,可以被基于Java技术的web server动态加载并运行。

1.2 什么是Servlet容器

Servlet 容器是 web server 或 application server 的一部分,提供基于请求/响应发送模型的网络服务,解码基于 MIME 的请求,并且格式化基于 MIME 的响应。Servlet 容器也包含了管理 Servlet 生命周期。Servlet 容器可以嵌入到宿主的 web server 中,或者通过 Web Server 的本地扩展 API 单独作为附加组件安。Servelt 容器也可能内嵌或安装到包含 web 功能的 application server 中。所有 Servlet 容器必须支持基于 HTTP 协议的请求/响应模型,比如像基于 HTTPS(HTTP over SSL)协议的请求/应答模型可以选择性的支持。容器必须实现的 HTTP 协议版本包含 HTTP/1.0 和 HTTP/1.1。因为容器或许支持 RFC2616 (HTTP/1.1)描述的缓存机制,缓存机制可能在将客户端请求交给 Servlet 处理之前修改它们,也可能在将 Servlet 生成的响应发送给客户端之前修改它们,或者可能根据 RFC2616 规范直接对请求作出响应而不交给 Servlet 进行处理。Servlet 容器应该使 Servlet 执行在一个安全限制的环境中。在 Java 平台标准版(J2SE, v.1.3 或更高) 或者Java 平台企业版(Java EE, v.1.3 或更高) 的环境下,这些限制应该被放置在 Java 平台定义的安全许可架构中。比如,高端的 application server 为了保证容器的其他组件不受到负面影响可能会限制 Thread 对象的创建。Java SE 6 是构建 Servlet 容器最低的 Java 平台版本。

1.3 例子

以下是一个典型的事件序列:
1、客户端(如 web 浏览器)发送一个 HTTP 请求到 web 服务器;
2、Web 服务器接收到请求并且交给 servlet 容器处理,servlet 容器可以运行在与宿主 web 服务器同一个进
程中,也可以是同一主机的不同进程,或者位于不同的主机的 web 服务器中,对请求进行处理。
3、servlet 容器根据 servlet 配置选择相应的 servlet,并使用代表请求和响应对象的参数进行调用。
4、servlet 通过请求对象得到远程用户,HTTP POST 参数和其他有关数据可能作为请求的一部分随请求一
起发送过来。Servlet 执行我们编写的任意的逻辑,然后动态产生响应内容发送回客户端。发送数据到客户
端是通过响应对象完成的。
5、一旦 servlet 完成请求的处理,servlet 容器必须确保响应正确的刷出,并且将控制权还给宿主 Web 服务器。

1.4 Servlet与其他技术比较

从功能上看,servlet 位于公共网关接口(CGI)程序和私有的 server 扩展如 Netscape Server API(NSAPI) 或 Apache Modules 这两者之间。相对于其他 server 扩展机制 Servlet 有如下优势:
它们通常比 CGI 脚本更快,因为采用不同的处理模型。它们采用标准的 API 从而支持更多的 Web Server。
它们拥有 Java 编程语言的所有优势,包括容易开发和平台无关。它们可以访问 Java 平台提供的大量API。

1.5 与Java平台企业版的关系

Java Servlet API 3.1 版本是 Java 平台企业版 7 版本(http://java.sun.com/javaee/)必须的 API。Servlet 容器和servlet 被部署到平台中,为了能在 Java EE 环境中执行,必须满足 JavaEE 规范中描述的额外的一些要求。

1.6 与Java Servlet规范2.5版本间兼容性

1.6.1 监听器(Listener)顺序

该规范之前发布的版本,监听器以随机顺序被调用。从 Servlet3.0 开始,监听器调用顺序定义在第『8 76』页的“web.xml 装配描述符,web- fragment .xml 和注解”部分。

1.6.1 注解处理

在 servlet 2.5 中不存在 web-fragments 的概念,因此在 servlet2.5 中 metadata-complete 仅影响部署时的注解扫描。然而,在 servlet 3.0 和后来版本中,在运行时,metadata-complete 将影响扫描指定部署信息的所有注解和 web-fragments。在一个 web 应用程序中,描述符的版本不能影响你扫描哪些注解。规范的一个典型版本实现必须扫描配置中支持的所有注解,除非 metadata-complete 被指定。

Servlet接口

Servlet 接口是 Java Servlet API 的核心抽象。所有 Servlet 类必须直接或间接的实现该接口,或者更通常做法是通过继承一个实现了该接口的类从而复用许多共性功能。目前有 GenericServlet 和 HttpServlet 这两个类实现了 Servlet 接口。大多数情况下,开发者只需要继承 HttpServlet 去实现自己的 Servlet 即可。

2.1 请求处理法发

Servlet 基础接口定义了用于客户端请求处理的 service 方法。当有请求到达时,该方法由 servlet 容器路由到一个 servlet 实例。
Web 应用程序的并发请求处理通常需要 Web 开发人员去设计适合多线程执行的 Servlet,从而保证 service
方法能在一个特定时间点处理多线程并发执行。(注:即 Servlet 默认是线程不安全的,需要开发人员处理
多线程问题)通常 Web 容器对于并发请求将使用同一个 servlet 处理,并且在不同的线程中并发执行service 方法。

2.1.1 基于http规范的请求处理方法

HttpServlet 抽象子类在 Servlet 接口基础之上添加了些协议相关的方法,并且这些方法能根据 HTTP 请求类型自动的由 HttpServlet 中实现的 service 方法转发到相应的协议相关的处理方法上。这些方法是:

  • doGet 处理 HTTP GET 请求
  • doPost 处理 HTTP POST 请求
  • doPut 处理 HTTP PUT 请求
  • doDelete 处理 HTTP DELETE 请求
  • doHead 处理 HTTP HEAD 请求
  • doOptions 处理 HTTP OPTIONS 请求
  • doTrace 处理 HTTP TRACE 请求
    一般情况下,当开发基于 HTTP 协议的 Servlet 时,Servlet 开发人员将仅去实现 doGet 和 doPost 请求处理方法即可。如果开发人员想使用其他处理方法,其使用方式跟之前的是类似的,即 HTTP 编程都是类似。

2.1.2 附加方法

doPut和doDelete方法允许Servlet开发人员让支持HTTP/1.1的客户端使用这些功能。HttpServlet中的doHead方法可以认为是 doGet 方法的一个特殊形式,它仅返回由 doGet 方法产生的 header 信息。doOptions 方法返回当前 servlet 支持的 HTTP 方法(译者注:通过 Allow 响应头返回支持的 HTTP 操作,如 GET、POST)。doTrace 方法返回的响应包含 TRACE 请求的所有头信息。

2.1.3 有条件的GET支持

HttpServlet 定义了用于支持有条件 GET 操作的 getLastModified 方法。所谓的有条件 GET 操作是指客户端通过 GET 请求获取资源时,当资源自第一次获取那个时间点发生更改后才再次发生数据,否则将使用客户端缓存的数据。在一些适当的场合,实现此方法可以更有效的利用网络资源,减少不必要的数据发送。

2.2 实例数量

通过注解描述的(第 8 章 注解和可插拔性)或者在 Web 应用程序的部署描述符(第 14 章 部署描述符)
中描述的 servlet 声明,控制着 servlet 容器如何提供 servlet 实例。
对于未托管在分布式环境中(默认)的 servlet 而言,servlet 容器对于每一个 Servlet 声明必须且只能产生一
个实例。不过,如果 Servlet 实现了 SingleThreadModel 接口,servlet 容器可以选择实例化多个实例以便处
理高负荷请求或者串行化请求到一个特定实例。
如果 servlet 以分布式方式进行部署,容器可以为每个虚拟机(JVM)的每个 Servlet 声明产生一个实例。但
是,如果在分布式环境中 servlet 实现了 SingleThreadModel 接口,此时容器可以为每个容器的 JVM 实例化
多个 Servlet 实例。

2.2.1关于Single Thread Model

SingleThreadModel 接口的作用是保证一个特定servlet 实例的service 方法在一个时刻仅能被一个线程执行,一定要注意,此保证仅适用于每一个 servlet 实例,因此容器可以选择池化这些对象。有些对象可以在同一时刻被多个 servlet 实例访问,如 HttpSession 实例,可以在一个特定的时间对多个 Servlet 可用,包括那些实现了 SingleThreadModel 接口的 Servlet

2.3 Servlet生命周期

Servlet 是按照一个严格定义的生命周期被管理,该生命周期规定了 Servlet 如何被加载、实例化、初始化、处理客户端请求,以及何时结束服务。该声明周期可以通过 javax.servlet.Servlet 接口中的 init、service 和destroy 这些 API 来表示,所有 Servlet 必须直接或间接的实现 GenericServlet 或 HttpServlet 抽象类。

2.3.1 加载和实例化

Servlet 容器负责加载和实例化 Servlet。加载和实例化可以发生在容器启动时,或者延迟初始化直到容器决定有请求需要处理时。当 Servlet 引擎启动后,servlet 容器必须定位所需要的 Servlet 类。Servlet 容器使用普通的 Java 类加载设施加载 Servlet 类。可以从本地文件系统或远程文件系统或者其他网络服务加载。加载完 Servlet 类后,容器就可以实例化它并使用了。

2.3.2 初始化

一旦一个 Servlet 对象实例化完毕,容器接下来必须在处理客户端请求之前初始化该 Servlet 实例。初始化的目的是以便 Servlet 能读取持久化配置数据,初始化一些代价高的资源(比如 JDBC API 连接),或者执行一些一次性的动作。容器通过调用 Servlet 实例的 init 方法完成初始化,init 方法定义在 Servlet 接口中,并且提供一个唯一的 ServletConfig 接口实现的对象作为参数,该对象每个 Servlet 实例一个。配置对象允许 Servlet 访问由 Web 应用配置信息提供的键-值对的初始化参数。该配置对象也提供给 Servlet去访问一个ServletContext对象,ServletContext描述了Servlet的运行时环境。请参考第4章,“Servlet Context”获取 ServletContext 接口的更多信息。

2.3.2.1 初始化时的错误条件

在初始化阶段,servlet 实现可能抛出 UnavailableException 或 ServletException 异常。在这种情况下,Servlet不能放置到活动服务中,而且 Servlet 容器必须释放它。如果初始化没有成功,destroy 方法不应该被调用。在实例初始化失败后容器可能再实例化和初始化一个新的实例。此规则的例外是,当抛出的UnavailableException 表示一个不可用的最小时间,容器在创建和初始化一个新的 servlet 实例之前必须等待一段时间。

2.3.2.2 使用工具时的注意事项

当一个工具加载并内省某个 Web 应用程序时触发的静态初始化,这种用法与调用 init 初始化方法是有区别的。在 Servlet 的 init 方法没被调用,开发人员不应该假定其处于活动的容器环境内。比如,当某个 Servlet仅有静态方法被调用时,不应该与数据库或企业级 JavaBean(EJB)容器建立连接。

2.3.3 请求处理

Servlet 完成初始化后,Servlet 容器就可以使用它处理客户端请求了。客户端请求由 ServletRequest 类型的request 对象表示。Servlet 封装响应并返回给请求的客户端,该响应由 ServletResponse 类型的 response 对象表示。这两个对象(request 和 response)是由容器通过参数传递到 Servlet 接口的 service 方法的。在 HTTP 请求的场景下,容器提供的请求和响应对象具体类型分别是 HttpServletRequest 和HttpServletResponse。需要注意的是,由 Servlet 容器初始化的某个 Servlet 实例在服务期间,可以在其生命周期中不处理任何请求。

2.3.3.1 多线程问题

Servlet 容器可以并发的发送多个请求到 Servlet 的 service 方法。为了处理这些请求,Servlet 开发者必须为service 方法的多线程并发处理做好充足的准备。一个替代的方案是开发人员实现 SingleThreadModel 接口,由容器保证一个 service 方法在同一个时间点仅被一个请求线程调用,但是此方案是不推荐的。Servlet 容器可以通过串行化访问 Servlet 的请求,或者维护一个 Servlet 实例池完成该需求。如果 Web 应用中的 Servlet被标注为分布式的,容器应该为每一个分布式应用程序的 JVM 维护一个 Servlet 实例池。对于那些没有实现 SingleThreadModel 接口的 Servlet,但是它的 service 方法(或者是那HttpServlet 中通过 service 方法分派的 doGet、doPost 等分派方法)是通过 synchronized 关键词定义的,Servlet 容器不能使用实例池方案,并且只能使用序列化请求进行处理。强烈推荐开发人员不要去通过 service 方法(或者那些由 Service 分派的方法),因为这将严重影响性能。

2.3.3.2 请求处理时的异常

Servlet 在处理一个请求时可能抛出 ServletException 或 UnavailableException 异常。ServletException 表示在
处理请求时出现了一些错误,容器应该采取适当的措施清理掉这个请求。UnavailableException 表示 servlet 目前无法处理请求,或者临时性的或者永久性的。如果 UnavailableException 表示的是一个永久性的不可用,Servlet 容器必须从服务中移除这个 Servlet,调用它的 destroy 方法,并释放 Servlet 实例。所有被容器拒绝的请求,都会返回一个 SC_NOT_FOUND (404) 响应。如果 UnavailableException 表示的是一个临时性的不可用,容器可以选择在临时不可用的这段时间内路由任何请求到 Servlet。所以在这段时间内被容器拒绝的请求,都会返回一个 SC_SERVICE_UNAVAILABLE (503)响应状态码,且同时会返回一个 Retry-After 头指示此 Servlet 什么时候可用。容器可以选择忽略永久性和临时性不可用的区别,并把UnavailableExceptions 视为永久性的,从而 Servlet 抛出 UnavailableException 后需要把它从服务中移除。

2.3.3.3 异步处理

有时候,Filter 及/或 Servlet 在生成响应之前必须等待一些资源或事件以便完成请求处理。比如,Servlet 在进行生成一个响应之前可能等待一个可用的 JDBC 连接,或者一个远程 web 服务的响应,或者一个 JMS 消息,或者一个应用程序事件。在 Servlet 中等待是一个低效的操作,因为这是阻塞操作,从而白白占用一个线程或其他一些受限资源。许多线程为了等待一个缓慢的资源比如数据库经常发生阻塞,可能引起线程饥饿,且降低整个 Web 容器的服务质量。当Servlet 3.0 引入了异步处理请求的能力,使线程可以返回到容器,从而执行更多的任务。当开始异步处理请求时,另一个线程或回调可以或者产生响应,或者调用完成(complete)或请求分派(dispatch),这样,它可以在容器上下文使用 AsyncContext.dispatch 方法运行。
一个典型的异步处理事件顺序是:

  • 请求被接收到,通过一系列如用于验证的等标准的 filter 之后被传递到 Servlet。

  • servlet 处理请求参数及(或)内容体从而确定请求的类型。

  • 该 servlet 发出请求去获取一些资源或数据,例如,发送一个远程 web 服务请求或加入一个等待 JDBC 连接的队列。

  • servlet 不产生响应并返回。

  • 过了一段时间后,所请求的资源变为可用,此时处理线程继续处理事件,要么在同一个线程,要么通过
    AsyncContext 分派到容器中的一个资源上。

    Java 企业版的功能,如第 15.2.2 节,在第 15-178 页的“Web 应用环境”和第 15.3.1 节,在第 15-180页的“EJB 调用的安全标识传播”,仅提初始化请求的线程执行,或者请求经过 AsyncContext.dispatch方法被分派到容器。Java 企业版的功能可能支持由 AsyncContext.start(Runnable)方法使用其他线程直接操作响应对象。
    第八章描述的@WebServlet 和@WebFilter 注解有一个属性——asyncSupported,boolean 类型默认值为 false。当asyncSupported 设置为 true,应用通过执行 startAsync(见下文)可以启动一个单独的线程中进行异步处理,并把请求和响应的引用传递给这个线程,然后退出原始线程所在的容器。这意味着响应将遍历(相反的顺序)与进入时相同的过滤器(或过滤器链)。直到 AsyncContext 调用complete(见下文)时响应才会被提交。如果异步任务在容器启动的分派之前执行,且调用了 startAsync并返回给容器,此时应用需负责处理请求和响应对象的并发访问。
    从一个 Servlet 分派时,把 asyncSupported=true 设置为 false 是允许的。这种情况下,当 servlet 的 service方法不支持异步退出时,响应将被提交,且容器负责调用 AsyncContext 的 complete,以便所有感兴趣的 AsyncListener 得到触发知。过滤器作为清理要完成的异步任务持有的资源的一种机制,也应该使用 AsyncListener. onComplete 触发的结果。从一个同步 Servlet 分派到另一个异步 Servlet 是非法的。不过与该点不同的是当应用调用 startAsyn时将抛出 IllegalStateException。这将允许 servlet 只能作为同步的或异步的 Servlet。应用在一个与初始请求所用的不同的线程中等待异步任务直到可以直接写响应,这个线程不知道任何过滤器。如果过滤器想处理新线程中的响应,那就必须在处理进入时的初始请求时包装 response,并且把包装的 response 传递给链中的下一个过滤器,并最终交给 Servlet。因此,如果响应是包装的(可能被包装多次,每一个过滤器次),并且应用处理请求并直接写响应,这将只写响应的包装对象,即任何输出的响应都会由响应的包装对象处理。当应用在一个单独的线程中读请求时,写内容到响应的包装对象,这其实是从请求的包装对象读取,并写到响应的包装对象,因此对包装对象操作的所有输入及(或)输出将继续存在。
    如果应用选择这样做的话,它将可以使用 AsyncContext 从一个新线程发起到容器资源的分派请求。这将允
    许在容器范围内使用像 JSP 这种内容生成技术。
    除了注解属性外,我们还添加了如下方法/类:

  • ServletRequest

    • public AsyncContext startAsync(ServletRequest req, ServletResponse res)。这个方法的作用是将请求转换为异步模式,并使用给定的请求及响应对象和 getAsyncTimeout 返回的超时时间初始化它的AsyncContext。ServletRequest和ServletResponse参数必须是与传递给servlet的service或filter的doFilter方法相同的对象,或者是 ServletRequestWrapper 和ServletResponseWrapper 子类的包装对象。当应用退出 service 方法时,调用该方法必须确保 response 没有被提交。当调用返回的 AsyncContext 的AsyncContext.complete 或 AsyncContext 超时并且没有监听器处理超时时,它将被提交。异步超时定时
      器直到请求和它关联的响应从容器返回时才启动。AsyncContext 可以被异步线程用来写响应,它也能用来通知没有关闭和提交的响应。
      如果请求在不支持异步操作的 servlet 或 filter 范围中调用 startAsync,或者响应已经被提交或关闭,或者在同一个分派期间重复调用,这些是非法的。从调用 startAsync 返回的 AsyncContext 可以接着被用来进行进一步的异步处理。调用返回的 AsyncContext 的 hasOriginalRequestResponse()方法将返回 false,除非传过去的 ServletRequest 和 ServletResponse 参数是最原始的那个或不是应用提供的包装器。在请求设置为异步模式后,在入站调用期间添加的一些请求及(或)响应的包装器可能需要在异步操作期间一直保持,并且它们关联的资源可能也不会释放,出站方向调用的所有过滤器可以以此作为一个标志。一个在入站调用期间的过滤器应用的 ServletRequestWrapper 可以被出站调用的过滤器释放,只有当给定的 ServletRequest 是由 AsyncContext 初始化的且通过调用 AsyncContext.getRequest()返回的,不包括之前说的 ServletRequestWrapper。这规则同样适用于 ServletResponseWrapper 实例。
    • public AsyncContext startAsync() 是一个简便方法,使用原始请求和响应对象用于异步处理。请注意,如果它们在你想调用此方法之前被包装了,这个方法的使用者应该刷出(flush)响应,确保数据写到被包装的响应中没有丢失。
  • public AsyncContext getAsyncContext() – 返回由 startAsync 调用创建的或初始化的 AsyncContext。如果请求已经被设置为异步模式,调用 getAsyncContext 是非法的。

  • public boolean isAsyncSupported() – 如果请求支持异常处理则返回 true,否则返回 false。一旦请求传
    给了过滤器或 servlet 不支持异步处理(通过指定的注解或声明),异步支持将被禁用。

  • public boolean isAsyncStarted() – 如果请求的异步处理已经开始将返回 true,否则返回 false。如果这个请求自从被设置为异步模式后已经使用任意一个 AsyncContext.dispatch 方法分派,或者成功调用了AsynContext.complete 方法,这个方法将返回 false。public DispatcherType getDispatcherType() – 返回请求的分派器(dispatcher)类型。容器使用请求的分派器类型来选择需要应用到请求的过滤器。只有匹配分派器类型和 url 模式(url pattern)的过滤器才会被应用。允许一个过滤器配置多个分派器类型,过滤器可以根据请求的不同分派器类型处理请求。请求的初始分派器类型定义为 DispatcherType.REQUEST 。
    使用RequestDispatcher.forward(ServletRequest, ServletResponse) 或 RequestDispatcher.include(ServletRequest,ServletResponse) 分派时,它们的请求的分派器类型分别是 DispatcherType.FORWARD 或DispatcherType.INCLUDE ,当一个异步请求使用任意一个 AsyncContext.dispatch 方法分派时该请求的分派器类型是 DispatcherType.ASYNC。最后,由容器的错误处理机制分派到错误页面的分派器类型是DispatcherType.ERROR 。

  • AsyncContext – 该类表示在 ServletRequest 启动的异步操作执行上下文,AsyncContext 由之前描述ServletRequest.startAsync 创建并初始化。AsyncContext 的方法:

    • public ServletRequest getRequest() – 返回调用 startAsync 用于初始化 AsyncContext 的请求对象。当在异步周期之前调用了 complete 或任意一个 dispatch 方法,调用 getRequest 将抛出 IllegalStateException。
    • public ServletResponse getResponse() –返回调用 startAsync 用于初始化 AsyncContext 的响应对象。当在异步周期之前调用了 complete 或任意一个 dispatch 方法,调用 getResponse 将抛出IllegalStateException。
    • public void setTimeout(long timeoutMilliseconds) –设置异步处理的超时时间,以毫秒为单位。该方法调用将覆盖容器设置的超时时间。如果没有调用 setTimeout 设置超时时间,将使用容器默认的超时时间。一个小于等于 0 的数表示异步操作将永不超时。当调用任意一个 ServletRequest.startAsync 方法时,一旦容器启动的分派返回到容器,超时时间将应用到 AsyncContext。当在异步周期开始时容器启动的分派已经返回到容器后,再设置超时时间是非法的,这将抛出一个 IllegalStateException 异常。
    • public long getTimeout()– 获取 AsyncContext 关联的超时时间的毫秒值。该方法返回容器默认的超时时间,或最近一次调用 setTimeout 设置超时时间。
    • public void addListener(AsyncListener listener, ServletRequest req, ServletResponse res) –注册一个用于
      接收的 onTimeout, onError, onComplete 或 onStartAsync 通知的监听器。前三个是与最近通过调用任意ServletRequest.startAsync 方法启动的异步周期相关联的。onStartAsync 是与通过任意ServletRequest.startAsync 启动的一个新的异步周期相关联的。异步监听器将以它们添加到请求时的顺序得到通知。当 AsyncListener 得到通知,传入到该方法的请求响应对象与AsyncEvent.getSuppliedRequest()和AsyncEvent.getSuppliedResponse()是完全相同的。不应该对这些对象进行读取或写入,因为自从注册了 AsyncListener 后可能发生了额外的包装,不过可以被用来按顺序释放与它们关联的资源。容器启动的分派在异步周期启动后返回到容器后,或者在一个新的异步周期启动之前,调用该方法是非法的,将抛出IllegalStateException。
    • public createListener(Class clazz) –实例化指定的 AsyncListener 类。返回的 AsyncListener 实例在使用下文描述的 addListener 方法注册到 AsyncContext 之前可能需要进一步的自定义。给定的 AsyncListener 类必须定义一个用于实例化的空参构造器,该方法支持适用于AsyncListener 的所有注解。
    • public void addListener(AsyncListener)– 注册给定的监听器用于接收 onTimeout, onError, onComplete 或 onStartAsync 通知。前三个是与最近通过调用任意 ServletRequest.startAsync 方法启动的异步周期相关联的。onStartAsync 是与通过任意 ServletRequest.startAsync 启动的一个新的异步周期相关联的。异步监听器将以它们添加到请求时的顺序得到通知。当 AsyncListener 接收到通知,如果在请求时调用startAsync(req, res) 或 startAsync(),从 AsyncEvent 会得到同样的请求和响应对象。请求和响应对象可以是或者不是被包装的。异步监听器将以它们添加到请求时的顺序得到通知。容器启动的分派在异步周期启动后返回到容器后,或者在一个新的异步周期启动之前,调用该方法是非法的,将抛出IllegalStateException。
    • public void dispatch(String path)– 将用于初始化 AsyncContext 的请求和响应分派到指定的路径的资源。该路径以相对于初始化 AsyncContext 的 ServletContext 进行解析。与请求查询方法相关的所有路径,必须反映出分派的目标,同时原始请求的 URI,上下文,路径信息和查询字符串都可以从请求属性中获取,请求属性定义在 9-98 页的 9.7.2 章节,“分派的请求参数”。这些属性必须反映最原始的路径元素,即使在多次分派之后。
    • public void dispatch()– 一个简便方法,使用初始化 AsyncContext 时的请求和响应进行分派,如下所示。 如果使用 startAsync(ServletRequest, ServletResponse)初始化 AsyncContext,且传入的请求是HttpServletRequest 的一个实例,则使用 HttpServletRequest.getRequestURI()返回的 URI 进行分派。否则分派的是容器最后分派的请求 URI。下面的代码示例 2-1,代码示例 2-2 和代码示例 2-3 演示了不同情况下分派的目标 URI 是什么。

    代码示例 2-1:

// 请求到 /url/A 
AsyncContext ac = request.startAsync(); 
... 
ac.dispatch(); // 异步分派到 /url/A

代码示例 2-2 :

// 请求到 /url/A 
// 转发到 /url/B 
request.getRequestDispatcher(“/url/B”).forward(request, response); 
// 从 FORWARD 的目标内启动异步操作
AsyncContext ac = request.startAsync(); 
ac.dispatch(); // 异步分派到 /url/A

代码示例 2-3:

// 请求到 /url/A 
// 转发到 /url/B 
request.getRequestDispatcher(“/url/B”).forward(request, response); 
// 从 FORWARD 的目标内启动异步操作
AsyncContext ac = request.startAsync(request, response); 
ac.dispatch(); //异步分派到 /url/B
  • public void dispatch(ServletContext context, String path)-将用于初始化 AsyncContext 的请求和响应分派到指定 ServletContext 的指定路径的资源。
  • 之上定义了 dispatch 方法的全部 3 个变体,调用这些方法且将请求和响应对象传入到容器的一个托管线程后将立即返回,在托管线程中异步操作将被执行。请求的分派器类型设置为异步(ASYNC)。不同于 RequestDispatcher.forward(ServletRequest, ServletResponse) 分派,响应的缓冲区和头信息将不会重置,即使响应已经被提交分派也是合法的。控制委托给分派目标的请求和响应,除非调用了ServletRequest.startAsync() 或 ServletRequest.startAsync(ServletRequest, ServletResponse),否则响应将在分派目标执行完成时被关闭。在调用了 startAsync 方法的容器启动的分派没有返回到容器之前任何dispatch 方法的调用将没有任何作用。 AsyncListener.onComplete(AsyncEvent), AsyncListener.onTimeout(AsyncEvent)和 AsyncListener.onError(AsyncEvent)的调用将被延迟到容器启动的分派返回到容器之后。通过调用 ServletRequest.startAsync.启动的每个异步周期至多只有一个异步分派操作。相同的异步周期内任何试图执行其他的异步分派操作是非法的并将导致抛出
    IllegalStateException。如果后来在已分派的请求上调用 startAsync,那么所有的 dispatch 方法调用将和之上具有相同的限制。
  • 任何在执行 dispatch 方法期间可能抛出的错误或异常必须由容器抓住和处理,如下所示:
    • 调用所有由 AsyncContext 创建的并注册到 ServletRequest 的 AsyncListener 实例的AsyncListener.onError(AsyncEvent) 方法, 可以通过 AsyncEvent.getThrowable()获取到捕获的Throwable。
    • 如果没有监听器调用 AsyncContext.complete 或任何 AsyncContext.dispatch 方法,然后执行一个状态码为 HttpServletResponse.SC_INTERNAL_SERVER_ERROR 的出错分派,并且可以通过RequestDispatcher.ERROR_EXCEPTION 请求属性获取 Throwable 值。
    • 如果没有找到匹配的错误页面,或错误页面没有调用 AsyncContext.complete() 或任何AsyncContext.dispatch 方法,则容器必须调用 AsyncContext.complete。
  • public boolean hasOriginalRequestAndResponse()– 该方法检查 AsyncContext 是否以原始的请求和响应对象调用 ServletRequest.startAsync() 完成初始化的,或者是否通过调用ServletRequest.startAsync(ServletRequest, ServletResponse)完成初始化的,且传入的 ServletRequest 和ServletResponse 参数都不是应用提供的包装器,这样的话将返回 true。如果 AsyncContext 使用包装的请求及(或)响应对象调用 ServletRequest.startAsync(ServletRequest, ServletResponse)完成初始化,那么将返回 false。在请求处于异步模式后,该信息可以被出站方向调用的过滤器使用,用于决定是否在入站调用时添加的请求及(或)响应包装器需要在异步操作期间被维持或者被释放。
  • **public void start(Runnable r)**该方法导致容器分派一个线程,该线程可能来自托管的线程池,用于运行指定的 Runnable 对象。容器可能传播相应的上下文信息到该 Runnable 对象。
  • public void complete()– 如果调用了 request.startAsync,则必须调用该方法以完成异步处理并提交和关闭响应。如果请求分派到一个不支持异步操作的 Servlet,或者由 AsyncContext.dispatch 调用的目标servlet 之后没有调用 startAsync,则 complete 方法会由容器调用。这种情况下,容器负责当 servlet 的
    service 方法一退出就调用 complete()。 如果 startAsync 没有被调用则必须抛出 IllegalStateException。在调用ServletRequest.startAsync() 或 ServletRequest.startAsync(ServletRequest, ServletResponse) 之后且在调用任意 dispatch 方法之前的任意时刻调用 complete()是合法的。在调用了 startAsync 方法的容器启动的分派没有返回到容器之前该方法的调用将没有任何作用。AsyncListener.onComplete(AsyncEvent)的调用将被延迟到容器启动的分派返回到容器之后。
  • ServletRequestWrapper
    • public boolean isWrapperFor(ServletResponse res)- 检查该包装器是否递归的包装了给定的ServletResponse,如果是则返回 true,否则返回 false。
  • AsyncListener
    • public void onComplete(AsyncEvent event) – 用于通知监听器在 Servlet 上启动的异步操作完成了。
    • public void onError(AsyncEvent event) – 用于通知监听器异步操作未能完成。
    • public void onStartAsync(AsyncEvent event) – 用于通知监听器正在通过调用一个ServletRequest.startAsync 方法启动一个新的异步周期。正在被重新启动的异步操作对应的 AsyncContext可以通过调用给定的 event 上调用AsyncEvent.getAsyncContext 获取。
  • 在异步操作超时的情况下,容器必须按照如下步骤运行:
    • 当异步操作启动后调用注册到ServletRequest的所有AsyncListener实例的AsyncListener.onTimeout 方法。
    • 如果没有监听器调用 AsyncContext.complete() 或任何 AsyncContext.dispatch 方法,执行一个状态码为HttpServletResponse.SC_INTERNAL_SERVER_ERROR 出错分派。
    • 如果没有找到匹配的错误页面,或者错误页面没有调用 AsyncContext.complete() 或任何AsyncContext.dispatch 方法,则容器必须调用 AsyncContext.complete()。
  • 如果在 AsyncListener 中调用方法抛出异常,将记录下来且将不影响任何其他 AsyncListener 的调用。
  • 默认情况下是不支持 JSP 中的异步处理,因为它是用于内容生成且异步处理可能在内容生成之前已经完
    成。这取决于容器如何处理这种情况。一旦完成了所有的异步活动,使用 AsyncContext.dispatch 分派到的
    JSP 页面可以用来生成内容。

2.3.4 终止服务

Request

Servlet Context

Reponse

过滤器

会话

注解和可插拔

分派请求

Web应用

应用生命周期

映射请求到Servlet

安全

变更历史

概览
1.1什么是Servlet

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值