Struts 2核心技术与Java EE框架整合开发实战
17.3Struts2整合JSF
目前基于JSF规范较成熟的框架有两个,一个Sun的JSFUI,另一个是Apache的MyFaces框架。因为Struts2提供了对MyFaces更好的插件支持,因此本示例采用Apache的MyFaces。整合之前让我们先来比较一下这两种表示层的框架。
17.3.1Struts2整合JSF的优点
下面从不同方面比较一下Sturts2与JSF各自的特点。
首先,在标签库方面,Struts2的标签库相对要少一些,且不可以自定义;而JSF可以自定义标签。JSF框架拥有丰富的页面组件,如果需要的话可以自己编写相应的组件,或者扩展组件;而在JSP的页面中JSF提供了页面验证标签,可以做简单的长度和类型的验证。Struts2的验证可以有两种方式,form验证与validator验证,功能上要比JSF强大。JSF的组件都是绑定到Bean的,而且数据验证的方法也可以绑定,这一点可以增强验证的功能。而对于验证的错误提示信息,它们都提供了国际化,使得验证更人性化。
其次比较一下导航,二者之间的导航功能的相同点是都通过在XML文件中配置导航规则。Struts2的XML中配置页面跳转的类型,如转发,重定向,由Action返回的字符串来决定导航的目标;而JSF在导航规则中设定页面导航,当某个页面请求到来时,根据导航规则调用指定的Action方法进行处理,并返回一个逻辑视图,然后跳转到与逻辑视图对应的页面。JSF同时支持在页面中绑定按钮触发Action的具体方法,导航原理也是一样的。
最后比较一下Struts2与JSF处理请求的方式。Struts2调用指定的方法处理请求(如果没有指定具体方法则默认调用excute方法)。JSF采用了普通的POJO类作为它的Action,将Action类绑定到页面组件,通过值变监听与事件监听进行请求处理。相比之下,JSF处理请求的方式要比Struts2复杂,不方便系统升级。
总而言之,如果将JSF做为Struts2的视图层,用Struts2的Action做模型,可以开发出完美的应用系统。
接下来就将讲解Struts2与JSF的结合使用。
17.3.2Struts2与JSF整合过程
每种框架都有它独到的设计之处,Struts2的可扩展性使得它的生命力非常顽强。Struts2提供了多种框架的插件包,它与MyFaces整合就是利用插件来实现的。下面我们介绍如何进行二者的结合应用。
首先下载Struts2的JSF插件,下载地址是http://struts.apache.org/downloads.html。目前最高的插件版本是2.0.11,我们使用这个最新的版本与myfaces进行整合。
Apache的MyFaces下载地址是http://myfaces.apache.org./download.html,目前的最高版本是1.2.2,本示例使用的是1.1.5。下载后得到名为myfaces-core-1.1.5的压缩文件,将该文件解压,得到lib包下的运行库文件(.jar文件)。
17.3.3整合应用实例
2008年是奥运年,因此我们采用目前最流行的奥运啦啦队员的选拔活动为主题,设计一个Struts2+JSF应用的示例。
奥运啦啦队员选拔队员的设计分为3种功能:增加选手、查询选手、修改选手。
按如下的顺序创建示例程序。
(1)配置环境:配置Struts2+JSF整合过程的运行环境。
(2)配置struts.xml文件:配置JSF拦截器与请求的Action。
(3)创建页面:注册选手页面,显示所有选手列表页面,修改选手页面。
(4)创建JavaBean。选手信息类PlayerInfo与控制器类OlympicAction。
(5)配置Web应用文件:配置Struts2请求转发控制器。
(6)发布运行:演示发布运行后的结果页面。
下面详细介绍各个环节的实现过程。
(1)配置应用程序运行环境。
添加Struts2核心资源包、Struts2的JSF插件包、MyFaces资源包。
(2)配置struts.xml。
利用Struts2+JSF开发视图层,需要的配置文件是struts.xml。这个文件配置信息分为两个部分,一个是JSF拦截器的配置,另一个是Struts2的Action配置。
首先看一下JSF拦截器的配置:
在struts.xml文件中需要配置JSF的拦截器,使得所有的JSF的请求都能被正确处理。这个拦截器在Struts的插件包中已经定义好了,继承这个包就可以使用这些拦截器。拦截器的配置如代码17-17所示。
代码17-17struts.xml中JSF拦截器的配置
<!--重写拦截器,将其命名在包myJSF中-->
<packagename="myJSF"extends="JSF-default">
<interceptors>
<interceptor-stackname="JSFFullStack">
<interceptor-refname="params"/>
<interceptor-refname="basicStack"/>
<interceptor-refname="JSFStack"/>
</interceptor-stack>
</interceptors>
<default-interceptor-refname="JSFFullStack"/>
</package>
接下来配置请求的Action。
在请求的Action配置中需要继承myJSF拦截器,用来处理JSF页面的组件。
本应用中来自页面的请求共有4种,如下所示。
lwelcome.action:请求显示欢迎页面。配置的result类型为JSF,使用JSF解析welcome.jsp页面中的组件。
lview.action:请求显示所有选手列表。配置的result类型为JSF,使用JSF解析view.jsp页面中的组件,显示所有已报名选手的列表信息。
lregister.action:请求增加选手。配置两种result。
result的name=“JSF”,使用JSF解析register.jsp页面中的组件。
result的name=“view”、type类型为chain,即增加选手后直接请求显示所有选手类表的view.action。
lfindone.aciton:请求显示修改选手信息
result类型为“JSF”,使用JSF解析findone.jsp页面中的组件。
result的name=“view”、type类型为“chain”,即修改选手后直接请求显示所有选手类表的view.action。
struts.xml的详细配置,如代码17-18所示。
代码17-18struts.xml的详细配置
<!--定义我们的Action的包,并且要继承myJSF-->
<packagename="olympic"extends="myJSF">
<!--首次进入的欢迎页面-->
<actionname="welcome">
<resulttype="JSF"/>
</action>
<!--显示所有选手-->
<actionname="view"class="com.sunyang.olympic.OlympicAction">
<resultname="success"type="JSF"/>
</action>
<!--选手报名-->
<actionname="register"class="com.sunyang.olympic.OlympicAction">
<resultname="success"type="JSF"/>
<resultname="view"type="redirect">view.action</result>
</action>
<!--修改信息-->
<actionname="findone"class="com.sunyang.olympic.OlympicAction"method="findone">
<resultname="success"type="JSF"/>
<resultname="view"type="redirect">view.action</result>
</action>
</package>
(3)创建页面。
示例程序共设计4个页面,分述如下。
lwelcome.jsp。欢迎页面。该页面显示两种链接:“我现在就要报名”链接到新增加选手页面;“我想看看都谁报名了”链接到查看所有选手列表信息页面。
lregister.jsp。增加用户页面。单击欢迎页面的“我现在就要报名”,跳转到本页面。该页面显示增加选手的信息,页面使用JSF输入组件标签,表单提供用户编号、姓名、年龄、性别与联络方式,所有的信息都由选手填写,而且实现数据验证功能,对于不合法的数据类型同时提供错误信息。
lview.jsp。查看所有选手列表信息。该页面采用JSF制表标签,循环遍历显示所有选手信息。该页面还提供了另一个链接“这里还没有我”,单击该链接跳转到增加选手页面。
lfindone..jsp。该页面用来提供选手修改个人信息。
单击查看所有选手列表信息页面的选手编号,跳转到本页面。本页面采用JSF输入类标签,显示该选手编号对应的详细信息,如选手编号、选手姓名、性别、年龄及联系方式,供选手修改。修改后单击“我确定修改”,跳转到显示所有选手类表页面。
①欢迎页面welcome.jsp,如代码17-19所示。
代码17-19welcome.jsp
<%@pagelanguage="java"import="java.util.*"pageEncoding="GBK"%>
<%@taglibprefix="f"uri="http://java.sun.com/JSF/core"%>
<%@taglibprefix="h"uri="http://java.sun.com/JSF/html"%>
<!DOCTYPEHTMLPUBLIC"-//W3C//DTDHTML4.01Transitional//EN">
<html>
<head>
<title>MyJSP'welcome.jsp'startingpage</title>
</head>
<body>
<f:view>
<h3>奥运啦啦队员海选报名啦!!!</h3>
<h3>请选择:</h3>
<h:outputLinkvalue="register.action">
<h:outputTextvalue="我现在就要报名!!!"/>
</h:outputLink>
<h:outputLinkvalue="view.action">
<h:outputTextvalue="我想看看都谁报名了!!!"/>
</h:outputLink>
</f:view>
</body>
</html>
②增加选手的注册页面register.jsp。
单击“我现在就要报名”跳转到注册页面,这个页面的代码很多,标签中的“value”值就是配置在Action中注入的player的各种属性值,如代码17-20所示。
代码17-20register.jsp
<%@pagelanguage="java"contentType="text/html;charset=GBK"%>
<%@taglibprefix="f"uri="http://java.sun.com/JSF/core"%>
<%@taglibprefix="h"uri="http://java.sun.com/JSF/html"%>
<html>
<head>
<title>注册信息</title>
</head>
<body>
<f:view>
<h3>同一个世界,同一个梦想</h3>
<h3>感谢您对奥运的支持,请填写下列信息......</h3>
<h:form>
<h:panelGridcolumns="3">
<h:outputTextvalue="选手编号"/>
<h:inputTextid="id"size="30"value="#{action.player.id}"required="true"/>
<h:messagefor="id"/>
<h:outputTextvalue="选手姓名"/>
<h:inputTextid="name"size="30"value="#{action.player.name}"
required="true">
<f:validateLengthminimum="2"maximum="100"/>
</h:inputText>
<h:messagefor="name"/>
<h:outputTextvalue="选手性别"/>
<h:inputTextid="sex"size="30"value="#{action.player.sex}"required="true">
<f:validateLengthminimum="1"maximum="6"/>
</h:inputText>
<h:messagefor="sex"/>
<h:outputTextvalue="选手年龄"/>
<h:inputTextid="age"size="30"value="#{action.player.age}"required="true">
<f:validateLengthminimum="1"maximum="100"/>
</h:inputText>
<h:messagefor="age"/>
<h:outputTextvalue="联系方式"/>
<h:inputTextid="tel"size="30"value="#{action.player.tel}"required="true">
<f:validateLengthminimum="2"maximum="13"/>
</h:inputText>
<h:messagefor="tel"/>
</h:panelGrid>
<h:commandButtonvalue="报名了"action="#{action.save}"/>
<br/>
</h:form>
</f:view>
</body>
</html>
③查看所有选手信息列表页面view.jsp。
在注册选手页面,填写选手信息后,单击“报名了”跳转到查看所有选手的页面,该页面将所有选手信息遍历显示到表格中。标签<h:dataTable>用来显示表格,<f:facet>用来显示表头,<h:column>用来显示表列,详细的设计如代码17-21所示。
代码17-21view.jsp
<%@pagelanguage="java"contentType="text/html;charset=GBK"%>
<%@taglibprefix="f"uri="http://java.sun.com/JSF/core"%>
<%@taglibprefix="h"uri="http://java.sun.com/JSF/html"%>
<html>
<head>
<title>查看所有选手报名信息</title>
</head>
<body>
<f:view>
<h3>已经有这么多人报名了!!!!!</h3>
<h3>这里有我吗?没有请单击加入他们</h3>
<h:dataTablevalue="#{action.select}"var="p"style="text-align:center;width:500px"
border="1">
<h:column>
<f:facetname="header">
<h:outputTextvalue="选手编号"/>
</f:facet>
<h:outputLinkvalue="findone.action">
<f:paramname="playerid"value="#{p.id}"/>
<h:outputTextvalue="#{p.id}"/>
</h:outputLink>
</h:column>
<h:column>
<f:facetname="header">
<h:outputTextvalue="选手姓名"/>
</f:facet>
<h:outputTextvalue="#{p.name}"/>
</h:column>
<h:column>
<f:facetname="header">
<h:outputTextvalue="选手性别"/>
</f:facet>
<h:outputTextvalue="#{p.sex}"/>
</h:column>
<h:column>
<f:facetname="header">
<h:outputTextvalue="选手年龄"/>
</f:facet>
<h:outputTextvalue="#{p.age}"/>
</h:column>
<h:column>
<f:facetname="header">
<h:outputTextvalue="联系方式"/>
</f:facet>
<h:outputTextvalue="#{p.tel}"/>
</h:column>
</h:dataTable>
<p>
<h:outputLinkvalue="register.action">
<h:outputTextvalue="这里还没有我!!"/>
</h:outputLink>
</p>
</f:view>
</body>
</html>
④修改选手页面findone.jsp。
单击查看显示所有选手列表页面中的选手编号,跳转到修改选手信息页面。
在findone.jsp中需要增加一个<h:inputHidden>标签用来隐式的标识“playerid”。将选手编号为“playerid”的属性值显示到页面中。最后提交action的modify方法,详细的设计如代码17-22所示。
代码17-22findone.jsp
<%@pagelanguage="java"contentType="text/html;charset=GBK"%>
<%@taglibprefix="f"uri="http://java.sun.com/JSF/core"%>
<%@taglibprefix="h"uri="http://java.sun.com/JSF/html"%>
<html>
<head>
<title>修改信息</title>
</head>
<body>
<f:view>
<h3>同一个世界,同一个梦想</h3>
<h3>感谢您对奥运的支持,请修改下列信息>>></h3>
<h:form>
<h:inputHiddenvalue="#{action.playerid}"/>
<h:panelGridcolumns="3">
<h:outputTextvalue="选手编号"/>
<h:inputTextid="id"size="30"value="#{action.player.id}"required="true"/>
<h:messagefor="id"/>
<h:outputTextvalue="选手姓名"/>
<h:inputTextid="name"size="30"value="#{action.player.name}"required="true">
<f:validateLengthminimum="2"maximum="100"/>
</h:inputText>
<h:messagefor="name"/>
<h:outputTextvalue="选手性别"/>
<h:inputTextid="sex"size="30"value="#{action.player.sex}"required="true">
<f:validateLengthminimum="2"maximum="6"/>
</h:inputText>
<h:messagefor="sex"/>
<h:outputTextvalue="选手年龄"/>
<h:inputTextid="age"size="30"value="#{action.player.age}"
required="true">
<f:validateLengthminimum="1"maximum="100"/>
</h:inputText>
<h:messagefor="age"/>
<h:outputTextvalue="联系方式"/>
<h:inputTextid="tel"size="30"value="#{action.player.tel}"required="true">
<f:validateLengthminimum="2"maximum="13"/>
</h:inputText>
<h:messagefor="tel"/>
</h:panelGrid>
<h3>感谢您对奥运的支持,提交前请确认您的信息</h3>
<h:commandButtonvalue="我确定修改!!!"id="modifyCommand"action=
"#{action.modify}"/>
<br/>
</h:form>
</f:view>
</body>
</html>
(4)创建JavaBean,包括选手信息类PlayerInfo与控制器类OlympicAction。
①首先创建选手信息类PlayerInfo.java。
该类中定义选手的各种属性,包括id(编号)、name(名称)、age(年龄)、sex(性别)、tel(联系方式),如代码17-23所示。
代码17-23PlayerInfo.java
packagecom.sunyang.olympic;
publicclassPlayerInfo{
privateintid;
privateStringname;
privateintage;
privateStringsex;
privateinttel;
//定义默认的构造函数
publicPlayerInfo(){}
//重写构造函数
publicPlayerInfo(intid,Stringname,Stringsex,intage,inttel){
this.id=id;
this.name=name;
this.sex=sex;
this.age=age;
this.tel=tel;
}
//省略属性的getter和setter方法
}
②然后创建控制器类OlympicAction.java。
OlympicAction.java用来处理增加选手页面、修改选手页面、显示所有选手列表页面、修改选手信息页面功能的各种请求。该类需要继承ActionSupport,注入选手信息类PlayerInfo对象。添加处理页面表单的方法如下。
l预存储数据:本应用没有持久化,因此这里采用预先填写3条数据,存储在List对象中。
l增加选手:单击增加选手页面中的“报名了”,触发该方法。本应用中的所有选手信息,除了预先存储的数据以外,新增加的选手数据,均存储在会话session当中。
l查询所有选手:单击“我想看看都谁报名了”或是在修改选手页面中更新选手信息后,单击“我确定修改!”时触发此方法,此方法调用存储在List中的数据,如果会话已经被创建,就返回会话当中的列表List;否则返回预先存储的列表List。
l查找单个选手:该方法根据页面表单中选手的编号,遍历会话session中存储的选手对象,并将该对象中的数据返回给修改选手信息页面,供选手修改。
l修改选手信息:当选手在修改信息页面中更新当前信息后,点击“我确定修改!”提交到本方法。此方法将根据提交的选手编号,更新存储在会话session中的对象信息,并跳转到查看所有选手信息列表页面。
具体如代码17-24所示。
代码17-24OlympicAction.java
packagecom.sunyang.olympic;
publicclassOlympicActionextendsActionSupport{
//注入选手POJO
privatePlayerInfoplayer;
//实例化选手对象
publicOlympicAction(){
player=newPlayerInfo();}
//定义选手的id属性,用来接受页面值信息
privateintplayerid;
//省略他们的getter和setter
//定义封装选手对象的list
List<PlayerInfo>list=newArrayList<PlayerInfo>();
//硬性地存储3条数据,
publicList<PlayerInfo>setValue(){
//定义会话session
HttpSessionsession=ServletActionContext.getRequest().getSession();
if(session.getAttribute("player")==null){
PlayerInfono1=newPlayerInfo(2008,"Andy","man",25,1111111111);
PlayerInfono2=newPlayerInfo(2009,"Lily","female",25,22222222);
PlayerInfono3=newPlayerInfo(2010,"Kaka","man",25,1111111111);
list.add(no1);
list.add(no2);
list.add(no3);
session.setAttribute("player",list);
}
list=(List)session.getAttribute("player");
returnlist;
}
//遍历所有的选手并将其传值到页面
publicList<PlayerInfo>getSelect(){
ListlistValue=newArrayList();
listValue=setValue();
returnlist;
}
//增加选手对象
publicStringsave(){
HttpSessionsession=ServletActionContext.getRequest().getSession();
if(session!=null){
list=setValue();
list.add(player);
session.setAttribute("player",list);
}
return"view";
}
//遍历单个选手,并将其值传到页面
publicStringfindone(){
HttpSessionsession=ServletActionContext.getRequest().getSession();
if(session!=null){
list=(List)session.getAttribute("player");
for(inti=0;i<list.size();i++){
PlayerInfop=list.get(i);
if(p.getId()==playerid){
this.player=p;
}
}
}
return"success";
}
//用来修改选手信息
publicStringmodify(){
HttpSessionsession=ServletActionContext.getRequest().getSession();
if(session!=null){
list=(List)session.getAttribute("player");
for(inti=0;i<list.size();i++){
PlayerInfop=list.get(i);
if(p.getId()==player.getId()){
list.remove(p);
list.add(player);
}
}
}
return"view";
}
}
(5)配置Web应用文件。
Struts2中使用的JSF的UI标签、JavaBean的绑定功能,都需要使用JSF的Servlet来处理。因此在Web应用文件中需要配置的JSFSevlet处理.action的所有请求,同时要配置Struts2的请求转发。如代码17-25所示。
代码17-25web.xml
<?xmlversion="1.0"encoding="UTF-8"?>
<web-appid="JSF"version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<filter>
<filter-name>struts</filter-name>
<filter-class>org.apache.Struts2.dispatcher.FilterDispatcher</filter-class>
</filter>
<filter-mapping>
<filter-name>struts</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.apache.myfaces.webapp.StartupServletContextListener</listener-class>
</listener>
<!--JSF的servlet-->
<servlet>
<servlet-name>faces</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!--配置所有的.action的请求都有JSF的servlet处理-->
<servlet-mapping>
<servlet-name>faces</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
</web-app>
(6)最后发布运行。
单击“我想看看都谁报名了”后,进入到查看所有队员信息的页面。
单击欢迎页面的“我现在就要表名!!!”或者页面的“这里还没有我”后进入到增加新的啦啦队选手的页面。
当单击“报名了”后,跳转到查看所有选手信息的页面。
单击选手编号,可以修改选手的各项信息。
单击“我确定修改!!!”按钮后跳转到查看所有选手页面。
至此,Struts2和JSF的整合就完成了,发布运行后,得到预期的效果。但要注意的是,示例中我们将值存储在session会话中,并没有被真正的持久化,如果要想将数据持久化,就需要增加持久层及业务层相应代码,这些操作可以参考其它整合持久化框架的章节。