Servlet 接口(第二篇)

Servlet接口是Java Servlet API 最上层抽象接口,其他都是扩展这个接口,然而Servlet包本身实现这个接口(分别是GenericServlet 和 HttpServlet),为啥要Servlet要实现两个接口,方便开发者扩展,也算提供一个示例,每个接口都有一个常规实现,开发者有两种选择,要么重写,要么用默认的。一般开发者都是扩展HttpServlet,毕竟web浏览器HTTP协议是天下

  • image-20201225195847686

1、请求处理方法

  • Servlet接口定义一个方法 service, 顾名思义就是服务客户端请求的,一个请求过来,servlet容器会将请求路由到某个servlet实例上。
  • 在想一个问题,servlet实例一般应该比客户端请求要少,难道多请求不处理吗?所有在实现这个servlet方法时候需要考虑到并发请求情况,通常处理方式,web服务器会利用多线程去调用servlet的方法。

1.1、HTTP 特有处理请求的方法

  • image-20201225195648107
  • 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提供服务能力

  • 异步处理过程

    1. 一个请求经过正常过滤器验证等等到达servlet服务(客人风尘仆仆骑马来到一家龙门客栈)
    2. servlet根据请求参数和内容去确定请求性质(店长:请问你打尖还是住店?)
    3. servlet根据请求做一些事情,比如请求数据库获取数据,就需要等待获取JDBC连接(招呼小二过来,去二楼打扫一间房,这个肯定需要花费时间)
    4. servlet直接返回不产生响应(response)结果(店长:先坐下来吃点东西,房间马上收拾好)
    5. 经过一段时间,需要资源已经准备好,处理该事件的线程继续同一线程继续处理或使用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)   
         
      
    • 异步发生超时,容器需要执行如下步骤

      1. 首先会调用AsyncListener.onTimeout方法,这个监听器是在ServletRequest初始化注册的
      2. 如果没有监听器去调用AsyncContext.complete()或AsyncContext.dispatch方法,将会产生一个错误状态code,HttpServletResponse.SC_INTERNAL_SERVER_ERROR.
      3. 没有匹配到错误页,或错误页没有调用AsyncContext.dispatch或AsyncContext.complete()方法,那么容器就需要调用这个AsyncContext.complete方法。(事情总有人做,你不做,就是别人做)
    • AsyncListener 是独立,不会相互影响,如果一个AsyncListener发生异常,不会影响到另一个AsyncListener的执行

    • 异步处理状态流转图

      • image-20201226230834235
3.3.4、线程安全
  • 除了startAsync 和complete 方法, 其他方法都有可能不是线程安全的
3.3.5、更新处理过程
  • 在HTTP/1.1,通过header切换协议,也就是可以自定义协议,但是容器是不知道的,HttpUpgradeHandler中进行处理
  • 开发者需要保证ServletInputStream 和ServletOutputStream是线程安全的

4、Service的结束

  • 简单来说就是要调用destroy() 方法去释放资源
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值