• 第二章 Servlet 接口(JavaTM Servlet 规范3.1 )


    Servlet 接口

    Servlet 接口是 Java Servlet API 的核心抽象。所有 servlet 要么直接要么间接地实现该接口,通过扩展一个类实现此接口。在 Java Servlet API 中有两个类 GenericServlet 和 HttpServlet 实现了此 Servlet 接口。为了更多目的,开发者将扩展 HttpServlet 来实现他们的 servlet。

    2.1 请求处理方法

    为处理客户端请求,基础 Servlet 接口定义了一个 service 方法。servlet 容器将每个请求路由到一个 servlet 的实例,然后调用此方法。

    对Web应用并发请求的处理,通常需要 Web 开发者设计在特定时间 service 方法中能处理多线程执行的 servlet。

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

    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方法产生的头。doOptions 方法返回此 servlet 支持哪些 HTTP 方法。doTrace 生成一个响应包含 TRACE 请求发送的所有头实例。


    2.1.3  条件 GET 支持 


    HttpServlet 接口定义了 getLastModified 方法支持条件 GET 操作。只有如果资源在指定的时间后被修改,条件 GET 操作请求的资源才会被发送。在合适的情况下,该方法的实现可以辅助网络资源的有效利用。

    2.2 实例个数

    既可以通过(在第8章“注解和可插拔性”描述)注解也可以通过包含此 servlet 的 Web 应用的部署描述符的一部分的(在第14章“部署描述符”中描述的)servlet 声明,控制 servlet 容器如何提供此 servlet 的实例。

    对于没有寄宿在分布式环境(默认情况)的 servlet,servlet 容器必须对每个 servlet 声明仅使用一个实例。但是为 servlet 实现 SingleThreadModel 接口,servlet 容器可以实例化多个实例来处理大量请求加载和序列化多个请求到一个特别实例。

    当 servlet 作为在部署描述符中标记为分布式应用的一部分被部署时,容器可能每个 JVM 每个 servlet 声明只有(实例化)一个实例。但是,如果分布式应用中的 servlet 实现了 SingleThreadModel 接口,容器可以在容器的每个JVM中实例化多个 servlet 的实例。(当servlet在分布式应用中部署时,容器只为每个 servlet 实例化一个实例,但是如果分布式引用中的 servlet 实现了SingleThreadModel接口,容器可以实例化多个servlet实例。)

    2.2.1 注意单线程模型

    SingleThreadModel 接口的使用保证在同一时刻在给定的 servlet 实例的 service 方法中只有一个线程在执行。要注意的是这个保证只适用于每个 servlet 实例,由于容器可以选择池化这些对象。可以同时被多个 servlet 访问的对象,比如 HttpSession 实例,可以在任意特定时间对多个 servlet 可用,包括哪些实现了 SingleThreadModel 的 servlet 。

    建议开发者使用其他的方式替代实现该接口来解决这些问题,比如避免变量实例的使用或同步访问这些资源的代码块。SingleThreadModel 接口在规范的本版本中已经被废弃。

    2.3 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的连接)和执行其他一次性活动。容器通过使用实现 ServletConfig 接口的唯一对象(每 servlet 声明)调用 Servlet 接口的 init 方法初始化 servlet 实例。配置对象允许 servlet 访问来自 Web 应用配置信息的键值对初始化参数。配置对象还提供给 servlet 访问一个描述 servlet 运行环境的对象(实现了 ServletContext 接口)。参看第 4 章“Servlet 上下文”获取关于 ServletContext 接口的更多信息。

    2.3.2.1  初始化错误的情况

    初始化期间,servlet 可能抛出 UnavailableException  或 ServletException 。在这种情况下,servlet 容器不能把 servlet 放进活动服务中并且必须释放它。当被认为未成功初始化时,不会调用 destroy 方法。在初始化失败后,容器可能会实例化和初始化一个新的实例。此规则的例外是当 UnavailableException 指示了一个不可用最小时间时,容器在创建和初始化一个新的 servlet 实例之前必须等待此时间过去。

    2.3.2.2  工具注意事项

    工具加载或者内省Web应用时静态初始化方法的触发是有别于调用 init 方法的。直到 servlet 接口的 init 方法被调用,开发者不能假定 servlet 位于活动容器运行中。例如,当只有静态初始化方法被调用时,servlet 不应该尝试建立到数据库或企业 JavaBean 容器的连接。


    2.3.3  请求处理

    servlet 被正确初始化之后,servlet 容器可以使用它来处理客户端请求。请求使用 ServletRequest 类型的请求对象来表示。servlet 通过调用 ServletResponse 类型的提供对象的方法来填充对请求的响应。这些对象作为参数传递给 Servlet 接口的 service 方法。  

    在HTTP请求时,容器提供 HttpServletRequest 和 HttpServletResponse 类型的对象。

    注意被 servlet 容器放到服务中的 servlet 实例,在其整个生命周期内有可能不处理请求。

    2.3.3.1  Multithreading Issues 多线程问题

    servlet 容器可以通过 servlet 的 service 方法发送并发请求。为了处理这些请求,Servlet 开发者必须在 service 方法中为使用多线程并发处理做好充分准备。虽然不推荐使用,对开发者来说一个替代方案是实现 SingleThreadModel 接口,它要求容器保证在同一时刻在 service 方法中只有一个请求线程。servlet 容器可以通过串行化 servlet 上的请求或维护 servlet 实例的池来满足要求。如果 servlet 是标记为分布式的 Web 应用的一部分,容器可以在分布式应用的每个 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 容器可以选择不通过该 servlet 路由任何请求。在此期间被容器拒绝的请求必须返回带有表示何时可用的 Retry-After 头信息的 SC_SERVICE_UNAVAILABLE (503) 响应。

    容器可以选择忽略永久和暂时不可用的区别并且将所有的 UnavailableExceptions 视为永久的,从而将抛出 UnavailableException 的 servlet 从服务中移除。

    2.3.3.3 异步处理

    有时 filter 和 servlet 在生成响应之前必须等待某些资源或事件以便完成请求处理。例如,servlet 在生成响应之前,可能需要等待一个可用的 JDBC 连接,一个远程 web 服务的响应,一个 JMS 消息或一个应用事件。在 servlet 中等待占用线程和其他受限资源的阻塞操作是低效的。慢资源比如数据库可能经常让很多线程阻塞等待访问并且导致线程饥饿和降低整个 web 容器的服务质量。

    引入请求的异步处理允许线程返回容器并执行其他任务。当在请求上开始异步处理时,另外的线程或回调要么生成响应并调用 complete 要么使用 AsyncContext.dispatch 方法分派此请求以便它可以在容器上下文中运行。   典型异步处理的事件循序是:

    1. 请求被接收并且通过验证等普通 fitlers 传递到 servlet。


    2. servlet 处理请求参数和(或)内容来确定请求的类型。


    3. servlet 发出对资源或数据的请求,例如发送一个远程 web 服务请求或加入队列等待 JDBC 连接。


    4. servlet 不产生响应返回。


    5. 一段时间之后,请求资源变的可用,处理线程继续事件处理,要么在相同的线程中要么通过使用 AsyncContext 分派到容器中的一个资源。


    企业版功能比如 15.2.2 节 “Web应用环境”和 15.3.1 节 “EJB调用中的安全标识传播”只对执行初始请求的线程或当请求通过 AsyncContext.dispatch 方法被容器分派时可用。Java 企业版功能对通过 AsyncContext.start(Runnable)方法在响应对象上直接操作的线程可用。

    在 8 章声明的 @WebServlet 和 @WebFilter 注解有一个布尔型的属性 asyncSupported ,它的默认值是 false。当 asyncSupported 被设置为 true 时,应用可以通过调用 (AsyncContext的) startAsync 在一个单独的线程中开启异步处理,给它传递请求和响应对象的引用,然后从容器的原线程上退出。这意味着响应将以相反的顺序穿过与进入时相同的 filters(或 filter链)。响应在 AsyncContext 调用 complete 之前不会被提交。如果异步任务执行前[调用 startAsync 的容器启动的分派]已经返回了容器,应用负责处理对请求和响应对象的并发访问。(?)

    允许从 asyncSupported 设置为 true 的 servlet 分派到 asyncSupported 设置为 false 的 servlet。 在此情况下,不支持异步的 servlet 的 service 方法退出时 respose 被提交,并且容器负责调用AsyncContext 的 complete 方法以便任何感兴趣的 AsyncListener 实例被通知到。过滤器也应该使用AsyncListener.onComplete 通知作为清理(为完成异步任务而使用的)资源的一种机制。

    从同步 servlet 派发到 异步 servlet 是非法的。然而 IllegalStateException 的抛出被推迟到应用调用startAsync 时。这将允许 servlet 要么作为一个同步servlet要么作为一个异步servlet。

    在一个不同于初始请求使用的线程上,应用正在等待的异步任务可以直接写到响应。此线程不知道任何过滤器。如果一个过滤器想在此新线程上处理响应,当它处理“进入”初始请求时它必须包装请求,并且传递此包装响应到过滤器链中的下一个过滤器,最后到达 servlet。因此如果响应被包装(可能多次,每个过滤器一次)并且应用处理请求并且直接写到响应,实际上是写到响应的包装器,比如,添加到响应的任何输出仍将被响应包装器处理。当应用在单独线程中读取请求,给响应添加输出时,实际上是从请求包装器读取和写到响应包装器,因此任何输入和(或)输出的操作将通过包装器持续发生。

    此外,如果应用选择如此做,它可以使用 AsyncContext 将请求从新线程到分派到容器的一个资源。这将允许在容器范围内使用类似JSP的内容生成技术。

    除了注解属性之外,我们还增加了以下方法/类:

    ■ ServletRequest

    ■ public AsyncContext startAsync(ServletRequest req, ServletResponse res). 此方法将请求转换为异步模式并且使用给定的请求和响应对象和getAsyncTimeout 返回的超时时间初始化它的AsyncContext。ServletRequest 和 ServletResponse 参数必须要么与传给调用 servlet 的 service 或 filter 的 doFilter 方法的参数相同,要么是包装他们的 ServletRequestWrapper 或 ServletResponseWrapper 类的子类。对该方法的调用应确保应用退出 service 方法时响应没有被提交。当返回的AsyncContext调用AsyncContext.complete 或AsyncContext的超时时间已到并且没有相关的 listeners处理超时事件时,响应被提交。异步定时器将不会启动超时,直到请求和它相关的响应已经从容器中返回。AsyncContext 可以用来从异步线程上向响应写数据。它也可以用来只是提醒响应没有关闭和提交。

    如果此请求处在不支持异步操作的 servlet 或 filter 作用域中,调用 startAsync,或者响应已经提交和关闭,或者在同一 dispatch 中再次调用都是非法的。从调用 startAsync 返回的 AsyncContext 可以被用做进一步的异步处理。调用返回的 AsyncContext 的AsyncContext.hasOriginalRequestResponse()将返回 false,除非传入的ServletRequest 和ServletResponse 是原始的对象或者没有携带应用提供的包装器。在请求被转换成异步模式之后,有些在(过滤器)入站调用期间添加的请求和/或响应包装器可能需要在异步操作期间保持不变并且他们关联的资源可能不能被释放,任何在出站方向上调用的过滤器可以用此做一个标记。在过滤器入站调用期间应用的 ServletRequestWrapper 可以被过滤器的出站调用释放,仅在如果给定的 ServletRequest 是用来初始化 AsyncContext 的那个并且可以通过调用 AsyncContext.getRequest() 返回的那个,不包括所说的 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.FORWARD 或 DispatcherType.INCLUDE,而通过AsyncContext.dispatch 方法之一被分派的异步请求的分派器类型被设定为 DispatcherType.ASYNC。最后被容器错误处理机制分派到错误页面(error page)的请求的分派器类型被设定为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 指定,默认设置为30000毫秒(30秒)。等于小于 0 的值表示异步操作永远不会超时。一旦容器启动调度在任一 ServletRequest.startAsync 方法被调用期间返回到容器,超时时间将应用到AsyncContext。异步周期已经在容器启动调度上开启,容器启动调度已经返回到容器之后,调用此方法设置超时时间是非法的,将导致IllegalStateException。


    ▪   public long getTimeout() -获取与 AsyncContext 关联的超时时间,以毫秒为单位,该方法返回容器的默认超时时间或者通过最近一次 setTimeout 调用设置的超时时间。

    ▪   public void addListener(AsyncListener listener, ServletRequest req, ServletResponse res) -为onTimeout, onError, onComplete 或 onStartAsync 事件通知设置给定的 listener 。前面的三个与最近的通过调用任一 ServletRequest.startAsync 方法启动的异步周期关联。 onStartAsync 与通过调用 ServletRequest.startAsync 方法之一的新的异步周期关联。异步 listener 将按照添加给请求的顺序被依次提醒。当 AsyncListener 被通知时,传递给该方法的请求和响应对象与从 AsyncEvent.getSuppliedRequest() 和 AsyncEvent.getSuppliedResponse() 方法获得的对象一致。这些对象不应该被读取或写入,因为自从 AsyncListener 注册后,可能已经发生了额外的包装,但是这些对象可以用来释放与他们关联的资源。在在容器启动分派的异步周期已经启动并且返回到容器和新异步周期启动之前,调用该方法是非法的,将会导致IllegalStateException异常。

    ▪   public <T extends AsyncListener> createListener(Class<T> clazz) - 实例化给定的AsyncListener 类。返回的 AsyncListener 实例在使用 addListener 方法注册到 AsyncContext 之前可以做进一步的自定义。给定的 AsyncListener 必须有一个用来实例化的无参构造器。该方法支持任何适用于 AsyncListener 的注解。

    ▪   public void addListener(AsyncListener) -为onTimeout, onError, onComplete 或 onStartAsync 事件通知设置给定的 listener 。前面的三个与最近的通过调用任一 ServletRequest.startAsync 方法启动的异步周期关联。 onStartAsync 与通过调用 ServletRequest.startAsync 方法之一的新的异步周期关联。如果调用请求的 startAsync(req, res) 或 startAsync()方法,当 AsyncListener 被通知时,从 AsyncEvent 可以获取准确一致的请求和响应对象。此请求和响应对象有可能被包装过。异步 listener 将会按照添加给 request 的顺序依次被通知到。在在容器启动分派的异步周期已经启动并且返回到容器和新异步周期启动之前,调用该方法是非法的,将会导致IllegalStateException异常。

    ▪   public void dispatch(String path) – 将用来初始化 AsyncContext 的请求和响应分派到给定路径的资源。此路径将以相对与初始化 AsyncContext 的 ServletContext 解析。与请求查询方法相关的所有路径必须反映分派目标,而原始请求URI,上下文路径(context path),路径信息(path info)和请求字符串可以用请求属性(在 9.7.2 节“分派请求参数”中定义)中获取。这些属性必须总是反映原始路径元素,即便是在多次分派之后。

    ▪  public void dispatch() - 为分派初始化 AsyncContext 的请求和响应提供一个便利方法。如果 AsyncContext 是通过  startAsync(ServletRequest, ServletResponse) 初始化的并且传递的 请求对象是 HttpServletRequest 的实例,那么分派到的 URI 是HttpServletRequest.getRequestURI()的返回值。否则分派到的 URI 是最后一次被容器分派的请求的URI。下面的例子演示了在不同的情况下分派的目标 URI 是什么:

    CODE EXAMPLE 2-1 



    // REQUEST to /url/A

    AsyncContext ac = request.startAsync();

    ...

    ac.dispatch();// ASYNC 分派到 /url/A

     

    CODE EXAMPLE 2-2

     

    // REQUEST 到 /url/A

    // FORWARD 到 /url/B

    request.getRequestDispatcher(“/url/B”).forward(request,

    response);

    //从 FORWARD 的目标内启动异步操作

    AsyncContext ac = request.startAsync();

    ac.dispatch(); // ASYNC 分派到 /url/A

     

    CODE EXAMPLE 2-3

    // REQUEST to /url/A

    // FORWARD to /url/B

    request.getRequestDispatcher(“/url/B”).forward(request,

    response);

    //从 FORWARD 的目标内启动异步操作

    AsyncContext ac = request.startAsync(request, response);

    ac.dispatch(); // ASYNC 分派到 /url/B

     

    ▪  public void dispatch(ServletContext context, String path) -将初始化 AsyncContext 的请求和响应分派到在给定 ServletContext 中给定路径的资源。 


    ▪   对上面定义的三个 dispatch 方法变体来说,将请求和响应对象传递到容器托管线程后,调用这些方法将立即返回,分派操作将在此线程上执行。请求的分派器类型将设置为 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 方法,那么任何 dispatch 方法的调用都有如同上面相同的限制。

    ▪  在 dispatch 方法执行期间可能产生的任何错误或异常都必须被容器捕获和处理,如下所示:


    ▪  i. 调用所有由 AsyncContext 创建的并注册到 ServletRequest 的所有 AsyncListener 实例的 AsyncListener.onError(AsyncEvent) 方法并且可以通过 AsyncEvent.getThrowable() 获取可用的
