Servlet规范之Servlet顶层接口

Posted 顧棟

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Servlet规范之Servlet顶层接口相关的知识,希望对你有一定的参考价值。

Servlet Interface

文章是对 JSR-000340 JavaTM Servlet 3.1 Final Release的Java™ Servlet规范的翻译,尚未校准

文章目录


Servlet接口是Java Servlet API的核心抽象。所有的Servlet都直接实现这个接口,或者更常见的是通过扩展一个实现该接口的类。

Java Servlet API中实现Servlet接口的两个类是GenericServletHttpServlet。大多数情况下,开发者将扩展HttpServlet来实现他们的Servlet。

请求处理方法

基本的Servlet接口定义了一个用于处理客户端请求的service方法。对于servlet容器路由到servlet实例的每个请求,都会调用这个方法。

处理对Web应用程序的并发请求通常要求Web开发者设计的Servlet能够处理在某一特定时间在service方法中执行的多个线程。

一般来说,Web容器通过在不同的线程上并发执行service方法来处理对同一Servlet的并发请求。

HTTP特定的请求处理方法

HttpServlet抽象子类在基本Servlet接口之外增加了额外的方法,这些方法会被HttpServlet类中的服务方法自动调用,以帮助处理基于HTTP的请求。

这些方法是:

  • doGet for handling HTTP GET requests
  • doPost for handling HTTP POST requests
  • doPut for handling HTTP PUT requests
  • doDelete for handling HTTP DELETE requests
  • doHead for handling HTTP HEAD requests
  • doOptions for handling HTTP OPTIONS requests
  • doTrace for handling HTTP TRACE requests

通常,在开发基于HTTP的Servlet时,Servlet开发者只会关注doGet和doPost方法。其他方法被认为是供非常熟悉HTTP编程的程序员使用的方法。

额外方法

doPutdoDelete方法允许Servlet开发者支持HTTP/1.1 客户端,并采用这些功能。HttpServlet中的doHead方法是doGet方法的一种专门形式,它只返回由doGet方法产生的头文件。doOptions方法响应的是servlet支持哪些HTTP方法。doTrace方法会生成一个响应,其中包含所有TRACE请求中发送的头文件的所有实例。

有条件的GET支持

HttpServlet接口定义了getLastModified方法以支持有条件的GET操作。

一个有条件的GET操作只要求在资源自指定时间后被修改时才发送。在适当的情况下,这个方法的实现可以帮助有效地利用网络资源。

实例数

通过第8章 "Annotations and pluggability"中描述的注解或第14章 "Deployment Descriptor"中描述的包含Servlet的Web应用部署描述符的一部分,Servlet声明控制了Servlet容器如何提供Servlet的实例。

对于不在分布式环境中托管的servlet(默认),servlet容器必须在每个servlet声明中只使用一个实例。

然而,对于实现SingleThreadModel接口的servlet来说,servlet容器可以实例化多个实例来处理大量的请求负载,并将请求序列化到一个特定的实例。

如果servlet被部署为应用程序的一部分,在部署描述符中被标记为可分发的情况下,容器在每个Java虚拟机(JVM™)1上的每个servlet声明可能只有一个实例。

然而,如果分布式的应用程序中的 servlet 实现了SingleThreadModel接口,那么容器可以在容器的每个 JVM 中实例化该 servlet 的多个实例。

关于单线程模型的注意事项

使用SingleThreadModel接口可以保证每次只有一个线程在一个给定的servlet实例的service方法中执行。需要注意的是,这种保证只适用于每个servlet实例,因为容器可能会选择将这类对象作为池。那些可以同时被多个servlet实例访问的对象,如HttpSession的实例,在任何特定时间都可能被多个servlet使用,包括那些实现SingleThreadModel的servlet。

建议开发者采取其他手段来解决这些问题,而不是实现这个接口,例如避免使用实例变量或同步访问这些资源的代码块。

SingleThreadModel接口在这个版本的规范中被废弃了。

Servlet生命周期

