servlet异步
Servlet API是Java EE标准的一部分,自1998年正式发布2.1规范以来,一直是基于Java的企业体系结构的重要组成部分。
它是一种自以为是的API,用于服务围绕一些基本概念构建的请求/响应协议:
- 兼容的容器 ,这是专用的运行时,它可以是独立服务器(过去更常见)或基于库的嵌入式运行时(如今更常见)。 它可以支持一次托管多个Web应用程序,并在它们之间隔离类加载。 它还可以提供管理功能,例如应用程序部署,启动,停止,资源分配,JNDI命名上下文,带有连接池的JDBC数据源,HTTP适配器,线程池等。 基本上,它是Java EE功能的集中管理包,可以删除兼容的应用程序。
- 一个或多个servlet ,即实现
Servlet
接口的类,它不是特定于HTTP的,因为Servlet规范通常是为请求/响应协议设计的。 实现接口意味着处理大多数容器已经处理过的servlet配置信息,因此扩展作为规范一部分的抽象类(如GenericServlet
甚至HttpServlet
)更为普遍(方便)。 除了生命周期管理外,其余要实现的方法是请求处理程序 ,当请求进入时,容器将调用该请求处理程序 ,并应为它们提供服务。 他们将通过处理从容器本身作为参数接收的可变请求和响应对象(也包括标准接口),或者通过在发生某些意外情况时引发异常来进行此操作,容器将根据配置方式对其进行适当的管理,例如,通过重定向到JSP页面。 它们还可以包括处理并将处理(的一部分)委托给一个全新的处理链,该处理链通过RequestDispatcher
映射到一些不同的URL。 这是一种链接servlet的机制,主要在2.3引入过滤器之前使用。 - 一个或多个过滤器 ,扩展了
Filter
接口,并且与servlet相似,不同之处在于它们支持链接 ,即它们按顺序排列,可以将请求处理(的一部分)委托给链中的下一个过滤器,并执行发布-处理完成时。 Servlet始终位于过滤器链的末尾。 - 设置信息 ,例如请求和过滤器到HTTP请求的映射,可以通过多种方式提供,从XML描述符到类注释再到实际的初始化代码。
- 请求服务线程 :每个请求由一个专用线程服务,该线程将运行该请求本身已映射到的整个筛选器链,并阻塞与HTTP请求和响应相关的网络I / O操作以及任何其他线程完成请求处理所需的-blocking调用。
剖析Servlet API
如果我们试图表征长期存在的Servlet API,则可以将其限定为:
- 绝对涉及对象,因为涉及的每个概念(无论多么抽象)都已被对象化并转换为接口或类。 “ Servlet”,“ Filter”,“ RequestDispatcher”都是这种建模风格的示例。 唯一的例外是容器本身,它是幕后的无处不在的参与者,但没有唯一的表示形式,可以通过显式参与者或诸如上下文之类的次要对象进行间接处理。
- 它是基于(面向对象)模式的 ,我们可以识别其中的许多 模式 。
- 它具有状态机语义 ,它是有状态的 ,并且是可变的,因为假设请求处理过程处于某种状态(这是所有API的对象化参与者的状态的总和,包括容器),那么会有一些操作将其转换为一个新的,可部分检查的不同状态,同时禁止其他过渡(例如,在提交响应后转发请求)。
- 它是基于处理程序的,因为您(开发人员)在感觉舒适时不会请求传入的请求,而是将它们推送到您的servlet中,您不得不将它们编码为目标化的请求处理程序。
- 它是低级的,因为它不提供路由机制,也没有建立特定的范例,例如MVC。
- 它最初是同步的,因为处理程序应该在调用计算上下文(堆栈)内完成请求处理,而根本不打算推迟请求处理。
- 它明确地基于线程,因为规范指出处理程序的计算上下文是servlet容器线程。 同步和基于线程的结合在一起基本上意味着Servlet API最初被设计为线程阻塞的 。
总而言之,它是一个非常复杂且自以为是的API,尽管它基于非常普遍的观点并且具有很长的向后兼容历史。
顺便说一句:Clojure的戒指,HTTP服务器的全新明晰和简约视图
尽管Clojure社区非常多元化,并且在每个领域都有很多非常有趣的选择,但Web的“事实上”的Clojure低级基本标准框架是Ring 。
鉴于HTTP几乎是一个无状态的请求-响应协议,HTTP请求服务自然是一个非常适合于功能性,输入-输出建模风格的领域。 实际上,Ring认为HTTP请求总共具有3个具有直接关系的功能实体:
- 处理程序是一个函数,其功能是接收一个具有众所周知的键名和值类型的Clojure映射作为唯一输入,表示HTTP请求,并生成另一个必须具有特定结构的Clojure映射作为其输出(表示HTTP响应(这是但是过度简化,因为Ring允许返回更简单的数据结构以方便使用)。
- 中间件是一个接收处理程序功能并生成另一个处理程序功能的功能。 因此,中间件是一种高阶函数,旨在以某种特定方式丰富任何处理程序的逻辑,例如拦截和处理文件系统请求或使用多部分预处理信息来丰富请求本身,因此类似于Servlet过滤器,尽管通过一流的函数之类的函数式编程思想使其变得更加简单。 请注意,可以通过功能组合的直接方式将中间件按特定顺序链接 ,因为将中间件应用于处理程序所获得的是另一个处理程序,然后可以将多个中间件功能应用于该处理程序。
- 适配器是一个接收处理程序函数且不返回任何内容的函数,作为其主要输入。 它的目的纯粹是产生一些HTTP服务器的副作用,该HTTP服务器将使用提供的处理程序来处理请求,因此实际上是某些先前存在的(或新的)HTTP服务器技术的适配器。 它的功能接口不是标准的,因为它可以接收的输入在很大程度上取决于技术,但是一种常见的模式是许多适配器将处理程序作为第一个参数来接收,然后将与实现相关的选项映射(或其他序列)作为第一个参数。第二个。 此外,最常用的选项(例如侦听接口和端口)在大多数适配器中往往具有相同的键名。
Ring也是一种自以为是的API,并且在某些方面它并不偏离流行的概念,例如,它仍然是基于处理程序的,尽管适配器只是功能的想法使得将其用作嵌入式HTTP非常简单。其他完全传统的应用程序的边界”; 加上它是同步的, 这非常好,因为它使代码简单易维护。 仍然需要对主题保持新鲜,清晰和简约的观点,尝试完全消除偶发的复杂性,并提供最少数量的正交概念,以利用功能编程思想和动态语言灵活性来简洁有效地处理领域的内在复杂性为了这个 这种方法非常符合Clojure语言本身的精神。
请注意,Ring并没有提及执行上下文:对于某些人来说,基于轻量级光纤而不是重量级线程来为其阻塞API实现适配器是完全可以的:这正是Comsat提供的,Ring的清晰性和简约性极大地简化了此类编写整合。
Servlet 3.0异步
Servlet规范的Async附加示例说明了以下事实:OOP不一定简化有状态API的复杂性。 有时相反,它只能通过在表中散布状态,将其拆分并放入对象中来提供这样做的危险幻觉。
这种错觉实际上会使情况恶化,因为它使我们认为,开发有状态API的看似非常简单的想法确实可以正常工作而不会产生意想不到的后果。
Servlet 3.0中 Async功能背后的“简单”思想是一种新的请求模式,即异步请求模式 。 当通过startAsync方法调用将请求切换为异步时,我们“简单地”告诉容器,只要请求处理链(过滤器和servlet)返回并且其关联的容器线程完成,我们就根本不意味着请求处理已经完成,因此不应将响应发送回HTTP客户端。 相反,应将其保留,直到其他执行上下文发出确实已完成请求处理的信号为止,并且它将通过对startAsync
调用返回的AsyncContext
对象的complete
或dispatch
方法调用来complete
。
不用说,异步模式与先前存在的Servlet API的有状态移动部分可能存在几种交互作用:接下来,我们将对其中的一些进行交互。
异步模式下的错误处理
AsyncContext
提供了注册有关请求处理进度和异常情况的侦听器的功能,但是我们将在容器线程之外运行自管理执行上下文,因此容器无法为我们捕获和处理异常。
相反, AsyncContext
确实提供了一种新的处理委托形式,它将控制权转移回容器管理的线程,这就是dispatch
方法的目的。 通过在请求属性中设置错误条件(和任何其他相关信息)之后使用它,并检查请求的调度程序类型,我们可以验证我们确实在处理源自异步处理流的异常条件,然后选择重新引发异常,这一次可以依靠容器的管理能力。
这种方法有点复杂,并且基本上需要使用经过改进的前向/调度功能,实际上,事实上,这些功能以前已被过滤器弃用。 但是它可以工作,并且能够模仿在同步设置中发生的错误处理流程。 衡量它的效率当然很有趣。
Servlet API也一直提供sendError
功能,但到目前为止, 尚不清楚它是否(以及如何)以异步模式工作,即使在大多数流行的servlet容器(如Jetty和Tomcat)中,这种情况也很容易导致开放问题 。
在异步模式下过滤
Servlet 3.0规范明确禁止在与容器线程不同的执行上下文中运行过滤器链接。 此限制意味着,在请求处理链末尾的唯一处理程序(即Servlet)可以将请求置于异步模式,而预处理过滤器逻辑只能在容器的线程中执行。
这是非常不幸的,因为自从引入过滤器以来,许多流行的框架和应用程序开发人员就使用它们来执行大量的请求处理,而这些请求处理可以受益于在单独的执行上下文(例如光纤)中运行而不会阻塞昂贵的容器线程。
实际上,在流行的servlet容器中有一些关于此限制的未解决问题 。
Servlet 3.1:异步HTTP I / O
Servlet 3.0能够从请求处理完成中分离容器的线程和Servlet的处理代码,但是用于读取请求和写入响应的I / O仍然是线程阻塞的。
Servlet 3.1通过setReadListener
和setWriteListener
方法向请求添加了异步I / O功能 (如果它们已经处于异步模式)。
关于这套新的API有一些缺点:
- 最多可以注册一个读和一个写侦听器。
- 仅在将请求置于异步模式后 ,API才会强制注册它们。
- 侦听器接口是全新的,例如,与NIO API没有任何共同点。
- 异步API允许更高效的实现,但是这样做的方式是错误的 ,那就是采用卷积编程模型,而不是提供比线程更有效的执行上下文,同时保持极其有用的“阻塞”抽象。 另一方面, 异步API可以很容易地转换为高效和富于表现力的光纤阻塞 API 。
“现状”和前进的方向
许多具有实质性结构的组织已经在基于servlet的技术上进行了大量投资,改变它们的方向是一项相关的成本,需要权衡具体的好处。
他们中的一些人对此感到满意,并且不受现有缺点的影响。 至于其他的可能性,并希望将来会发生,将来的servlet规范将解决这些问题,但是servlet API是一个庞大而复杂的规范。 它还需要保持一定程度的向后兼容性,因此发布规范审查可能要花费一些时间,更不用说servlet容器正确,有效和可靠地实现它们了。
当然,可以使用诸如Ring之类的servlet替代方案,并且某些组织可以决定转移到不同的API上来为它们付出代价,以提高生产力并允许构建更多可维护的代码资产。 对于新的实现,此成本通常可以更低,而不是移植现有的实现。
如果您的组织中基于servlet的API最明显的缺点是效率或异步编程模型的缺点,那么Comsat中存在一个非常可行且成本低廉的替代方案:它将使您仍然可以使用简单的阻塞抽象和熟悉的抽象Servlet API(以及Web和DB领域中的许多其他流行的和标准的API),但具有光纤提供的效率水平。
翻译自: https://www.javacodegeeks.com/2015/04/on-servlets-and-async-servlets.html
servlet异步