2005年11月9日
Web应用是Web服务的一种动态扩展。Web应用可分为两类:
- 面向表示的Web应用。在响应请求时,面向表示的Web应用产生动态的Web页面,这种页面包含了各种不同的标记语言(如HTML,
XML, 等等)。
- 面向服务的Web应用。面向服务的Web应用实现细粒度Web服务的端点。面向服务Web应用通常为面向表示应用所调用。
在Java2平台上,Web组件为Web
服务器提供了动态扩展能力。Web组件可以是Java
Servlets或者JSP页面。Servlets是Java编程语言中用于动态地处理请求和建立响应的一种类。JSP页面是基于文本的文档,它像
servlets一样执行,但是允许使用更自然的方法来创建静态内容。尽管servlet和JSP页面可以交替地使用,但是它们各有千秋。
Servlets最适合于面向服务的Web应用,同时还擅长于管理面向表示的Web服务的控制功能,例如发送请求和处理非原文的数据。而JSP页面则更适
合于产生基于文本的标记,例如:HTML、SVG、WML以及XML等。
Web组件以一个名为Web容器
的运行时平台中的服务作为支撑。在Java Web服务开发包(Java Web Services Developer
Pack, Java WSDP)中的Web组件在Tomcat Web容器内运行。Web容器提供了一些服务,例如请求发送、安全、并发以及生命周期管理。它还使得Web组件可以访问多种API,
例如命名、事务处理以及e-mail等方面的API。
本章里描述了Web应用的组织、配置以及安装和部署过程。第11章和第12章中讲述如何开发面向服务的Web应用的Web组件。第14章和第15章讲述如何开发面向表示Web的应用的Web组件。JSP技术的许多特征是由Java
Servlet技术决定的,因此即使你不打算编写servlet,也应该熟悉这方面的知识。
大多数Web应用都使用HTTP协议,而且对HTTP的支持也正是Web组件的一个主要方面。对于HTTP协议特征的简要总结,参见HTTP概述。
Web应用的生命周期
Web应用由Web组件、静态资源文件(如图像)、帮助类和库组成。Java
WSDP提供许多支持服务,用于增强Web组件的性能并使得它们更易于开发。不过,由于要充分考虑这些支持服务,所以Web应用在创建和运行过程上与传统的stand-alone式的Java类有所不同。
在Web应用被部署以后,就可以对其行为的某些方面进行配置。配置信息通过Web应用部署描述文件来维护,该文件是一种XML格式的文本文件。部署描述文件必须遵从Java
Servlet规范中描述的模式。
创建、部署和执行Web应用的过程可以总结为以下几步:
1.开发Web组件代码(可能还包括部署描述文件)。
2.建立Web应用组件连同所有静态资源(例如图像)和组件要引用到的帮助类。
3.将应用安装或部署到Web容器中。
4.访问指向该Web应用的URL。
开
发Web组件代码这方面的知识将在以后的章节中讲述。接下来的几节将对步骤2到步骤4作出详细的讲解,并给出一个Hello,World式的面向表示的应
用的例子。该应用允许用户输入名字到HTML表单中(见图4-1),并且在名字提交之后显示一条问候信息(见图4―2)。
图4-1 问候表单
图4-2
响应
这个Hello应用包含了两个Web组件,用于产生问候信息和响应。在本指南中,该应用有两个版本:一个是servlet版,名为Hello1,它的组件通过两个servlet类(GreetingServlet.java和ResponseServlet.java)来实现。另一个是JSP版,名为Hello2,它的组件通过两个JSP页面(greeting.jsp 和 response.jsp)来实现。这两个版本的例子展示了一个包含Web组件的应用在大包、部署和运行时所涉及到的任务。如果你是在线浏览本指南,请务必下载本指南的捆绑包,以获得这个示例的源代码。
Web应用归档文件
如果要发布一个Web应用,你可以将其打包到一个Web应用归档文件(Web
application archive,WAR)中,WAR与JAR类似,都是用作Java类库的包。除了应用的Web组件外,Web应用的归档文件还包含其他的一些文件,包括:
· 服务器端的实用工具类(如数据库bean、购物车等等)。这些类通常与JavaBeans组件的结构一致。
· 静态Web表示内容(如HTML,图像和声音文件等)
· 客户端类(applet和实用工具类)
Web组件和静态Web内容文件叫做Web资源。
Web应用的运行可以通过一个WAR文件,或者通过一个同为WAR格式的解包目录。
WAR目录结构
WAR的顶级目录是该应用的文档根目录。这个文档根目录下存放着JSP页面、客户端类和归档文件,以及静态Web资源。
文档根目录下含有一个名为WEB-INF的子目录,其中包含了以下文件和目录:
· web.xml
—— Web应用的部署描述文件。
· 标签库描述文件。
· classes
—— 一个子目录,包含了服务器端类:servlets,实用工具类和JavaBeans组件。
· lib
—— 一个子目录,包含了库(标签库和所有作为服务器端类的实用工具类)
还可以创建应用专用(application-specific)的子目录(即包目录),可以在文档根目录下创建,或者在WEB-INF/classes
目录下创建。
指南示例的目录结构
为了便于迭代开发并且将Web应用的源代码与已编译的文件分开,实例的源代码存储在每个应用目录mywebapp
下,其结构如下:
· build.xml
—— Ant生成文件
· context.xml
——可选应用配置文件
· src
—— servler和Javabeans组件的Java源代码。
· web
—— JSP页面和HTML页面、图像。
随示例一起发布的Ant
build文件(build.xml)中包含了一些目标 (target),用以在mywebapp
的build
子目录中创建解包的WAR结构,将文件拷贝和编译到那个目录中,并且通过专门的Ant任务调用manager
命令,来安装、重新装载、移除、部署和解部署应用。该指南示例的Ant目标是:
· prepare
(准备) —— 创建build目录和WAR子目录。
· build
(编译) —— 编译和拷贝mywebapp
Web应用文件到build目录下。
· install
(安装)—— 使用Ant install
任务通知Tomcat安装应用(见安装Web应用)。
· reload
(重新装载)—— 使用Ant reload
任务通知Tomcat重新装载应用(见更新Web应用)。
· deploy
(部署) —— 使用Ant deploy任务通知Tomcat部署应用(见部署Web应用)。
· undeploy
(解除部署)——使用Ant undeploy任务通知Tomcat解除部署应用(见解除部署Web应用)
· remove
(移除) —— 使用 Ant remove
任务通知Tomcat移除应用(见移除Web应用)
创建WAR
手工的创建WAR文件有两种方法:
· 使用随J2SE SDK一起发布的JAR工具。只需简单地在指南示例的build目录下执行如下命令:
jar cvf mywebapp
.war
.
· 使用Ant war
任务
这两种方法都要求有创建好的Web应用部署描述文件。
配置Web应用
Web
应用的配置是通过包含在Web应用部署描述文件中的元素的设置来实现的。你可以使用文本编辑器来手工创建这些描述文件。接下来的章节将对你想配置的Web
应用特征作一个简要的介绍。其中,许多的安全参数都可以在配置时指定,这些内容在第18章可以看到。如果你想得到这些特征的完整列表和描述,参见Java
Servlet规范。
在接下来的几节中,将给出一些示例来演示Hello,World应用的配置过程。如果Hello,World没有使用指定的配置特征,那么本节将给出其他的示例来展示部署描述文件的元素并描述指定这种特征的一般过程。
注
意:描述符元素必须在部署描述文件中按照下列顺序出现:icon,display,description,distributable,context
-param,filter,filter-mapping,listener,servlet,servlet-mapping,session-config,mime-mapping,welcome-file-list,error-page,taglib,resource-env-ref,resource-ref,security-contraint,login-config,seurity-role,env-entry.
序言
既然部署描述文件是一种XML文档,它就需要一个序言。Web应用的部署描述文件的序言如下所示:
别名路径
当Tomcat收到一个请求时,它必须决定使用哪个Web组件来处理请求。这一步是通过将包含在请求中的URL路径映像到一个Web组件来实现的。URL路径包含上下文根目录(参见Installing
Web Application,http://java.sun.com/webservices/docs/1.1/tutorial/doc/WebApp5.html#wp75667)和一个别名路径。
http://<host>:8080/context_root
/alias_path
http://<host>:8080/context_root
/alias_path
在servlet
可以被访问前,Web容器必须最少有一个该组件的别名路径。别名路径必须以一个“/”开始,以一个字符串或者一个带扩展名的通配符(例如:*.jsp)结
束。既然Web容器自动地映像到以*.jsp结束的别名路径,你无需为JSP页面指定别名路径,除非你想通过一个名称,而不是其文件名来引用JSP页面。
在更新Web应用中讨论的实例中,问候页面有一个别名response.jsp,但它仍然是通过其文件名来引用的。
在Web
部署描述文件中设置servlet版的Hello应用的映像,必须添加下列servlet和servlet-mapping元素到Web应用部署描述文件
中。为了定义JSP页面的别名,必须替换在servlet元素中的servlet-class子元素和jsp-file子元素。
greeting
greeting
no description
GreetingServlet
response
response
no description
ResponseServlet
greeting
/greeting
response
/response
上下文和初始化参数
同
一个WAR中的各Web组件共享一个表示它们的应用上下文的对象(见访问Web上下文)。你可以传递参数到上下文或者Web组件。要实现这一点,你必须添
加一个context-param或init-param元素到Web应用部署描述文件中。context-param是顶级web-app元素的子元
素。init-param是servlet元素的子元素。下面的元素用于声明一个上下文参数,该参数设置在第17章中讨论的示例的资源束:
javax.servlet.jsp.jstl.fmt.localizationContext
messages.BookstoreMessages
...
事件监听器
要添加事件监听器类,必须添加一个listener元素到Web应用部署描述文件中。下面的元素描述了在第14章和第17章用到的监听器类)。
listeners.ContextListener
过滤器映像
Web
容器使用过滤器映像声明来决定应用于某个请求的过滤器,并且决定这些过滤器的顺序。正如在别名路径中描述的那样,该容器将请求URL与一个servlet
相匹配。要决定引用哪个过滤器,容器通过servlet名或者URL模式来匹配过滤器映像声明。过滤器被调用的顺序就是过滤器映像声明在过滤器映像列表中
出现的顺序,过滤器映像声明将请求URI与一个servlet匹配起来。
要指定过滤器映像,必须添加一个filter和filter-mapping元素到Web应用部署描述文件中。下面的元素用于声明有序的过滤器,并将其映像到在第14章讨论过的Receipt
servlet:
OrderFilter
filters.OrderFilter
OrderFilter
/receipt
错误映像
可
以指定状态代码与Web资源之间的映像,其中的状态代码可以是一个HTTP响应中返回的,也可以是由任何Web组件返回的一个Java编程语言异常中返回
的。要设置映像,必须添加一个元素到部署描述文件中。下面的元素用于将OederException映像到第14章
中使用的页面errorpage.html。
exception.OrderException
/errorpage.html
注意:你也可以为包含在WAR内的JSP页面定义错误页面。如果错误页面是同时为WAR和JSP页面而定义的,则JSP页面的错误页面居先。
指向环境条目、资源环境条目或资源的引用
如
果Web组件需要引用环境条目,资源环境条目或资源(如数据库),必须通过在Web应用部署描述文件中的,<
resource-env-ref>或元素来声明引用。下面的元素用于声明一个指向某个数据源的引用,该
数据源在本指南关于Web技术的几章中要用到。
jdbc/BookDB
javax.sql.DataSource
Container
安装Web应用
上下文(context)是映射到一个Web应用的名称。例如,Hello1应用的上下文是/hello1。为了将应用安装到Tomcat,需要通知Tomcat有一个新的可用上下文。
可以使用Ant
install任务来通知Tomcat有一个新的上下文。注意,在Tomcat重新启动之后,安装好的应用还不能使用。要永久地部署应用,参见部署Web应用。
Ant
install任务告诉由url属性指定的在本地运行的管理器安装一个应用,其上下文由path属性指定,其安装位置包含了由war属性指定的该Web应用的文件。war属性的值可以是一个WAR文件,如:
jar:file:/path/to/bar.war!/
或者是一个解包目录:
file:/path/to/foo。
username="username"
password="password" />
username和password属性将在Tomcat Web应用管理程序中讨论。
这里无需提供一个war属性,你可以使用config属性来指定配置信息:
path="mywebapp"
config="file:build/context.xml"
username="username"
password="password"/>
config属性指出一个配置文件包含上下文条目的格式
docBase="../docs/tutorial/examples/web/bookstore1/build"
debug="0">
注意,这个上下文条目通过它的docBase属性隐式地指定了Web应用文件的位置。
本指南示例build文件包含一个Ant
install target,它将调用Ant install 任务:
description="Install
web application" depends="build">
config="file:build/context.xml"
username="${username}"
password="${password}"/>
Ant
install任务要求Web应用部署描述文件(web.xml)是可用的。所有的指南示例应用都是随一个部署描述文件一起发布的。
To
install the Hello1 application described in Web
Application Life Cycle:
要安装Web应用生命周期中描述的Hello1应用,需按下列步骤:
1. 在终端窗口中,转到<JWSDP_HOME>/docs/tutorial/examples/web/hello1.
2.确认Tomcat已启动
3.执行ant install。install target通知Tomcat新的上下文是可用的。
部署Web应用
如果Tomcat正在运行,就可以使用Ant
deploy任务永久地将一个上下文部署到Tomcat。
war="file:/path/to/mywebapp.war"
username="username"
password="password" />
不同于install任务,install任务可以引用一个解包目录,deploy任务需要一个WAR。该任务加载WAR到Tomcat并且启动应用。也可以通过这个任务将其部署到一个远程服务器上。
下面是其他一些可用的部署方法,但是它们要求重新启动Tomcat:
· 将Web应用目录或WAR拷贝到/webapps。
·
将包含了上下文条目的名为mywebapp.xml的配置文件拷贝到/webapps。上下文条目的格式在<
JWSDP_HOME>/docs/tomcat/config/context.html中的
Server Configuration Reference里有描述。注意,上下文条目通过docBase属性隐式地指定了Web应用文件的位置。例如,下面是在第14章中讨论的应用的上下文条目:
docBase="../docs/tutorial/examples/web/
bookstore1/build" debug="0">
一些实例build文件包含一个像Ant
deploy 任务一样调用的Ant deploy 目标。
列出已安装和部署的Web应用
如果想列出当前在Tomcat内可使用所有Web应用,你可以使用Ant
list任务:
指南示例build文件包含一个像调用Ant
list 任务一样调用的Ant list 目标。
还可以通过运行Manager
Application来查看列出的应用:
http://
:8080/manager/list
运行Web应用
Web应用是在Web浏览器引用映像到组件的URL时执行的。一旦安装了或者部署了Hello1应用,便可通过将浏览器指向
http://
:8080/hello1/greeting
来运行Web应用。
用运行Tomcat的主机名替代。如果浏览器运行在与Tomcat相同的主机上,就可以用localhost替代。
更新Web应用
在开发期间,经常需要对Web应用进行修改。在修改servlet之后,必须:
1.重新编译servlet类。
2.更新服务器上的应用。
3.重新在客户端装载URL。
更新JSP页面时,无需重新编译或重新装载应用,因为Tomcat会自动完成这些事情。
为了尝试这一特征,修改Hello应用的servlet版本。例如,你可以将GreetingServlet
返回的问候信息修改为:
Hi, my name is Duke. What's yours?
更新文件的过程为:
1. 编辑在源码目录/docs/tutorial/examples/web/hello1/src中的GreetingServlet.java文件。
2. 运行ant build任务。该任务将servlet重新编译到build目录中。
更新服务器中的应用这一过程取决于你是否使用Ant
install任务来安装或者使用Ant deploy任务来部署这个应用。
重新装载Web应用
如果你已经使用Ant
install命令安装了应用,就可以使用Ant reload任务来更新服务器中的应用:
username="username
" password="password
" />
示例build文件包含一个
Ant remove目标,该目标调用 Ant remove任务。从而更新服务器中的Hello1应用,执行ant
reload。为了观察经过更新的应用,重新装载客户端的Hello1的URL。注意,reload
任务只改变Java类,而不会改变Web.xml文件。为了重新装载web.xml,需要将应用删除
(参见移除Web应用)并重新安装。
在浏览器上你可以看到如图4-3所示的屏幕:
图4-3 新的问候信息
为了在该实例的JSP版本上尝试这一特性,首先建立和部署JSP版的Hello应用:
1.
在终端窗口中,转到
<JWSDP_HOME>/docs/tutorial/examples/web/hello2.
2. 运行ant build
。build
目标将产生所有必需的编译并且拷贝文件到
<JWSDP_HOME>/docs/tutorial/examples/web/hello2/build
目录。
3. 运行ant install
。install
目标将build目录拷贝到/webappsbuild并通知Tomcat有新的应用。
修改某一个JSP文件。然后运行ant
build
将经过修改的文件拷贝到docs/tutorial/examples/web/hello2/build。记住,这里无需重新装载服务器中的应用,因为当某个JSP文件被修改时,Tomcat会自动检测到这一情况。要查看经过修改的应用,
需重新装载客户端的Hello2的URL。
重新部署Web应用
如果已经通过使用Ant
deploy任务部署了应用,就可以通过接连使用Ant undeploy
任务(见解除部署Web应用)和Ant
deploy
任务来更新这个应用。
移除Web应用
如果想从服务中移除已安装的Web应用,可以调用
Ant remove
任务:
username="username
" password="password
" />
示例build文件包含一个
Ant remove目标,该目标调用 Ant remove任务。
解除部署Web应用
如果想移除一个已部署的Web应用,可以使用Ant
undeploy任务:
username="username
" password="password
" />
一些实例build文件包含一个Ant
undeploy目标,该目标调用Ant undeploy任务。
Web应用的国际化和本地化
所谓的国际化是指使得应用能够支持各种语言和数据格式的过程。所谓本地化,是指使得一个国际化的应用能够支持某种特定的或者本地的语言的过程。虽然所有客户端用户接口都应该被国际化和本地化,但是国际化和本地化对于Web应用来说尤其重要,因为Web本身就是无所不及的。如果想要很好地鸟瞰一下国际化与本地化问题,参见
http://java.sun.com/docs/books/tutorial/i18n/index.html
有两种方法可以用于对Web应用进行国际化:
·
在每个目标位置中提供一个JSP页面,并且使用一个控制器servlet来发送请求到适当的页面上(取决于被请求的位置)。如果需要对一个页面或者一个完
整的Web应用的数据进行国际化,这种方法比较有用。
· 将页面上所有的本地敏感的数据 (例如错误信息,串字符或按钮标签)
分离到资源束内,然后访问这些数据,来获取响应的翻译过来的信息并将其插入到页面当中。这样一来,就不是在代码中直接创建字符串,而是创建一个包含了翻译
过来的信息的资源束,然后使用相应的关键字从该资源束中读出翻译过来的信息。资源束可以由一个文本文件(属性资源束)或一个包含了映像的类(列表资源束)
来支持。
在
下面的关于Web技术的几章中,Duke's
Bookstore实例被分别国际化和本地化成英语和西班牙语。关键字-值对包含在名为message.BookMessage_*.class的列表资
源束。为了了解在资源束中的关键字-值对是怎样的,请看在文件messages.BookMessages.java中添加的几行:
{"TitleCashier",
"Cashier"},
{"TitleBookDescription",
"Book Description"},
{"Visitor",
"You are visitor number "},
{"What",
"What We"re Reading"},
{"Talk",
" talks about how Web components can transform the
way
you develop applications
for the Web. This is a must read for
any self respecting
Web developer!"},
{"Start",
"Start Shopping"},
为了获得对应于某个给定用户的正确字符串,Web组件从请求中获取位置(由浏览器的语言首选项设定),为那个位置打开资源束,然后将该资源束作为会话属性保存:
ResourceBundle messages
= (ResourceBundle)session.
getAttribute("messages");
if (messages
== null) {
Locale
locale=request.getLocale();
messages
= ResourceBundle.getBundle("WebMessages",
locale);
session.setAttribute("messages",
messages);
}
Web组件从会话中获取资源束:
ResourceBundle messages
=
(ResourceBundle)session.getAttribute("messages");
然后用下面的语句查找与关键字TitleCashier相关的字符串:
messages.getString("TitleCashier");
上面对国际化Web应用的介绍非常简要。如果要了解更多该主题的相关信息,参见Java
BluePrints:
http://java.sun.com/blueprints
今天确定了WEB开发的框架。确定采用XMLC+Tomcat+JavaBean的框架。从现在开始关注这方面问题!
2005年11月8日
Struts和JSF/Tapestry都属于表现层框架,这两种分属不同性质的框架,后者是一种事件驱动型的组件模型,而Struts只是单纯的MVC模式框架,老外总是急吼吼说事件驱动型就比MVC模式框架好,何以见得,我们下面进行详细分析比较一下到底是怎么回事?
首先事件是指从客户端页面(浏览器)由用户操作触发的事件,Struts使用Action来接受浏览器表单提交的事件,这里使用了Command模式,每个继承Action的子类都必须实现一个方法execute。
在struts中,实际是一个表单Form对应一个Action类(或DispatchAction),换一句话说:在Struts中实际是一
个表单只能对应一个事件,struts这种事件方式称为application event,application event和component
event相比是一种粗粒度的事件。
struts重要的表单对象ActionForm是一种对象,它代表了一种应用,这个对象中至少包含几个字段,这些字段是Jsp页面表单中的
input字段,因为一个表单对应一个事件,所以,当我们需要将事件粒度细化到表单中这些字段时,也就是说,一个字段对应一个事件时,单纯使用
Struts就不太可能,当然通过结合JavaScript也是可以转弯实现的。
而这种情况使用JSF就可以方便实现,
<h:inputText id="userId" value="#{login.userId}"> <f:valueChangeListener type="logindemo.UserLoginChanged" /> </h:inputText> |
#{login.userId}表示从名为login的JavaBean的getUserId获得的结果,这个功能使用struts也可以实现,name="login" property="userId"
关键是第二行,这里表示如果userId的值改变并且确定提交后,将触发调用类UserLoginChanged的processValueChanged(...)方法。
JSF可以为组件提供两种事件:Value Changed和 Action. 前者我们已经在上节见识过用处,后者就相当于struts中表单提交Action机制,它的JSF写法如下:
<h:commandButton id="login" commandName="login"> <f:actionListener type=”logindemo.LoginActionListener” /> </h:commandButton> |
从代码可以看出,这两种事件是通过Listerner这样观察者模式贴在具体组件字段上的,而Struts此类事件是原始的一种表单提交
Submit触发机制。如果说前者比较语言化(编程语言习惯做法类似Swing编程);后者是属于WEB化,因为它是来自Html表单,如果你起步是从
Perl/PHP开始,反而容易接受Struts这种风格。
基本配置
Struts和JSF都是一种框架,JSF必须需要两种包JSF核心包、JSTL包(标签库),此外,JSF还将使用到Apache项目的一些commons包,这些Apache包只要部署在你的服务器中既可。
JSF包下载地址:http://java.sun.com/j2ee/javaserverfaces/download.html选择其中Reference Implementation。
JSTL包下载在http://jakarta.apache.org/site/downloads/downloads_taglibs-standard.cgi
所以,从JSF的驱动包组成看,其开源基因也占据很大的比重,JSF是一个SUN伙伴们工业标准和开源之间的一个混血儿。
上述两个地址下载的jar合并在一起就是JSF所需要的全部驱动包了。与Struts的驱动包一样,这些驱动包必须位于Web项目的WEB-INF/lib,和Struts一样的是也必须在web.xml中有如下配置:
<web-app> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet>
<servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>*.faces</url-pattern> </servlet-mapping> </web-app> |
这里和Struts的web.xml配置何其相似,简直一模一样。
正如Struts的struts-config.xml一样,JSF也有类似的faces-config.xml配置文件:
<faces-config> <navigation-rule> <from-view-id>/index.jsp</from-view-id> <navigation-case> <from-outcome>login</from-outcome> <to-view-id>/welcome.jsp</to-view-id> </navigation-case> </navigation-rule>
<managed-bean> <managed-bean-name>user</managed-bean-name> <managed-bean-class>com.corejsf.UserBean</managed-bean-class> <managed-bean-scope>session</managed-bean-scope> </managed-bean> </faces-config>
|
在Struts-config.xml中有ActionForm Action以及Jsp之间的流程关系,在faces-config.xml中,也有这样的流程,我们具体解释一下Navigation:
在index.jsp中有一个事件:
<h:commandButton label="Login" action="login" />
action的值必须匹配form-outcome值,上述Navigation配置表示:如果在index.jsp中有一个login事件,那么事件触发后下一个页面将是welcome.jsp
JSF有一个独立的事件发生和页面导航的流程安排,这个思路比struts要非常清晰。
managed-bean类似Struts的ActionForm,正如可以在struts-config.xml中定义ActionForm的scope一样,这里也定义了managed-bean的scope为session。
但是如果你只以为JSF的managed-bean就这点功能就错了,JSF融入了新的Ioc模式/依赖性注射等技术。
Ioc模式
对于Userbean这样一个managed-bean,其代码如下:
public class UserBean {
private String name;
private String password;
// PROPERTY: name
public String getName() { return name; }
public void setName(String newValue) { name = newValue; }
// PROPERTY: password
public String getPassword() { return password; }
public void setPassword(String newValue) { password = newValue; }
}
<managed-bean> <managed-bean-name>user</managed-bean-name> <managed-bean-class>com.corejsf.UserBean</managed-bean-class> <managed-bean-scope>session</managed-bean-scope>
<managed-property> <property-name>name</property-name> <value>me</value> </managed-property>
<managed-property> <property-name>password</property-name> <value>secret</value> </managed-property> </managed-bean> |
faces-config.xml这段配置其实是将"me"赋值给name,将secret赋值给password,这是采取Ioc模式中的Setter注射方式。
Backing Beans
对于一个web form,我们可以使用一个bean包含其涉及的所有组件,这个bean就称为Backing Bean, Backing Bean的优点是:一个单个类可以封装相关一系列功能的数据和逻辑。
说白了,就是一个Javabean里包含其他Javabean,互相调用,属于Facade模式或Adapter模式。
对于一个Backing Beans来说,其中包含了几个managed-bean,managed-bean一定是有scope的,那么这其中的几个managed-beans如何配置它们的scope呢?
<managed-bean> ... <managed-property> <property-name>visit</property-name> <value>#{sessionScope.visit}</value> </managed-property>
|
这里配置了一个Backing
Beans中有一个setVisit方法,将这个visit赋值为session中的visit,这样以后在程序中我们只管访问visit对象,从中获取
我们希望的数据(如用户登陆注册信息),而visit是保存在session还是application或request只需要配置既可。
UI界面
JSF和Struts一样,除了JavaBeans类之外,还有页面表现元素,都是是使用标签完成的,Struts也提供了struts-faces.tld标签库向JSF过渡。
使用Struts标签库编程复杂页面时,一个最大问题是会大量使用logic标签,这个logic如同if语句,一旦写起来,搞的JSP页面象俄罗斯方块一样,但是使用JSF标签就简洁优美:
<jia:navigatorItem name="inbox" label="InBox" icon="/images/inbox.gif" action="inbox" disabled="#{!authenticationBean.inboxAuthorized}"/>
|
如果authenticationBean中inboxAuthorized返回是假,那么这一行标签就不用显示,多干净利索!
先写到这里,我会继续对JSF深入比较下去,如果研究过Jdon框架的人,可能会发现,Jdon框架的jdonframework.xml中
service配置和managed-bean一样都使用了依赖注射,看来对Javabean的依赖注射已经迅速地成为一种新技术象征,如果你还不了解
Ioc模式,赶紧补课。
附Jsf核心教程一个JSF案例:login.rar
2005年10月23日
轻量级容器可以动态地使系统主要组件之间的耦合变松散。不同的容器包含相同的设计模式,但却具有根本不同的哲学。本文帮助您在下列三种轻量级容器之间作出最佳选择:Spring Framework、HiveMind 和 PicoContainer。
2002
年在科罗拉多的一次旅行中,我完美地感受了阿肯色河。在三段不同的漂流中,这条河展示了令人惊异的多样性。柔美的布朗峡谷有着开阔的急流,翻滚着巨大的波
浪。Royal Gorge
别具特色的悬崖峭壁引导着巨大而笔直的峡谷之下的水力,在这条直线上发生一点小闪失都会受到长途游泳的惩罚。Numbers
具有精密的落差,需要人们在范围狭窄的圆石花园里精确操纵。在一条河里,我有了三次极不相同的体验。
在我的上一篇文章“轻量级开发的成功秘诀,第 3 部分:Spring 露出水面”中,我们学习了轻量级容器的基本原理。本文将向您展示三种最流行的容器:
这三种容器都源于依赖注入,但每种容器都具有极不相同的特征。当我介绍每种容器的高级描述时,您将看到正在运行的每种框架,以及可以应用每种框架的环境。
核心哲学
这三种容器都接受 POJO (plain old Java object),都具有对象生命周期的钩子(所以它们可以在创建或销毁 bean
时调用您的代码),都执行依赖注入。您可能认为这些主旋律将导致相似的容器,但事实并非如此。尽管植入每种容器的代码可能相似,但容器本身反映了不同的能
力、风格和整体哲学。总而言之,每种容器的作者都忠于他们的哲学。
Spring Framework
作为开放源码框架的 Geneva,Spring Framework 为数百个 Java 2 Platform, Enterprise
Edition (J2EE) API 和开放源码框架提供了轻量级容器和胶水代码 (glue code)。Spring 有一个最重要的前景:让
J2EE 更易使用。读完一些示例和书籍之后,您将看到一些常见的主题:
- Spring 支持三种依赖注入——setter、构造函数 和 方法 注入——但总的来说,最流行的模型是 setter 注入。
- 在灵活性和简单性之间,Spring 的 XML 风格配置更重视灵活性。您可以做任何事情,但对于初学者来说,配置文件是晦涩难懂的。
- Spring 的创始人认为,容器只是整体框架的一小部分。Spring 的大部分价值来源于支持该框架的数千行胶水代码。它易于插入任何系统中。
- Spring 框架是三种容器实现中最完美的。一般来说,优秀的文档都是完美编写的。
- Spring 具有自动连线 (autowire) 方式,但大多数示例都没有使用它。我并不十分了解这个决策,但有时候,能够看到明确列出的依赖关系是不错的。
- Spring 提供了完整的 AOP 框架,使得更容易附加服务。您可以使用 Spring 自己的框架或依赖丰富的 AspectJ 集成(参阅 参考资料)。
如果要用一个短语来形容 Spring,我会说让企业更强。
HiveMind
Howard Lewis Ship 是 Jakarta Tapestry Web 框架的创建者,他还创建了
HiveMind。作为一个容器,HiveMind 是灵巧、干净且易于使用的。与其他许多较好的开放源码框架一样,Ship 创建 HiveMind
是为了让它帮助解决现实问题。但是,HiveMind 向传统的轻量级容器添加了两个创新:
- 最重要的 HiveMind 创新是模块。据 Ship 所说,Eclipse 插件激发了他的 HiveMind 模块的灵感。
- HiveMind 强制您编写接口。(与所有轻量级容器一样,它不提供接口,而由您自己提供接口。)
- HiveMind 是用户友好的,它提供称为 HiveDoc 的文档工具,友好简明的 XML 配置,以及行准确的错误报告。
- HiveMind 用户通常优先选择 setter 注入,但该容器还支持构造函数注入。
如果用一个短语来形容 HiveMind 的话,我会说它是概念正确 的。
PicoContainer
到目前为止,PicoContainer 最重要的特征是它的尺寸。它没有提供许多附加物,但它具有完整的依赖注入容器。PicoContainer 还具有一些惟一特性:
- PicoContainer 很小,所以它没有拦截器、AOP 或相似类型的服务,而选择了让其他框架创建这些服务。
- PicoContainer 支持 Java 配置技术,而不支持 XML 配置技术,这与其他容器一样。
- PicoContainer 流行的使用模型是构造函数注入,但它也支持 setter 注入。
- PicoContainer 没有提供许多文档,而且一些现有文档是不完整的,但您不会太需要。
- PicoContainer 具有一个自动连线方式,它很不错。
- PicoContainer 的发展似乎有点停滞。
如果用一个短语来形容 PicoContainer 的话,我会选择理论完美,但不如 Spring 或 HiveMind 实用。
编程模型
现在我将向您展示社区中流行的编程示例,以帮助您更好地理解容器的作者希望您如何使用它们。我使用 PicoContainer 中的 Kiss
示例来展示 autowiring 和 Java 技术风格的配置,使用 HiveMind 加法器示例来展示模块能力,使用 Spring
PetClinic 应用程序来展示 Hibernate 集成。
Kiss 示例 (PicoContainer)
在这三个容器中,PicoContainer 具有最简单的编程模型。要查看 Kiss 示例,可从 PicoContainer.org 下载它。安装该示例,浏览到 docs\Two+minute+tutorial.htm,然后您会看到两个组件:
清单 1. 两个 Kiss 组件
public class Boy { public void kiss(Object kisser) { System.out.println("I was kissed by " + kisser); } } public class Girl { Boy boy;
public Girl(Boy boy) { this.boy = boy; }
public void kissSomeone() { boy.kiss(this); } }
|
这两个类是自解释的。Girl 对 Boy 有依赖关系。该依赖关系将通过构造函数被注入。先实例化一个容器:
MutablePicoContainer pico = new DefaultPicoContainer();
|
然后注册两个组件:
pico.registerComponentImplementation(Boy.class); pico.registerComponentImplementation(Girl.class);
|
稍后您可以向 PicoContainer 请求一个对象,然后操作它:
Girl girl = (Girl) pico.getComponentInstance(Girl.class); girl.kissSomeone();
|
这样就差不多了。编程模型是优雅的,基于构造函数的风格意味着您无需包括无参构造函数。对本例中的 Girl 调用这种函数将会使该对象处于不一致的状态,因为 kiss
方法将抛出异常。
加法器示例 (HiveMind)
现在,让我们看一下 HiveMind 的编程示例。从 Apache Jakarta Project 下载 HiveMind,然后查看加法器示例。您会看到接口和实现。(记住:HiveMind 强制编写接口。)
清单 2. 加法器示例接口和实现
public interface Adder { public double add(double arg0, double arg1); }
public class AdderImpl implements Adder { public double add(double arg0, double arg1) { return arg0 + arg1; } }
|
将该服务暴露在 XML 文件中,如下所示:
清单 3. 将该服务暴露在 XML 文件中
<module id="examples" version="1.0.0">
<service-point id="Adder" interface="org.apache.hivemind.examples.Adder">
<create-instance class="org.apache.hivemind.examples.impl.AdderImpl"/>
</service-point> </module>
|
然后,其他应用程序就可以使用该服务了,如下所示:
清单 4. 其他应用程序可以使用该服务
Registry registry = RegistryBuilder.constructDefaultRegistry(); Adder adder = (Adder) registry.getService("examples.Adder", Adder.class);
... adder.add(arg0, arg1)
|
注意,HiveMind 的模块让您可以将多个服务组合到一起。如果您需要向容器中的服务添加功能,可以使用拦截器:
清单 5. 使用拦截器添加功能
<module id="examples" version="1.0.0"> <service-point id="Adder" interface="org.apache.hivemind.examples.Adder">
<create-instance class="org.apache.hivemind.examples.impl.AdderImpl"/>
<interceptor service-id="hivemind.LoggingInterceptor"/> </service-point> </module>
|
PetClinic 应用程序 (Spring)
Spring 处理事情的方法有些不同。因为 Spring 框架不带有简单的应用程序,我从我的书籍 Spring: A Developer's Notebook 中选择了一个。您可以从 O'Reilly Media 获取该示例代码。解压示例 4,它展示了一个用于 RentaBike 商店的带有属性的 CommandLineView
对象,该对象最终成为该应用程序的数据访问对象。
清单 6. CommandLineView 对象
public class CommandLineView { private RentABike rentaBike; public CommandLineView() {} public void setRentABike(RentABike rentaBike) {this.rentaBike = rentaBike;} public RentABike getRentaBike() { return this.rentaBike; } ... }
|
RentaBike 是具有您希望在自行车商店对象中看到的各种方法的接口:
清单 7. 接口方法
public interface RentABike { List getBikes(); Bike getBike(String serialNo); void setStoreName(String name); String getStoreName(); }
|
没有显示 ArrayListBikeStore
,它是 BikeStore 接口的存根实现。注意,Spring 允许编写接口,但不强制编写接口。下面是描述该应用程序中 bean 的 XML 配置文件:
清单 8. 描述应用程序 bean 的 XML 配置文件
<beans> <bean id="rentaBike" class="com.springbook.ArrayListRentABike"> <property name="storeName"><value>Bruce's Bikes</value></property> </bean> <bean id="commandLineView" class="com.springbook.CommandLineView"> <property name="rentaBike"> <ref bean="rentaBike" /> </property> </bean> </beans>
|
该上下文中有两个 bean。commandLineView
bean 依赖于 rentaBike
bean。该应用程序通过为 rentaBike
属性指定 rentaBike
名称,显式解析该依赖关系。注意,PicoContainer 自动连接这种显式关系,Spring 也可以,但大多数用户不使用它的自动连线选项。Spring 还允许您通过拦截器或 AOP 向外观的任何方法添加服务。
比较
既然已经看到每种容器的哲学,下面是对每种环境的无形特性的详细比较,比如市场份额、整体质量(fit and finish)和整体特性列表。毕竟,即使编程模型是完美的,但如果没有文档,或者由于缺乏社区而您必须自己支持它,那么它也不会成为一个好容器。
活动社区
Spring 有一个充满活力的社区,和一个支持该框架的称为 Interface21 的职业服务公司。这很重要,因为您知道您可以获得良好的支持,公司才有动力来支持 Spring 框架。我在社区的经历简直太美好了。Spring 贡献者、创始人和用户都以杰出的内容填满了留言板。
HiveMind 框架是一个 Apache Jakarta 项目,所以有着扎实的基础。它有一个正在成长的萌芽社区。该框架的创始人
Howard Lewis Ship 是独立顾问、优秀导师和不屈不挠的提倡者。但是,要利用 HiveMind 的质量帮助或者查找其 Web
站点之外的内容仍然十分困难。尽管如此,它的在线帮助似乎不错,而且社区似乎正在成长。Hibernate
获得了有趣的胜利,它被选中——或者更应该说,Ship 被选中——组成 TheServerSide.com
的新基础设施,TheServerSide.com 是最重要的 Java 技术社区之一。
PicoContainer 也是一个 Apache Jakarta 项目,它似乎发展缓慢。截止本文撰稿,PicoContainer
的最后一次主要代码发行是在 2004 年 11 月。您看不到太多有关 PicoContainer 的新文章,这有点惭愧,因为我喜欢
PicoContainer 的一些哲学。事实上,我不太确定有没有三种开放源码轻量级容器的空间,尤其是最近第四种轻量级容器项目 Avalon
关闭之后。
就每个社区生成的活动而言,Spring 无疑是优胜者。Interface21 的支持、奇思妙想的论坛、活跃的邮件列表以及社区的跟踪记录都是无与伦比的。
整体质量
社区的大小和实力通常驱动开放源码项目的整体质量。充满活力的社区需要更好的文档和示例,而且它们会参与完成结尾的详细信息。
Spring 团队编写了可与我见过的一些比较好的商业产品相媲美的文档。如果这还不够的话,您还可以找到至少五本主要 Spring
书籍和其他许多包含 Spring 内容的出版物。(我自己曾撰写过两本有关 Spring 的书籍,其中一本书中包括 Jolt-winning Better, Faster, Lighter Java 一章,另一本是快速入门书籍 Spring: A Developer's Notebook)。错误消息是专业性和描述性的。与第三方框架和 API 的集成是所有 Java 技术框架中最好的。包装是经过深思熟虑的,不过略有多余。(它帮助我开始把一些比较小的项目划分成模块。)示例是优秀且有指导意义的。
与 Tapestry 一样,HiveMind 也具有好的整体质量。Ship 自己以那些让 HiveMind
变得简单易用的特性而自豪,比如行准确的错误报告;友好简明的 XML 语法;良好的文档工具 HiveDoc。与用于低级详细信息的 JavaDoc
文档结合使用,您可以更好地描述您的应用程序(HiveMind 模块)的高级特性,从而完善它们之间的依赖关系。
PicoContainer 编程模型感觉自然,但文档不完整(许多方法标记看起来过时好几个月了),而且没有许多使用该容器的真实世界示例。有时候,我会觉得自己在独自穿过鬼魂出没的破屋。
但使用 PicoContainer 确实有一个主要优点。因为您配置现实世界的对象时,会得到一些编译时错误检查。实际上,该容器太小太轻了,以至于除了基本配置之外,没有什么能出错。PicoContainer 做了一项合理的工作。
特性
我不想过多地讨论特性。如果您正在寻找许多胶水代码来减少您的开放源码收藏夹的集成或某特定 J2EE API,Spring
无疑是最佳选择。HiveMind 不尝试参与竞争。相反,它与 Spring 的服务兼容。PicoContainer
不构建而且也不尝试构建附加物,而是选择让开放源码项目为其提供服务。到目前为止,它的效果不太好。
哪一个最好?
目前,只有一个真正的答案。HiveMind 具有有趣的创新,PicoContainer
具有易于使用的模型(理论上),但社区似乎已经投票选择了 Spring Framework。随着时间的推移,新的容器可能会成长,HiveMind
可能不断获得市场份额,但目前,Spring 是您的最佳选择。
如果您愿意冒一些险,而使用不太成熟或不太流行的容器,您可能决定实现 HiveMind(如果需要模块级别的配置)或
PicoContainer(如果想要微小的容器)。如果需要许多胶水代码来集成持久引擎、事务处理策略和安全性等方面,Spring
具有最完整的组件堆。但请记住:您可以在 HiveMind 容器中使用 Spring 组件。
2005年10月10日
这是第一次使用,试发!以后将在这里写写自己的学习Java的经历和心情!