一个servlet是通过一个定义良好的生命周期来管理的,这个生命周期定义了它是如何被加载和实例化的,如何被初始化,如何处理来自客户端的请求,以及如何退出服务。这个生命周期在API中通过javax.servlet.Servlet接口的initservicedestroy方法来表达,所有Servlet必须直接或间接通过GenericServletHttpServlet抽象类来实现。

加载和实例化

Servlet容器负责加载和实例化Servlet。

加载和实例化可以在容器启动时进行,也可以延迟到容器确定需要servlet来服务请求时进行。

当servlet引擎启动时,需要的servlet类必须由servlet容器定位。servlet容器使用正常的Java类加载设施来加载servlet类。加载可以来自本地文件系统、远程文件系统或其他网络服务。

在加载Servlet类之后,容器将其实例化以便使用。

初始化

在servlet对象被实例化后,容器必须在处理来自客户端的请求前初始化servlet。

提供初始化是为了让servlet能够读取持久的配置数据,初始化昂贵的资源(如基于JDBC™ API的连接),并执行其他一次性活动。容器通过调用Servlet接口的init方法,用实现ServletConfig接口的唯一(每个Servlet声明)对象来初始化Servlet实例。这个配置对象允许servlet从Web应用的配置信息中访问name-value初始化参数。配置对象提供了让servlet访问一个描述servlet运行环境的对象(实现ServletContext接口)。

关于ServletContext接口的更多信息,见第4章 “Servlet Context”。

初始化时的错误情况

在初始化过程中,Servlet实例可以抛出一个UnavailableException或一个ServletException。在这种情况下,该Servlet不能被放入活动服务中,必须由Servlet容器释放。destroy方法不被调用,因为它被认为是不成功的初始化。

在初始化失败后,容器可以实例化和初始化一个新的实例。这条规则的例外情况是,当UnavailableException指示了最短的不可用时间时,容器必须在创建和初始化新的 servlet 实例之前等待这段时间的过去。

工具方面的考虑

当一个工具加载和内省一个Web应用程序时,触发静态初始化方法要与调用init方法区分开来。在调用Servlet接口的init方法之前,开发者不应认为Servlet处于活动的容器运行状态。例如,当只有静态(类)初始化方法被调用时,servlet不应试图建立与数据库或Enterprise JavaBeans™容器的连接。

请求处理

在servlet被正确初始化后,servlet容器可以用它来处理客户的请求。请求由ServletRequest类型的请求对象表示。servlet通过调用所提供的ServletResponse类型的对象的方法来填写对请求的响应。这些对象被作为参数传递给Servlet接口的service方法。在HTTP请求的情况下,由容器提供的对象是HttpServletRequestHttpServletResponse类型。请注意,一个由servlet容器提供服务的servlet实例在其生命周期内可能不会处理任何请求。

多线程的问题

一个Servlet容器可以通过Servlet的service方法发送并发请求。为了处理这些请求,Servlet开发者必须在service方法中为多线程的并发处理做出充分的规定。

尽管不推荐这样做,但对开发者来说,另一个选择是实现SingleThreadModel接口,它要求容器保证在服务方法中一次只有一个请求线程。一个servlet容器可以通过在servlet上序列化请求,或者通过维护一个servlet实例池来满足这一要求。如果servlet是被标记为分布式的Web应用程序的一部分,那么容器可以在应用程序被分发到的每个JVM中维护一个servlet实例池。

对于没有实现SingleThreadModel接口的Servlet来说,如果service方法(或派发给HttpServlet抽象类的service方法的doGetdoPost等方法)已经用synchronized关键字定义,Servlet容器就不能使用实例池方法,而必须通过它来序列化请求。强烈建议开发者在这种情况下不要synchronized service方法(或调度给它的方法),因为这对性能有不利的影响。

请求处理过程中的异常情况

一个servlet可以在服务请求的过程中抛出一个ServletException或一个UnavailableExceptionServletException表明在处理请求的过程中发生了一些错误,容器应该采取适当的措施来清理该请求。UnavailableException表示servlet暂时或永久地无法处理请求。

