Servlet接口是Java Servlet API 最上层抽象接口,其他都是扩展这个接口,然而Servlet包本身实现这个接口(分别是GenericServlet 和 HttpServlet),为啥要Servlet要实现两个接口,方便开发者扩展,也算提供一个示例,每个接口都有一个常规实现,开发者有两种选择,要么重写,要么用默认的。一般开发者都是扩展HttpServlet,毕竟web浏览器HTTP协议是天下
1、请求处理方法
- Servlet接口定义一个方法 service, 顾名思义就是服务客户端请求的,一个请求过来,servlet容器会将请求路由到某个servlet实例上。
- 在想一个问题,servlet实例一般应该比客户端请求要少,难道多请求不处理吗?所有在实现这个servlet方法时候需要考虑到并发请求情况,通常处理方式,web服务器会利用多线程去调用servlet的方法。
1.1、HTTP 特有处理请求的方法
- HttpServlet实现了service()方法,这个抽象类定义7个方法对应HTTP 7种请求方式处理
- doGet – Get请求
- dePost – Post请求
- doPut – Put请求(如果是restful风格的话,这个应该是修改)
- doDelete – Delete请求
- doHead – Head请求 (获取请求头信息)
- doOptions – Options请求 (预先请求),一般出现ajax表单提交时候
- doTrace – Trace请求(这个没有用过)
一般你只需关心 doGet 和doPost方法
1.2、添加额外的方法
- doPut 和doDelete 只是为了支持HTTP/1.1支持的新特性
- doHead 其实就是doGet,只是返回是头上信息
- doOptions, 试探请求,(跨域经常会出现)
- doTrace 所有请求头信息都会发送
1.3、有条件GET方式支持
- getLastModified方法,简单来说,客户端来请求服务器某个资源,如果这个资源没有修改过,还需要传给客户端吗?显然可以不用传,客户已经缓存这个资源,减少网络传输
2、实例的数量
- 一般来说Servlet容器中,每种servlet有且只存在一个Servlet实例,但是如果它实现SingleThreadModel接口,就有多个实例,(简单来说,一般情况下是单例,如果你实现SingleThreadModel接口,就原生了)
2.1、需要注意Single Thread Model接口
- 简单来说不建议使用实现Single Thread Model接口, HttpSession在多个servlet可能共享这个session,这就一致性问题,同时修改,本质上这个限制它使用。线程不安全的
3、Servlet 生命周期
- servlet从创建,初始化,服务请求,销毁,对应API方法(init, service, destroy)
3.1、加载和实例化
- 不管立即和延时,servlet 容器有义务去加载servlet的类和实例化servlet实例。
3.2、初始化
- 实例化之后,需要进行初始化操作,初始化啥呢?无非是构建可以提供服务的环境,肯定就有一些配置信息,那么配置信息是怎么加载到servlet中去,所以需要实现ServletConfig, 当然也可以实现ServletContext接口,详情需要查看第四章关于ServletContext接口介绍
3.2.1、初始化时发生异常
- 可能会抛出UnavailableException 或 ServletException
- 如果初始化不成功,servlet 容器会释放资源,destroy方法不会调用,没有初始化,何来销毁
- 还有种可能,实例化部分成功,部分失败,这个时候需要等初始化过程阶段完成之后,等资源释放之后,才可以新增一个新servlet实例。
3.2.2、工具考虑
- 工具静态初始化和直接调用init方法有区别
- 如果调用静态初始化之后,并不代表servlet已经起来,可以去做连接数据库相关操作
3.3、请求处理
- 支持request/response 模式, 那么servlet定义两个对象,ServletRequest和ServletResponse
- 当然对于HTTP协议,定义HttpServletRequest和HttpServletResponse
- 注意:人可以一生无所事事,servlet也有可能在整个生命周期不会处理任何请求
3.3.1、多线程的问题
- 考虑并发访问的问题,但是又不推荐实现SingleThreadModel接口,这个要求容器能够保证有且仅有一个请求线程去在执行service方法,servlet容器要么通过序列实现或servlet实例池
- 或者synchronized关键字修饰,但是它会影响性能(那到底采用什么方式实现呢?)
3.3.2、在处理请求过程发生异常
- 发生异常有两种,一种是ServletException,一种是UnavailableException
- ServletException 是处理请求过程中发生的异常,这个异常servlet容器需要采取适合的方式去清理这些请求
- UnavailableException 这个异常表示服务处理不了请求,比如服务挤爆,所以这个不可用异常可能是临时或者永久的
- 如果UnavailableException暗示这个servlet是永久不可用, 那么Servlet容器就会移除这个servlet,调用destroy方法,释放servlet实例的资源,任何请求请求到这个servlet上都会返回404(SC_NOT_FOUND)
- 如果UnavailableException暗示这个servlet是临时不可用, Servlet容器将在临时不可用阶段路由其他servlet提供服务,这个时候由这个Servlet拒绝将会返回SC_SERVICE_UNAVAILABLE(503)状态,然后header会带有retry-after字段表示等会重试
- 当然容器实现可以把UnavailableException都当做永久不可用,按永久不可用处理
3.3.3、异步处理
-
异步就是为提高web提供服务能力
-
异步处理过程
- 一个请求经过正常过滤器验证等等到达servlet服务(客人风尘仆仆骑马来到一家龙门客栈)
- servlet根据请求参数和内容去确定请求性质(店长:请问你打尖还是住店?)
- servlet根据请求做一些事情,比如请求数据库获取数据,就需要等待获取JDBC连接(招呼小二过来,去二楼打扫一间房,这个肯定需要花费时间)
- servlet直接返回不产生响应(response)结果(店长:先坐下来吃点东西,房间马上收拾好)
- 经过一段时间,需要资源已经准备好,处理该事件的线程继续同一线程继续处理或使用AsyncContext对象分派给容器处理(小二告诉店长已经收拾好房间,店长通知客人入住)
-
ServletRequest 类核心方法
-
/** 这个方法是将一个请求放入到异步模型中,然后请求对象和响应对象,以及getAsyncTimeout方法返回值去初始化一个AsynContext对象 **/ public AsyncContext startAsync(ServletRequest req, ServletResponse res) /** 使用原生对象去进行异步处理,你应该体现用户在调用这个方法之后需要flush一下,防止数据丢失 **/ public AsyncContext startAsync() /** 获取异步模型时,异步上下文信息,如果是非异步模型,调用这个方法是非法的 **/ public AsyncContext getAsyncContext() /** 表示请求是否支持异步,如果一个请求经过不支持异步过滤器和servlet,这个返回false **/ public boolean isAsyncSupported() /** 如果异步处理已经开始,返回true,否则返回false,比如调用AsyncContext.dispatch或调用AsynContext.complete方法 **/ public boolean isAsyncStarted() /** 获取派发类型,DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ERROR. **/ public DispatcherType getDispatcherType()
-
-
AsyncContext 类, 这个类代表执行异步操作上下文,它在ServletRequest.startAsync创建的
-
public ServletRequest getRequest() 获取请求 public ServletResponse getResponse() /** 默认是30000, */ public void setTimeout(long timeoutMilliseconds) /** 注册一个给定监听器去通知onTimeout, onError, onComplete或onStartAsync, */ public void addListener(AsyncListener listener, ServletRequest req, ServletResponse res) /** 转发请求 */ public void dispatch(String path) /** 利用 AsyncContext去转发请求 */ public void dispatch() /** 表示请求是不是原始,有没有经过包装,那么怎么算是经过包装? */ public boolean hasOriginalRequestAndResponse() /** 容器会分一个线程去处理Runnable r的run方法内容, 这个线程可能来自于线程池,当然容器也有可能将上下文信息传递到Runnable对象中 */ public void start(Runnable r) /** 异步的处理时候,由servlet去调用这个方法,提交和关闭响应,而同步请求则是需要容器去调用这个方法。 */ public void complete()
-
// 例子1 // 请求到 /url/A AsyncContext ac = request.startAsync(); // ... ac.dispatch(); // 将请求异步转发到 /url/A 处理 // 例子2 // 请求 /url/A // 转发 /url/B request.getRequestDispatcher("/url/B").forward(request, response); // 在Forward的目标操作中开启异步操作 AsyncContext ac = request.startAsync(); ac.dispatch(); // 异步转发到 /url/A // 例子3 // 请求 /url/A // 转发 /url/B request.getRequestDispatcher("/url/B").forward(request, response); // 在Forward的目标操作中开启异步操作 AsyncContext ac = request.startAsync(request, response); ac.dispatch(); // 异步转发到 /url/B
-
在执行dispatch方法会发生异常,所以需要妥善处理
- AsyncListener.onError(AsyncEvent) , 处理AsyncEvent事件异常
- 如果监听器调用AsyncContext.complete或AsyncContext.dispatch方法,那么就会有异常code, HttpServletResponse.SC_INTERNAL_SERVER_ERROR
- 如果匹配不到错误页,或者错误页没有调用AsyncContext.complete()方法, 容器必须调用AsyncContext.complete
-
-
ServletRequestWrapper
-
/** 递归去检查是否包装指定的ServletRequest对象, 有返回true */ public boolean isWrapperFor(ServletRequest req)
-
-
ServletResponseWrapper
-
/** 递归去检查是否包装指定的ServletResponse对象 */ public boolean isWrapperFor(ServletResponse req)
-
-
AsyncListener
-
/** 通知监听器异步操作已经完成 */ public void onComplete(AsyncEvent event) /** 通知监听器异步操作已经发生了超时 */ public void onTimeout(AsyncEvent event) /** 通知监听器处理发生异常 */ public void onError(AsyncEvent event) /** 调用ServletRequest.startAsync进行通知监听器 */ public void onStartAsync(AsyncEvent event)
-
异步发生超时,容器需要执行如下步骤
- 首先会调用AsyncListener.onTimeout方法,这个监听器是在ServletRequest初始化注册的
- 如果没有监听器去调用AsyncContext.complete()或AsyncContext.dispatch方法,将会产生一个错误状态code,HttpServletResponse.SC_INTERNAL_SERVER_ERROR.
- 没有匹配到错误页,或错误页没有调用AsyncContext.dispatch或AsyncContext.complete()方法,那么容器就需要调用这个AsyncContext.complete方法。(事情总有人做,你不做,就是别人做)
-
AsyncListener 是独立,不会相互影响,如果一个AsyncListener发生异常,不会影响到另一个AsyncListener的执行
-
异步处理状态流转图
-
3.3.4、线程安全
- 除了startAsync 和complete 方法, 其他方法都有可能不是线程安全的
3.3.5、更新处理过程
- 在HTTP/1.1,通过header切换协议,也就是可以自定义协议,但是容器是不知道的,HttpUpgradeHandler中进行处理
- 开发者需要保证ServletInputStream 和ServletOutputStream是线程安全的
4、Service的结束
- 简单来说就是要调用destroy() 方法去释放资源