Servlet Interface

Servlet Interface

Servlet接口是Java Servlet API 核心抽象接口。大多数Servlet实现直接实现Servlet接口,或者是继承已经实现Servlet接口的抽象类。Java Servlet API里有2个默认Servlet接口实现类,GenericServlet类和HttpServlet类。对于大多数研发,可以继承HttpServlet类实现Servlet接口。

Servlet Interface

2.1、Request处理方法

Servlet接口定义了处理Request的service方法。Servlet Container 将请求路由到Servlet接口实现类,并调用service方法。

WebServer可以使用多线程执行Servlet的Service方法来处理并发的请求。

2.1.1 Http Request 处理方法

HttpServlet针对处理HTTP Request 添加额外的方法,这些方法会在service方法中根据HTTP Request 自动调用。这些方法是:

  • doGet 处理GET请求
  • doPost 处理Post 请求
  • doPut 处理PUT 请求
  • doDelete 处理DELETE 请求
  • doHead 处理HEAD 请求
  • doOptions 处理OPTIONS请求
  • doTrace处理TRACE请求

2.2 Servlet实例个数

Servlet可以通过2种方式部署。第一种是使用注解(第8章《注解和插件》),第二种是作为WebServer的一部分(第14章《部署描述》),这2种部署方式中Servlet Container会控制

Servlet的实例个数。

当Servlet做为WebServer局部组成一起部署,Servlet Container只会创建1个Servlet实例。

若Servlet 的实现类实现了SingleThreadModel则Servlet Container会创建多个Servlet实例,用于处理高负载的请求流量,并会按顺序处理请求。

当Servlet作为应用程序的局部组成一起部署,Servlet Container只会在1个JVM虚拟机里创建1个Servlet实例。

若Servlet 的实现类实现了SingleThreadModel则Servlet Container会在1个JVM虚拟机里创建多个Servlet实例。

2.2.1 关于SingleThreadModel的知识

SingleThreadModle保证只有一个线程执行给定的servlet实例的service方法,SingleThreadModel只保证servlet实例同一时间仅被一个线程执行,不保证其他对象的访问。

因为Servlet Container会可能会将其他对象放在缓存池中,例如HttpSession对象。

同一时间,可能会有多个Servlet实例访问HttpSession对象,即使实现SingleThreadModel的Servlet实例也是共享其他对象。

不推荐使用该方法来处理这个问题,可以利用其他方法解决,比如避免使用实例变量或者对于访问资源的代码加锁。SingleThreadModel接口在这个版本后被弃用。

2.3 Servlet 生命周期

Servlet生命周期包括Servlet的加载和实例化,处理客户请求,移除至服务之外。

这些都体现在Servlet的3个方法:init(),service(),destroy()。

所有Servlet实现类都需要实现这些方法。

Servlet Interface

2.3.1 加载和实例化

加载和初始化servlet是servlet容器的职责。加载和初始化servlet可以是在servlet容器启动完成后,也可以当有客户端请求再加载和初始化servlet。

servlet容器需要加载servlet的class文件,可以是本地class文件、远程class文件,找到class文件后再加载class文件。加载成功后,servlet container即可初始化servlet实例。

2.3.2 初始化

当Servlet实例创建完成,Servlet Container 会调用Servlet接口的init(ServletConfig servletConfig,ServletContext servletContext)进行初始化Servlet实例。

Servlet的初始化是为了执行耗时的操作如JDBC连接,或者一次性任务。

ServletConfig对象存储着WebApplication的配置信息,Servlet可以读取所在WebApplication的配置,ServletContext对象存储着Servlet运行时环境信息。

2.3.3 处理请求

Servlet初始化完成后,Servlet Container就可以将请求路由至Servlet,交给其处理。

Servlet Container会将客户端请求使用ServletRequest对象表示,Servlet使用ServletResponse对象填充响应。

若是HTTP请求,客户端请求对象使用HttpServletRequest,客户端响应对象使用HttpServletResponse

2.3.3.1 多线程问题

Servlet Container发送并发请求调用Servlet的service方法。研发者应该在service方法中使用多线程准备充足的资源并发处理请求。

研发者通过实现Servlet的同时集成SingleThreadModel,使得Servlet Container将请求串行调用Servlet的service方法。但是不提倡这种会降低WebApplication性能的做法。

研发者通过在service方法中使用关键字synchronized声明线程串行访问代码。但是不提倡这种会降低性能的做法。

2.3.3.2 处理请求发生异常

Servlet处理请求期间可能会抛出2种异常,ServletException和UnavailableException。

ServletException代表处理请求期间发生错误,ServletContainer应该将Request对象清理。

UnavailableException代表Servlet临时不可用或长期不可用。ServletContainer对于UnavailableException暗示临时不可用的时长的情况,需要将不可用期间的请求响应503,并在请求头加上Retry-After 提示客户端多久后可以重试。

ServletContainer对于UnavailableException暗示长期不可用的时长的情况,需要将不可用期间的请求响应404,并调用Servlet的destroy方法销毁Servlet。

Servlet Container也可能不区分上述的2种不可用,统一视为长期不可用。

