Java EE 6 Web 层综述:Servlet 获得异步支持、扩展性改善

  • Charles Humble
  • 张龙

2010 年 1 月 19 日

话题:Java语言 & 开发

很多 Java Web 应用都是基于某个框架的,如 Apache Wicket、Java ServerFaces、Struts 或是 Spring MVC 等等。要想使用框架,开发者需要在应用的 web.xml 配置文件中注册框架的切入代码,如 Servlet、Filter 或是 Listener。这么做的后果就是部署描述符变得很庞大,同时导致框架所用的 XML 与特定于应用的 XML 混杂在了一起。Servlet 3.0 规范的一个主要目标就是让开发者无需编辑 web.xml 部署描述符就能部署 Servlet、Filter 和 Listener,同时可以将 web.xml 文件拆分成多个模块。为了实现这一点,Servlet 3.0 规范增加了基于注解的配置(@WebServlet、@ServletFilter 以及 @WebServletContextListener),这使得我们可以不再需要 web.xml 文件,同时规范还引入了一个新的概念:Web 片段(Web Fragment)。

Web 片段可以将框架的“样板”XML 与应用的其他配置分开,并且能够实现应用的自我注册。Web 片段必须放在名为 web-fragment.xml 的文件中,该文件只要位于 Web 应用的 classpath 下即可,但通常都将其放到 META-INF 目录下或是框架的 jar 文件中。XML 以 <web-fragment> 元素开始,里面包含的元素与 web.xml 部署描述符大同小异。如下代码所示:

<web-fragment>
	<filter>
		<filter-name>MyXSSFilter</filter-name>
		<filter-class> MyXssFilter</filter-class>
	</filter>
	<servlet>
		<servlet-name>myFrameworkServlet</servlet-name>
		<servlet-class> MyFrameworkServlet</servlet-class>
	</servlet>
	<listener>
		<listener-class> MyFrameworkListener</listener-class>
	</listener>
</web-fragment>

容器在部署时会处理 XML 片段并组装成最终的部署描述符。由于容器负责组装 web.xml 文件,因此如果需要按照特定的顺序来调用框架的 Servlet、Listener 或 Filter 时就可能产生问题。为了避免这个问题,Servlet 3.0 API 支持绝对与相对顺序的部署描述符。我们可以在 web.xml 文件中使用 <absolute-ordering> 元素指定绝对顺序,这样 WEB-INF/lib 下的每个 jar 都可以通过 META-INF/web-fragment.xml 文件的 <name> 元素获得一个名字。接下来,Web 应用的 WEB-INF/web.xml 文件可以通过 <absolute-ordering> 元素按照顺序列举出这些片段名,这个顺序就是 jar 的调用顺序,同时还有一个可选的 <others/> 元素用于指定是否以及何时包含那些未命名的 jar 文件。由于部署者可以选择只列出那些受信任的 jar 以进行部署,这样就可以避免意外情况的发生。除此之外,通过顺序还可以排除那些不需要被扫描的 jar,这样就可以加快应用的部署速度。最后,如果你不想在产品环境下看到自我注册的情况发生,那就可以在 web.xml 文件中使用 <metadata-complete> 元素,这会告诉 Web 容器只去寻找注解而非 Web 片段。

由于既支持片段,又可以使用注解作为另一种配置机制,Servlet 3.0 可以插入框架的共享拷贝,比如 JAX-WS、JAX-RS 以及 JSF 等,他们都构建在 Web 容器之上,使用了 ServletContainerInitializers。这些框架是通过 jar services API 被检测到的,同时还可以指定其处理的类型列表。对于 WEB-INF/lib 下的任何 jar 来说,只要其中包含的类被检测到都会传递给 ServletContainerInitializer。这样,我们还可以将同样的 API 作为 ServletContextListeners。

从 Servlet API 首次发布以来,构建 Web 应用的方法发生了翻天覆地的变化,尤其是使用越来越多的异步 Web 技术。这些技术(一般统称为 Ajax 或是 Web 2.0)对于 Web 客户端(比如浏览器)与服务器端之间的传输机制产生了重要的影响,因为客户端会在一个页面中向服务器端发出更多的请求而不是每次请求都刷新一次页面。

长时间的服务器端处理会恶化这一情况,比如等待 JDBC 连接池中的连接,或是等待 JMS 队列中的消息等。在 Servlet 中等待实在是太低效了,因为这种阻塞会消耗线程以及其他有限的系统资源。鉴于此,Servlet 3.0 引入了异步处理请求的功能,这样线程就可以返回到容器中并执行其他任务。在请求上的异步处理开始时,其他线程或是回调既可以生成响应,也可以分发请求以便通过 AsyncContext.dispatch 方法在容器上下文中执行请求。

由于异步 Servlet 的行为与同步的差别非常大,因此 Servlet 3.0 要求开发者指定 asyncSupported=true 以表示 Servlet 支持异步请求。不仅是 Servlet,Filter 也可以异步执行。Servlet 3.0 通过新的 ServletRequest 方法来支持异步处理,比如 startAsync() 会返回一个 AsyncContext 对象,该对象用于持有传递给方法的 request 与 response 对象。这里,处理原始请求的线程还可以执行其他操作。此外,API 还引入了一个新的 Listener 类:AsyncListener,它会告诉我们异步操作何时结束或者是否超时了。AsyncContext 类拥有一个 complete() 方法,凭借该方法我们可以在异步操作结束后提交响应。AsyncListener 类拥有一个 dispatch() 方法,它会将异步请求转发给容器,这样其他框架(比如 JSP)就可以生成响应了。

除了引入大量的新技术和新方法外,Servlet 3.0 规范还对其他地方进行了大量的增强:HttpServletRequest 终于获得对 multipart/form-data MIME 类型的内置支持了、Cookie 类开始支持“HttpOnly” cookie 以避免某些跨站点的脚本攻击、ServletContext API 也得到了更新,我们可以通过编程的方式将 Servlet 和 Filter 加到上下文中了。

查看英文原文:The Java EE 6 Web Tier: Servlets Gain Asynchronous Support, Improved Extensibility

Java语言 & 开发