如果UnavailableException表示永久不可用,那么 servlet 容器必须从服务中移除 servlet,调用其destroy方法,并释放servlet实例。容器因该原因而拒绝的任何请求必须以SC_NOT_FOUND(404)响应返回。如果 UnavailableException 表示暂时不可用,那么容器可以选择在暂时不可用的时间段内不通过该 servlet 发出任何请求。容器在这段时间内拒绝的任何请求都必须以 SC_SERVICE_UNAVAILABLE(503)响应状态以及表明不可用性何时终止的 Retry-After 标头返回。

容器可以选择忽略永久性和暂时性不可用之间的区别,把所有的UnavailableExceptions都当作永久性的,从而把抛出任何UnavailableException的 servlet 从服务中删除。

异步处理

有些时候,过滤器、servlet无法在生成响应之前不等待资源或事件就完成对请求的处理。例如,servlet可能需要等待一个可用的JDBC连接,等待来自远程Web服务的响应,等待JMS消息,或者等待一个应用程序事件,然后再继续生成响应。在servlet中等待是一种低效的操作,因为它是一种阻塞操作,会消耗一个线程和其他有限的资源。通常情况下,像数据库这样的慢速资源可能会有许多线程被阻塞,等待访问,并可能导致线程饥饿和整个网络容器的服务质量差。引入请求的异步处理是为了让线程可以返回到容器中并执行其他任务。当请求的异步处理开始时,另一个线程或回调可以生成响应并调用complete或调度请求,这样它就可以使用AsyncContext.dispatch方法在容器的上下文中运行。

异步处理的典型事件序列是:

  1. 请求被接收,并通过正常的认证过滤器等传递给servlet。
  2. 处理请求参数、内容以确定请求的性质。
  3. 发出对资源或数据的请求,例如,发送一个远程网络服务请求或加入一个等待JDBC连接的队列。
  4. 返回时不产生响应。
  5. 一段时间后,被请求的资源变得可用,处理该事件的线程在同一线程中继续处理,或通过使用AsyncContext调度到容器中的资源。

诸如"Web Application Environment"和"Propagation of Security Identity in EJB™ Calls"等 Java 企业版特性只对执行初始请求的线程可用,或者当请求通过“AsyncContext.dispatch”方法被分派到容器时可用。Java 企业版特性中,通过 AsyncContext.start(Runnable) 方法,可以直接在响应对象上操作的其他线程。

第8章中描述的@WebServlet和@WebFilter注解有一个属性 - asyncSupported,是一个布尔值,默认值为false。当 asyncSupported被设置为 "true "时,应用程序可以通过调用startAsync(见下文)在一个单独的线程中开始异步处理,并将请求和响应对象的引用传递给它,然后在原始线程中退出容器。这意味着响应将遍历(以相反的顺序)在进入时遍历的相同的过滤器(或过滤器链)。在AsyncContext上调用complete(见下文)之前,响应不会被提交。如果异步任务在调用startAsync的容器发起的调度返回到容器之前执行,应用程序将负责处理对请求和响应对象的并发访问。

允许从asyncSupported=true的servlet向asyncSupported设置为false的servlet进行调度。在这种情况下,当不支持async的servlet的service方法被退出时,响应将被提交,容器有责任在AsyncContext上调用complete,这样任何感兴趣的AsyncListener实例将被通知。AsyncListener.onComplete通知也应该被过滤器作为一种机制来清理它一直坚持的资源,以便完成异步任务。

从一个同步servlet调度到一个异步servlet是非法的。然而,抛出IllegalStateException的决定被推迟到应用程序调用 startAsync时。这将允许servlet作为一个同步或异步servlet发挥作用。

应用程序正在等待的异步任务可以直接写入响应,在一个不同于初始请求的线程上。这个线程对任何过滤器一无所知。如果过滤器想在新的线程中操作响应,它就必须在处理初始请求时 "在路上 "将响应包装起来,并将包装好的响应传递给链中的下一个过滤器,最终传递给servlet。因此,如果响应被包装了(可能是多次,每个过滤器一次),应用程序处理请求并直接写入响应,它实际上是写给响应包装器的,也就是说,任何添加到响应中的输出仍将由响应包装器处理。当应用程序在一个单独的线程中从请求中读取,并向响应中添加输出时,它实际上是从请求封装器中读取,并向响应封装器中写入,因此封装器打算进行的任何输入、输出操作将继续发生。

