在开始本章节内容之前,先看下如下代码:
<servlet>
<servlet-name>SpringDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:spring-servlet.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
看这个配置熟悉不熟悉,那么为什么这么配置呢?我们往下看
public class DispatcherServlet extends FrameworkServlet;
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware;
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware;
主要的处理在HttpServletBean,FrameworkServlet 里面,奇不奇怪,为什么这么配置下就可以了呢?为什么这些类是这个样子呢?
带着疑问,我们开始本系列第二节:servlet接口
Servlet 接口
Servlet 接口是 Java Servlet API 的核心抽象。所有 Servlet 类必须直接或间接的实现该接口,或者更通常做法是通过继承一个实现了该接口的类从而复用许多共性功能。目前有 GenericServlet 和 HttpServlet 这两个类实现了 Servlet 接口。大多数情况下,开发者只需要继承 HttpServlet 去实现自己的 Servlet 即可。
规范解读: 这段描述了什么是servlet接口,及servlet实现类的方式!即开发者需要怎么做!
2.1 请求处理方法
Servlet 基础接口定义了用于客户端请求处理的 service 方法。当有请求到达时,该方法由 servlet 容器路由到一个 servlet 实例。Web 应用程序的并发请求处理通常需要 Web 开发人员去设计适合多线程执行的 Servlet,从而保证 service方法能在一个特定时间点处理多线程并发执行。(注:即 Servlet 默认是线程不安全的,需要开发人员处理多线程问题)通常 Web 容器对于并发请求将使用同一个 servlet 处理,并且在不同的线程中并发执行 service 方法。
规范解读:这个章节里面其实说明了几件事情:
1: 接收请求的不是servlet实例,而是servlet容器
2: 具体路由到那个servlet,有servlet容器分配(具体分配规则,在后续章节在说)
3: 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 方法产生的 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 实例。
规范解读:对于这段我们要了解一下几个事情:
1: servlet的初始化工作是servlet容器进行的
2: 一般情况下,一个servlet仅有一个实例,可以理解成单例模式
3: 如果servlet实现了SingleThreadModel 的话,servlet容器以池的方式实例化多个servlet!
了解了这几点,那么为什么说servlet是非线程安全的,也就有答案了: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 容器不能使用实例池方案,并且只能使用序列化请求进行处理。
规范解读:
1: web应用一定会存在多线程的问题,为避免多线程中,脏读等问题,不建议在servlet中使用成员变量,如果必须使用,请使用原子变量
2: 不要在doget,dopost等方法中使用synchronized 关键字,这样会严重影响性能!
3: 如果必须保证servlet一次只能被一个线程执行,请实现SingleThreadModel
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 方法运行。一个典型的异步处理事件顺序是:
1. 请求被接收到,通过一系列如用于验证的 filter 之后被传递到 Servlet。
2. servlet 处理请求参数及(或)内容体从而确定请求的类型。
3. 该 servlet 发出请求去获取一些资源或数据,例如,发送一个远程 web 服务请求或加入一个等待 JDBC 连接的队列。
4. servlet 不产生响应并返回。
5. 过了一段时间后,所请求的资源变为可用,此时处理线程继续处理事件,要么在同一个线程,要么通过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 是非法的。不过与该点不同的是当应用调用 startAsync时将抛出 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 <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 启动的一个新的异步周期相关联的。异步监听器将以它们添加到请求时的顺序得到通知。当 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 方法期间可能抛出的错误或异常必须由容器抓住和处理,如下所示:
i. 调 用 所 有 由 AsyncContext 创 建 的 并 注 册 到 ServletRequest 的 AsyncListener 实 例 的
AsyncListener.onError(AsyncEvent) 方法, 可以通过 AsyncEvent.getThrowable()获取到捕获的
Throwable。
ii. 如果没有监听器调用 AsyncContext.complete 或任何 AsyncContext.dispatch 方法,然后执行一
个状态码为 HttpServletResponse.SC_INTERNAL_SERVER_ERROR 的出错分派,并且可以通过
RequestDispatcher.ERROR_EXCEPTION 请求属性获取 Throwable 值。
iii. 如果没有找到匹配 的错误页面 ,或错误页 面没有调 用 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(ServletRequest req)- 检 查 该 包 装 器 是 否 递 归 的 包 装 了 给 定 的ServletRequest,如果是则返回 true,否则返回 false。
■ ServletResponseWrapper
■ 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-1 描述了各种异步操作的状态转换。
2.3.3.4 线程安全
除了 startAsync 和 complete 方法,请求和响应对象的实现都不保证线程安全。这意味着它们应该仅在请求处理线程范围内使用或应用确保线程安全的访问请求和响应对象。
如果应用使用容器管理对象创建一个线程,例如请求或响应对象,这些对象必须在其生命周期内被访问,
就像定义在 3-31 页 3.12 节的“ Request 对象的生命周期”和 5.7 节的“ Response 对象的生产周期”。 请注意,除了 startAsync 和 complete 方法,请求和响应对象不是线程安全的。如果这些对象需要多线程访问,需要同步这些访问或通过包装器添加线程安全语义,比如,同步化调用访问请求属性的方法,或者在线程内为响应对象使用一个局部输出流。
2.3.3.5 升级处理
在 HTTP/1.1, Upgrade 通用头( general-header)允许客户端指定其支持和希望使用的其他通信协议。如果服务器找到合适的切换协议,那么新的协议将在之后的通信中使用。 Servlet 容器提供了 HTTP 升级机制。不过, Servlet 容器本身不知道任何升级协议。协议处理封装在 HttpUpgradeHandler 协议处理器。在容器和
HttpUpgradeHandler 协议处理器之间通过字节流进行数据读取或写入。
当收到一个升级( upgrade)请求, servlet 可以调用 HttpServletRequest.upgrade 方法启动升级处理。 该方法实例化给定的 HttpUpgradeHandler 类,返回的 HttpUpgradeHandler 实例可以被进一步的定制。应用准备和发送一个合适的响应到客户端。退出 servlet service 方法之后, servlet 容器完成所有过滤器的处理并标记连接已交给 HttpUpgradeHandler 协议处理器处理。然后调用 HttpUpgradeHandler 协议处理器的 init 方法,传入一个 WebConnection 以允许 HttpUpgradeHandler 协议处理器访问数据流。Servlet 过滤器仅处理初始的 HTTP 请求和响应,然后它们将不会再参与到后续的通信中。换句话说,一旦请求被升级,它们将不会被调用。
协议处理器( ProtocolHandler)可以使用非阻塞 IO( non blocking IO)消费和生产消息。
当处理 HTTP 升级时,开发人员负责线程安全的访问 ServletInputStream 和 ServletOutputStream。
当升级处理已经完成,将调用 HttpUpgradeHandler.destroy 方法。
2.3.4 终止服务( End of Service)
Servlet 容器没必要保持装载的 Servlet 持续任何特定的一段时间。一个 Servlet 实例可能会在 servlet 容器内保持活跃( active)持续一段时间(以毫秒为单位), Servlet 容器的寿命可能是几天,几个月,或几年,或者是任何之间的时间。
当 Servlet 容器确定 servlet 应该从服务中移除时,将调用 Servlet 接口的 destroy 方法以允许 Servlet 释放它使用的任何资源和保存任何持久化的状态。例如,当想要节省内存资源或它被关闭时,容器可以做这个。
在 servlet 容器调用 destroy 方法之前,它必须让当前正在执行 service 方法的任何线程完成执行,或者超过了服务器定义的时间限制。
一旦调用了 servlet 实例的 destroy 方法,容器无法再路由其他请求到该 servlet 实例了。如果容器需要再次使用该 servlet,它必须用该 servlet 类的一个新的实例。在 destroy 方法完成后, servlet 容器必须释放 servlet实例以便被垃圾回收
总结:
1: servlet通常是单例的
2; servlet容器负责初始化servlet
3: servlet是非线程安全的
4: servlet可以异步执行
对于servlet的异步问题,不做过多的解释,原因是:没具体用过,不敢误导大家,有了解了在补充吧