Throwable。

    ▪  ii. 如果没有 listeners 调用 AsyncContext.complete 或 任何 AsyncContext.dispatch 方法, 那么使用等于 HttpServletResponse.SC_INTERNAL_SERVER_ERROR 状态码执行一个错误分派,并且将可用的 Throwable 作为请求属性RequestDispatcher.ERROR_EXCEPTION 的值。


    ▪   iii. 如果没有找到匹配的错误页面(error page),或错误页面没有调用 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 方法之前任何时候调用该方法都是合法的。如果在调用 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) -用来通知 listener 在 ServletRequest 开启的异步操作已经完成。

    ■ public void onTimeout(AsyncEvent event) -用来通知 listener 在 ServletRequest 开启的异步操作已经超时。

    ■ public void onError(AsyncEvent event) -用来通知 listener 异步操作已经失败。

    ■ public void onStartAsync(AsyncEvent event) -用来通知 listener 一个新的异步周期被通过调用一个ServletRequest.startAsync 方法初始化。与异步操作相对应的被重新初始化的 AsyncContext (The AsyncContext corresponding to the asynchronous operation that is being reinitialized)可以通过给的事件(event参数)的 AsyncEvent.getAsyncContext 方法获得。

    ▪   在异步操作超时事件中,容器必须执行下面的步骤:

      ▪ 调用异步操作初始化时注册在 ServletRequest 上的所有 AsyncListener 实例的 AsyncListener.onTimeout 方法。  

      ▪ 如果没有 listener 调用 AsyncContext.complete() 或 任何 AsyncContext.dispatch 方法,使用等于 HttpServletResponse.SC_INTERNAL_SERVER_ERROR 的状态码执行一个错误分派。 


      ▪如果没有发现匹配的错误页面,或错误页面没有调用 AsyncContext.complete() 或任一 AsyncContext.dispatch 方法,容器必须调用 AsyncContext.complete()。 


    ▪   当调用一个 AsyncListener 中的方法时,如果抛出了异常,异常被记录并且不会影响任何其他 AsyncListeners 的调用。 


    ▪   默认情况下,不支持JSP中的异步处理,因为 JSP 是用来内容生成并且异步处理可能(应该?)在产生内容之前已经完成。取决于容器如何处理这种情况。一旦所有的异步活动完成,可以使用 AsyncContext.dispatch 分派到的JSP 页面来产生内容。(Once all the async activities are done, a dispatch to the JSP page using the AsyncContext.dispatch can be used for generating content.)

    ▪   下面所示的图 2-1 描绘了各种异步操作的状态转换:


     

    2.3.3.4  线程安全


    不同于 startAsync 和 complete 方法,请求和响应对象的实现不保证是线程安全。这意味着它们要么只能在请求处理线程范围使用,要么应用必须保证对请求和响应对象的访问是线程安全的。


    如果应用创建的线程使用容器托管对象,比如请求或响应对象,这些对象只能在此对象的生命周期内被访问。“请求对象的声明周期”和 “响应对象的声明周期”分别在 3.12 节 和 5.7 节被定义。注意不同于 startAsync  和 complete  方法,请求和响应对象不是线程安全的(呵呵,又一遍)。如果这些对象被多线程访问,访问应该被同步或通过包装器添加线程安全,例如同步调用访问请求属性的方法,或者在一个线程中为响应对象使用一个局部输出流。

    2.3.3.5  升级处理


    在 HTTP 1.1中,升级通用头(general-header)允许客户端指定它支持和想使用的其它通信协议。如果服务器发现它适合切换协议,那么新协议将在后续通信中被使用。