另外,如果应用程序选择这样做,它可以使用AsyncContext将请求从新线程dispatch到容器中的资源。这样就可以在容器的范围内使用JSP等内容生成技术。

除了注释属性之外,我们还有以下方法/类 添加:

  • ServletRequest

    • public AsyncContext startAsync(ServletRequest servletRequest,ServletResponse servletResponse)该方法将请求放入异步模式,并用给定的请求和响应对象以及getAsyncTimeout返回的超时来初始化它的AsyncContext。ServletRequest和ServletResponse参数必须是传递给调用Servlet的服务或过滤器的doFilter方法的相同对象,或者是包裹它们的ServletRequestWrapper或ServletResponseWrapper类的子类。对这个方法的调用可以确保应用程序退出服务方法时,响应不会被提交。当AsyncContext.complete在返回的AsyncContext上被调用时,或者AsyncContext超时并且没有相关的监听器来处理超时时,它就被提交了。在请求及其相关的响应从容器返回之前,异步超时的计时器不会启动。AsyncContext可以用来从异步线程写到响应。它也可以被用来通知响应没有被关闭和提交。

      如果请求在不支持异步操作的servlet或过滤器的范围内,或者如果响应已经提交并关闭,或者在同一调度期间再次调用startAsync是非法的。然后,从调用startAsync返回的AsyncContext可以用于进一步的异步处理。在返回的AsyncContext上调用AsyncContext.hasOriginalRequestResponse()将返回false,除非传递的ServletRequest和ServletResponse参数是原始参数或没有携带应用程序提供的包装器。在这个请求被放入异步模式后,在出站方向上调用的任何过滤器可能会将此作为一种指示,即它们在入站调用期间添加的一些请求和/或响应包装器可能需要在异步操作的持续时间内保持原位,并且它们的相关资源可能不会被释放。只有在用于初始化AsyncContext并将由AsyncContext.getRequest()的调用返回的给定ServletRequest不包含所述ServletRequestWrapper的情况下,在过滤器的入站调用期间应用的ServletRequestWrapper才可能被过滤器的出站调用释放。对于ServletResponseWrapper实例也是如此。

    • public AsyncContext startAsync()是作为一种便利提供的,它使用原始请求和响应对象进行异步处理。请注意这个方法的用户在调用这个方法之前,如果他们被包裹了,如果你愿意的话,应该刷新响应,以确保任何写入包裹的响应的数据不会丢失。

    • 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()- 返回一个请求的调度器类型。请求的调度器类型被容器用来选择需要应用于请求的过滤器。只有具有匹配的调度器类型和URL模式的过滤器才会被应用。允许为多种调度器类型配置的过滤器查询请求的调度器类型,允许过滤器根据其调度器类型以不同方式处理请求。一个请求的初始调度器类型被定义为DispatcherType.REQUEST。通过RequestDispatcher.forward(ServletRequest, ServletResponse)RequestDispatcher.include(ServletRequest, ServletResponse)调度的请求的调度器类型分别为DispatcherType.FORWARDDispatcherType.INCLUDE,而通过AsyncContext.dispatch方法之一调度的异步请求的调度器类型则为DispatcherType.ASYNC

  • AsyncContext-该类表示在ServletRequest上启动的异步操作的执行上下文。AsyncContext是通过调用ServletRequest.startAsync来创建和初始化的,如上所述。以下是AsyncContext中的方法:

    • public ServletRequest getRequest() - 返回用于通过调用startAsync方法之一来初始化AsyncContext的请求。在完成或任何调度方法先前在异步循环中被调用时,调用getRequest将导致IllegalStateException。

    • public ServletResponse getResponse() - 返回用于通过调用startAsync方法之一来初始化AsyncContext的响应。在异步循环中完成或任何调度方法先前被调用时,调用getResponse将导致IllegalStateException

    • public void setTimeout(long timeoutMilliseconds) - 设置异步处理的超时,单位是毫秒。对这个方法的调用会覆盖容器所设置的超时。如果没有通过调用setTimeout来指定超时,则使用30000作为默认值。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方法之一与新的异步周期相关联。异步监听器将按照它们被添加到请求中的顺序被通知。传入该方法的请求和响应对象与AsyncEvent.getSuppliedRequest()和AsyncEvent.getSuppliedResponse()通知AsyncListener时的对象完全相同。这些对象不应该被读出或写入,因为自从给定的AsyncListener被注册后,可能已经发生了额外的包装,但可以被用来释放与之相关的任何资源。在启动异步循环的容器发起的调度返回到容器之后,在新的异步循环启动之前调用这个方法是非法的,会导致IllegalStateException。

    • public <T extends AsyncListener> createListener(Class<T> clazz) -实例化给定的AsyncListener类。返回的AsyncListener实例可以在通过调用下面指定的addListener方法与AsyncContext注册之前被进一步定制。给定的AsyncListener类必须定义一个零参数的构造函数,用于实例化它。这个方法支持任何适用于AsyncListener的注释。

    • public void addListener(AsyncListener) - 为onTimeout、onError、onComplete或onStartAsync的通知注册指定的监听器。前三个与最近通过调用ServletRequest.startAsync方法之一启动的异步周期相关。onStartAsync是通过ServletRequest.startAsync方法之一与新的异步循环相关联。如果startAsync(req, res)或startAsync()在请求中被调用,当AsyncListener被通知时,完全相同的请求和响应对象可以从AsyncEvent中获得。请求和响应可以被包装,也可以不被包装。异步监听器将按照它们被添加到请求中的顺序被通知。在启动异步循环的容器发起的调度返回到容器后,在新的异步循环启动前调用这个方法是非法的,会导致IllegalStateException。

    • public void dispatch(String path) - 将用于初始化AsyncContext的请求和响应分配给给定路径的资源。给定的路径被解释为与初始化AsyncContext的ServletContext相对。请求中所有与路径相关的查询方法都必须反映调度目标,而原始请求URI、上下文路径、路径信息和查询字符串可以从 "Dispatched Request Parameters "中定义的请求属性中获得。这些属性必须始终反映原始的路径元素,即使是在多次派发之后。

    • public void dispatch() - 作为一种便利,提供了用于初始化AsyncContext的请求和响应的调度,具体如下。如果AsyncContext是通过startAsync(ServletRequest, ServletResponse)初始化的,并且传递的请求是HttpServletRequest的一个实例,那么分配到HttpServletRequest.getRequestURI()返回的URI。否则,派发到请求的URI,当它最后被容器派发时。下面的例子CODE EXAMPLE 2-1、CODE EXAMPLE 2-2和CODE EXAMPLE 2-3展示了在不同情况下调度的目标URI是什么。

      CODE EXAMPLE 2-1

      // REQUEST to /url/A
      AsyncContext ac = request.startAsync();
      ...
      ac.dispatch(); // ASYNC dispatch to /url/A
      

      CODE EXAMPLE 2-2

      // REQUEST to /url/A
      // FORWARD to /url/B
      request.getRequestDispatcher(/url/B).forward(request,response);
      // Start async operation from within the target of the FORWARD
      AsyncContext ac = request.startAsync();
      ac.dispatch(); // ASYNC dispatch to /url/A
      

      CODE EXAMPLE 2-3

      // REQUEST to /url/A
      // FORWARD to /url/B
      request.getRequestDispatcher(/url/B).forward(request,response);
      // Start async operation from within the target of the FORWARD
      AsyncContext ac = request.startAsync(request, response);
      ac.dispatch(); // ASYNC dispatch to /url/B
      
    • public void dispatch(ServletContext context, String path) - 将用于初始化AsyncContext的请求和响应分派给给定ServletContext中具有给定路径的资源。

    • 对于上面定义的调度方法的所有3种变化,对方法的调用在将请求和响应对象传递给容器管理线程后立即返回,调度操作将在该线程上执行。请求的调度器类型被设置为ASYNC。与RequestDispatcher.forward(ServletRequest, ServletResponse)调度不同,响应缓冲区和头文件不会被重置,而且即使响应已经被提交,调度也是合法的。对请求和响应的控制被委托给调度目标,当调度目标完成执行时,响应将被关闭,除非ServletRequest.startAsync()ServletRequest.startAsync(ServletRequest, ServletResponse) 被调用。如果在调用 startAsync 的容器发起的调度返回到容器之前调用任何调度方法,那么该调用将在容器发起的调度返回到容器之后才生效。AsyncListener.onComplete(AsyncEvent)AsyncListener.onTimeout(AsyncEvent)AsyncListener.onError(AsyncEvent)的调用也将被延迟到由容器发起的调度返回容器之后。每个异步周期最多可以有一个异步调度操作,它是由调用ServletRequest.startAsync方法之一开始的。任何试图在同一异步周期内执行额外的异步调度操作都是非法的,并将导致IllegalStateException。 如果startAsync随后被调用到被调度的请求上,那么任何调度方法都可以被调用,其限制与上述相同。

    • 在执行调度方法的过程中可能发生的任何错误或异常都必须由容器捕获并处理,具体如下:

      • 对与创建AsyncContext的ServletRequest注册的AsyncListener的所有实例调用AsyncListener.onError(AsyncEvent)方法,并通过AsyncEvent.getThrowable()使Throwable可用。
      • 如果没有一个监听器调用AsyncContext.complete或任何AsyncContext.dispatch方法,那么执行一个状态代码等于HttpServletResponse.SC_INTERNAL_SERVER_ERROR的错误分派,并使Throwable作为RequestDispatcher.ERROR_EXCEPTION请求属性的值可用。
      • 如果没有找到匹配的错误页,或者错误页没有调用 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 的服务方法被退出,就应该由容器负责调用 complete() 。如果startAsync没有被调用,则必须抛出一个IllegalStateException。在调用ServletRequest.startAsync()或ServletRequest.startAsync(ServletRequest, ServletResponse)之后、调用其中一个dispatch方法之前,随时调用这个方法是合法的。如果在调用 startAsync 的容器发起的调度返回到容器之前调用这个方法,那么这个调用将在容器发起的调度返回到容器之后才会生效。AsyncListener.onComplete(AsyncEvent)的调用也将被推迟到容器发起的调度返回容器之后。

  • ServletRequestWrapper

    • public boolean isWrapperFor(ServletRequest req)- 递归地检查此包装器是否包装了给定的ServletRequest,如果是则返回true,否则返回false。
  • ServletResponseWrapper

    • public boolean isWrapperFor(ServletResponse res)- 递归地检查此包装器是否包装了给定的ServletResponse,如果是则返回true,否则返回false。
  • AsyncListener

    • public void onComplete(AsyncEvent event) -用于通知监听器在ServletRequest上开始的异步操作的完成。
    • public void onTimeout(AsyncEvent event) - 用于通知监听器在ServletRequest上开始的异步操作的超时情况。
    • public void onError(AsyncEvent event) - 用于通知监听器,异步操作未能完成。
    • public void onStartAsync(AsyncEvent event) - 用于通知监听器,通过调用ServletRequest.startAsync方法之一,正在启动一个新的异步循环。与正在被重新初始化的异步操作相对应的AsyncContext可以通过调用给定事件上的AsyncEvent.getAsyncContext获得。
  • 在异步操作超时的情况下,容器必须通过以下步骤运行:

    • 在所有与启动异步操作的ServletRequest注册的AsyncListener实例上调用AsyncListener.onTimeout方法。
    • 如果没有一个监听器调用AsyncContext.complete()或任何AsyncContext.dispatch方法,则执行一个状态代码等于HttpServletResponse.SC_INTERNAL_SERVER_ERROR的错误分派。
    • 如果没有找到匹配的错误页,或者错误页没有调用 AsyncContext.complete() 或任何 AsyncContext.dispatch 方法,则容器必须调用 AsyncContext.complete()。
  • 如果在调用AsyncListener中的方法时抛出一个异常,它会被记录下来,并且不会影响任何其他AsyncListeners的调用。

  • JSP中的异步处理在默认情况下是不被支持的,因为它被用于内容生成,异步处理必须在内容生成之前完成。如何处理这种情况由容器决定。一旦所有的异步活动都完成了,就可以使用AsyncContext.dispatch向JSP页面进行调度,以生成内容。

  • 下面的图是描述各种异步操作的状态转换的图。

