Servlet 4 摘译之Servlet接口篇

撰写于 2018-02-15 修改于 2018-12-15 标签 翻译

Servlet是可以由Java实现的Web服务器组件动态加载的Java类,能够生成动态内容。

Servlet容器,有时也叫servlet引擎,负责调用Servlet API。Servlet容器通过“请求-响应”方式实现了servlet和web客户端的通信。Servlet容器需要实现MIME类型的请求和响应的编解码。所有servlet容器都应实现HTTP1.1和HTTP2.0协议,但对于HTTPS的支持不做强制要求。

Servlet接口

javax.servlet.GenericServletjavax.servlet.http.HttpServlet是两个常用的javax.servlet.Servlet接口的实现类,其中前者是个抽象类,定义了service抽象方法。通常,开发人员会继承后者实现自己的方法。

service方法其实是Servlet接口定义的,用于处理来自客户端的请求。每一次请求都会调用一次,因此需要合适的并发机制应对并发请求。

1 HttpServlet抽象类

HttpServlet抽象类提供了若干具体的Http协议相关的方法可以被service方法自动调用:

方法 Http请求
doGet GET
doPost POST
doDelete DELETE
doHead HEAD
doOptions OPTIONS
doTrace TRACE

HttpServlet抽象类的doHead方法只返回doGet方法的响应的头部;doOptions方法返回的是servlet所支持的http方法;doTrace方法返回的是客户端发送的TRACE方法的头部,主要用来debug。

Servlet设计目标是作为请求的终点,因此不支持CONNECT方法(主要用于代理)。

2 一个Servlet

通常,每一个Sevlet容器在每一个JVM环境里只生成一个Servlet对象。因此,开发人员需要自己处理好并发请求带来的同步问题。

如果实现了SingleThreadModel接口,那么Servlet容器会维护一个对象池,但此时容器在同一时间只允许一个线程调用其中一个对象。

SingleThreadModel已经是官方弃用API

3 Servlet生命周期

Servlet生命周期通过接口的initservicedestory方法来管理。

3.1 加载与实例化

Sevlet容器负责Servlet实现类的加载与实例化,可以是容器启动后立即执行,也可以延迟执行。

Sevlet容器启动时,必须知晓Servlet实现类的文件位置。可以是本地文件系统,也可以是其他网络系统。

Sevlet容器使用一般的Java类加载机制来加载Servlet实现类,加载完成后会立即实例化。

3.2 初始化

Servlet类实例化完成后,容器应该立刻初始化servlet,并在响应请求之前完成。

Servlet对象初始化,一般会加载持久化的配置信息,初始化开销较大的对象以及执行其他一次性操作。

初始化动作通过调用init方法来完成,参数是一个javax.servlet.ServletConfig对象。

初始化过程有可能会抛出UnavailableException或者ServletException异常。这时,Servlet对象不会进入活跃状态,而是会被容器释放。

初始化失败后,容器稍后可以尝试实例化并初始化一个新的Servlet对象。

特别注意

init方法的调用不同于一般意义的静态方法。
开发人员必须在init方法成功调用后,才能认为servlet进入可用状态。

3.3 处理请求

Servlet对象成功的初始化后,容器就可以处理用户请求了。用户请求包含在ServletRequest的对象中,而Servlet对象的响应通过调用ServletResponse对象方法来完成。这两个对象都是service方法的参数。对于http请求,有相应的HttpServletRequestHttpServletResponse对象。

需要注意的是,Servlet对象在其整个生命周期内也许不会处理到客户端的请求。

3.3.1 多线程

Servlet容器应当能够使用多个线程并发地处理请求。

3.3.2 处理请求的异常

Servlet对象在处理请求的过程中有可能会抛出ServletException或者UnavailableException

前者表示Servlet对象在处理过程中出错,该异常应当Servlet容器被妥善处理。

后者表示Servlet对象无法处理请求。如果是永久性的无法处理,容器应该调用destory方法释放Servlet对象,解除活跃状态。所有因此而拒绝的请求都应该接受到SC_NOT_FOUND404返回状态码。如果是临时性的无法处理,容器应该拒绝向Servlet对象转发请求,并返回SC_SERVICE_UNAVAILABLE503返回状态码,而且响应头部应该包含Retry-After字段,说明该临时性状态何时解除。

3.3.3 异步处理

Servlet容器支持异步的处理请求。容器可以有多条线程:一些在组织响应报文,调用complete方法;另一些可能在调用AsyncContext.dispatch方法分发请求。

一个典型的异步调用流程应该是:

  1. Servlet对象接收请求,开始处理
  2. Servlet对象发送耗时请求
  3. Servlet对象返回,没有响应
  4. 当请求资源可用时,处理请求的线程可以选择继续执行,也可以通过AsyncContext.start将该任务分发至容器中的其他资源(下面是官方文档的原文)

第十五章“Web Application Environment”和“Propagation of Security Identity in EJBTM Calls”两节中提到的特性,适用于初期响应请求的线程,或者请求通过AsyncContext.dispatch方法分发至容器。 其他线程可以通过AsyncContext.start(Runnable)方法直接操作响应对象。

@WebServlet@WebFilter注解有一个布尔属性asyncSupported,默认值为false。如果这个属性为true,调用startAsync可以在不同的线程中异步的处理请求,传参为请求和响应对象的指针,然后在初始线程中返回。响应对象会按照请求对象经过的过滤器链逆序返回。当AsyncContext调用complete方法后,响应对象才会定型。当异步任务正在执行,但startAsync方法还在分发任务时,【应用】应该负责处理对响应对象和请求对象的并发获取。

asyncSupported属性为trueServlet对象可以向属性为falseServlet对象分发请求。这种情况下,后者的service方法退出的时候,响应对象才会定型。而容器需要调用AsyncContext.complete方法已通知感兴趣的AsyncListener对象。过滤器应该调用AsyncListener.onComplete方法清楚其所关联的资源,保证异步任务的成功返回。

同步Servlet对象向异步Servlet对象分发任务是非法的。但是IllegalStateException异常会在调用startAsync方法的抛出。因此,同步Servlet对象可以及时转成异步类型。

This would allow a servlet to either function as a synchronous or an asynchronous servlet.

AsyncContext.dispatch方法可以实现:异步任务在任意线程上执行,向响应对象中写入内容。这个线程未必知道请求所经过的过滤器链。所以过滤器必须在请求传入的时候,包装响应对象。这样,写入响应对象的内容,仍然要被过滤器所处理。这样就保证了在任意线程向响应对象写入内容的效果是一致的。

Site by Zhang,Xin using Hexo & Random

Hide