servlet 容器提供了 HTTP 升级机制。但是 servlet 容器本身不知道任何升级协议。此协议处理被封装在 HttpUpgradeHandler 里。servlet 容器和 HttpUpgradeHandler之间使用字节流进行数据读写。当接收到升级请求,servlet 可以调用HttpServletRequest.upgrade 方法开始升级处理,此方法实例化给定的 HttpUpgradeHandler 类,返回的 HttpUpgradeHandler  实例可以进一步自定义。应用准备和发送给客户端一个合适的响应。退出 servlet 的 service 方法之后,容器完成所有过滤器的处理并且标记此连接将被 HttpUpgradeHandler 处理。然后调用 HttpUpgradeHandler 的 init 方法,传递一个 WebConnection 允许协议处理器访问数据流。servlet 过滤器只处理初始 HTTP 请求和响应,它们不会参与后续的通信,换句话说,一旦请求被升级它们将不会被调用。


    HttpUpgradeHandler 可以使用非阻塞 IO 消费和生产信息。

    当处理 HTTP 升级时,开发者有责任线程安全的访问 ServletInputStream 和 ServletOutputStream。

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

    2.3.4 结束服务

    servlet 容器没有必要保持 servlet 任何特定的时长处于加载状态。servlet 实例可以在 servlet 容器内保持活跃毫秒级时长,servlet 容器生命周期(可能是几天,几个月或者几年),或者处于两者之间的任意时长。

    当 servlet 容器决定一个 servlet 应该从服务中移除时,它调用 Servlet 接口的 destory 方法允许 servlet 释放它使用的资源并且保存持久化状态。比如,容器在想节省内容资源或被关闭时它可以这么做。

    当 servlet 容器调用 destory 方法之前,它必须允许当前运行在 servelet 的 sevice 方法中的任何线程完成执行或者达到服务器定义的时间限制。

    当 servlet 实例的 destory 方法一旦被调用,容器不能路由其它请求到 servlet 的那个实例。如果容器想再次使用此 servlet,它必须使用该 servlet 类的一个新实例。

    在 destory 方法完成之后,servlet 容器必须释放 servlet 实例以便可以进行垃圾收集。

  • 相关阅读:
    微服务热部署,jrebel激活破解
    微服务改代码热部署
    MYBATIS框架的映射关系
    postgrepsql数据库保留两位有效数字
    It's likely that neither a Result Type nor a Result Map was specified
    idea启动微服务项目报错
    微服务自动加载common包
    No function matches the given name and argument types. You might need to add explicit type casts postgrepsql数据库
    "Connect to localhost:9000 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused: connect",
    链表的排序 (选择和冒泡)
  • 原文地址:https://www.cnblogs.com/whilliy/p/5699134.html
Copyright © 2020-2023  润新知