Java EE 7精粹

978-7-115-37548-3
作者: 【美】Arun Gupta
译者: 韩陆
编辑: 陈冀康
分类: Java

图书目录:

详情

本书详细介绍了几个Java EE7的核心规范,包括WebSockets、Batch Processing、RESTful Web Services和Java Message Service,可以说涵盖了Java EE 7最核心、最精粹的技术内容。本书帮助你使用书中的多种结束,来构建端到端的应用,这将有助于你理解Java EE开发中的设计模式。

图书摘要

版权信息

书名:Java EE 7精粹

ISBN:978-7-115-37548-3

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

• 著    [美] Arun Gupta

  译    韩 陆

  责任编辑 陈冀康

• 人民邮电出版社出版发行  北京市丰台区成寿寺路11号

  邮编 100164  电子邮件 315@ptpress.com.cn

  网址 http://www.ptpress.com.cn

• 读者服务热线:(010)81055410

  反盗版热线:(010)81055315


Copyright© 2013 by O’Reilly Media, Inc.

Simplified Chinese Edition, jointly published by O’Reilly Media, Inc. and Posts & Telecom Press, 2014. Authorized translation of the English edition, 2013 O’Reilly Media, Inc., the owner of all rights to publish and sell the same.

All rights reserved including the rights of reproduction in whole or in part in any form.

本书中文简体版由O’Reilly Media, Inc.授权人民邮电出版社出版。未经出版者书面许可,对本书的任何部分不得以任何方式复制或抄袭。

版权所有,侵权必究。


Java EE 7是Java EE最新版本的平台,它为建立Web应用和企业应用提供了简单、易用和全栈式的服务。

本书提供了Java EE 7平台中关键规范的概述。全书共16个章节,对Java EE 7平台中的每一个概念都给予了详细的解释,并通过一系列简单的代码示例来帮助读者更好地了解这个平台。作者Arun是Java EE团队的核心成员,也是Oracle的Java布道者,对Java EE 7平台的启动有重要贡献,是对Java EE 最有发言权的技术专家。

本书适合那些对Java有一些基本了解或者对Java EE 平台感兴趣的读者,阅读本书无需具备Java EE平台早期版本的知识。另外,对于了解设计模式的读者而言,通过本书描述的大部分技术还能实现实际应用的构建。


简单是智者的追求,Java EE 7为开发者带来了前所未有的清爽体验,同时提供了功能的增强。Java平台历经19年(1995年Java诞生、1999年J2EE发布),依然保持着活力和生产力,无疑为开发者带来了信心和快乐。

Java相关的书籍不乏经典,而在Java EE的图书中,本书当属经典的入门读物。著者Arun Gupta是Java的布道者,非常了解使用者的诉求,针对Java EE的每个模块,Arun都娓娓道来、切中要点,为读者呈现了简单、易于上手的实例,阅读本书将是一次快乐的学习之旅。同时,Arun踩过足够多的坑,在本书中,遇坑必填的做法会使读者颇为受益。

值得一提的是,在我翻译过程中,Arun对原文出现的勘误的修正速度快得惊人,每次我在twitter上告知他勘误编号后,最多半天时间他就修正了。因此,本书得以更准确地传达著者的意图。

非常感谢邮电出版社的Jacky Chen,本书能够面世得益于Jacky对我的信任和鼓励。从2013.10.13提交试译样章,到2014.06.30提交英中混合初稿,Jacky都提出和很中肯的意见和很贴心的鼓励,使得译文能够在2014.07.12提交。

虽然在翻译的过程中,笔者竭尽全力,但由于笔者才疏学浅,翻译水平更是平庸,如有疏漏望见谅,并虚心接受读者的意见和反馈。本书的中文版勘误地址是:https://github.com/feuyeux/java-ee-7-essentials。欢迎读者交流,一起进步,我的邮件地址是:feuyeux@163.com。

韩陆

2014年10月于杭州


Arun Gupta是甲骨文的Java布道者。作为Java EE团队的创始成员之一,他的工作是创建和培育Java EE、GlassFish和WebLogic社区。他带领一个跨职能的团队通过战略、规划和内容、营销活动和方案的执行推动Java EE 7平台的全球启动。他非常热衷于开发和接洽合作伙伴、客户、JUG、Java champions以及其他传播于世界各地的Java的精华。Arun拥有丰富的演讲经验,他在30多个国家演讲过无数的话题。作为一名作者,Arun有一个多产的博客:blogs.oracle.com/arungupta,同时他是一位跑步爱好者和环球旅行者,请在twitter关注他:@arungupta,他很平易近人。


北京航空航天大学软件工程硕士。热爱编程,热衷于开源社区的技术交流和分享,将持续学习作为一种生活习惯。曾辗转于用友总部、新浪、Avaya和Technicolor从事研发工作。现就职于阿里巴巴总部,从事敏捷和自动化测试的研发工作。《JSF和Richfaces使用指南》、《Java Restful Web Services使用指南》作者。


《Java EE 7精髓》一书的封面动物是亚洲玻璃鱼(属双边鱼科)。仅发现于亚洲和大洋洲的水域中,双边鱼科的鱼被分成8属,包括约40种。除了亚洲玻璃鱼,双边鱼科还包括了条纹玻璃鲶鱼、婆罗洲玻璃鲶鱼、沃氏副双边鱼和三条纹非洲玻璃鲶鱼。双边鱼科的大部分成员都相当小,但较大的品种可以长到最大10英寸。

双边鱼科中最受水族爱好者欢迎的成员是兰副双边鱼,因其通体透明。许多玻璃鱼种的鱼,都可以透过皮肤看到其内部的器官和骨骼。不幸的是,这一显著的特点导致了人类直接注入染料到鱼的体内来制作霓虹灯的条纹或斑点。这个过程对鱼非常有害,大多数鱼在手术过程中死亡。活下来的鱼被作为“彩绘”或“迪斯科”鱼出售,但他们很容易受感染和患疾,通常数周或数月内死亡。1997年,英国《Practical Fishkeeping》杂志发动了一次基本上是成功的战役来阻止商户备货被染色的鱼。虽然这次运动能够阻止几乎一半在英国的商店出售这些染色鱼,但是这个问题仍然存在于全球市场。

尽管玻璃鱼难养声名在外,实际上如果能给予合适的环境,玻璃鱼是让水族馆增辉的好添置。根据种类的不同,它们自然栖息的水的范围从淡水到咸水而不同,但大多数更喜欢淡水而不是咸水。最好让他们保持群居而不是单个或一对,比起孤单的一两条,成组的鱼的行为将更加积极和大胆。

封面图片来自一个活动板,产地不明。封面字体是Adobe的ITC Garamond。文字的字体是Adobe的Minion Pro;标题字体是Adobe的Myriad Condensed;代码字体是Dalton Maag的Ubuntu Mono。


作为Java EE平台规范引导小组的一员,自从1999年Java EE被推出以来,我一直在指导着它的发展轨迹。Arun从一开始就是Java EE的团队的核心成员。Java EE平台已经在过去的13年中显著地发展。2006年发布的Java EE 5是一直持续到今天的一个主题的开始:使开发Java EE应用程序更加容易。2009年发布的Java EE 6包含了CDI,为这一主题做出了显著的贡献。最新发布的Java EE 7继续着这个主题,着眼于开发人员的生产力。Arun已经参与了多个不同的领域的Java EE开发,他参与其中的共同点是真正了解开发人员和实际应用。他的Java EE背景和他目前作为Java EE技术布道者的角色,让他成为唯一有资格向开发人员介绍最新的Java EE技术的人。

在这本书中,Arun调研了Java EE的最新版本的所有关键技术,让开发人员品味这些新功能,并展示了开发Java EE应用程序是多么的容易。Arun扩展了他的《Java EE 6 Pocket Guide》这本流行的著作,以更深入地覆盖更多的技术。特别是致力于Java EE 7的新技术和现有技术的新功能。具备一些Java EE经验的开发人员以及新的Java EE开发人员将会发现本书是Java EE 7的一个非常有用的概述。

本书的每一章都深入介绍了一个Java EE的技术点,以帮助读者了解这是项什么样的技术,它的最佳应用是什么,以及如何上手这一技术。虽然它不是一个完整的教程,但是有经验的开发人员会发现它提供了适当的层次来了解技术细节,以使受众了解这项技术。章节中充满的简短代码片断会让开发人员赏心悦目。

在描述了Java EE的关键技术点之后,Arun将这些技术在本书的最后一章集成在一起,通过动手实验引导读者使用这些技术完成实际应用的开发。这是Arun的经验的真正亮点。没有什么比看到正在运行的应用程序代码,更能展示如何将这些技术真正运用在实际工作中了。