线程安全

除了startAsynccomplete方法外,请求和响应对象的实现不能保证是线程安全的。这意味着它们要么只能在请求处理线程的范围内使用,要么应用程序必须确保对请求和响应对象的访问是线程安全的。如果应用程序创建的线程使用容器管理的对象,例如请求或响应对象,那么这些对象必须只能在对象的生命周期内被访问,这一点分别在第3-31页的第3.12节 "Lifetime of the Request Object"和第5-50页的第5.7节 "Lifetime of the Response Object"中定义。请注意,除了startAsync和complete方法外,请求和响应对象不是线程安全的。如果这些对象在多个线程中被访问,访问应该是同步的,或者通过一个包装器来增加线程安全,例如,同步调用方法来访问请求属性,或者在一个线程中为响应对象使用一个本地输出流。

升级处理

在HTTP/1.1中,Upgrade general-header允许客户指定其支持并希望使用的额外通信协议。如果服务器认为切换协议是合适的,那么在随后的通信中就会使用新的协议。

servlet容器提供了一个HTTP升级机制。然而,servlet容器本身并不了解升级后的协议。协议处理被封装在HttpUpgradeHandler中。在servlet容器和HttpUpgradeHandler之间的数据读取或写入是以字节流的形式进行的。

当收到一个升级请求时,servlet可以调用HttpServletRequest.upgrade方法,从而启动升级过程。这个方法实例化了给定的HttpUpgradeHandler类。返回的HttpUpgradeHandler实例可以被进一步定制。应用程序准备并向客户端发送一个适当的响应。在退出servlet的service方法后,servlet容器完成了对所有过滤器的处理,并标记连接由HttpUpgradeHandler处理。然后它调用HttpUpgradeHandlerinit方法,传递一个WebConnection以允许协议处理程序访问数据流。servlet过滤器只处理最初的HTTP请求和响应。它们并不参与后续的通信。换句话说,一旦请求被升级,它们就不会被调用。