2.3.3.3 异步处理

Servlet可能要等待资源或者事件才能完成请求,例如:Servlet需要连接JDBC,等待远程服务的响应,JMS 信息 等等IO操作。频繁的等待资源可能会导致整个Servlet Container的服务质量。所以引入异步处理请求。

引入异步请求,允许线程可以返回给Servlet Container再处理其他任务。

当引入异步处理请求,线程可能开始处理请求,可能在完成请求,或者分发请求。异步处理请求的流程可能会如下:

1、请求到达Servlet Container,并通过身份认证的过滤器,转至servlet。

2、servlet处理请求参数或请求内容决定请求的性质

3、servlet像请求资源,例如请求数据库链接,发起远程Web请求,加入JDBC的连接队列。

4、servlet返回且没有产生响应

5、过一段时间,请求的资源可用,线程继续接下来的处理可能是同一个线程,也可能是容器通过使用AsyncContext申请的其他资源。

在第8章@WebServlet 和 @WebFilter 注解,存在属性asyncSupported,默认值是true,当设置为true时,则应用程序可以异步处理请求,通过调用AsyncContext.startAsync()。传递给StartAsync的参数是请求对象和响应对象。直至调用AsyncContext.complete()响应才完成。

将异步的Servlet传递给同步的Servlet是允许的,费异步的Servlet在执行完service方法后,就完成了请求。Servlet Container的有义务调用AsyncContext.complete()来通知AsyncListener监听者,监听者则可以清理资源。

将同步的Servlet传递给异步的Servlet是非法的。

应用程序等待的异步任务可以是直接返回响应给客户端,且处理异步任务的线程不同于初始化请求的线程。

该线程是不会知晓任何filter。如果filter是希望单独的线程处理响应,则需要包装响应对象,然后将包装好的响应对象传递给下一个filter,最终响应对象到达servlet。

如果响应对象被包装过,应用程序直接写被包装过的响应对象的内容给客户端。

ServletRequest

  • public AsyncContext startAsync(ServletRequest req, ServletResponse res),这个方法将请求置于异步处理模式,并且根据给定的request和response构建AsyncContext对象。AsyncContext对象getAsyncTimeOut方法返回超时时间。

ServletRequest和ServletResponse应该是相同的对象,因为会调用servlet.service()方法,或者是filter的doFilter(),或者是

ServletRequestWrapper和ServletResponseWrapper的子类。

这个方法确保当service方法结束时未完成响应。直至调用AsyncContext.complete()方法或者是到达AsyncContext超时时间。超时的计时开始时间是当请求和请求关联的响应被容器返回。AsyncContext不能在异步的线程中直接写响应体,可以用来通知监听者响应完成或者响应未关闭。

篇幅有限,不在一一罗列核心类,使用图概括核心API:

Servlet Interface

Servlet的状态流程图,如图所示:

Servlet Interface
2.3.3.4 线程安全

相比startAsync()和complete()方法,request和response的实现是不保证线程安全的。

这就意味着request和response仅限在处理线程内处理或者是应用程序保证访问request和response是线程安全的。

如果request和response处于多线程环境,则必须保证加锁访问request和response,或者包装request和response,在包装代码中开启加锁访问request和response。

2.3.3.5 升级处理

在HTTP/1.1中,客户端携带header声明支持的通讯协议。如果服务端支持该通讯协议,在下面的通信中,将会使用新协议通信。

Servlet Container有一套完成的HTTP升级机制。Servlet Container自身不知道升级的细节,这些全部封装在HttpUpgradeHandler类中。Servlet Container和HttpUpgradeHandler之间使用字节流读取数据和发送数据。

当Server接收到升级请求,Servlet Container 会调用HttpServletRequest.upgrade()方法进行升级。这个方法会实例化HttpUpgradler对象并将对象返回,应用程序准备和发送数据到客户端。在service()方法之后,Servlet Container 完成了所有filter和标记连接被HttpUpgradler处理,它会调用HttpUpgradler的init()方法,传递WebConnection允许访问数据。

Servlet Container只负责初始化request和response,接下来的升级请求,不在参与其中。

HttpUpgradler也许会使用非阻塞IO消费数据和产生消息。

研发有义务保证访问ServletInputStream和ServletOutputStream的线程安全。

一旦升级完成,HttpUpgradler.destroy()会被调用。

2.3.4 服务结束

Servlet Container不需要在特定时间加载Servlet,Servlet Container或许或持有Servlet一段时间,或者一天、一个月、一年等。

当Servlet Container 要移除Servlet,必须调用Servlet的destroy()方法,允许Servlet释放占据的资源,持久化数据等操作。

在调用Servlet的destroy()方法之前,需要确保当前正在执行的线程都完成后,再销毁。

一旦销毁Servlet,不能再将请求路由到旧的Servlet,而应该路由到新创建的Servlet对象处理。

一旦销毁Servlet,Servlet Container需要释放Servlet对象保证垃圾回收该内存。

Original: https://www.cnblogs.com/code-for-me/p/16741816.html
Author: 夕琴
Title: Servlet Interface

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/714219/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球