Java EE是我们已经开发了多年的丰富的平台。通过所有老版本和新版本的技术排序来找出开发Java EE应用程序的最佳途径是艰巨的。虽然这些年我们已经使Java EE应用程序开发更加容易,但有时无法从众多的Java EE规范中读到这样的信息。与应用程序开发人员多年来一起工作的经验、动手实验教学,以及Java EE的布道让Arun能够提供所有适当深度的关键信息。这本书为开发人员贴心地概述了Java EE平台,尤其是Java EE 7的新功能。

Bill Shannon

Oracle公司Java EE平台规范领导小组架构师 2013年6月


Java EE 7平台建立在以前的版本之上,侧重于提高生产力和拥抱HTML5。本书面向的是想快速概览这个平台和回来复习基础知识的读者。

本书提供了Java EE 7平台中关键规范的概述(每章一个规范)。本书绝不是一本介绍不同规范中每一个概念的详尽指南或教程,而是通过简单的代码示例来解释不同规范的主要概念。阅读本书,无需具备Java EE平台早期版本的知识,但你需要对Java有一些基本了解,以便理解代码。

这本书的重要部分来自《Java EE 6 Pocket Guide》(O'Reilly出版)。添加的新章节覆盖了平台的新技术。添加的新内容或对现有部分的更新反映了平台的变化。如果已经读过《Java EE 6 Pocket Guide》,那么你就能以更快的速度读完这本书。否则,可以从头到尾地阅读这本书,或者根据兴趣阅读特定的章节。

我还提供了一个自学指导,是关于如何使用本书中描述的大部分技术来构建端到端应用的。这需要开发人员了解设计模式,以使用Java EE 7来构建一个实际应用。

希望你会喜欢这本书!

本书中使用以下的印刷约定。

斜体

表示新的术语、URL、电子邮件地址、文件名和文件扩展名。

固定宽度

用于程序列表以及段落中引用的程序元素,如变量或函数名、数据库、数据类型、环境变量、语句和关键字。

等宽斜体

表示应替换为用户提供的值或由上下文决定的值的文本。

补充材料(代码实例、练习等)可以在http://oreil.ly/javaee7-files下载。

这本书的目的是帮你完成工作。一般来说,如果在你的程序和文档中使用这本书的代码示例,不需要与我们联系来授权,除非你直接复制代码的显著部分。例如,使用本书的几段代码来写一个程序是不需要许可的。销售或分发O'Reilly书籍的CD-ROM的例子是需要许可的。引用本书和引用示例代码来回答问题不需要许可,引用了相当大数量的示例代码到你的产品文档是需要获得许可的。

增加署名,对此我们表示赞赏,但这不是必需的。一个署名通常包括标题、作者、出版商和ISBN。例如:“Java EE 7 Essentials by Arun Gupta (O′Reilly). Copyright 2013 Arun Gupta, 978-1-449-37017-6.”。

如果觉得使用的代码示例超出合理使用或上面给出的权限,请随时与我们联系:permissions@oreilly.com。

Safari浏览器联机丛书(www.safaribooksonline.com)是专业内容的点播数字图书馆,提供来自世界各地引领技术和业务的作者编撰的书籍和视频两种形式。

专业技术人员、软件开发人员、网页设计师,以及商业和创意专业人士使用Safari联机丛书作为研究、解决问题、学习和认证培训的主要资源。

Safari浏览器联机丛书提供了一系列的产品组合和针对组织、政府机构和个人的定价方案。用户在一个完全可搜索的数据库中,有机会获得成千上万的书籍、培训视频,以及正式出版前的手稿,他们来自O′Reilly Media、Prentice Hall Professional、Addison-Wesley Professional、Microsoft Press、Sams、Que、Peachpit Press、Focal Press、Cisco Press、John Wiley & Sons、Syngress、Morgan Kaufmann、IBM Redbooks、Packt、Adobe Press、FT Press、Apress、Manning、New Riders、McGraw-Hill、Jones & Bartlett、Course Technology和dozens。有关Safari的联机丛书的详细信息,请访问我们的网站。

请将对本书的意见和问题寄至:

美国:

O' Reilly Media, Inc.

1005 Gravenstein Highway North

Sebastopol, CA 95472

中国:

北京市西城区西直门南大街2号成铭大厦C座807室(100035)

奥莱利技术咨询(北京)有限公司

本书有一个网站,在这里我们列出了勘误表、示例和任何其他信息。可以访问这个页面:http://oreil.ly/javaee7。想发表评论或询问有关这本书的技术问题,请发送电子邮件至:bookquestions@oreilly.com。

关于我们的书籍、课程、会议和新闻的更多信息,请参阅我们的网站:http://www.oreilly.com

在Facebook上找到我们:http://facebook.com/oreilly

在Twitter上关注我们:http://twitter.com/oreillymedia

在YouTube上观看我们:http://www.youtube.com/oreillymediaAcknowledgments

没有众人的支持就不可能有这本书。

首先,非常感谢O'Reilly出版社对我的信任并为我提供了写这本书的机会。在编辑、审查、校对和出版过程中,他们的团队提供了卓越的支持。

整个过程中,O'Reilly的Meghan Blanchette提供了卓越的编辑上的帮助,帮助我完成中期审查,提供技术审查中样式和排版的反馈,并在需要时与团队的其他成员一起联系我。

Rachel Monaghan和Kara Ebrahim帮助我审稿并确保稿件的收尾。一并感谢那些我没有和他们直接联系,但是在其他诸多方面给予了我帮助的O'Reilly团队的其他成员。

Markus Eisele(@myfear、http://blog.eisele.net)、John Yeary(@jyeary、http://javaevangelist.blogspot.com)和Bert Ertman(@BertErtman、http://bertertman.wordpress.com)负责详细的校对和技术审查,确保相关的内容被准确地覆盖。他们深刻的意见表现出丰富的经验和知识。

来自世界各地的开发人员的大量讨论帮助我更好地理解技术,对此我深表感谢。感谢我在Oracle和各个不同JSR规范领导小组的同事,解释了不同技术的预期使用用例。并感谢我身边的所有人,他们牺牲了很多必要的休息时间来帮助我的写作。


Java平台企业版(Java EE)提供了一个用于开发Web应用和企业应用的标准的平台。这些应用通常设计为多层的分层应用,包含:Web框架的前端层、提供安全和事务的中间层、提供对数据库或遗留系统连接的后端层。这些应用程序应该具备快速响应性和适应用户需求增长的可伸缩性。

Java EE平台为每个层中不同的组件定义了API,同时还提供了一些额外的服务,比如命名(naming)、注入(injection)和跨平台的资源管理等。这些组件部署在提供运行期支持的容器中,容器为应用组件提供底层的Java EE API联合视图。Java EE应用组件不会直接与其他Java EE应用组件交互。他们使用容器的协议和方法来和彼此以及平台服务进行交互。将容器置于应用组件和Java EE服务之间,使容器可以透明地注入组件所需的服务,比如声明式事务管理、安全检查、资源池和状态管理等。这种基于容器的模型和对资源访问的抽象使得开发人员从实现通用的底层基础任务中得以解放。

平台的每个组件都定义了单独的规范,规范中描述了API、javadoc以及预期的运行期行为。

Java EE 7发布于2013年6月,Java EE 7为建立Web应用和企业应用提供了一个简单的、易用的和全栈式的服务。这样的初衷和服务始于Java EE 5,在Java EE 6中持续发展,并在Java EE 6中为简化开发实践迈出了第一步。

Java EE 7平台建立在以前的版本之上,有以下三个主要目标。

WebSocket协议作为HTML5技术集的组成部分,为现代的、交互式的Web应用带来了新层次的开发简易性和网络执行效率。它提供了一个双向的、在一条TCP(Transmission Control Protocol,即传输控制协议)信道上的客户端和服务器之间全双工的通信。Java EE 7为开发和部署WebSocket客户端和端点定义了一组新的标准API。

JSON是Web应用中轻量级的数据交换通用格式。到现在为止(译者注:Java EE 7发布之前),开发人员需要在项目中绑定第三方的JSON库来处理JSON数据。Java EE 7定义了一个新的可移植的API,采用流或者对象模型来解析、生成、转换和查询JSON格式的数据。

JavaServer Faces(JSF)引入了pass-through属性和元素,pass-through能够几乎完全控制视图中的每个单个元素的用户体验,这使得HTML5-friendly标记可以很容易地嵌入到网页中。

JMS API已经大大简化。JMSContext提供了统一功能的Connection对象和Session对象。此外,一些JMS接口实现了Autocloseable接口,因此在使用完可以被容器自动关闭。进一步简化JMS API的例子还包括:准确的错误处理、抛出运行期异常而不是定义受查异常、JMSProducer上的方法链,以及简化信息发送的实现代码等。

如果REST应用中没有客户端API(在JAX-RS2中引入的API),开发人员需要使用基本的HttpURLConnection类API来实现客户端连接,还要为此编写相关的代码。

应用程序中使用更多的默认配置(比如一个用于访问运行环境中的数据库的预配置数据源,一个用于访问JMS provider的预配置的JMS ConnectionFactory,以及一个预配置的ManagedExecutor服务)为使用Java EE 7平台开发的新手提供了无缝的、现成的体验。

上下文和依赖注入(Contexts and Dependency Injection,CDI)规范现在是一个默认启用的核心组件模型。这使得平台更具凝聚力和完整性。CDI拦截器(CDI interceptors)现在能够更广泛地适用于Bean。在EJB(Enterprise JavaBean组件)之外,使用@Transactional注解可以为POJO(普通Java对象)带来处理事务的语义。Bean验证(Bean Validation)可以使用拦截器对方法的参数和结果进行自动验证。

样板代码更少、默认配置更多,以及一个内聚的完整平台,这些特质加在一起推动了开发人员使用最新版本的Java EE平台构建应用的生产力。

Java平台的批处理应用是平台中的一个新的功能,对企业客户非常重要。它使开发人员能够轻松地以面向item或task的方式,定义非交互式的、面向批量的、长时间运行的作业。

Java EE的另一个新功能是并发工具包(Concurrency Utilities),它是对Java SE的Concurrency Utilities API的扩展,用于Java EE容器管理的环境中,以便让适当的容器管理的运行期上下文可以执行某些任务。

平台中的这个功能使开发人员能够利用标准的API,从而减少了对第三方框架的依赖。

在Java EE 7之前,Java EE 6平台已经改进了提高开发人员生产力的功能,并添加了更多的功能。

Java EE 7平台是遵从JCP 2.9作为JSR 342而开发的。JCP过程对每一个JSR定义了以下三个主要可交付成果。

描述了被提议的组件及其功能的一份正式的文件。

对被提议的规范的二进制实现。参考实现有助于确保被提议的规范能以一个二进制形式实现,并为规范过程提供不断的反馈。

Java EE的参考实现建立在GlassFish社区中。

用于验证参考实现是否符合被提议的规范的一组测试。技术兼容包使多家厂商可以提供与被提议的规范兼容的实现。

Java EE 7包还定义了跨平台需求的平台规范,还包含以下组件的规范。

不同的组件工作在一起提供了一个集成的技术栈,如图1-1所示。

图1-1 Java EE 7架构

Java EE 7中删节了JAX-RPC(JSR101)、JAXR(JSR93)、EJB实体Bean(JSR153的一部分)和Java EE Application Deployment(JSR88)。

Java EE的参考实现建立在GlassFish社区中,GlassFish服务器开源版4.0提供了完全兼容Java EE 7的、自由和开源的应用服务器。在GlassFish的Web Profile发行版中同样可用,可以从http://glassfish.org下载(译者注:该地址会自动转向https://glassfish.java.net)。GlassFish应用服务器是易用的(ZIP安装器和与NetBeans/Eclipse/IntelliJ集成)、轻量级的(最小下载大小为37MB,磁盘/内存占用小)和模块化的(基于OSGi,容器按需启动)。

在Java EE 7之前,GlassFish服务器开源版3.1.2.2提供了完全兼容Java EE 6的应用服务器。它还提供具备了高可用性和带命令行界面(CLI)的集中管理平台的集群、基于Web的管理控制台和REST管理/监控API。Oracle GlassFish Server是Oracle商用的GlassFish服务器发行版,可以直接从网上下载:http://oracle.com/goto/ glassfish。截至发稿(译者注:指英文原版发稿),已经有18种与Java EE 6兼容的应用程序服务器。

TCK对所有Java EE许可证的持有者有效,用于测试他们各自的实现。

为改善平台的功能性和丰富性,增加了一些新的规范。为使平台更简单、更易用,修订了几个现有的组件规范。

新规范的主要功能描述如下。

1.Java API for WebSocket

2.Java API for JSON Processing

3.Batch Applications for Java Platform

4.Concurrency Utilities for Java EE

更新后,规范的主要功能描述如下。

1.Java API for RESTful Web Services

2.Java Message Service

3.Expression Language

4.Enterprise JavaBeans

5.Servlets

6.JavaServer Faces

7.Java Persistence

8.Interceptors

9.Contexts and Dependency Injection

10.Bean Validation

11.Java Transaction

12.JavaMail

13.Java EE Connector Architecture


Servlet定义在JSR340规范中,完整的规范可以在如下地址下载。

https://jcp.org/aboutJava/communityprocess/final/jsr340/index.html

Servlet是一种托管在Servlet容器的Web组件,可以生成动态内容。Web客户端使用请求/响应模式与Servlet交互。Servlet容器负责Servlet的生命周期,接收请求和发送响应,并执行这一过程中所需的编码/解码。

Servlet类是使用@WebServlet注解并继承javax.servlet.http.HttpServlet类的POJO。

下面是一个Servlet定义的示例:

默认的Servlet名是完全限定的类名,可使用注解的name属性覆盖。Servlet可以部署在多个URL:

@WebInitParam可以用于指定Servlet的初始化参数:

HttpServlet接口为每个HTTP请求:GET、POST、PUT、DELETE、HEAD、OPTIONS和TRACE的处理,提供了一个doXXX方法。通常情况下,开发人员只关注覆盖doGet和doPost方法。下面的代码显示了处理GET请求的Servlet。

在这段代码中:

同时,可以发送和检索HTTP Cookie。开发人员负责填充HttpServletResponse,然后容器将捕获的HTTP头信息或体消息发送给客户端。

这段代码显示了Servlet接收的HTTP GET请求是如何显示一个简单的响应给客户端的。

请求参数可以由GET和POST请求传递。在一个GET请求中,这些参数通过键/值对形式的查询字符串来传递。下面是一个使用请求参数调用前述Servlet的URL示例:

在一个POST请求中,请求参数也可以通过编码在请求体的POST数据中来传递。GET和POST的请求参数都可以从HttpServletRequest中获得:

每次请求的请求参数可以不同。

初始化参数也被称为init params,可以在Servlet上定义来存储启动和配置信息。如前所述,@WebInitParam用于为一个Servlet指定初始化参数:

可以通过覆盖javax.servlet.Servlet接口的生命周期调用方法init、service和destroy来操控servlet的默认行为。通常情况下,数据库连接的初始化发生在init方法中,数据库连接的释放发生在destroy方法中。

还可以使用Web应用程序的部署描述文件web.xml中的servlet和servlet-mapping元素定义Servlet。如下,在web.xml中定义了一个Account Servlet:

注解配置的方式覆盖了大部分的常见用例,因此web.xml不是必需的。但某些情况下web.xml是必需的,例如指定servlet的顺序只能使用web.xml中完成。

如果web.xml中的metadata-complete元素的值设置为true,那么类中定义的注解将不被处理:

在部署描述文件web.xml中定义的值会覆盖使用注解定义的值。

Servlet被打包在一个以.war文件形式存在的Web应用程序中。多个Servlet可以被打包在一起,他们共享一个Servlet上下文。Servlet上下文类ServletContext中提供了有关Servlet运行环境的详细信息,用于与容器通信。例如,读取封装在Web应用程序中的资源、写入日志文件或者分派一个请求。

可以从HttpServletRequest中获得ServletContext实例:

为实现Session跟踪,Servlet可以发送一个名为JSESSIONID的HTTP Cookie到客户端。这个Cookie可以标记为HttpOnly,从而确保Cookie不会暴露给客户端的脚本代码,因此有助于减轻多种跨站脚本的攻击:

此外,Servlet可以使用URL rewriting作为Session跟踪的基础。ServletContext. getSessionCookieConfig方法返回一个SessionCookieConfig实例,该实例可以用于配置Cookie的不同属性。

HttpSession接口用于查看和操控有关Session的信息,比如Session标识符和创建时间,并且可以将对象绑定到Session中。如下,创建了一个新的Session对象:

session.setAttribute和session.getAttribute方法用于将对象绑定到session中。

如果需要进一步处理一个请求,Servlet可以将该请求转发(forward)到另一个Servlet中。使用RequestDispatcher可以将请求分派到不同的资源中,RequestDispatcher实例可以从HttpServletRequest.getRequestDispatcher方法或者ServletContext. getRequestDispatcher方法中获得。前者可以接受相对路径,而后者可以接受当前上下文的相对路径:

在这段代码中,bank代指部署在相同上下文中另一个Servlet。

ServletContext.getContext方法可以获得不同上下文的ServletContext实例。从中可以获得RequestDispatcher实例来分派该上下文的请求。

可以通过调用HttpServletResponse.sendRedirect方法重定向(redirect)一个Servlet响应到另一个资源。这个临时的重定向响应到客户端,然后客户端发出一个新的请求到指定的URL。注意,在本例中,原始请求的对象在重定向的URL中不可用。重定向可能会稍微慢一些,因为它需要客户端发送两次请求,而转发(forward)是在容器内进行的:

在这里,响应重定向到http://example.com/SomeOtherServlet。注意,这个URL可以是不同的主机/端口,并且可以向容器发送相对或绝对路径。

除了使用@WebServlet和web.xml声明Servlet,也可以使用ServletContext.addServlet方法以编程方式来定义。该方法可以编码在ServletContainerInitializer.onStartup方法或者ServletContextListener.contextInitialized方法中。Event Listeners一节有更多的描述。

当应用程序启动时,使用给定的ServletContext调用ServletContainerInitializer. onStartup方法。addServlet方法返回ServletRegistration.Dynamic,用于创建URL映射、设置安全角色、设置初始化参数和管理其他配置项:

Servlet过滤器用于更新进入Servlet的请求和走出Servlet的响应中的有效负载和头信息。过滤器不产生响应,他们只修改或适配请求和响应,认识到这一点是很重要的。Servlet过滤器的一些典型的用例包括身份验证、日志记录、数据压缩和加密。Servlet过滤器与Servlet封装在一起来处理动态或静态内容。

Servlet过滤器通过静态内容中指定的URL匹配模式与一个Servlet或一组Servlet相关联。可以使用@WebFilter注解来定义一个过滤器:

@WebFilter("/ *")
public class LoggingFilter implements javax.servlet.Filter {
  public void doFilter(HttpServletRequest request,
         HttpServletResponse response,
         FilterChain chain){
   // . . .
  }
}

在所示的代码中,LoggingFilter被应用到Web应用程序中的所有Servlet和静态内容页面。参数chain通过调用FilterChain.doFilter()方法继续处理过滤链。如果该方法没有被调用,过滤链上当前过滤器的下一个过滤器将不会被调用。

可以在@WebFilter使用@WebInitParam来指定过滤器的初始化参数。

过滤器和目标Servlet总是在相同的调用线程中执行。多个过滤器可以排列在一个过滤器链中。

还可以使用<filter>和一个<filter-mapping>元素在部署描述符文件中定义的过滤器:

除了使用@WebFilter注解和web.xml来声明过滤器,也可以使用ServletContext. addFilter()方法,编程方式地定义过滤器。在ServletContainerInitializer.onStartup方法或ServletContextListener.contextInitialized方法中可以做到这一点。addFilter()方法的返回类型为ServletRegistration.Dynamic,该返回值可以用于添加映射URL匹配模式、设置初始化参数,以及处理其他配置项:

事件监听器提供对ServletContext、HttpSession和ServletRequest对象生命周期的回调事件。这些监听器实现了支持上述对象状态变化事件通知的接口类。每个类可以通过@WebListener注解定义或者在web.xml中声明,抑或通过ServletContext. addListener()方法注册。一个典型的监听器例子是以编程方式注册一个额外的Servlet而没有明确要求程序员这样做,或者应用程序级别的数据库连接的初始化和恢复。

可以有多个监听器类来监听每种事件类型,他们可能会在其中的容器指定顺序来为每个事件类型调用监听器Bean。在应用程序关闭期间,以相反的顺序通知这些监听器。

Servlet上下文监听器用于监听该上下文中资源的事件:

ServletContextAttributeListener用于监听上下文属性的变化:

HttpSessionListener监听Session中资源的事件:

HttpSessionActivationListener用于监听Session被钝化或激活的事件:

HttpSessionAttributeListener用于监听Session属性的变化:

HttpSessionBindingListener用于监听Session绑定和松绑对象的事件:

ServletRequestListener监听Request中资源的事件:

ServletRequestAttributeListener用于监听Request属性的变化。

还有AsyncListener,它是用于管理异步事件,比如异步执行已完成、超时或发生错误。

除了使用@WebListener注解定义和在web.xml中声明监听器,还可以使用ServletContext.addListener()方法以编程方式定义他们。可以在ServletContainer Initializer.onStartup或ServletContextListener.con textInitialized方法中做到这一点。

当应用程序为给定的ServletContext启动时,ServletContainerInitializer.onStartup方法会被调用:

服务器端的资源是昂贵的,应谨慎使用。试想一个Servlet不得不等待的场景:连接池中提供一个有效的JDBC连接,接收JMS消息或从文件系统中读取资源。等待“长时间运行”的过程完全返回,阻塞了线程(等待,坐着,什么也不做),服务器资源没有得到最佳的使用。这正是异步处理的用武之地。服务器可以异步执行,在等待长时间运行的过程完成期间,控制(或线程)被返回到容器来执行其他任务。请求处理在长期运行的过程返回之后,在同一个线程继续执行,也可以将长期运行的过程分派给一个新的资源。一个典型长期运行的过程的用例是一个聊天应用程序。

异步行为需要在Servlet中显式启用。可以为@WebServlet注解添加asyncSupported属性来实现:

也可以在web.xml中通过设置<async-supported>元素为true,或者以编程方式在注册过程中调用ServletRegistration.setAsyncSupported(true)来启用异步行为。

然后就可以使用request的startAsync()方法,在一个单独的线程中启动异步处理。该方法返回类型是AsyncContext,代表异步请求执行的上下文。然后,可以通过(显式地)调用AsyncContext.complete()方法或者(隐式地)分派到另一个资源来完成异步请求。在后一种情况下,容器完成异步请求的调用。

比方说,长期运行的过程的实现如下:

该服务类可以在doGet()方法中调用:

在这段代码中,请求示例request置入异步模式。AsyncListener注册了监听事件,包括请求处理完成、请求处理已超时或者请求处理发生错误。长期运行的服务在一个单独的线程中被调用,该服务最终调用AsyncContext.complete()方法,发出请求处理完成的信号。

一个请求可以从一个异步的Servlet分派到一个同步的Servlet,但反之是非法的。

异步行为在Servlet的过滤器是可用的。

Servlet 3.0允许异步请求处理,但只允许传统的I/O,这限制了应用程序的可扩展性。在典型应用中,在while循环中读取ServletInputStream:

如果传入的数据被阻塞或者数据流慢于服务器的读取,那么服务器线程就要等待数据。如果数据被写入到ServletOutputStream,同样也可能发生这样的等待。这限制了Web容器的可扩展性。

非阻塞I/O允许开发人员在有可读的数据时执行读操作或者在有写入的数据时执行写操作。这不仅增加了Web容器可伸缩性,也增加了可以同时处理的连接数量。非阻塞I/O只能用于异步请求处理的Servlet、Servlet过滤器和升级处理。

Servlet 3.1通过引入两个新的接口实现了非阻塞I/O:ReadListener和WriteListener。这些接口定义了回调方法,用于在有可读或者可写内容时被调用而不阻塞线程。

在本例中doGet()方法需要重写:

调用setXXXListener方法表明使用非阻塞I/O来代替传统的I/O。

ReadListener有以下三个回调方法。

在这段代码中,onDataAvailable()回调方法被调用时数据可以不受阻塞地读取。ServletInputStream.isReady()方法用于检查数据可以被读取而不会阻塞,然后将数据读出。context.complete()方法在onAllDataRead()方法和onError()方法中被调用,以发出完成读取数据的信号。ServletInputStream.isFinished()方法可被用于检查非阻塞I/O读取的状态。

在ServletIntputStream中最多可以注册一个ReadListener。

WriteListener有以下两个回调方法。

在ServletOutputStream中最多可以注册一个WriteListener。ServletOutputStream. canWrite()方法是个新的方法,用于检查非阻塞数据是否可写。

包含在库或框架JAR包的META-INF目录中的一个web fragment片段是部分或全部在web.xml文件中。如果这个框架绑定在WEB-INF/lib目录中,容器会选择和配置框架,而无需开发人员显示地做到这一点。

Web Fragment可以包括几乎所有web.xml中指定的元素。然而,其顶级元素必须是web-fragment,并且相应的文件必须命名为webfragment.xml。这将允许Web应用程序逻辑分区:

开发人员可以指定在web.xml和web-fragment.xml中定义的资源的加载顺序。web.xml中的<absolute-ordering>元素用于指定资源加载的确切顺序,web-fragment.xml中的<ordering>元素用于指定相对排序。这两个命令是互斥的,绝对顺序覆盖相对顺序。

绝对顺序包含一个或多个<name>元素用于指定资源的名称,<name>元素定义的顺序即是加载顺序。<others/>元素允许指定其他没有命名的资源的加载顺序:

在这段代码中,web.xml中指定的资源先被加载,随后是MyServlet和MyFilter。

<ordering>元素中的零个或一个<before>和<after>元素用于指定资源,使web-fragment命名的资源在其之前、之后被加载:

这段代码将要求容器在加载MyServlet资源(在其他地方定义的)之后,加载MyFilter资源。

如果web.xml中metadata-complete设置为true,那么web-fragment.xml文件不会被处理。解决web.xml和web-fragment.xml之间的冲突时,web.xml文件具有最高的优先级。

如果一个web-fragment.xml文件没有<ordering>元素并且web.xml没有<absolute- ordering>元素,资源被假定不具有任何顺序依赖性。

Servlet通常通过互联网访问,因此其安全需求是通用的。可以使用注解或在web.xml中指定Servlet的安全模型,包括角色、访问控制和认证要求。

@ServletSecurity注解用于指定Servlet的实现类的所有方法或特定的doXXX()方法的安全约束。容器将强制执行相应的doXXX()消息只能由指定角色的用户调用:

在这段代码中,@HttpMethodConstraint注解用于指定doGet()方法可以由R2角色的用户调用,doPost()方法可以由R3和R4角色的用户调用。在@HttpConstraint注解用于指定所有其他方法可以由R1角色的用户调用。角色被映射到安全主体或容器组中。

安全约束也可以在web.xml中的<security-constraint>元素中指定。在<security- constraint>中,<web-resource-collection>元素用于指定HTTP操作和网络资源的约束,<auth-constraint>元素用于指定允许访问该资源的角色,<user-data-constraint>元素在其子元素<transport-guarantee>中表明客户端和服务器之间的数据应该以何种方式加以保护:

这个部署描述符只要求/account/ *路径下的GET方法是被保护的。该方法只能由manager角色的用户访问,数据传输的约束是INTEGRITY。除了GET方法,其他的所有HTTP方法是不受保护的。

如果HTTP方法没有在security-constraint中列举,由约束定义的保护适用于全部的HTTP(扩展)方法:

在这段代码中,所有的HTTP方法在/account/ *路径下都受保护。

当<securityconstraint>中至少列出一个<http-method>元素时,没有在该<security-constraint>中列出的HTTP协议方法在Servlet 3.1中被定义为未保障的(uncovered):

在这个代码片段中,只有HTTP GET方法是受保护的,而所有其他的HTTP协议方法,如POST和PUT是未保障的。

<http-method-omission>元素用于指定不受约束保护的HTTP方法清单:

在这段代码中,只有HTTP GET方法是不受保护的,而所有其他的HTTP协议方法受保护。

<deny-uncovered-http-methods>元素,是Servlet 3.1中的新元素,用于拒绝对未保障的HTTP方法的HTTP方法请求。被拒绝的请求返回一个403(SC_FORBIDDEN)状态码:

在这段代码中,<deny-uncovered-http-methods>元素确保HTTP GET调用时所需的安全认证,而所有其他的HTTP方法被拒绝,HTTP状态码为403。

@RolesAllowed注解、@DenyAll注解、@PermitAll和@TransportProtected注解提供了另一种为特定的资源或资源方法指定安全角色的方式:

如果注解在类和方法两个级别指定,在该方法中指定的注解覆盖在类中指定的。

Servlet 3.1引入了以下两个新的预定义角色。

相比在一个特定的角色上指定而言,这使得可以在一个更高的层次上指定安全约束。

最多可以在目标上指定一个@RolesAllowed、@DenyAll或者@PermitAll注解。@TransportProtected注解可以和@RolesAllowed或@PermitAll注解组合使用。

Servlet可以配置为HTTP基本认证、HTTP摘要认证、HTTPS客户端,以及基于表单的认证:

这段代码显示了如何实现基于表单的身份验证。登录表单必须包含输入用户名和密码字段,字段必须分别命名为j_username和j_password。表单的action总是命名为j_security_check。

Servlet的3.1要求密码表单字段的属性autocomplete="off",进一步加强基于Servlet表单的安全性。

HttpServletRequest还提供了编程式的安全方法login()、logout()和authenticate()。

login()方法验证在密码验证领域(具体到一个容器)中提供为ServletContext配置的用户名和密码。这确保了getUserPrincipal()方法、getRemoteUser()方法和getAuthType()方法的返回值有效。login()方法可以作为基于表单登录的替代方法。

authenticate()方法使用为ServletContext配置的容器登录机制来验证当前请求的用户。

可以使用ServletContext.getResource()方法和ServletContext.getResourceAsStream()方法来访问绑定在.war文件中的资源。资源路径被指定为以“/.”前导的字符串,该路径被解析为相对于上下文根目录或者相对于绑定在WEB-INF/lib目录中JAR文件的META-INF/resources目录:

myApplication.war 
 WEB-INF 
  lib
   library.jar

library.jar具有以下结构:

library.jar 
 MyClass1.class 
 MyClass2.class 
 stylesheets
  common.css 
 images
  header.png 
  footer.png

通常情况下,如果必须在Servlet中访问样式表和图像的目录,需要手动把他们解压到Web应用程序的根目录。Servlet 3.0中允许库在META-INF/resources目录打包的资源:

library.jar 
 MyClass1.class 
 MyClass2.class 
 META-INF
  resources
   stylesheets
    common.css 
   images
    header.png 
    footer.png

在本例中,资源不需要被提取到应用程序的根目录,可以直接被访问。这将允许直接访问绑定在META-INF/resources目录下的第三方JAR文件中的资源,而无需直接手动解压缩。

在扫描绑定在WEB-INF/lib目录中的JAR文件之前,应用程序总是会在根目录中寻找资源。扫描WEB-INF/lib目录中的JAR文件的顺序是不确定的。

HTTP错误代码或Servlet抛出的异常可以映射到一个和应用程序绑定的资源,以定制Servlet产生错误时显示的内容。这使得Web应用程序可以细粒度地映射错误到自定义页面。这些页面是通过<error-page>元素定义的:

添加上述代码片段到web.xml后,客户端试图访问一个不存在的资源时,将显示/error-404.jsp页面。通过添加其他的<error-page>元素,可以轻松地为其他HTTP状态码实现这种映射。

<exception-type>元素用于映射一个Servlet抛出的异常到Web应用程序中的资源:

添加上述片段web.xml后,如果Servlet抛出org.example.MyException异常,客户端将显示/error.jsp页面。可以通过添加其他<error-page>元素轻松地为其他异常实现这种映射。

<error-page>声明对于每个类名和HTTP状态代码必须是唯一的。

可以在Servlet中定义@MultipartConfig注解,表示它期待的请求类型是multipart/form-data。HttpServletRequest.getParts()方法和HttpServletRequest.getPart()方法提供multipart请求的各个部分:

在这段代码中:

Servlet 3.1增加了一个新方法Part.getSubmittedFileName()来获取客户端指定的文件名称。

如下Servlet可以从JSP页面调用:

在这段代码中,表单被以multipart/formdata编码类型提交到FileUploadServlet。

HTTP 1.1(RFC 2616)第14.42节定义的升级机制,允许从HTTP 1.1过渡到一些其他的、不兼容的协议。协议变更后的应用层通信的功能和性质是完全依赖于所选择的新的协议。在客户端和服务器之间协商升级后,后续请求使用新选择的协议交换消息。一个典型的例子是RFC 6455的Opening Handshake一节中描述的如何从HTTP协议升级到WebSocket协议。

Servlet容器提供了一个HTTP升级机制。然而,Servlet容器本身不感知升级协议。协议处理被封装在HttpUpgradeHandler中。Servlet容器和HttpUpgradeHandler之间的数据读写的形式是字节流。

由Servlet.service()方法决定是否升级。升级是通过添加一个新的方法HttpServletRequest.upgrade()和两个新的接口javax.servlet.http.HttpUpgradeHandler和javax.servlet.http.WebConnection来实现的:

请求处理查找头信息中的Upgrade字段并基于其值决定是否升级。在这个例子中,如果Upgrade字段值等于echo,连接就会升级。响应状态和头信息被正确设置。upgrade()方法被HttpServletRequest调用,并传递一个HttpUpgradeHandler实例。

在退出Servlet的service方法后,Servlet容器完成所有过滤器的处理,并标记由HttpUpgradeHandler的实例来处理连接:

这段代码显示了HttpUpgradeHandler的实现。Servlet容器调用HttpUpgradeHandler的init()方法,传递一个WebConnection实例以允许协议Handler访问数据流。当升级处理完成后,HttpUpgradeHandler.destroy()方法被调用。

Servlet过滤器只处理初始的HTTP请求和响应,不参与后续的通信。


JavaServer Faces(JSF)定义在JSR344规范中,完整的规范可以在如下地址下载。

https://jcp.org/aboutJava/communityprocess/final/jsr344/index.html

JSF是基于Java的Web应用程序开发的服务器端用户界面(UI)框架。使用JSF可以实现:

JSF应用程序包括:

Facelets是JSF的视图声明语言(又名视图处理器)。他是JSP的替代者,JSP现在只保留了向后兼容性。在JSF2规范中引入的新功能,如复合组件和Ajax,只会暴露给Facelets的使用者。Facelets的主要优点包括一个功能强大的模板系统,可重用和易于开发,更好的错误报告(包括行号),以及对设计人员友好。

Facelets页面使用XHTML1.0和层叠样式表(CSS)来编写。XHTML1.0的文档是遵循XML1.0规则书写的HTML4文档。页面必须符合XHTML-1.0-Transitional DTD的要求。

使用XHTML定义的一个简单的Facelets页面如下:

在这段代码中,XML声明之后是文档类型定义(DTD)。页面的根元素是html,其中声明了命名空间http://www.w3.org/1999/xhtml。声明XML命名空间是为了在Web页面中使用标签库,Facelets的html标签(那些以h开头的标签)和普通的html标签用于添加组件。

表3-1给出了Facelets支持的标准标签库集合。

表3-1 Facelets支持的标准标签库

前  缀

URI

示  例

h

http://xmlns.jcp.org/jsf/html

h:head,h:inputText

f

http://xmlns.jcp.org/jsf/core

f:facet,f:actionListener

c

http://xmlns.jcp.org/jsp/jstl/core

c:forEach,c:if

fn

http://xmlns.jcp.org/jsp/jstl/functions

fn:toUpperCase,fn:contains

ui

http://xmlns.jcp.org/jsf/facelets

ui:component,ui:insert

按照惯例,使用XHTML创建的Web页面使用.xhtml作为扩展名。

Facelets提供表达式语言(EL)集成,这使得后台Bean可以和前台UI之间实现双向的数据绑定:

在这段代码中,EL表达式#{name.value}代表请求作用域的CDI Bean的value字段的值:

重要的是要为这个CDI Bean添加@Named注解,以使其可以依赖注入到EL表达式中。

在JSF2.2中,@javax.faces.bean.ManagedBean注解是未来的版本中不建议使用的,所以强烈建议使用@Named来代替。

JSF2.2还引入了新的CDI作用域:javax.faces.view.ViewScoped。在一个Bean上指定该注解,可以将这个Bean与当前视图绑定。javax.faces.bean.ViewScoped注解是未来的版本中不建议使用的,所以强烈建议使用新引入的作用域。

同样地,一个EJB可以在EL表达式注入:

这是一个无状态的会话Bean,包含一个返回客户名称列表的业务方法。@Named标记它可以依赖注入到EL表达式中,可以在Facelets的EL表达式中使用:

在这段代码中,客户名称列表的返回值显示在一个表格组件当中。请注意getCustomerNames方法是如何作为一个属性用在EL表达式中的。

Facelets还提供了编译期EL表达式验证。

此外,Facelets提供了一个功能强大的模板系统,使我们可以在一个Web应用程序的多个页面之间提供一致的界面外观。基础页面称为模板,是通过Facelets的模板标签创建的。这个页面为Web页面定义了一个缺省页面骨架,包括内容占位符,会在将来的页面中使用模板来添肉。模板客户端页面使用这个模板,为模板骨架中定义的占位符提供具体的肉(实际的内容)。

表3-2列出了一些在模板和模板客户端页面中使用的通用标签。

表3-2 常见的Facelets标签为模板

标  签

说  明

ui:composition

定义页面的布局,是模板的可选标签。如果使用template属性时,这个标签的子标签定义模板的布局。否则,只是一组元素的组合,可以在任何地方插入。这个标签之外的内容将被忽略

ui:insert

用在模板页面,为模板要插入的内容定义占位符。在模板客户端页面中,与之匹配的ui:define标签,替换占位符的内容

ui:define

用在模板客户端页面,定义内容去替换匹配的ui:insert标签

ui:component

插入一个新的UI组件到JSF组件树。这个标签之外的任何组件或内容片段将被忽略

ui:fragment

类似ui:component,但不忽视这个标签之外的内容

ui:include

引入src属性所指向的文件,作为当前Facelets页面的一部分

模板页面的外观示例如下:

在这段代码中,在页面中使用<div>和CSS(在这里没有显示)定义结构。ui:insert定义了被模板客户端页面替换的内容。

模板客户端页面示例如下:

在这段代码中,名为top和bottom的ui:insert没有定义,那么这些部分使用模板页面的ui:define元素的名称匹配模板中的ui:insert元素,因此模板的这部分内容被替换。

JSF定义了处理资源的标准方式,如图片、CSS或JavaScript文件。这些资源都需要通过组件来正确呈现。

这些资源可以被打包在Web应用程序的/resources目录中或classpath的/META-INF/resources目录中。资源也可以被本地化、版本化,以及整理到库中。

资源可以在EL表达式中被引用:

在这段代码中,header.jpg位于标准的资源目录中。

如果资源位于一个名为corp的库(打包资源的文件夹)中,那么就可以使用library属性访问:

JavaScript可以被引入:

在这段代码中,myScript.js是封装在标准的资源目录下脚本目录中的JavaScript资源。

CSS样式表可以被引入:

ResourceHandler API提供编程的方式来服务这些资源。

JSF使用Facelets的功能和资源的处理来定义复合组件,定义在Facelets标记文件中的一个或多个JSF组件组成了一个复合组件。这个.xhtml文件存储在资源库中,可以从页面的任意区域创建一个可重用的组件。

复合组件在定义页面中定义,在使用页面中使用。定义页面使用<cc:interface>定义元数据(或参数),使用<cc:implementation>定义实现,其中cc是http://xmlns.jcp.org/jsf/composite/命名空间的前缀。JSF规范的未来版本可能会放宽对指定元数据的规定,因为它可以从实现本身派生。

可以使用JSF1.2定义一个复合组件,但这需要对JSF的生命周期有更深入的了解,并且需要编写多个文件。JSF2只用一个XHTML文件即可实现,简化了复合组件的定义。

例如,在一个Facelet中有如下代码片段来显示登录表单:

这段代码将呈现一个两行三列的表,如图3-1所示。

图3-1 在浏览器中输出的JSF Facelets页面

第一列显示要输入的字段的提示,第二列显示文本输入框,数据可以输入其中,第三列(它开始是空的)用于显示对应字段的消息。第一行的输入值绑定到User.name字段,第二行中的输入值绑定到User.password字段。还有一个命令按钮,单击该按钮将调用UserService的Bean的Register方法。

如果这个登录表单要被显示在多个页面,那么应当将这段代码片段转换成一个复合组件而不是到处复制这段代码。这需要将代码片段复制到一个.xhtml文件中,该文件位于标准资源目录的一个库中。基于约定优于配置的思想,这段代码片段会自动分配一个命名空间和一个标签名。

比如,前述的片段被复制到resources/mycomp目录下的login.xhtml文件中,其定义的页面所示如下:

在这段代码中,cc:interface用于定义描述组件特性的元数据,如支持的属性、facet和事件监听的连接点。cc:implementation包含代替复合组件的标记。

复合组件的命名空间是把http://xmlns.jcp.org/jsf/composite/和mycomp连接起来。标签名是不带.xhtml后缀的页面文件名:

比方说,这段代码片段在不同页面中单击提交按钮时,需要传递不同的值表达式(而非#{ user.name })并调用不同的方法(而不是#{ userService.register})。定义的页面可以传递的值如下:

在这段代码中,所有的参数都显式地在cc:interface中被指定。第三个参数有一个目标属性指向ccForm:loginButton。

在cc:implementation中:

用户名、密码和actionListener传递使用页面中需要的属性:

现在,使用页面可以传递不同的backing bean,在点击提交按钮时,可以调用不同的业务方法。

总而言之,复合组件提供了以下好处。

JSF定义了标准的请求处理生命周期阶段。应用程序开发人员并不需要知道生命周期的详细信息,但在某些情况下我们可以从中得到帮助,比如何时验证,何时转换,通常在何时处理事件,怎样改变事件的处理方式和时间。

一个JSF页面是由被称为视图的UI组件树来表示的。当客户端向该页面发起请求时,开始一个生命周期。在生命周期中,JSF的实现必须建立视图,同时考虑保存此前提交页面的状态。当客户端提交页面时,JSF的实现必须执行一些任务,比如验证视图中组件的数据输入,将输入数据转换为服务器端指定的类型,并且将数据绑定到backing bean。JSF的实现为生命周期中的一系列步骤执行所有这些任务。

应用程序的不同组件经历以下明确定义的请求处理生命周期阶段。

恢复并创建一个服务器端的组件树表示来自客户端的UI信息。

如果是第一次向一个URL发起请求,那么创建一个新的视图对象并呈现给客户端。这个视图同时存储在当前FacesContext实例中。如果已经在FacesContext中发现视图状态,那么视图被恢复并显示。

任何与UI组件连接的自定义的转换器、验证器和渲染器,在这个阶段被恢复。如果UI组件的值被直接映射到一个托管Bean定义的属性,那么该属性的值将被恢复并与视图相关联。大部分的工作是由ViewHandler.restoreView方法来处理的。

这个阶段将使用来自客户端的请求参数、请求头信息、Cookie等更新服务器端组件。

更具体地说,UIComponent.processDecodes方法被所有组件调用。这一阶段可能结束于处理验证阶段或渲染响应阶段。如果任何转换或验证失败,则终止当前操作,控制直接转到渲染响应阶段,展示转换或验证的错误给客户端。

这一阶段将处理所有UIComponent已配置的验证和数据类型转换。

在这个阶段中,UIComponent.processValidators方法被所有组件调用。如果有任何转换或验证错误发生,那么当前进程被终止,并且控制被引导到渲染响应阶段报告发生的任何错误。

到达这个阶段意味着请求值的语法是有效的。

UIComponent的值会与模型对象同步,模型对象通常是backing bean。在这个阶段中,UIComponent.processUpdates方法被所有组件调用。将请求值赋值给模型对象,也可能会导致排队和触发事件。

调用应用的逻辑并执行导航处理。

所有已注册的UIComponent的监听器被调用。例如,所有的动作组件(如命令按钮或超链接)有默认的动作监听器,会在这个阶段被调用。

渲染响应并将其返回给客户端应用程序。

在渲染响应之前,应用程序通过调用UIViewRoot.saveState方法在缓存中存储视图的状态。

JSF原生支持添加Ajax功能到网页。JSF允许局部视图的处理,其中只有视图中的一些组件用于处理该响应。JSF可以摘选页面上的某些组件,渲染局部页面,而不是整个页面。

有以下两种方法可以启用这种支持。

编程方式的Ajax集成是通过资源处理机制启用的。jsf.js是在javax.faces库中预定义的资源。该资源包含用于Ajax和JSF页面交互的JavaScript API。可以在页面中使用outputScript标签来启用:

也可以向服务器发起异步请求:

在这段代码中:

只处理视图的一部分组件(本例中是指name和password组件)的能力被称为局部视图处理。类似地,只渲染输出页面中一部分组件(本例中是status组件)被称为局部输出渲染。

表3-3列出了渲染(render)属性的取值。

表3-3 f:ajaxrender属性的取值

取  值

说  明

@all

页面上的所有组件全选

@none

页面上的组件都不选,这是默认值

@this

触发请求的当前组件

@form

表单内的所有组件全选

IDs

使用空格分隔的指定id的组件

EL表达式

解析为EL表达式的字符串集合

执行(execute)属性采用了类似的取值,但该属性的默认值是@this。

注意login方法的签名。它必须返回void,并使用javax.faces.event.ActionEvent作为唯一的参数。

通过启用f:ajax来声明Ajax集成。该标签可以嵌套在一个组件内(为单个组件启用Ajax),或者使用该组件“包装”多个组件(为多个组件启用Ajax)。

上面的代码可以被更新为如下风格的Ajax:

在这段代码中,我们使用f:ajax的execute属性来指定输入组件的列表,使用render属性指定被渲染的输出组件。默认情况下,如果f:ajax嵌套在一个组件内,并且没有指定事件,异步请求的触发将基于父组件的默认事件(本例中,是指命令按钮的单击事件)。

可以在f:ajax中指定延迟(delay)属性。该属性值以毫秒为单位。如果在延迟时间内有多个请求发出,那么只有最近的请求被发送,其他的请求被丢弃。

这段代码设置的延迟为200毫秒。默认值是300毫秒,但你也可以指定属性值为none来禁用此机制。

f:ajax标签可以包装多个组件:

在这段代码中,f:ajax的listener属性对应的Java方法如下:

这个listener方法被子组件的默认事件调用(本例中,是指h:inputText组件的值改变事件)。可以在子组件的内嵌f:ajax中指定额外的Ajax功能。

JSF提供了在HTTP GET请求中,映射URL参数到EL表达式的支持。还提供了产生对GET友好的URL的支持。

视图参数可以用于在GET请求中,映射URL参数到EL表达式。可以通过添加下面的代码片段到一个Facelets页面来实现:

访问Web应用程序的index.xhtml?name=jack将会:

可以使用f:event实现在页面渲染前处理视图参数:

在这段代码中,#{user.process}指定的方法可在渲染页面所需的任何初始化之前执行。

可以使用h:link和h:button产生对GET友好的URL。在其中指定预期的Facelets页面,而不是手动构造URL:

这段代码被翻译成如下HTML标签:

视图参数很容易指定:

在这段代码中,如果#{user.name} 绑定值为“Jack”,那么这段代码被翻译成如下HTML标签:

同样地,h:button可以用于指定输出:

这段代码将产生如下HTML标签:

转换器、验证器和监听器是服务器端的附属对象,用于为页面上的组件添加更多的功能。行为是客户端的扩展点,可以使用行为定义的脚本增强组件的渲染内容。

转换器用于把组件中输入的数据从一种格式转换为另一种格式(例如,string到number)。JSF提供了几种内置的转换器,如f:convertNumber和f:convertDateTime。他们可应用于任何可编辑的组件:

在这段代码中,文本框中输入的文本将被转换为一个整数,如果文本不能转换,则抛出一条错误信息。

可以很容易地创建自定义的转换器:

在这段代码中,方法getAsObject和getAsString用于执行模型数据对象和适于页面渲染表述的字符串对象之间的,对象到字符串和字符串到对象的转换。这个POJO实现了Converter接口,同时还标有@FacesConverter注解。这样的转换器就可以在JSF页面中使用:

@FacesConverter的value属性值一定要和converterId属性值匹配。

校验器用于验证输入组件接收的数据。JSF提供了几种内置的验证器,如f:validateLength和f:validateDoubleRange。这些验证器可应用于任何可编辑的组件:

<h:inputText value="#{user.name}" id="name">
  <f:validateLength minimum="1" maximum="10"/>
</h:inputText>

在这段代码中,输入的文本长度验证为1到10个字符之间。

如果长度超出规定的范围,抛出一条错误消息。

可以很容易地创建自定义的验证器:

在这段代码中,如果待验证的值验证成功,方法返回。否则,抛出异常ValidatorException。这个验证器可以应用于任何可编辑的组件:

@FacesValidator的value属性值一定要匹配这里的f:validator的id属性值。

JSF还提供了对Bean验证(Bean Validation)定义的约束的内置集成。除了在Bean中放置约束注解,没有额外的工作需要开发人员来做,因为违反约束的错误信息会自动转换成FacesMessage并显示给最终用户。当验证特定组件时,f:validateBean可以指定validationGroups以指示哪个验证组应被考虑在内。这将在第11章中进行详细说明。

监听器用于监听组件上的事件。事件可以是值发生变化、按钮被点击、链接被点击,或者发生了其他情况。监听器可以是托管Bean的方法或是Bean类本身。

ValueChangeListener可以注册到任何可编辑的组件上:

在这段代码中,相关的表单提交时,User类中的nameUpdated方法被调用。可以通过实现ValueChangeListener接口创建一个类级别的监听器,然后使用f:valueChangeListener标签在页面中指定该监听器。

与转换器、验证器和监听器不同,行为用于增强其客户端功能,通过为组件声明附属脚本来实现。例如,f:ajax定义为一个客户端行为。客户端行为还允许执行客户端验证、客户端日志记录、显示工具提示,以及其他类似的功能。

可以通过扩展ClientBehaviorBase类和标记@FacesBehavior注解来定义自定义行为。

除了使用JSF内置的验证器和创建自定义的验证器,还可以使用Bean验证为一个backing bean定义指定的约束。

试想在一个简单的Web应用程序中有一个页面,其表单内包含多个文本字段:

假设每个文本字段绑定到一个托管Bean的属性,且该属性至少有一个相关的Bean验证约束注解:

每个h:inputText元素是由一个UIInput组件支持的,包含一个验证器实例,它的id和javax.faces.Bean相关联。与用户指定的验证约束关联的验证器的validate方法,在处理验证阶段被调用。

javax.faces.Bean标准验证器也确保每一个导致ConstraintViolation的模型数据被包装到FacesMessage中,并与所有其他类型的验证器一样添加到FacesContext中。然后,该消息与其他验证消息的处理一样显示给用户。

一个或多个验证组可以与一个输入标签关联:

这也可以用于创建跨多个页面的验证。

验证组也可以与一组输入标签关联:

在这段代码中,标识为#{person.name}和#{person.age}的字段的相关约束会被验证。

JSF定义了隐式和显式的导航规则。

隐式导航规则寻找一个动作(例如链接或按钮的点击)的结果。如果发现一个Facelets页面与动作结果匹配,该页面将被呈现:

在这段代码中,单击该按钮将显示在同一个目录下的login.xhtml页面。

可以在faces-config.xml中使用<navigation-rule>指定显式的导航,可以使用<If>指定有条件导航:

在这段代码中,只有当用户的身份是一个优质客户(premiumcustomer)时,从index.xhtml到login.xhtml的页面导航才会发生。

JSF2.2引入了Faces Flow。这个功能借用了ADF Task Flows、Spring Web Flow和Apache MyFaces CODI的核心概念,使其标准化为JSF2.2的一部分,为在应用程序中定义控制流程,提供一种模块化的方法。

Faces Flow将相关网页和相应的后台Bean封装成一个模块。该模块具有定义良好的入口点和出口点,可以由应用程序开发人员来分配。通常在Faces Flow中的对象被设计为允许用户来完成的任务,该任务需要在若干不同视图输入信息。应用程序也因此成为流而不仅仅是视图的集合。

试想有一个多页面的购物车,第一个页面用于选择项目,第二个页面用于选择配送,第三个页面用于输入信用卡详细信息,第四个页面用于确认订单。可以使用托管Bean去捕获数据,会话作用域的变量在页面之间传递信息,点击按钮来调用后台EJB的业务逻辑,以及从一个页面到另一个页面的(有条件的)导航规则。这样的实现会有如下问题。

Faces Flow提供了以下针对这些问题的解决方案。

应用程序的流不再局限于在页面之间流动,而是被定义为在“节点”之间流动。有以下五种不同类型的节点。

这些节点定义流的入口点和出口点。

新引入的CDI作用域@FlowScoped定义了指定流中Bean的作用域。这使得当进入/退出作用域时,自动激活/去活Bean:

在这段代码中,这个Bean有两个流作用域的变量:address和creditCard。Bean定义的流为flow1。

还引入新的流存储EL对象,#{flowScope}。该流存储对象映射到facesContext.getApplication().getFlowHandler().getCurrentFlowScope():

在这段代码中,文本框中输入的值和#{flowScope.value}绑定,这个EL表达式的值能够被流中的其他页面访问。

可以使用<flow-definition>以声明方式定义流,或使用FlowBuilder API以编程方式定义流。这两种机制是互斥的。

流可以打包在JAR文件或目录中。JAR包要求流在JAR文件中的META-INF/faces-config.xml中显式地声明。流节点封装在META-INF/flows/ <flowname>,其中<flowname>是一个JAR目录项,名字是对应的FlowDefinition类中指定的流id。如果@FlowScoped Bean和通过FlowBuilder定义的流被打包在JAR文件中,他们必须附有META-INF/beans.xml:

在这个JAR包中:

在这段代码中:

同样地,MyFlow2Definition类定义flow2。

流打包在目录中,使用约定优于配置。这些约定是:

遵循方才定义的约定,在这个目录中:

在这段片段中:

WEB-INF目录将包含页面所需的其他资源,比如backing bean。

JSF2引入了Facelets作为默认的视图声明语言(VDL)。Facelets允许使用XHTML和CSS创建模板,模板可以为应用程序的不同页面提供一致的界面外观。JSF2.2定义了资源库契约,一种与资源关联的模板库,可以以可重用和可互换的方式应用到整个应用程序。应用程序中可配置的视图集合将可以声明自己是资源库契约中任何模板的模板客户端。

资源库契约驻留在Web应用程序的根目录的契约目录中:

在这段代码中:

契约是基于调用URL pattern应用的。根据此处指定的配置,red契约将被应用到faces/index.xhtml,契约将被应用到faces/new/index.xhtml。

契约可以被打包在JAR文件的META-INF/contracts项中。在JAR文件中的每个契约必须有一个标记文件。文件名由符号常量javax.faces.application.ResourceHandler. RESOURCE_CONTRACT_XML的值给出:

我们应用程序的契约目录的内容被打包在JAR文件(比如名叫layout.jar)的META-INF/contracts项中。这个JAR文件可以被打包到WEB-INF/lib中,其所声明的模板可以在应用程序中使用:

可以使用一个新的layout.jar文件,提供一组类似的插入点和资源(可能有不同的CSS)声明,来改变应用程序的界面外观。

改变f:view中ui:composition的template属性值可以动态地改变页面的模板:

在这段代码中:

backing bean的定义很简单:

HTML5为现有的HTML元素增加了一系列的新属性。这些属性包括input元素的type属性,可以支持例如电子邮件、网址、电话、数量、范围和日期类型的值:

这段代码可以让浏览器检查输入的文字是否为电子邮件格式。

此外,自定义数据属性,也被称为data-*属性,可以被定义为存储页面或应用程序私有的自定义数据。每个HTML元素可能有指定的任意数量、任何值的自定义数据属性:

这段代码引入了数据长度data-length为自定义数据属性。

这些属性不会被页面渲染,但可以通过JavaScript读取。该属性可以在JavaScript中被访问:

在JSF2.2之前,默认情况下,这些新引进的类型和data-*属性不被组件支持。JSF组件是否支持这组可用的属性,是由UIComponent和Renderer组合来决定。在某些情况下,属性的值由UIComponent或Renderer来解释(例如,h:panelGrid的列属性),而在其他情况下,该值直接传给用户代理(例如,h:inputText的lang属性)。在这两种情况下,UIComponent/Renderer事先知道这组可允许使用的属性。

JSF2.2引入了passthrough属性,这让我们可以列出任意键/值对,直接传递给用户代理,而不经由UIComponent或Renderer解释。可以用以下三种不同的方式来指定passthrough属性。

在这段代码中,p是命名空间的短名称。

在这段代码中,type属性的类型为email时,被标记为是一个passthrough属性。

#{user.myAttributes}必须指向一个Map <String,Object>,其中的值可以是文字或值表达式。

这种机制可以应用于任何JSF组件而不限于HTML5元素。

JSF 2定义了多样的标签。每个组件是由一个UIComponent类支持的。这些组件标签由HTML的RenderKit渲染器渲染为一个HTML元素。

下面是中小企业常用组件:

h:inputFile是在JSF2.2增加的一个新组件。该组件允许上传文件。该组件可用于h:form内并绑定到有javax.servlet.http.Part类型属性的backing bean上:

在这段代码中:

文件上传也可以包装在一个<f:ajax>标签内:

这段代码类似于前面,有以下所示的区别。

这段代码将打印Ajax请求通过的JSF Ajax生命周期的事件源和名称,接收标准生命周期事件。


相关图书

Effective Java中文版(原书第3版)
Effective Java中文版(原书第3版)
Java核心技术速学版(第3版)
Java核心技术速学版(第3版)
Java编程动手学
Java编程动手学
Java研发自测入门与进阶
Java研发自测入门与进阶
Java开发坑点解析:从根因分析到最佳实践
Java开发坑点解析:从根因分析到最佳实践
Java EE企业级应用开发实战(Spring Boot+Vue+Element)
Java EE企业级应用开发实战(Spring Boot+Vue+Element)

相关文章

相关课程