HttpUpgradeHandler可以使用非阻塞的IO来消耗和产生消息。

在处理HTTP升级时,开发者有责任对ServletInputStreamServletOutputStream进行线程安全访问。

当升级处理完成后,HttpUpgradeHandler.destroy将被调用。

结束服务

servlet容器不需要在任何特定的时间段内保持servlet的加载。一个servlet实例可以在servlet容器中保持活跃,时间可以是几毫秒,也可以是servlet容器的生命周期(可能是几天、几个月或几年),或者是两者之间的任何时间。

当servlet容器确定一个servlet应该从服务中移除时,它会调用Servlet接口的destroy方法,以允许servlet释放它正在使用的任何资源并保存任何持久性状态。例如,当容器想节省内存资源时,或者当它正在关闭时,它可以这样做。

在Servlet容器调用destroy方法之前,它必须允许当前在Servlet的service方法中运行的任何线程完成执行,或者超过服务器定义的时间限制。

一旦在一个servlet实例上调用了destroy方法,容器就不能将其他请求路由到该servlet实例。如果容器需要再次启用该servlet,它必须用该servlet类的一个新实例来进行。

在destroy方法完成后,servlet容器必须释放servlet实例,这样它就有资格进行垃圾回收。

以上是关于Servlet规范之Servlet顶层接口的主要内容,如果未能解决你的问题,请参考以下文章

Servlet编程专题1之Servlet生命周期

Servlet编程专题8之Servlet规范中的监听器

JavaEE之JavaWeb核心之Servlet

JavaWeb核心之Servlet

javaWeb核心之servlet

JavaWeb核心之Servlet