- 浏览: 6074016 次
- 性别:
- 来自: 上海
文章分类
最新评论
-
chimpp55:
这个能做成一个工程吗,我想在本地跑,自己做了,感觉没有用
struts1的测试(MockStrutsTestCase类) -
www.sendmaillist.com:
学习好文章。
邮件和邮件服务器 -
www.sendmaillist.com:
发邮件还是找专业的邮件edm平台吧。比如 amazon ses ...
邮件和邮件服务器 -
kevenfox:
生产电脑不应该修改原来的接口,修改的成本太大,这同样不符合开闭 ...
工厂模式 -
zhlfresh163.com:
"var tilelayer=new BMap.Ti ...
百度地图API的学习
Struts2实例
一、准备工作及实例
1.解压struts-2.1.6-all.zip
apps目录:struts2自带的例子程序
docs目录:官方文档。
lib目录:存放所有jar文件。
Src目录:源文件存放地
2.六个基本包
struts2-core-2.1.6.jar:开发的核心类库
freemarker-2.3.13.jar:struts2的UI标签的模板使用freemarker编写
commons-logging-1.0.4.jar:日志包
ognl-2.6.11.jar:对象图导航语言,通过它来读写对象属性
xwork-2.1.2.jar:xwork类库,struts2在其上进行构建
commons-fileupload-1.2.1.jar:文件上传组件,2.1.6版本后必须加入此jar包
特别需要说明的是目前strust2的最新版本是struts-2.1.6,它作为2.1.X的正式版。特别要注意导入commons-fileupload-1.2.1.jar包,在此jar包中包含了RequestContext类,如果不导入该jar包将会报异常。
3.初识struts2配置文件
(1).web.xml文件
主要完成对StrutsPrepareAndExecuteFilter的配置(在以前的版本中是对FilterDispatcher配置,新版本同样支持用FilterDispatcher配置),它的实质是一个过滤器,它负责初始化整个Struts框架并且处理所有的请求。这个过滤器可以包括一些初始化参数,有的参数指定了要加载哪些额外的xml配置文件,还有的会影响struts框架的行为。除了StrutsPrepareAndExecuteFilter外,Struts还提供了一个ActionContexCleanUp类,它的主要任务是当有其它一些过滤器要访问一个初始化好了的struts框架的时候,负责处理一些特殊的清除任务。
(2).struts.xml文件
框架的核心配置文件就是这个默认的struts.xml文件,在这个默认的配置文件里面我们可以根据需要再包括其它一些配置文件。在通常的应用开发中,我们可能想为每个不同的模块单独配置一个struts.xml文件,这样也利于管理和维护。这也是我们要配置的主要文件。
(3).struts.properties(参default.properties)
在Struts框架使用了很多属性,我们可以通过改变这些属性来满足我们的需求。要改变这些属性,只需在struts.properties文件中指定属性的key和value即可。属性文件可以放在任何一个包含在classpath中的路径上,但是通常我们都把它放在/WEB-INF/classes目录下面。我们可以在struts-default.properties文件中找到一个属性的列表。
(4)struts-default.xml
此文件是struts2框架默认加载的配置文件,它定义了struts2一些核心bean和拦截器,它会自动包含(included)到struts.xml文件中(实质是通过<packageextends="struts-default">),并为我们提供了一些标准的配置。我们可以在struts2-core.jar中找到这个文件。
(5)其它配置文件
velocity.properties,struts-default.vm,struts-plugin.xml
4.让MyEclipse提示xml信息
当我们在编写struts.xml时,发现eclipse并不会给出帮助提示,那是因为MyEclipse默认并不支持struts2,所以我们需要手工导入dtd以支持提示。步骤:[window][preferences][MyEclipse][FilesandEditors][XML][xmlCatelog]然后在右边点add添加:location为dtd文件所在的位置(struts-2.0.dtd文件struts2-core-2.1.6.jar中可以得到),KeyType选择URI,Key为struts-2.0.dtd文件中文档声明的内容(http://struts.apache.org/dtds/struts-2.0.dtd),在struts.xml文件中也有此key值。
5.如何使用alt+/提示
在MyEclipse6.5中,默认的提示为Ctrl+Space,而它会与我们的输入法切换冲突,使提示失效。找到key,先取消ContentAssist命令的绑定,再用“alt+/”来绑定。
6.实例
步骤一,新建myStruts2项目,并导入struts2的六个基本jar包。
步骤二,建立LoginAction文件,主要代码如下:
packagecom.asm;
importcom.opensymphony.xwork2.Action;
publicclassLoginActionimplementsAction{
privateStringusername;
privateStringpassword;
...省略get/set方法
publicStringexecute()throwsException{
if(username.equals("struts2")){
return"loginSuccess";
}else{
return"loginFailure";
}
}
}
说明:实现了Action接口,主要是为了保证execute的正确定义,其实我们也可以不实现此接口,只要能保证execute方法书写的正确书写(方法名,返回值)。
步骤三,在struts.xml文件中注册LoginAction。此配置文件要放在src目录下,实质就是成为classpath环境变量下的文件。主要代码如下:
<?xmlversion="1.0"encoding="UTF-8"?>
<!DOCTYPEstrutsPUBLIC
"-//ApacheSoftwareFoundation//DTDStrutsConfiguration2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<packagename="myFirst"namespace="/"extends="struts-default">
<actionname="login"class="com.asm.LoginAction">
<resultname="loginSuccess">/success.jsp</result>
<resultname="loginFailure">/failure.jsp</result>
</action>
</package>
</struts>
说明:package后面会有详细说明。action元素中的name属性值指定了此action所指定的请求路径为“login.action”。后面login.jsp中的<formaction=...>属性值就会参照此name属性。
步骤四、提供jsp页面
login.jsp主要代码:
<body>
<formaction="<%=request.getContextPath()%>/login.action"method="get">
户名:<inputtype="text"name="username"><br>
密码:<inputtype="password"name="password"><br>
<inputtype="submit"value="login">
</form>
</body>
failure.jsp主要代码
<%@pagelanguage="java"import="java.util.*"pageEncoding="UTF-8"%>
<%@tagliburi="/struts-tags"prefix="s"%>
<html>
<body>
登录失败,错误的用户名:<s:propertyvalue="username"/><br>
<ahref="<%=request.getContextPath()%>/login.jsp">返回</a>
</body>
</html>
说明:使用了标签库,在struts2中使用标签库非常简单,只需要像上面那样导入标签库便可以使用所有的struts2的所有标签
success.jsp主要代码
<body> 登录成功!</body>
步骤五、配置web.xml。完成核心监听器注册。内容如下:
<?xmlversion="1.0"encoding="UTF-8"?>
<web-appid="WebApp_9"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/j2eehttp://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<filter>
<filter-name>struts2</filter-name>
<!--<filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
-->
<filter-class> org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
说明:注释掉的部分为以前2.1.4版本中用的核心filter类。StrutsPrepareAndExecuteFilter类的init方法将会读取类路径下默认的配置文件struts.xml,并以javabean形式存放在内存中,以后struts2对用户的每次请求将使用内存中数据,而不是重读struts.xml文件。
步骤六、发布测试。
简要分析执行流程:
当输入login.jsp访问jsp页面填写完相关信息并提交给login.action时,它会首先被在web.xml中配置的过滤器监听到,过滤器会去查找strust.xml文件,并结合namespace查找名为login的action,查找到此action便交给其处理,LoginAction内部会执行execute方法,并返回结果result(result也是参照的struts.xml中action下的result配置)。关于表单传参,主要是参照的action中的方法名,而非属性名。
7.开启struts2自带的开发模式常量
在以前的开发中,当修改一些配置时总是不能及时地更新到服务器,我们总会重新部署或重启来更新改变的内容,在struts2中可以通过一个常量来达到此目的。即在struts.xml中的<struts>元素下增加如下内容:<constantname="struts.configuration.xml.reload"value="true"/>这样配置后,当配置文件修改保存时就会及时更新到服务器中。其它一些常量:
<!--指定WEB应用的编码集,相当于调用HttpServletRequest.setCharacterEncodint方法,如果使用了velocity或freemarker,它也用于指定输出的编码格式-->
<constantname="struts.i18n.encoding"value="UTF-8"/>
<!--指定请求后缀为.action,指定多个请求后缀用逗号分隔-->
<constantname="struts.action.extension"value="action"/>
<!--设置浏览器是否缓存静态内容,建议:开发阶段关闭,运行时开启-->
<constantname="struts.serve.static.browserCache"value="false"/>
<!--当struts.xml配置文件修改后,系统是否重新加载该文件,开发阶段打开此功能-->
<constantname="struts.configuration.xml.reload"value="true"/>
<!--开发提示:出错时打印更详细的信息-->
<constantname="struts.devMode"value="true"/>
<!--指定请求的后缀可以是.do或.action-->
<constantname="struts.action.extension"value="do,action"/>
注意:在struts2.1.6版本中存在一个bug:即配置了struts.i18n.encoding常量也不能解决中文乱码问题,原因是此版本在获取请求参数后才调用了setCharacterEncoding()方法进行编码设置。解决此bug的方法是配置一个filter,并在doFilter方法中增加如下代码:request.setCharacterEncoding(“UTF-8”);在以后的2.1.8版本中解决了此问题及2.1.6中存在的其它bug,建议新项目使用2.1.8版本。
8.vo传参模式
Copy上面的myStruts2项目,改名为myStruts2Vo项目。作如下修改:在LoginAction中有两个字段:username,password。把此两个属性重构到com.asm.vo.User类中,然后在LoginAction中提供User对象及相应的get/set方法。现在需要注意的是在login.jsp中会有如下的修改:
户名:<inputtype="text"name="user.username"><br>
密码:<inputtype="password"name="user.password"><br>
关键就是改掉name属性值。其它基本无变动。后话:假如此此User对象并不能和Model层的相应对象完全对应,我们还应借助此User对象在Action中构建出Model层的相应对象,这样,在exectue方法中便能通过构建的Model对象作为参数与Model层交互。
9.ModerDriven传参模式(不建议采用)
Copy上面的myStruts2Vo项目,改名为myStruts2Model项目。重点是修改LoginAction,修改后的主要内容如下:
packagecom.asm;
importcom.asm.vo.User;
importcom.opensymphony.xwork2.Action;
importcom.opensymphony.xwork2.ModelDriven;
publicclassLoginActionimplementsAction,ModelDriven<User>{
privateUseruser=newUser();
publicStringexecute()throwsException{
if(user.getUsername().equals("struts2")){
return"loginSuccess";
}else{
return"loginFailure";
}
}
publicUsergetModel(){
returnuser;
}
}
说明:它实现了ModelDriven接口,并使用了泛性机制(必须),因此要求jdk1.5以上。
现在需要注意的是在login.jsp中name属性值为User中两个字段,和第一个实例一样。说明:此方式一般不会使用,在此略作了解。
10.为什么要使用struts2代替struts1.x
(1)struts2的execute方法中的参数不会依赖于servletAPI,实现了也servlet解耦,是一种无侵入式的设计。
(2)struts2提供了拦截器,利用拦截器可以进行AOP编程,实现权限拦截等功能。
(3)struts2提供了类型转换器,我们可以很容易地对请求参数转换成需要的类型。
(4)提供了同种表现层技术支持,如JSP、freeMarker、velocity等
(5)可以对指定的方法进行校验,可以轻松地实现表单校验功能
(6)提供了全局范围、包范围和action范围的国际化资源文件管理实现。
二、struts.xml配置及例程
1.配置文件的优先级
在struts2中一些配置(比如常量)可以同时在struts-default.xml(只读性),strtus-plguin.xml(只读性),struts.xml,struts.properties和web.xml文件中配置,它们的优先级逐步升高,即是说后面的配置会覆盖掉前面相同的配置。
2.配置形式
下面以对struts.i18n.encoding=UTF-8的配置为例进行说明:
在struts.xml配置形式如下:
<constantname="struts.i18n.encoding"value="gbk"></constant>
在struts.properties的配置形式如下:
struts.i18n.encoding=UTF-8
在web.xml中配置如下:
<filter>
<filter-name>struts2</filter-name>
<filter-class> org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
</filter-class>
<init-param>
<param-name>struts.i18n.encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
说明:官方声称配置了此常量可以解决中文乱码问题,但实事上并不能达到目的,在前面的三个项目中,如果我们在表单中输入中文,其结果是会出现乱码。解决此问题参看[一.7的注意]。这是struts2.1.6中的一bug,它的下一版2.1.8已解决此问题。
3.package配置相关
属性名 |
是否必须 |
说明 |
Name |
是 |
Package的唯一标识,不允许同名 |
Extends |
否 |
指定要继承的包 |
Namespace |
否 |
指定名称空间 |
Abstract |
否 |
声明包为抽象否 |
下面我们建立struts2package项目来进行package相关测试:
说明:在上面的配置文件中所用到的Test1Action和Test2Action这两个Action都只是继承了com.opensymphony.xwork2.ActionSupport类,而ActionSupport默认返回的就是“success”,所以当点击上面的链接分别转到了forward目录下的test1.jsp和test2.jsp。下面重点来看这个package元素的namespace属性及action的name属性,它们共同定义了action所映射到的实质文件。上图展示了链接地址和action的对应关系,所以当我们要想访问一个action所关联到的jsp文件时,应该用namespace+action的name关于它的内容测试可以参考struts2package项目。
补充:通常情况下,action元素的name是属性值是不能出现“/”的,所以希望通过action中name属性来实现多级映射,需要在sturts.xml中增加如下属性:
<constantname="struts.enable.SlashesInActionNames"value="true"/>这样配置后就可以再action的name元素中使用“/”了。比如:
<packagename="tt3"extends="struts-default">
<actionname="test3/test3"class="com.asm.Test3Action">
<resultname="success">/forward/test3.jsp</result>
</action>
</package>
然后输入<ahref="<%=path%>/test3/test3.action">test3</a><br>链接地址就可以访问了
强调:namespace默认值“”,即不配置namespace属性。它的意思是:如果action不能进行完整路径匹配,则会来此namespace下进行匹配,比如:.../test/test/test.action,如果参照namespace及action的name不能找到也之完全对应的action,它会再到依次追溯到上级目录中查找,即是说它会以…/test/test.action这样的路径来对应namespace和action的name进行查找。如果返回到最终的目录仍找不到,它就会到namespace="/"对应的包下查找名为test的action,如果仍找不到,它就会去默认的namespace下查找名为test的action,如果找到则执行此action。另外,namespace也可以配置成namespace="/"。它代表配置为项目的根。总结action的名称探索顺序:完全对应、逐步追溯到上级目录查找、"/"下查找、默认namespace下查找。
为什么要提出namespace,主要是避免多人共同开发项目出现名字冲突。如果不使用namespace,多个人所写的action中可能出现重名的现象,这样当项目合并时就会出现冲突。而有了namespace可以在项目开发时由项目经理给每一个人分不同的namespace,这样每个开发人员只需要保证自己所写的action不同名即可。
namespace引发的链接问题:当我们为action配置了namespace时,访问此action的形式总会是如下形式:.../webappname/xxx/yyy/ActionName.action而当此action成功执行跳转到某个jsp页面时,如想在此jsp页面写链接,一定要写绝对路径,因为相对路径是相对.../webappname/xxx/yyy/,而如果以后我们修改了action的namespace时,相对路径又要变,所以链接不能写成相对路径。以下介绍绝对路径的写法:通常用myeclipse开发时建立一个jsp文件,默认总会有如下内容:
<%
Stringpath=request.getContextPath();
StringbasePath=request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
我们写绝对路径可以参此内容。还可以参<head>下的<basehref="<%=basePath%>">来完成绝对路径的书写。
4.分工合作include:指定多个配置文件
比如让jack来单独开发一个action,在jack.xml中的配置文件为:
<struts>
<packagename="jack"namespace="/jack"extends="struts-default">
<actionname="test4"class="com.asm.Test4Action">
<resultname="success">/forward/test4.jsp</result>
</action>
</package>
</struts>
然后在struts.xml文件中增加如下内容:<includefile="jack.xml"></include>它实质就是把jack.xml中的<package>及其内容写进struts.xml中的<struts>根元素下。
链接:<ahref="<%=path%>/jack/test4.action">test4</a>这样便可以访问到了forward目录下的test4.jsp了。
5.tomcat认证访问
接上例:namespce的作用除了在前面提到的避免协同开发名字冲突外,还为认证提供一个条件。比如jack开发的东西所关联到的页面需要权限才能被访问。由于多为tomcat中的内容,下面只列出步骤。
步骤一,tomcat的conf目录下tomcat-users.xml内容如下:
<?xmlversion='1.0'encoding='utf-8'?>
<tomcat-users>
<rolerolename="manager"/>
<rolerolename="admin"/>
<userusername="jack"password="jack"roles="admin,manager"/>
<userusername="tom"password="tom"roles="manager"/>
</tomcat-users>
步骤二,在web.xml中增加如下内容:
<security-constraint>
<web-resource-collection>
<web-resource-name>jack</web-resource-name>
<url-pattern>/jack/*</url-pattern>
<http-method>POST</http-method>
<http-method>GET</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>
<security-role>
<role-name>admin</role-name>
</security-role>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>inputauthenticationmessage</realm-name>
</login-config>
这样配置完成后,当我们访问.../jack中的任何内容都会要求输入密码认证信息,认证时输入tomcat-users.xml配置的admin权限的用户名和密码即可访问(这里就只有jack用户名可以访问)
6.初识拦截器
拦截器能在action被调用之前和被调用之后执行一些“代码”。Struts2框架的大部分核心功能都是通过拦截器来实现的,如防止重复提交、类型转换、对象封装、校验、文件上传、页面预装载等等,都是在拦截器的帮助下实现的。每一个拦截器都是独立装载的(pluggable),我们可以根据实际的需要为每一个action配置它所需要的拦截器。
在myStruts2项目下,重新对配置文件作如下修改:
<packagename="myFirst"namespace="/"extends="struts-default">
<interceptors>
<interceptorname="timer"
class="com.opensymphony.xwork2.interceptor.TimerInterceptor"/>
<interceptorname="params" class="com.opensymphony.xwork2.interceptor.ParametersInterceptor"/>
</interceptors>
<actionname="login"class="com.asm.LoginAction">
<interceptor-refname="timer"></interceptor-ref>
<interceptor-refname="params"></interceptor-ref>
<resultname="loginSuccess">/success.jsp</result>
<resultname="loginFailure">/failure.jsp</result>
</action>
</package>
首先在package中定义了两个拦截器,然后在loginaction中引用了这两个拦截器,需要说明的是这里使用的拦截器都是系统自带的拦截器。其实在extends所继承的struts-default中就包含了很多拦截器,也包括我们这里所用的拦截器,但如果在此action中不使用params拦截器,将会报空指针错,因为params拦截器的作用是传递表单参数,如果不使用此拦截器就不能在action中得到表单参数,所以引用时会报空指针错。虽然extends继承的strust-default自带有params拦截器,但是当我们自己引用了拦截器时,继承struts-default将不会再为我们分配默认的拦截器(有点类似构造器),但是我们仍然可以通过<interceptor-refname="defaultStack"/>来继续使用struts-defalut的拦截器。补充:由于上面的package继承于struts-default,而我们这里所用到的timer和params都是在struts-defalut中定义过,所以即使我们在<interceptors>中没有定义过这两个拦截器,也可以直接在action中引用。
使用</interceptor-stack>组合多个拦截器:比如我们想把上面的params和timer这两个拦截器组合:
<interceptor-stackname="timer_param">
<interceptor-refname="timer"/>
<interceptor-refname="params"/>
</interceptor-stack>
然后再在action引用<interceptor-refname="timer_param"/>”,效果和分别引用两个是一样的。其实我们使用strtus-default中的<interceptor-refname="defaultStack"/>也是使用interceptor-stack方式。
7.Action中的method属性
在struts1.x中我们知道通过继承DispatchAction可以实现把多个Action进行统一操作,在struts2中实现action的统一操作也很简单。我们以crud操作为例,把crud集中到一个Action中。
步骤一、建立CRUDAction,内容如下:
packagecom.asm;
importcom.opensymphony.xwork2.ActionSupport;
publicclassCRUDActionextendsActionSupport{
publicStringadd(){
return"success";
}
publicStringdel(){
return"success";
}
publicStringupdate(){
return"success";
}
publicStringquery(){
return"success";
}
}
步骤二、配置此Action,为了清晰明了,专为此Action,建立一个配置文件crud.xml,主要内容如下:
<struts>
<packagename="crud"extends="struts-default"namespace="/crud">
<actionname="add"class="com.asm.CRUDAction"method="add">
<resultname="success">/crud/addSuccess.jsp</result>
</action>
<actionname="del"class="com.asm.CRUDAction"method="del">
<resultname="success">/crud/delSuccess.jsp</result>
</action>
<actionname="update"class="com.asm.CRUDAction"method="update">
<resultname="success">/crud/updateSuccess.jsp</result>
</action>
<actionname="query"class="com.asm.CRUDAction"method="query">
<resultname="success">/crud/querySuccess.jsp</result>
</action>
</package>
</struts>
分析:上面的method方法的值来源于CRUDAction中方法的名字,这样当我们访问上面的每一个Action时,它实质是和method指定的方法关联上。
步骤三、把crud.xml配置文件并入struts.xml中,只需增加如下代码:
<includefile="jack.xml"></include>
步骤四、编写相应的jsp页面,在此略去crud文件夹下的四个跳转jsp页面(addSuccess.jsp等),重点是crud.jsp页面。内容如下:
<html>
<%
Stringpath=request.getContextPath();
%>
<body>
<ahref="<%=path%>/crud/add.action">添加数据</a><br>
<ahref="<%=path%>/crud/del.action">删除数据</a><br>
<ahref="<%=path%>/crud/query.action">查询数据</a><br>
<ahref="<%=path%>/crud/update.action">修改数据</a><br>
</body>
</html>
步骤五、发布测试。
补充扩展,动态调用DMI:不使用method实现统一.我们在crud.xml中增加如下内容:
<actionname="op"class="com.asm.CRUDAction">
<resultname="success">/crud/op.jsp</result>
</action>
然后再在crud.jsp中定义如下链接:
<ahref="<%=path%>/crud/op!add.action">添加数据</a><br>
<ahref="<%=path%>/crud/op!del.action">删除数据</a><br>
<ahref="<%=path%>/crud/op!query.action">查询数据</a><br>
<ahref="<%=path%>/crud/op!update.action">修改数据</a><br>
注意查看上面的链接地址,它们都是针对opaction,然后再加地上“!+CRUDAction中相应的方法名”,最后再写上.action即可以访问到统一页面op.jsp。这样做虽然能减少页面,但是由于它们实质用到的是同一个Action,所以这就意味着我们要使用的拦截器相同,相同的跳转result。实际中这种方式很少使用,在此略作了解。如果不想使用动态方法调用,我们可以通过常量来关闭,即在struts.xml中增加如下配置:
<constantname="struts.enable.DynamicMethodInvocation"value="false"/>
扩展2:在CRUDAction中使用do。举例:我们在CRUDAction中增加一个新的方法,内容如下:
publicStringdoMain(){
return"success";
}
然后再在在crud.xml中增加如下内容:
<actionname="main"class="com.asm.CRUDAction"method="main">
<resultname="success">/crud/main.jsp</result>
</action>
注意:配置中method属性值是doMain中去掉do后M小写。然后再在crud.jsp中增加如下链接:
<ahref="<%=path%>/crud/main.action">main页面</a><br>
随后便可以访问到.../crud/main.jsp页面了。
8.使用ForwardAction实现页面屏蔽。
我们在jsp页面之间写链接总会是.../xxx.jsp,而如果我们想屏蔽掉具体的jsp,只需要所jsp页面配置成一个ForwardAction即可实现。示例如下:在根目录下有一个index.jsp主页,我们strtus.xml中作如下配置:
<packagename="def"extends="struts-default">
<actionname="forward">
<result>/index.jsp</result>
</action>
</package>
说明:如果没有未action指定class,默认就是ActionSupport类,如果没有为action指定method属性,则默认执行execute方法,如果没有指定result的name属性,默认值为success。知道了这些再结合ActionSupport的源码就不难理解实现转发的原理了。
随后再在前面第7点扩展中用到的op.jsp中增加如下代码:
<ahref="<%=request.getContextPath()%>/forward.action">forward</a>
最后再测试访问op.jsp,在op.jsp中页面中直接点链接便可以跳到index.jsp,观察地址栏发现此时跳到index页面是进行的服务器跳转,如果我们在上面的配置中的result增加type属性变成<resulttype="redirect">/index.jsp</result>,实现的跳转就是客户端跳转。补充:像这种forward形式的action实质是执行的ActionSupport这个Action。因此配置它的result可以参看此类的api文档,比如它常用的resultname有:success、login、input等。
8.使用default-Action配置统一访问
default-action-ref,当访问没有找到对应的action时,默认就会调用default-action-ref指定的action.同样在上面的package中增加如下内容:
<default-action-refname="error"></default-action-ref>
<actionname="error">
<result>/other/error.jsp</result>
</action>
上面一段内容就是说当我们访问的action不能被找到时便指向名为error的action中去,接着我们在下面配置了这个errorAction。但是要注意,一个package内只配置一个<default-action-ref>,如果配置多个,就无法预测结果了.此时我们只要输入.../myStruts2/luanFangWen.action这样的形式,它就会去访问这个默认的<default-action-ref>,通常我们会为它配置一个错误页面,以提示用户访问的页面不存在。在web开发中,我们还可以把这个默认的action访问配置成主页,这样当用户访问一些不存在的action时,总会跳到主页上去。
通过此配置,只要是访问一个不存在的action便会转向到.../other目录下的error.jsp页面。但是如果访问是其它的不存在资源则仍是报tomcat所标识的404错误,我们可以在web.xml中作如下配置:
<error-page>
<error-code>404</error-code>
<location>/other/404error.jsp</location>
</error-page>
这样配置后,访问错误页面将跳到.../other/404error.jsp页面中去。补充说明:如果我们用ie访问时,如果选中了[工具][IE选项][高级][浏览][显示友好的http错误信息],则配置的错误页面将失效,因为找不到资源时会报HTTP404错误,而ie截取到此错误进行了它自身的友好处理,所以我们设置<error-page>就失效。
小结Action
在struts2中一个普通的java类只要有publicStringexecute()这样的方法都可以配置成一个Action,另外我们可以实现Action接口来使java类成为一个Action,但通常的做法是继承ActionSupport类,这也是以后的项目中惯用的方法,也是推荐的首选方法。与struts1.x不同的是:在struts2中每一个Action被请求访问时都会new出这个Action对象,所以Action本身不存在线程安全的问题。
9.使用通配符
建立struts2wildcard项目,此实例基本仿照前面前面第7点的实例改写而成。为了使用通配符,只需要改写配置文件即可。此实例未使用通配时的配置文件如下:
<actionname="addUser"class="com.asm.UserAction"method="addUser">
<resultname="success">/user/addUser.jsp</result>
</action>
<actionname="delUser"class="com.asm.UserAction"method="delUser">
<resultname="success">/user/delUser.jsp</result>
</action>
<actionname="queryUser"class="com.asm.UserAction"method="queryUser">
<resultname="success">/user/queryUser.jsp</result>
</action>
<actionname="updateUser"class="com.asm.UserAction"method="updateUser">
<resultname="success">/user/updateUser.jsp</result>
</action>
我们注释掉上面的配置,使用通配符只需如下内容即可达到相同的效果:
<actionname="*User"class="com.asm.UserAction"method="{1}User">
<resultname="success">/user/{1}User.jsp</result>
</action>
原理:当有.../addUser.action请求时,如果不能在当前应用中找到完全相同的addUser名字的Action时,通配符配置这时就起作用了,按通配原则,它便和上面的name="*User"相配成功,这里不难明了*此时代指的内容是add,再来看method恰恰是引用第一个*的内容,所以它的method此时的完整名为addUser,它刚好和com.asmUserAction中的addUser方法相对,所以它会去addUser方法,再来看下面的result配置所指代的页面,它也用到了{1},所以它的完整页面是/addUser.jsp。其实如果我们有良好的编程命名习惯,所有的Action我们都只需要进行一次配置。举例:规定所有的Action类都用XXXAction来命名,类中所有的CRUD方法都用add/del/update/query。Jsp页面也用add/del/update/query_XXX.jsp这样的形式。即配置文件可以写成如下形式:
<actionname="*_*"class="com.asm.{2}Action"method="{1}">
<resultname="success">.../{1}_{2}.jsp</result>
</action>
Name中第一个*代表CRUD操作的名字,第二个*代表类的名字。所以访问链接地址举例如下:
.../del_User.action将访问到User类的del方法,成功后跳到del_User.jsp页面。补充说明{0}是代表name中所有的*组合。
10.使用0配置:ZEROAnnotation
11.Result配置详解
说明:在前面的许多案例中我们所用到的Action基本都继承自ActionSupport这个类,而在这个类中我们定义了五个字段:SUCCESS,NONE,ERROR,INPUT,LOGING。我们可以直接返回这些字段值,这些字段值实质是被定义成:StringSUCCESS=”success”这样的形式,所以我们只要在Result元素中用它们的小写即可。
<result>标准完整形式如下:
<resultname="success"type="dispatcher">
<paramname="location">/default.jsp</param>
</result>
如果我们都采用默认的形式,最终可以简写成:<result>/default.jsp</result>
探讨type类型:
Type类型值 |
作用说明 |
对应类 |
chain |
用来处理Action链 |
com.opensymphony.xwork2.ActionChainResult |
dispatcher |
用来转向页面,通常处理JSP |
org.apache.struts2.dispatcher.ServletDispatcherResult |
redirect |
重定向到一个URL |
org.apache.struts2.dispatcher.ServletRedirectResult |
redirectAction |
重定向到一个Action |
org.apache.struts2.dispatcher.ServletActionRedirectResult |
plainText |
显示源文件内容,如文件源码 |
org.apache.struts2.dispatcher.PlainTextResult |
freemarker |
处理FreeMarker模板 |
org.apache.struts2.views.freemarker.FreemarkerResult |
httpheader |
控制特殊http行为的结果类型 |
org.apache.struts2.dispatcher.HttpHeaderResult |
stream |
向浏览器发送InputSream对象,通常用来处理文件下载,还可用于返回AJAX数据。 |
org.apache.struts2.dispatcher.StreamResult |
velocity |
处理Velocity模板 |
org.apache.struts2.dispatcher.VelocityResult |
xslt |
处理XML/XLST模板 |
org.apache.struts2.views.xslt.XSLTResult |
以上对type类型作简要的说明,下面来看实例:当一个Action处理后要返回的Result是另一个Action时,作如何配置,关键就是配置type类型。下面建立struts2result项目说明
步骤一:建立两个Action:TestAction、Test2Action
步骤二:web.xml配置省略。struts.xml主要配置内容如下:
<struts>
<packagename="resultTest"extends="struts-default">
<actionname="test"class="com.asm.TestAction">
<resultname="success"type="chain">
<paramname="actionName">test2</param>
</result>
</action>
<actionname="test2"class="com.asm.Test2Action">
<resultname="success">/test2Suc.jsp</result>
</action>
</package>
</struts>
说明:在名为“test”的action中,我们配置result元素的type类型值为chain,意为将继续把Action传递到下一个名为test2的Action中去,在test2.action中会把页面转向到test2Suc.jsp中去。在type类型为chain时,它的param有4个值可配,除了这里用到的name=”actionName”外(必须配置,否则报错),还有name=namespace|method|skipActions。其中namespace指定要转向到action的名字空间,由于此处是转到Action位于同一个namespace下,而namesapace的默认值thecurrentnamespace,所以可以省略不写(需要说明的是如果要跳到别的名称空间的action中去,除了使用namespace指定外,还可以用:/要跳去action所在名称空间的值/要跳去的action的name值)。Method用于指定转向到一个目标action所调用的方法,默认是调用下一个action的execute方法,所以此处仍可以省略。SkipActions是一个可选的属性,一般不用。具体可以参看chain所对应类的api帮助。
在本实例中,我们还在TestAction中设定一个username字段,并在execute方法执行为它赋了值,并在test2Suc.jsp中引用了此值。其实这种做法在web开发中还是很有用处,比如可以代替隐藏域。需要注意的是之所以在action的传递中能把设定的这个值保存下去,主要是因为转向都是服务器跳转。如果我们跳转时采取了客户端跳转,比如在test2action的result中指定type类型为redirect,要想传递参数可以在result指向的jsp页面中附加参数即可,我们可以在test2action的result中写成:
<resultname="success"type="redirect">
/test2Suc.jsp?username=${username}
</result>随后在test2Suc.jsp页面中引用时会出现三个问题:1.EL表达式引用失效,(EL表达式应该使用${param.username}形式)。我们也可以使用<%=request.getParameter("username")%>获取参数值。2.由于在前面的TestAction中设定的值为中文,而附加到这里的uri请求的参数后面时会出现乱码问题。(可以使用URI编码再解码解决此问题)3.值栈取值失效:因为每一次request共享同一个值栈,所以服务器端的forward跳转也是能共享同一值栈得。但是着当testaction执行后把请求交由test2action时,test2action采取的是redirect重定向到test2Suc.jsp页面,这时其实就是重发的一次request,所以在testaction保存的值栈内容全部失效。这也就是为什么我们要附加参数的原因。而参数是保存在actionContext中,所以采用了#的方式来取出值。图示说明:
步骤三,编写链接页面index.jsp。发布测试:
全局result:
如果我们所有的action均有可能跳到相同的页面,则不防使用全局result。为了方便引用我们专门建立一个package来存放公共的result。在会用到个全局的跳转时,只需要把继承自这个公共的package即可。
建立公共包,代码如下:
<packagename="pubResult"extends="struts-default"abstract="true">
<global-results>
<resultname="error">/error.jsp</result>
</global-results>
</package>
由于它下面没的action配置,所以我们可以像默认的struts-default包一样,声明abstract=true,这样声明表示此packgage下不会有action,它一般是用来让别的package继承。随后再在要用到全局result中引用这个公共的package。代码如下:
<packagename="testGlobal"extends="pubResult">
<actionname="error"class="com.asm.ErrorAction"></action>
<actionname="error2"class="com.asm.Error2Action"></action>
</package>这样操作相当于把全局的result加到了此package下的所有action中去。
动态Result:了解
步骤一:建立DynaAction,主要代码如下:
packagecom.asm;
publicclassDynaActionextendsActionSupport{
privateStringusername;
privateStringnextAction;
publicStringexecute()throwsException{
if(username.equals("admin")){
nextAction="admin";
}elseif(username.equals("user")){
nextAction="user";
}else{
nextAction=ERROR;
}
returnSUCCESS;
}
...省略get/set方法
}
步骤二、建立jsp页面dyna.jsp,主要是为了向DynaAction中传递username参数。
步骤三、相关配置如下:
<packagename="dynaTest"extends="pubResult">
<actionname="dyna"class="com.asm.DynaAction">
<resultname="success"type="chain">${nextAction}</result>
</action>
<actionname="admin">
<result>/admin.jsp</result>
</action>
<actionname="user">
<result>/user.jsp</result>
</action>
</package>
分析:当dyna.jsp把参数传递到DynaAction中去时,如果传递的值为admin,我们便设定了nextAction的值admin,在配置文件中我们通过${nextAction}(用在struts配置文件中的ognl,其实nextAction的值是存在值栈中,我们通过${}这样的形式取出。在此只作了解)来获取值便为admin,随后再继续把请求传递到下一个Action中去(此时也即admin.action),为了方便我们设定了两个ForwardAction:admin.action和user.action。这样便可以跳到指定的jsp页面。原理:dyna.action执行后会继续把请求传递给下一个Action,而下一个Action的到底是哪一个Action,是通过DynaAction中动态指定的,比如这里是根据传递的username的值指定。
12.异常处理
步骤一、建立struts2exception项目下,在该项目下建立登录页面login.jsp。主要代码如下:
<formaction="<%=request.getContextPath()%>/login.action">
username:<inputtype="username"name="username"><br>
<inputtype="submit"value="login">
</form>
步骤二、建立LoginAction,代码如下:
packagecom.asm;
publicclassLoginActionextendsActionSupport{
privateStringusername;
publicStringexecute()throwsException{
if(username.equals("exception")){
thrownewClassNotFoundException("类未被找到");
}elseif(username.equals("global")){
thrownewException("全局异常");
}else{
returnSUCCESS;
}
}
...省力get/set方法
}
步骤三、struts.xml配置文件如下:
<struts>
<packagename="ex"extends="def">
<actionname="login"class="com.asm.LoginAction">
<exception-mappingresult="myException"
exception="java.lang.ClassNotFoundException">
</exception-mapping>
<resultname="myException">/exception.jsp</result>
<resultname="success">/main.jsp</result>
</action>
</package>
<packagename="def"extends="struts-default"abstract="true">
<global-results>
<resultname="myGlobal">/globalException.jsp</result>
</global-results>
<global-exception-mappings>
<exception-mappingresult="myGlobal"
exception="java.lang.Exception">
</exception-mapping>
</global-exception-mappings>
</package>
</struts>
分析:异常处理机制较为简单,所以在此只略作说明。当登录时输入“exception”时,在LoginAction中会抛出会一个ClassNotFoundException异常,此异常我们采取的局部异常处理的方式,如果登录时输入“globla”,则会抛出Exception异常,此异常我们采取的是全局异常的处理方式,在ex包中我们继承了全局异常所在的包。提示:<exception-mapping>中的result属性的值来源于<result>元素中的name属性的值。从实例可以看出,我们一般把这种全局性的异常放在一个抽象包中供其实包继承。
三、在Action获取Scope对象
引言:在前面的Action操作中,关键就是Action中的exectue方法,但是此方法并没有request、session、application等对象作为参数,自然就不能利用这些对象来操作。下面我们建立struts2scope项目,并用四种方式来获取这些对象:
方式一、与Servlet解耦合的非IOC方式
获取的scope对象与容器无关,通过ActionContext获取。
LoginAction代码如下:
packagecom.asm;
publicclassLoginActionextendsActionSupport{
privateStringusername;
ActionContextcontext;
Maprequest;
Mapsession;
Mapapplication;
publicStringexecute()throwsException{
context=ActionContext.getContext();
request=(Map)context.get("request");
session=context.getSession();
application=context.getApplication();
request.put("req","requst属性");
session.put("ses","sesion属性");
application.put("app","application属性");
returnSUCCESS;
}
...省略username的get/set方法
}
struts.xml配置如下:
<struts>
<packagename="scope"extends="struts-default">
<actionname="login"class="com.asm.LoginAction">
<result>/loginSuc.jsp</result>
</action>
</package>
</struts>
login.jsp内容如下:
<formaction="<%=request.getContextPath()%>/login.action">
用户名:<inputtype="text"name="username"><br>
<inputtype="submit"value="login">
</form>
loginSuc.jsp的主要内容如下:
${requestScope.req}
${sessionScope.ses}
${applicationScope.app}
<h4>以下使用scope.getAttribute的形式来接受</h4>
request: <%=request.getAttribute("req")%><br>
session: <%=session.getAttribute("ses")%><br>
application:<%=application.getAttribute("app")%><br>
分析:通过ActionContext的getContext静态方法得到ActionContext对象,然后ActionContext对象调用get方法来获取一个存储在request范围中的对象。我们使用el或通过request.getAttribute这样的方式均可以获取对象值,这说明了这些Maprequest对象实际是存储在request范围内的对象。
方式二、与Servlet解耦合的IOC方式
我们建立Login2Action,主要代码如下:
packagecom.asm;
publicclassLogin2ActionextendsActionSupportimplementsRequestAware,SessionAware,ApplicationAware{
privateStringusername;
Maprequest;
Mapsession;
Mapapplication;
publicStringexecute()throwsException{
request.put("req","requst属性");
session.put("ses","sesion属性");
application.put("app","application属性");
returnSUCCESS;
}
publicvoidsetRequest(Map<String,Object>request){
this.request=request;
}
publicvoidsetSession(Map<String,Object>session){
this.session=session;
}
publicvoidsetApplication(Map<String,Object>application){
this.application=application;
}
...省略username的get/set方法
}
注册此Action的name为login2,随后修改登录提交为.../login2.action。便可以发布测试。说明:此方法其实和方式一很相似,只是在方式一中我们需要手动的为Maprequest赋值,但是在方式二中它是通过实现接口,在重写接口中的方法中完成对Maprequset的赋值,所以称之IOC方式。借助此例,略谈下依赖注入与控制反转:所谓依赖注入就是一个对象自己本身的初始化是依赖其它对象。比如这里Maprequest这些对象会依赖struts2来给其初始化,称为依赖注入,而依赖注入的就表示,这些对象的控制权不再由此类本身掌握,而是交给了别的对象,即是控制权反转了。强调:方式二是开发中主要用的方式,应重点掌握
方式三、与Servlet耦合的非IOC方式
建立Login3Action,代码如下:
packagecom.asm;
publicclassLogin3ActionextendsActionSupport{
privateStringusername;
HttpServletRequestrequest;
HttpSessionsession;
ServletContextapplication;
publicStringexecute()throwsException{
request=ServletActionContext.getRequest();
session=request.getSession();
application=ServletActionContext.getServletContext();
request.setAttribute("req","requst属性");
session.setAttribute("ses","sesion属性");
application.setAttribute("app","application属性");
returnSUCCESS;
}
...省略username的get/set方法。
}
此方法获取的纯粹的Scope对象,它与容器相关,这些Scope对象操作更强。同样只需要注册此Action并修改登录页面便可进行测试。
方式四、与Servlet耦合的IOC方式
建立Login4Action,代码如下:
packagecom.asm;
publicclassLogin4ActionextendsActionSupportimplementsServletRequestAware,ServletContextAware{
privateStringusername;
ActionContextcontext;
HttpServletRequestrequest;
HttpSessionsession;
ServletContextapplication;
publicStringexecute()throwsException{
context=ActionContext.getContext();
session=request.getSession();
request.setAttribute("req","requst属性");
session.setAttribute("ses","sesion属性");
application.setAttribute("app","application属性");
returnSUCCESS;
}
publicvoidsetServletRequest(HttpServletRequestrequest){
System.out.println("测试:"+request);
this.request=request;
}
publicvoidsetServletContext(ServletContextapplication){
System.out.println("测试:"+application);
this.application=application;
}
...省略username的get/set方法
}
同样只需要注册此Action并修改登录页面便可发布测试
四、OGNL与ValueStack(VS)
1.值栈入门
下面我们建立struts2ognl项目来练习ognl的使用。
步骤一、搭建strust2的开发环境
步骤二、建立LoginAction,主要代码如下:
packagecom.asm;
publicclassLoginActionextendsActionSupport{
privateUseruser;
publicStringexecute()throwsException{
returnSUCCESS;
}
...省略user的get/set方法
}
步骤三、配置此Action,struts.xml的主要内容如下:
<struts>
<constantname="struts.devMode"value="true"></constant>
<packagename="ognl"extends="struts-default">
<actionname="login"class="com.asm.LoginAction">
<result>/loginSuc.jsp</result>
</action>
</package>
</struts>
步骤四、编写login.jsp页面,主要代码如下:
<body>
<formaction="<%=request.getContextPath()%>/login.action"method="get">
用户名:<inputtype="text"name="user.username"><br>
密码:<inputtype="password"name="user.password"><br>
<inputtype="submit"value="login">
</form>
</body>
步骤五、编写loginSuc.jsp页面,主要代码如下:
<body>
调试:<s:debug></s:debug>
获取值栈中的username属性:<s:propertyvalue="user.username"/><br>
</body>
步骤六、发布测试及说明
当我们输入用户名并成功跳到logSuc.jsp页面后,会得到登录时输入的用户名信息。下面简要说明这一过程:
(1).login.jsp登录提交登录信息给login.action
(2).struts2监听到客户端的login.action请求,按配置文件要求,把此请求交给LoginAction处理。这表示会去newLoginAction(),当struts2new出此Action对象后会把这个对象放在contextmap中,只是这个Action非常特殊,所以放在值栈中,而放在值栈中的对象是可以直接引用的,放在其它contextmap中的对象引用时会要求加#。
(3).当newLoginAction时,表示它也会初始化此类中的对象,比如这里会去初始化User对象,但是要注意的是如果我们在用户名和密码什么都不输,再来用debug来看值栈中的user是,发现它仍会new此对象,因为尽管我们没用输入值,但是后台的set方法还是要被调用,所以会new出此对象,但是如果我们直接输入.../struts2ognl/login.action时我们会发现跳转到loginSuc.jsp页面时,用debug来看值栈中此Useruser,发现它的值为null。第二点要注意的是我们必须为User类提供默认的构造方法,否则将会出现如下错误:java.lang.InstantiationException:com.asm.vo.User
总结:1.Action会在请求时被创建,且会把创建的对象放到值栈中。
2.Action中的对象字段只有在需要时才会以new的形式初始化,而且这些对象字段必须提供默认的构造方法。
3.ValueStack对象贯穿整个Action的生命周期(每个Action类的对象实例会拥有一个ValueStack对象)。当Struts2接收到一个.action的请求后,会先建立Action类的对象实例,但并不会调用Action方法,而是先将Action类的相应属性放到ValueStack对象的顶层节点(vs对象相当于一个栈)。
补充:值栈(根)对象也可以直接使用EL表达式访问,比如这里可以直接通过${user.username}来获取username的值,我们知道el表达式只能访问四种scope范围内的对象,那为什么这里能访问到值栈对象呢?原因是struts2对HttpServletRequet进行了一次封装,封装的代码主要是重写了getAttribute方法,简述重写此方法的核心代码:首先在原始的HttpServletRequest对象中查找el表达式中的属性,如果找不到再通过ActionContext获取值栈对象,进而再从值栈对象中查找el表达式中要访问的属性。
2.OGNL入门
下面我们在com.asm.vo.User类中增加一个字段privateAddressaddres;,并提供此字段的get/set方法,随后再在login.jsp中增加如下代码:
城市:<inputtype="text"name="user.addres.city"><br>
然后再在loginSuc.jsp中增加如下代码:
获取城市属性:<s:propertyvalue="user.addres.city"/><br>
然后测试,会得到登录时输入的城市信息(中文会有乱码)。下面借助此例谈ognl的定义:在这个例子中,我们的LoginAction中有一个User对象,而在User对象中又有一个Address对象,这些对象之间依靠这种类的字段进行关联,或者说是依靠字段属性进行导航,这也就是OGNL的定义:ObjectGraphNavigationLanguage:对象图导航图语言,它是建立在值栈技术之上的一种全新语言。
补充:用%{}可以取出存在值堆栈中的Action对象,直接调用它的方法.我们在loginSuc.jsp中增加如下内容调用LoginAction中的get方法:
调用值栈对象中的方法:<s:propertyvalue="%{get()}"/>
LoginACtion中增加的get方法如下:
publicStringget(){
return"这是User中的get方法";
}
3.普通方法访问
首先在User中增加一个成员方法,代码如下:
publicStringget(){
return"这是User中的get方法";
}
在LoginAction中也有类似的get方法,随后再在loginSuc.jsp中增加如下代码:
调用值栈对象中的普通方法(2):<s:propertyvalue="user.username.length()"/><br>
调用值栈对象中的普通方法(1):<s:propertyvalue="user.get()"/><br>
调用LoginAction中的普通方法:<s:propertyvalue="get()"/><br>
最后测试,发现这些方法都可以访问到。
4.静态方法访问
在LoginAction中增加如下方法:
publicstaticStringgetSta(){
return"这是LoginAction中的静态方法";
}
然后在loginSuc.jsp中增加如下代码:
调用Action中的静态方法:<s:propertyvalue="@com.asm.LoginAction@getSta()"/><br>
调用LoginAction中的静态方_方式(2):<s:propertyvalue="@vs@getSta()"/><br>
说明:我们在方式二中用到@vs,只有那些值栈中的对象才可以这样写。
然后访问,发现访问不到,因为在struts2.1.6的版本中,struts.ognl.allowStaticMethodAccess的默认值为false,我们只需在struts.xml中增加如下内容:
<constantname="struts.ognl.allowStaticMethodAccess"value="true"/>
再来访问时便可以访问到。
5.默认类Math的访问
在loginSuc.jsp中增加如下代码:
调用Math类中的静态方法:<s:propertyvalue="@java.lang.Math@min(1,2)"/><br>
调用Math类中的静态方法_方式(2):<s:propertyvalue="@@min(1,2)"/><br>
调用Math类中的字段:<s:propertyvalue="@@PI"/><br>
说明:因为是默认的类,所以可以省略类名
6.调用普通类的构造方法
建立一个新的类:Student,在此省略代码。
然后在loginSuc.jsp中增加如下代码:
调用普通类中的构造方法:
<s:propertyvalue="newcom.asm.vo.Student('jack','20','85.5')"/><br>
调用普通类中的构造方法并访问其字段:
<s:propertyvalue="newcom.asm.vo.Student('jack','20','85.5').name"/>
说明:第一种是只new出对象,显示的时候其实是调用对象的toString方法。
7.集合对象初步
首先在LoginAction中增加如下字段并提供相应的get/set方法:
privateListmyList=newArrayList();
privateSetmySet=newHashSet();
privateMapmyMap=newHashMap();
然后再在execute方法中初始化这些集合对象,代码如下:
myList.add("list1");
myList.add("list2");
myList.add("list3");
myList.add("list4");
mySet.add("set1");
mySet.add("set3");
mySet.add("set1");
mySet.add("set2");
myMap.put("m1","map1");
myMap.put("m3","map3");
myMap.put("m2","map2");
最后在loginSuc.jsp中增加如下代码:
获取List:<s:propertyvalue="myList"/><br>
获取List中的第一个元素:<s:propertyvalue="myList[0]"/><br>
获取Set:<s:propertyvalue="mySet"/><br>
获取Set中的第一个元素(set无序,不能取到):<s:propertyvalue="mySet[0]"/><br>
获取Map:<s:propertyvalue="myMap"/><br>
获取Map中的key=m1的元素的值:<br>
方式一:<s:propertyvalue="myMap.m1"/>
方式二:<s:propertyvalue="myMap['m1']"/><br><hr>
获取List的大小:
<s:propertyvalue="myList.size"/>|<s:propertyvalue="myList.size()"/><br>
获取Map中所有键:<s:propertyvalue="myMap.keys"/><br>
获取Map中所有值:<s:propertyvalue="myMap.values"/><br>
最后测试,这些东西不多作解释。
8.集合对象进阶
首先在LoginAction中增加如下字段并提供相应的get/set方法:
privateListstudentList=newArrayList();
然后再在execute中为其初始化赋值,代码如下:
studentList.add(newStudent("jack",20,86.0f));
studentList.add(newStudent("lily",22,96.5f));
studentList.add(newStudent("tom",23,56.5f));
最后在loginSuc.jsp中增加如下代码:
获取List中的Student对象:<s:propertyvalue="studentList"/><br>
利用投影获取List中的name属性:<s:propertyvalue="studentList.{name}"/><br>
利用投影获取List中的age属性:<s:propertyvalue="studentList.{age}"/><br>
利用投影获取List中的第一个对象的name属性:<s:propertyvalue="studentList.[0]{name}"/>或者<s:propertyvalue="studentList.{name}[0]"/><br>
利用选择获取List中grade>60的student信息:
<s:propertyvalue="studentList.{?#this.grade>60}"/><br>
利用选择获取List中grade>60的student名字信息:
<s:propertyvalue="studentList.{?#this.grade>60}.{name}"/><br>
利用选择获取List中grade>60的第一个student名字信息:
<s:propertyvalue="studentList.{?#this.grade>60}.{name}[0]"/><br>
利用选择获取List中grade>60的第一个student名字信息(链表):
<s:propertyvalue="studentList.{^#this.grade>60}.{name}"/><br>
利用选择获取List中grade>60的最后一个student名字信息(链表):
<s:propertyvalue="studentList.{$#this.grade>60}.{name}"/><br>
说明:这里重点是说明?#的使用,结合此例来看,studentList中有许多Stutdent对象,我们可以用条件来限制取哪些对象,这些条件必须以?#开始,并且条件要用{}括起。而this是指在判断studentList中的对象是否符合条件的当前对象。?#是指取出符合条件的所有Student对象,而^#是指取出符合条件的第一个对象,$#是指取出符合条件的最后一个对象。
9.N语法top语法
我们在loginSuc.jsp中增加如下下代码:
N语法[0]:<s:propertyvalue="[0]"/><br>
N语法[1]:<s:propertyvalue="[1]"/><br>
N语法[0].top:<s:propertyvalue="[0].top"/><br>
N语法[1].top:<s:propertyvalue="[1].top"/><br>
N语法top:<s:propertyvalue="top"/><br>
N语法取值:<s:propertyvalue="[0].user.username"/><br>
N语法取值:<s:propertyvalue="top.user.username"/><br>
说明:规定栈顶的对象为[0],而我们只使用[0]的意思是从值栈中第一个对象取,一直取至栈底。N的意思是从值栈中的第N个对象开始,取到栈底为止。如果要想访问某个对象,需要使用[N].top,它的意思是取出符合N语法的栈顶对象,比如在这里,[0]会取出两个对象,而[0].top是取出这两个对象的栈顶对象。纯top可以简洁地取出值栈中的栈顶对象。
为什么要提出N语法,当我们通过chain链访问时,值栈中可能有两个以上的Action对象,如果这些对象中存在相同的属性,N便能正确区分他们。通常,这些Action对象的入栈顺序是:先访问先入栈。
从上面的N语法取值实例中,我们知道[N]top语法的一个重要作用就是能通过它们引用值栈对象中的属性。结合前面的五种[N]top语法实例,不难理解这里的取值实例。
补充:在此实例中,我们用<s:debug>调试会发现,值栈中还有一个DefaultTextProvider对象(因为此Action继承自ActionSupport),它的作用是获取资源文件中的内容(其实本质是ActionSupport重写了getText()方法),这也就是在国际化问题中我们能直接调用它的getText()方法的原因。
10.获取StackContext中的信息
我们知道,除了可以从值栈中获取信息,还可以从StackContext中获取信息,只是要加上#,下面我们通过scope对象来演示。首先是在LoginAction中增加如下字段:
MapmyRequest;
MapmySession;
随后再用前面提到的“在Action中获取Scope对象”的方式二来完成这些对象的初始化。即实现RequestAware和SessionAware接口。然后再在execute方法中增加如下内容:
myRequest.put("req","Req属性");
mySession.put("ses","Ses属性");
最后在loginSuc.jsp中增加如下代码:
获取Request属性:<s:propertyvalue="#request.req"/><br>
获取Session属性:<s:propertyvalue="#session.ses"/><br>
获取parameters属性:<s:propertyvalue="#parameters.mes"/>
说明:我们获取这些对象都用了#,因为这些对象都是存在一般的ContextMap中,而不是存在值栈中。别最后一个信息的获取是因为我们在login.jsp中增加了如下代码:
<inputtype="hidden"name="mes"value="themessageistransferbyhidden">
关于这些scope的更多信息可以参看下表:
名称 |
作用 |
例子 |
parameters |
包含当前HTTP请求参数的Map |
#parameters.id[0]作用相当于request.getParameter("id") |
request |
包含当前HttpServletRequest的属性(attribute)的Map |
#request.userName相当于request.getAttribute("userName") |
session |
包含当前HttpSession的属性(attribute)的Map |
#session.userName相当于session.getAttribute("userName") |
application |
包含当前应用的ServletContext的属性(attribute)的Map |
#application.userName相当于application.getAttribute("userName") |
Attr |
用于按request>session>application顺序访问其属性 |
#application.userName相当于application.getAttribute("userName") |
11.总结$#%的区别
$用于i18n和struts配置文件
#取得ActionContext的值
%将原来的文本串解析为ognl,对于本来就是ognl的文本不起作用。形式:%{要解析的文本串}
12.总结OGNL[重点]
OGNL是ObjectGraphicNavigationLanguage(对象图导航语言)的缩写,它是一个开源项目。Struts2使用OGNL作为默认的表达式语言。
相对于EL表达式,它提供了平时我们需要的一些功能,如:支持对象方法调用,支持各类静态方法调用和值访问,支持操作集合对象。OGNL有一个上下文的概念,这个上下文件实质就是一个Map结构,它实现了java.utils.Map接口,在struts2中上下文的实现为ActionContext,下面是上下文的结构示意图:
当struts2接受一个请求时,会迅速创建ActionContext,ValueStack,action。然后把action存放进ValueStack,所以action的实例变量可以接受OGNL访问。
访问上下文中的对象需要使用#号标注命名空间,如#application、#session。另外OGNL会设定一个根对象,在struts2中根对象就是ValueStack值栈对象,如果要访问根对象中对象的属性,则可以省略#命名空间,直接访问该对象的属性即可。在struts2中,根对象的实现类为OgnlValueStack,该对象不是我们想象的只存放单个值,而是存放一组对象,在OgnlValueStack类里有一个List类型的变量,就是使用这个List变量来存放一组对象。在root变量(List类型)中处于第一位的对象叫栈顶对象,通常我们在Ognl表达式里直接写上属性的名称即可访问root变量里对象的属性,搜索顺序是从栈顶对象开始寻找,如果栈顶对象不存在该属性,就会从第二个对象寻找,如果没有找到就从第三个对象寻找,依次往下寻找。注意:struts2中,OGNL表达式需要配合struts的标签才可以使用。
五、拦截器
在前面我们已经初步使用过拦截器,下面继续细细探讨。
1.概述strust2中的拦截器
拦截器是Struts2框架的核心,它主要完成解析请求参数、将请求参数赋值给Action属性、执行数据校验、文件上传等工作。Struts2设计的灵巧性,拦截器起了关键性的作用,当需要扩展Struts2功能时,只需要提供对应拦截器,并将它配置在Struts2容器中即可;如果不需要该功能时,也只需要取消该拦截器的配置即可。
Struts2内建了大量的拦截器,这些拦截器以name-class对的形式配置在struts-default.xml文件中,其中name是拦截器的名字,就是以后我们使用该拦截器的唯一标识;class则指定了该拦截器的实现类,如果我们定义的package继承了Struts2的默认struts-default包,则可以自由使用它下面定义的拦截器,否则必须自己定义这些拦截器。
2.自定义拦截器
自定义拦截器需要特别注意的是不要忘记引入struts2默认的拦截器。为了实现某些操作,我们可以自定义拦截器,自定义拦截器有三种方式定义。分别为实现Interceptor接口,继承抽象类AbstractInterceptor,继承MethodFilterInteceptor类。
方式一,实现Interceptor接口。
准备工作,新建struts2interceptor项目。构建一个登录环境:当我们点登录链接时,便成功登录(为了方便,这里不进行验证)。即在link.jsp页面中写如下链接:<ahref="<%=request.getContextPath()%>/login.action">登录</a>然后,我们点击此链接便可以登录。login.action在strutst.xml中的的配置如下:
<packagename="interceptor" extends="struts-default">
<actionname="login"class="com.asm.LoginAction">
<resultname="success">/success.jsp</result>
</action>
</package>
com.asm.LoginAction为了简单,com.asm.LoginAction总是返回SUCCESS;这样请求这个Action总会返回到.../success.jsp页面。
编写拦截器:MyInterceptor类,内容如下:
packagecom.asm;
importcom.opensymphony.xwork2.ActionInvocation;
importcom.opensymphony.xwork2.interceptor.Interceptor;
publicclassMyInterceptorimplementsInterceptor{
publicvoiddestroy(){
}
publicvoidinit(){
}
publicStringintercept(ActionInvocationinvocation)throwsException{
System.out.println("开始拦截");
Stringresult=invocation.invoke();
System.out.println("结束拦截");
returnresult;
}
}
为了使用此拦截器,我们必须将此拦截器进行注册,随后再在要使用此拦截器的Action中引用。即首先在<package>中注册,内容如下:
<interceptors>
<interceptorname="myIpt"class="com.asm.MyInterceptor"></interceptor>
</interceptors>
注册完成后,如果我们要在login.action中使用此拦截器,只需要在<action>中增加如下内容:
<interceptor-refname="myIpt"></interceptor-ref>
这样便成功为LoginAction配置了我们自定义的拦截器MyInterceptor,下面只需发布测试。
实例流程分析:当我们为LoginAction配置了拦截器时,并且有客户端请求此Action时,会首先被此拦截器拦住,然后执行System.out.println("开始拦截"),随后我们调用invocation.invoke()方法,它会把请求继续传递给下一个拦截器,下一个拦截器也会继续执行相应代码后再调用invoke方法继续传递,直到请求到达最后一个拦截器,它会把请求传递给Action,比如,我们这里只用到了一个拦截器,当它执行完成后,会把请求直接转交到LoginAction处理,LoginAction处理完成后,它会返回结果给MyInterceptor拦截器。
方式二、继承AbstractInterceptor抽象类
创建拦截器类MyAbstractInterceptor:主要代码如下:
packagecom.asm;
importcom.opensymphony.xwork2.ActionInvocation;
importcom.opensymphony.xwork2.interceptor.AbstractInterceptor;
publicclassMyAbstractInterceptorextendsAbstractInterceptor{
publicStringintercept(ActionInvocationinvocation)throwsException{
System.out.println("Abs开始拦截");
Stringresult=invocation.invoke();
System.out.println("Abs结束拦截");
returnresult;
}
}
然后注册此拦截器,在<interceptors>元素进行进行配置,内容如下:
<interceptorname="myAbs"class="com.asm.MyAbstractInterceptor"></interceptor>
随后再在LoginAction中引用此拦截器,即在<actionname="login"...>配置如下内容:
<interceptor-refname="myAbs"></interceptor-ref>
最后发布测试。
方式三、继承MethodFilterInteceptor类
创建拦截器类MyMethodFilterInterceptor,主要代码如下:
packagecom.asm;
importcom.opensymphony.xwork2.ActionInvocation;
importcom.opensymphony.xwork2.interceptor.MethodFilterInterceptor;
publicclassMyMethodFilterInterceptorextendsMethodFilterInterceptor{
protectedStringdoIntercept(ActionInvocationinvocation)throwsException{
System.out.println("method开始拦截");
Stringresult=invocation.invoke();
System.out.println("method结束拦截");
returnresult;
}
}
然后注册此拦截器,在<interceptors>元素进行进行配置,内容如下:
<interceptorname="myMet"class="com.asm.MyMethodFilterInterceptor">
</interceptor>
随后再在LoginAction中引用此拦截器,即在<actionname="login"...>配置如下内容:
<interceptor-refname="myMet"></interceptor-ref>
最后发布测试。
分析:当配置到此,实质便为LoginAction配置了三个拦截器,当我们点击登录时会在控制台打印出如下语句:
开始拦截
Abs开始拦截
method开始拦截
--先执行拦截器,再执行此Action
method结束拦截
Abs结束拦截
结束拦截
其实当我们点击登录时,本来是要访问LoginAction,最后会把LoginAction的执行结果传递给访问者。但是当我们配置了拦截器时,当我们去访问Action时,会首先被拦截,随后拦截器执行一些操作后才会继续把请求传递下去。下面作图说明拦截流程:
结合现实理解:比如我们要去某楼层找某人(LoginAction)取一个资源(LoginAction处理后返回的结果,这里表现为success.jsp),(1)进楼层时会被大门保安拦截检查(第一个拦截器:MyInterceptor拦截器),(2)检查通过后再进电梯时会被电梯保安员检查(第二个拦截器:MyAbstractInterceptor拦截器),(3)检查通过后再上到某楼层会被楼层保安员检查(第三个拦截器:MethodAction拦截器
),(4)检查通过后会找到某个(LoginAction),并与某个交谈(LoginAction处理),随后某个人和我们会带着请求的资源出去,(5)出去时,会依次被楼层,电梯,大门保安员检查,最终检查通过。某个人把资源给我们(实质就是返回请求资源给客户端)。其实拦截器的执行流程和过滤器差不多,所以我们不防用过滤器的眼光来看这些拦截器。
注意:我们在为LoginAction配置拦截器时,都没使用默认的拦截器,是原因这里的测试可以不用,但是以后在我们使用自定义的拦截器是,一定要加上默认的拦截器,否则会导致许多不可预知的结果。
补充:从上面的图并结合代码,我们可以看出拦截器的核心过程应该是ActionInvocation对这些拦截器回调处理,下面我们建立com.asm.interceptor.simulation来模拟这一过程,具体的代码参源文件,在此略去。在此我们作图分析ActionInvocation的实现过程:
补充2:上面分别使用了三种方式来创建自定义的拦截器,第一种方式是最原始的实现方式,第二种方式的好处是我们可以不必重写所有的方法,较常用。第三种方式进行了扩展,下面将会展示它的扩展性能。
3.使用来MethodFilterInterceptor灵活拦截
步骤一、建立MethodAction,代码如下:
packagecom.asm;
importcom.opensymphony.xwork2.ActionSupport;
publicclassMethodActionextendsActionSupport{
publicStringm1(){
returnSUCCESS;
}
publicStringm2(){
returnSUCCESS;
}
publicStringm3(){
returnSUCCESS;
}
}
步骤二、注册此Action,并为此Action配置拦截器。配置内容如下:
<actionname="*_*"class="com.asm.MethodAction"method="{2}">
<resultname="success">/{2}Suc.jsp</result>
<interceptor-refname="myMet">
</interceptor-ref>
</action>
我们为此Action配置了前面写的MyMethodFilterInterceptor拦截器,并在link.jsp中增加如下链接:
<ahref="<%=request.getContextPath()%>/Method_m1.action">m1</a><br>
<ahref="<%=request.getContextPath()%>/Method_m2.action">m2</a><br>
<ahref="<%=request.getContextPath()%>/Method_m3.action">m3</a><br>
当点m1时会访问到m1Suc.jsp页面,点m2、m3会分别访问到m2Suc.jsp、m3Suc.jsp页面。现在假如我们想访问m2、m3时不被拦截,我们只需修改MyMethodFilterInterceptor注册:修改内容为:
<interceptorname="myMet" class="com.asm.MyMethodFilterInterceptor">
<paramname="excludeMethods">m2,m3</param>
</interceptor>
它的作用和增加<paramname="includeMethods">m1</param>等价。上面是指定m2,m3方法调用时不被拦截,这里是指定只拦截m1。除了这种在注册拦截器时指定拦截外,还可以在引用拦截器时指定,即如下形式:
<interceptor-refname="myMet">
<paramname="excludeMethods">m2,m3</param>
<paramname="includeMethods">m1</param>
</interceptor-ref>
上面的两处<param>配置是等价的,但是如果〈param〉配置冲突,谁起作用?即如果我们对m1配置了excludeMethods同时又配置了includeMethods时,谁起作用,我们可以进行这些冲突的验证。以下是验证结果:
引用配置(在Action引用拦截器时配置)时,以includeMethods的配置为准。一旦我们为拦截器使用了<param>配置,而对m1这样的方法不配置任何,就不会被拦截。但是如果不使用<param>,它们全部都要被拦截。
注册配置时(在注册拦截器时配置),情况和“引用配置”完全一样。
引用配置和注册配置冲突时,以引用配置为准。
4.使用默认的execAndWait拦截器
当我们进行数据库查询等相关的操作时,如果服务器负荷过重可能不能及时把数据查询出来,进而会在状态拦显示“正在打开...”,但却一直转不到相关的页面,这将给客户端带来不便,甚于很多人会因此不愿使用网站的所有服务。对此我们可以在客户提交时,马上转到一个页面,并在该页面显示“您的请求已提交,服务器正在查询,请等待...”的内容,这样客户将不会陷于无赖的等待中。对于此要求,struts2可以轻松帮我们完成。下面新建struts2wait项目演示此实例。
建立LoginAction,代码如下:
packagecom.asm;
publicclassLoginActionextendsActionSupport{
publicStringexecute()throwsException{
Thread.sleep(5000);
returnSUCCESS;
}
}
说明:为了模拟服务器负荷过重,查询时间要很长。我们在使用了线程休眠的方式。
随后配置此Action,配置的主要内容如下:
<actionname="login"class="com.asm.LoginAction">
<interceptor-refname="defaultStack"></interceptor-ref>
<interceptor-refname="execAndWait"></interceptor-ref>
<resultname="wait">/wait.jsp</result>
<resultname="success">/success.jsp</result>
</action>
注意:在配置前我们先是使用了默认的拦截器,再此强调在我们为Action配置拦截器时,应该总是配上默认的拦截器。随后我们使用了execAndWait拦截器,如需要配置此拦截器,此拦截器一定要配置在最后,否则会出现一些难预知的结果。如果使用此拦截器,我们通常还会配置wait的result结果集,因为“Ontheinitialrequestoranysubsequentrequests(beforetheactionhascompleted),thewaitresultwillbereturned.Thewaitresultisresponsibleforissuingasubsequentrequestbacktotheaction,givingtheeffectofaself-updatingprogressmeter”,大概意思就是当我们请求的Action在未执行完,就是未返回结果时,会首先把waitresult返回,而在waitresult所指定的页面中通常会再次发送请求给原始的Action。所以wait.jsp的主要内容如下:
<head>
<metahttp-equiv="refresh"content="1;login.action">
</head>
<body> 查询请求已提交,正在查询数据,请等待... </body>
在此页面中,我们指定了每隔1秒便发送请求到login.action中去。这样,客户端便可以及时获取查询结果。结合此实例,我们简要分析流程:当我们发出请求到此Login.Action中去时,首先会被exeAndWait拦截器拦截到,这样它便跳转到wait.jsp页面,在wait.jsp页面中每隔1秒我们会继续发送此Action的请求,当再次请求到达LoginAction时,如果它已经返回,则会跳到此Action返回的页面,如果LoginAction未返回,则继续停留在wait.jsp中,再隔1秒又再次发送请求到LoginAction中去。
其实如果服务器能很快查询出结果,我们则不需要用到wait.jsp页面,我们只需在<interceptor-refname="execAndWait"></interceptor-ref>中增加如下一段配置:
<paramname="delay">6000</param>这样便延迟请求到达wait.jsp页面,这样当请求到达时它会在LoginAction中执行6秒时间再到wait.jsp,而6秒LoginAction足以执行完并返回结果,所以当拦截器
执行时首先检查到此Action已经返回结果。则拦截器会直接用此返回页面,如果此时发现LoginAction并未执行完,它便会把waitresutl指定的页面返回。需要说明的是,通常我们设定的延迟最多一秒,这里为了演示,设置的很长。图示此拦截器原理:
关于此拦截器的详细的配置及文档说明可以参看ExecuteAndWaitInterceptor类的api信息。
5.TokenInterceptor防止表单重复提交。
由于某些原因,用户在进行类似表单提交的操作后,以为表单未被提交,会进行多次的重复提交。为了避免用户多次提交给服务器带来负荷。我们会对表单提交这样的操作进行一些处理,以告诉用户不要重复提交。下面我们建立struts2token项目,使用struts2的token拦截器来实现此案例。
步骤一,编写login.jsp页面,内容如下:
<%@pagelanguage="java"pageEncoding="UTF-8"%>
<%@tagliburi="/struts-tags"prefix="s"%>
<html>
<body>
<formaction="<%=request.getContextPath()%>/login.action">
姓名:<inputtype="text"name="username"><br>
密码:<inputtype="password"name="password"><br>
<inputtype="submit"value="登录">
<s:token></s:token>
</form>
</body>
</html>
说明,此登录页面中的关键技术就是使用了标签库中的<s:token></s:token>标签,它的作用就是在用户访问此页面时会生成一个sessionId,在提交时会服务器会据此验证表单是否已提交。“Tosetatokeninyourform,youshouldusethetokentag.Thistagisrequiredandmustbeusedintheformsthatsubmittoactionsprotectedbythisinterceptor”,这句话的大概意思就是我们必须要在提交的表单中使用这个tokentag,这样提交到的Action便能配置TokenInterceptor拦截器验证表单是否重复提交。
步骤二,编写LoginAction,主要代码如下:
packagecom.asm;
publicclassLoginActionextendsActionSupport{
publicStringexecute()throwsException{
System.out.println("---->执行execute方法...");
returnSUCCESS;
}
}
步骤三,struts.xml主要配置内容如下:
<struts>
<packagename="tokenTest"extends="struts-default">
<actionname="login"class="com.asm.LoginAction">
<resultname="success">/success.jsp</result>
<resultname="invalid.token">/subError.jsp</result>
<interceptor-refname="token"></interceptor-ref>
<interceptor-refname="defaultStack"></interceptor-ref>
</action>
</package>
</struts>
说明:在此Action下,我们配置了token拦截器,另注意到在此Action下我们还配置了一个“invalid.token”result,因为“Thisinterceptorusesafairlyprimitivetechniqueforwhenaninvalidtokenisfound:itreturnstheresultinvalid.token,whichcanbemappedinyouractionconfiguration”。它的大概意思就是:提交时服务器如果根据token标签产生的sessionId判断出表单已提交,它则返回invalid.token指向的视图。比如这里,如果重复提交则会转到.../subError.jsp中去。另不要忘记了引入默认的拦截器栈。补充:关于token拦截器更多细节可以访问org.apache.struts2.interceptor.TokenInterceptor类的api说明。
步骤四,编写配置中所用到jsp页面,这些页面编写简单,在此省去。
步骤五、发布测试,请注意访问login.jsp页面时,查看源文件时会发现增加了两个隐藏域信息。
步骤六、更换拦截器:我们还可以使用tokenSession拦截器,它的功能比上面的增强,它能保证持有相同sessionId的并发请求等待第一个完成之后才能被提交处理,但是它返回的是action执行后的result.接着上例,我们只需要在配置中作如下修改:把上面的token拦截器改成<interceptor-refname="tokenSession"></interceptor-ref>即可。随后便可以测试,测试时会发现如果我们重复提交,它总是返回到上一次的success.jsp页面,但是它并不是经过LoginAction中的execute处理后返回(我们System.out.print语句在重复提交时并未打印出来),而是此拦截器判断出是重复后直接返回上一次提交转向的页面。
6.使用拦截器实现权限验证
为了说明此问题,我们建立struts2auth项目,流程图如下:
简短说明:当我们访问main.jsp页面,并试图通过此页面中的链接地址:note.action来访问到.../WEB-INF/note.jsp页面时,由于访问的note.action配置了拦截器,所以会被拦截,如果拦截器判断登录则可以访问,否则会跳到登录页面。如果我们从登录页面直接到main.jsp页面,再来访问note.action时,同样被拦截但是由于登录过,所以可以访问到此action对应的内容。由这里的分析可以看出关键点就登录成功时给出标志提供给拦截器判断是否成功登录。
步骤一,搭建好相关的开发环境,并准备好登录页面login.jsp,代码如下:
<formaction="<%=request.getContextPath()%>/login.action"method="post">
姓名:<inputtype="text"name="username"><br>
密码:<inputtype="password"name="password"><br>
<inputtype="submit"value="登录">
</form>
步骤二,建立相应的Action:LoginAction。代码如下:
packagecom.asm;
publicclassLoginActionextendsActionSupport{
privateStringusername;
Mapsession;
publicStringexecute()throwsException{
if(username.equals("admin")){
session=ActionContext.getContext().getSession();
session.put("loginSign","loginSuccess");
returnSUCCESS;
}else{
returnLOGIN;
}
}
...省略username的get/set方法
}
说明:我们这里是设定了只有登录用户名为admin时,此Action才设置登录标志。另这里获取Session对象采取的是“与Servlet解耦合的非IOC方式”。
步骤三,编写拦截器类,代码如下:
packagecom.asm.interceptor;
publicclassAuthInterceptorextendsAbstractInterceptor{
publicStringintercept(ActionInvocationinvocation)throwsException{
Mapsession=invocation.getInvocationContext().getSession();
//session=ActionContext.getContext().getSession();
if(session.get("loginSign")==null){
return"login";
}else{
Stringresult=invocation.invoke();
returnresult;
}
}
}
步骤四,配置此Action相关,主要配置内容如下:
<struts>
<packagename="tokenTest"extends="struts-default">
<interceptors>
<interceptorname="auth"
class="com.asm.interceptor.AuthInterceptor">
</interceptor>
<interceptor-stackname="authStack">
<interceptor-refname="auth"></interceptor-ref>
<interceptor-refname="defaultStack"></interceptor-ref>
</interceptor-stack>
</interceptors>
<actionname="login"class="com.asm.LoginAction">
<resultname="success">/main.jsp</result>
<resultname="login">/login.jsp</result>
</action>
<actionname="note">
<result>/WEB-INF/note.jsp</result>
<resultname="login">/login.jsp</result>
<interceptor-refname="authStack"></interceptor-ref>
</action>
</package>
</struts>
说明:结合前面的一些代码来看,当我们为note.action配置了前面写所的AuthInterceptor拦截器时,如果我们要访问note.action,拦截器会首先判断是否登录,如果登录则继续把请求传递下去,如果没有登录则会返回到登录页面。
步骤五、编写相关的其它jsp页面,然后发布测试。此实例应重点是进一步掌握拦截器的配置使用。作为“实现资源权限访问”,此实例不具参考价值。
7.拦截器中的注解
AnnotationWorkflowInterceptor:Invokesanyannotatedmethodsontheaction。意思是此拦截器可以调用在Action中任何有注解的方法。下面我们来演示它的使用,具体步骤如下:
步骤一,建立struts2annotationInt项目,并建立LoginAction类,代码如下:
packagecom.asm;
...省略导入的包
publicclassLoginActionextendsActionSupport{
privateStringusername;
@Before
publicStringmyBefore(){
System.out.println("调用myBefore方法");
returnLOGIN;
}
@After
publicvoidmyAfter()throwsInterruptedException{
Thread.sleep(5000);
System.out.println("----调用myAfter方法");
}
@BeforeResult
publicvoidmyBeforeResult(){
System.out.println("----调用myBeforeResult方法");
}
publicStringexecute()throwsException{
System.out.println("调用execute方法");
returnSUCCESS;
}
publicStringgetUsername(){
returnusername;
}
publicvoidsetUsername(Stringusername){
System.out.println("---调用set方法"+username);
this.username=username;
}
}
说明:要想使用方法成为被拦截器监视的注解方法,只需在方法关加上@...这样的形式并导入相关的类即可。
步骤二,编写相关的jsp及配置该Action,主要配置内容如下:
<struts>
<packagename="ano"extends="struts-default">
<interceptors>
<interceptorname="anno" class="com.opensymphony.xwork2.interceptor.annotations.AnnotationWorkflowInterceptor">
</interceptor>
<interceptor-stackname="annoStack">
<interceptor-refname="anno"></interceptor-ref>
<interceptor-refname="defaultStack"></interceptor-ref>
</interceptor-stack>
</interceptors>
<actionname="login"class="com.asm.LoginAction">
<resultname="success">/success.jsp</result>
<resultname="login">/login.jsp</result>
<interceptor-refname="annoStack"></interceptor-ref>
</action>
</package>
</struts>
结合配置说明:当我们为LoginAction配置了AnnotationWorkflowInterceptor拦截器时,LoginAction中的所有注解方法才真正生效。下面重点是来讨论这些方法的执行顺序及作用。
加@Before注解的方法:willbeinvokedbeforetheactionmethod.Ifthereturnedvalueisnotnull,itisreturnedastheactionresultcode。意思是在action的execute方法执行之前被调用,但是此方法如果返回不为空的话,它的返回结果将是真正的返回结果,比如这里我们returnLOGIN,这样无论以什么用户名登录,它总会返回到loginresult(这里为login.jsp页面)。但是从执前结果来看,在返回前仍执行了标记为@BeforeResult的方法:willbeinvokedaftertheactionmethodbutbeforetheresultexecution。意思是在返回结果集前调用此方法。下面我们把publicStringmyBefore()方法中的returnLOGIN注释掉,并让修改此方法的返回类型为void。随后登录测试(注意要重新部署当前项目),可以发现执行结果如下:
调用myBefore方法
---调用set方法
调用execute方法
----调用myBeforeResult方法
----调用myAfter方法
从执行的顺序来看,标记为@After的方法最后执行,并且可以发现:它会延时5秒执行,但是在延时执行时,浏览器并没有成功跳到success.jsp页面,而是在5秒后,控制台打印出myArter方法中的内容同步跳转到success.jsp页面。@After:willbeinvokedaftertheactionmethodandresultexecution。意为在execute方法执行并且返回结果后此方法被调用。但是从测试来看,标记为@After的方法是会影响到结果的返回(延时返回)。强调:注意方法的执行顺序,相关的内容可以参看AnnotationWorkflowInterceptor类的api文档。
8.使用PreResultListener实现回调
在进行本实例前请前复习:五.2自定义拦截器。因为PreResultListener对象一般是绑定在拦截器上使用。
下面我们新建struts2PreResultListener项目进行测试。
步骤一,建立类,实现PreResultListener接口,主要代码如下:
packagecom.asm;
importcom.opensymphony.xwork2.ActionInvocation;
importcom.opensymphony.xwork2.interceptor.PreResultListener;
publicclassMyPreResultListenerimplementsPreResultListener{
publicvoidbeforeResult(ActionInvocationinvocation,Stringres){
//System.out.println(invocation.getAction());
//System.out.println(invocation.getResultCode());
/**回调Action中的方法:
*LoginActionlg=(LoginAction)invocation.getAction();try{
*lg.execute();}catch(Exceptione){e.printStackTrace();}
*/
System.out.println("检验到PreResultListener被执行");
}
}
步骤二,copy前面在自定义拦截器中用到的三个拦截器,并绑定MyPreResultListener对象,首先是在MyInterceptor类中,我们只需要修改intercept方法即可,代码如下:
publicStringintercept(ActionInvocationinvocation)throwsException{
invocation.addPreResultListener(newMyPreResultListener());
System.out.println("开始拦截");
Stringresult=invocation.invoke();
System.out.println("结束拦截");
returnresult;
}
随后在MyMethodFilterInterceptor类中作类似修改。为了区别,我们在MyAbstractInterceptor类中不绑定MyPreResultListener对象。
步骤三,编写struts.xml文件,主要配置内容如下:
<struts>
<packagename="interceptor"extends="struts-default">
<interceptors>
<interceptorname="myIpt"class="com.asm.MyInterceptor">
</interceptor>
<interceptorname="myAbs"
class="com.asm.MyAbstractInterceptor">
</interceptor>
<interceptorname="myMet"
class="com.asm.MyMethodFilterInterceptor">
</interceptor>
</interceptors>
<actionname="login"class="com.asm.LoginAction">
<interceptor-refname="myIpt"></interceptor-ref>
<interceptor-refname="myAbs"></interceptor-ref>
<interceptor-refname="myMet"></interceptor-ref>
<resultname="success">/success.jsp</result>
</action>
</package>
</struts>
步骤四,编写相应的jsp页面,发布测试。
说明:此实例的只是简要地演示了PreResultListener的使用,所以相对简单。对于其它相关操作,我们可以从MyPreResultListener类注释掉的内容中找到一此端倪。强调:从执行结果来看,PreResultListener对象会在返回结果前执行,请注意结合拦截器执行的顺序来看。此实例目前作为了解。
六、使用标签
1.基础表单标签
准备工作:建立struts2tag项目,搭建好struts2的开发环境。在html我们常用的基础表单标签主要有文本域、密码域、提交、重置四种。它们在strust2中可以通过标签来生成。下面建立login.jsp页面,与这四种标签相关的内容如下:
<%@pagelanguage="java"pageEncoding="utf-8"%>
<%@tagliburi="/struts-tags"prefix="s"%>
<html>
<body>
<s:formaction="login"method="post"namespace="/my">
<s:textfieldlabel="用户名"name="user.username"required="true"requiredposition="right"/>
<s:passwordlabel="密码"name="user.password"required="true"/>
<s:resetvalue="重置"align="left"/>
<s:submitvalue="注册"align="left"/>
</s:form>
</body>
</html>
说明:label中的内容是显示在表单前的提示内容,required设为true,表示此表单项为必填内容。
2.单选按钮和复选框:
<s:radiolist="#{1:'男',0:'女'}"value="1"label="性别"name="user.sex"/>
<s:checkboxlistlist="#{1:'足球',2:'排球',3:'蓝球',4:'网球'}"name="user.love"label="爱好"/>
3.三种方式实现下拉列表
<s:beanid="p"name="com.asm.NativePlaceFormAction"></s:bean>
<s:beanname="com.asm.NativePlaceMapFormAction"id="pMap"></s:bean>
<s:beanname="com.asm.NativePlaceProFormAction"id="pp"></s:bean>
<s:selectlist="#p.place"label="籍贯"name="user.place"/>
<s:selectlist="#pMap.place"label="籍贯2"name="user.place"/>
<s:selectlist="#pp.place"listKey="pId"listValue="pName"label="籍贯3"name="user.place"headerKey="-1"headerValue="---省---"emptyOption="true"/>
说明:三种方式实现下拉列表分别对应了三个java类,这三个类的内容为:
NativePlaceFormAction主要代码为:
packagecom.asm;
publicclassNativePlaceFormActionextendsActionSupport{
privateList<String>place;
publicNativePlaceFormAction(){
place=newArrayList<String>();
place.add("山东省");
place.add("山西省");
place.add("河南省");
place.add("河北省");
place.add("四川省");
place.add("云南省");
}
...省略place的get/set方法
}
NativePlaceMapFormAction主要代码为:
packagecom.asm;
publicclassNativePlaceMapFormActionextendsActionSupport{
privateMap<Integer,String>place;
publicNativePlaceMapFormAction(){
place=newHashMap<Integer,String>();
place.put(1,"山东省");
place.put(2,"山西省");
place.put(3,"河南省");
place.put(4,"河北省");
place.put(5,"四川省");
place.put(6,"云南省");
}
...省略place的get/set方法
}
NativePlaceProFormAction主要代码为:
packagecom.asm;
publicclassNativePlaceProFormActionextendsActionSupport{
privateList<Object>place;
publicNativePlaceProFormAction(){
place=newArrayList<Object>();
newProvince(1,"山东省","济南");
place.add(newProvince(1,"山东省","济南"));
place.add(newProvince(2,"山西省","太原"));
place.add(newProvince(3,"河南省","郑洲"));
place.add(newProvince(4,"河北","石家庄"));
place.add(newProvince(5,"四川","成都"));
place.add(newProvince(6,"云南","昆明"));
}
...省略place的get/set方法
}
说明:此三种实现效果一样,但是在它们提交时传递给服务器的参数不同,具体可以参看login.jsp页面的源码。另外,这三种实现其实都依赖了<s:bean>设定的对象,如果我们不希望依赖<s:bean>来设定,可以通过配置action来实现:下面我们以NativePlaceFormAction说明:首先在struts.xml中配置此action,配置内容如下:
<actionname="npf"class="com.asm.NativePlaceFormAction">
<result>/login2.jsp</result>
</action>
随后,我们在login.jsp中增加如下内容:
<ahref="<%=request.getContextPath()%>/my/npf.action">另一个注册页面</a>
其中login2.jsp中的关键内容为:
<s:selectlist="place"label="籍贯"name="user.place"/>
我们可以发现:在login2.jsp中填写list的值时并没有用ognl表达式,因为我们通过npf.action来访问时,此Action已经被写入到了值栈中,所以我们可以直接引用。后面所用到的实例,我们都会把这样的类做成Action,这样如果我们想通过这种方式访问便只需要在struts.xml中配置即可
4.二级联动
<s:beanname="com.asm.TwoSelectAction"id="ts"></s:bean>
<s:doubleselect
list="#ts.place"
listKey="pId"listValue="pName"
name="user.place"
doubleList="#ts.citys[top]"
doubleListKey="cId" doubleListValue="cName"
doubleName="user.city"
label="籍贯4(二级联动)">
</s:doubleselect>
它所依赖的TwoSelectAction类的主要代码如下:
packagecom.asm;
publicclassTwoSelectActionextendsActionSupport{
privateList<Province>place;
privateMap<Province,List<City>>citys;
...省略place、citys中get/set方法
publicTwoSelectAction(){
place=newArrayList<Province>();
citys=newHashMap<Province,List<City>>();
Provincep1=newProvince(1,"山东省","济南");
Provincep2=newProvince(2,"山西省","太原");
Provincep3=newProvince(3,"河南省","郑洲");
Provincep4=newProvince(4,"河北","石家庄");
Provincep5=newProvince(5,"四川","成都");
Provincep6=newProvince(6,"云南","昆明");
place.add(p1);
place.add(p2);
place.add(p3);
place.add(p4);
place.add(p5);
place.add(p6);
//山东省的市:
Cityc1=newCity(1,"济南");
Cityc2=newCity(2,"招远市");
Cityc3=newCity(2,"寿光市");
Listp1City=newArrayList();
p1City.add(c1);
p1City.add(c2);
p1City.add(c3);
//山西省的市:
Cityc4=newCity(4,"太原市");
Cityc5=newCity(5,"大同市");
Cityc6=newCity(6,"晋中市");
Listp2City=newArrayList();
p2City.add(c4);
p2City.add(c5);
p2City.add(c6);
//河南省的市:
Cityc7=newCity(7,"郑州市");
Cityc8=newCity(8,"卫辉市");
Cityc9=newCity(8,"信阳市");
Listp3City=newArrayList();
p3City.add(c7);
p3City.add(c8);
p3City.add(c9);
//河北省的市:
Cityc10=newCity(10,"石家庄");
Cityc11=newCity(11,"晋州市");
Cityc12=newCity(12,"鹿泉市");
Listp4City=newArrayList();
p4City.add(c10);
p4City.add(c11);
p4City.add(c12);
//四川省的市:
Cityc13=newCity(13,"成都");
Cityc14=newCity(14,"南充");
Cityc15=newCity(15,"绵阳");
Listp5City=newArrayList();
p5City.add(c13);
p5City.add(c14);
p5City.add(c15);
//云南省的市:
Cityc16=newCity(16,"昆明市");
Cityc17=newCity(17,"安宁市");
Cityc18=newCity(18,"曲靖市");
Listp6City=newArrayList();
p6City.add(c16);
p6City.add(c17);
p6City.add(c18);
citys.put(p1,p1City);
citys.put(p2,p2City);
citys.put(p3,p3City);
citys.put(p4,p4City);
citys.put(p5,p5City);
citys.put(p6,p6City);
}
}
简要分析:此实例有些繁琐,主要思想:我们的place对象主要为一级列表服务,只要理解了前面的下拉列表,这里不难理解一级列表。而二级列表中我们使用#ts.citys[top]取出的一个List对象,这样也正是下拉列表所要求的对象类型(List,Map),而top是非常关键的,它明确指出我们取出的是栈顶的对象,这样就能根据一级列表的值来动态生成这个List对象。
5.其它表单标签
<s:selectname="singer"list="{}"label="歌星"headerKey="0"headerValue="--歌手名单--"emptyOption="true">
<s:optgrouplist="#{1:'任贤齐',2:'刘德华',3:'周杰伦'}"label="男歌手"/>
<s:optgrouplist="#{1:'萧亚轩',2:'蔡依林',3:'she'}"label="女歌手"/>
</s:select>
<s:comboboxlabel="来源调查"list="{'朋友介绍','电视广告','网络广告'}"name="from"/>
<s:updownselect
list="{'java','C#','VC','php','vb','vc','python'}"
moveDownLabel="下移一位"
moveUpLabel="上移一位"
selectAllLabel="全部选中"
label="您常用编程语言排名"
/>
<s:optiontransferselect
leftTitle="选择喜欢做的事:"
list="{'听歌','看电影','编程','玩游戏','chat'}"
name="love"
headerKey="0"
headerValue="喜欢做的事"
emptyOption="true"
rightTitle="选择讨厌做的事:"
doubleList="{'跳舞','唱歌','打篮球','旅游','shopping'}"
doubleName="hate"
doubleHeaderKey="0"
doubleHeaderValue="不喜欢的事"
doubleEmptyOption="true"
label="个人兴趣说明"
leftUpLabel="上移"
leftDownLabel="下移"
rightUpLabel="上移"
rightDownLabel="下移"
addToLeftLabel="<—添加"
addToRightLabel="添加—>"
addAllToLeftLabel="<—添加(All)"
addAllToRightLabel="添加(All)—>"
selectAllLabel="全选"
/>
<s:checkboxlabel="接受服务条款"value="false"name="user.accept"/>
有了前面的标签学习,这些标签很容易理解,只需结合显示效果和查看源码来加深它们的理解。但是特别要注意的是<s:checkbox>标签与</s:checkboxlist>的区别。
补充:使用struts2生成的表单标签会在标签内嵌套一些特殊的格式,在使用了struts2生成的标签所在网页内查看源代码可以发现多了一些如<tr><td>这样的格式化代码。如果不想struts2增加这些多余的格式化代码,可以在struts.xml中配置如下内容:
<!--struts2生成的表单标签使用默认的主题,即不附加格式化标签-->
<constantname="struts.ui.theme"value="simple"/>
6.其它常用标签的使用(代码参名为“补充”的文件夹下的tag.jsp)
(1)<s:set>标签
此标签主要用于设置一些属性值。
Scope:指定变量被设置的范围,该属性可以接受application、session、request、page或Action。如果没有设置该属性,则默认放置在OGNLContext中,我们可以通过#号来引用。
Value:赋给变量的值,如果没有设置该属性,则将ValueStack栈顶的值赋给变量。
Id/name/var:属性的引用名称,id/name均过时,建议用var来取代他们。
(2)<s:property>
Default:可选属性,如果需要输出的属性值为null,则显示属性指定的值
Escape:可选属性,指定是否格式化html代码。
Value:可选属性,指定需要输出的属性值,如果没有指定该属性,则默认输出ValueStack栈顶的值
Id:可选属性,指定该元素的标识
(3)<s:Iterator>
Value:可选属性,指定迭代的集合,如果没有指定该属性,则使用ValueStack栈顶的集合
Id:可选属性,指定集合里元素的id(已被标记为过时)
Status:可选属性,该属性指定迭代时当前迭代对象的一个实例,并把此实例放在ognl的上下文中,我们可以通过#号来引用这个实例。该实例包含如下几下方法:
IntgetCount:返回当前迭代了几个元素。
IntgetIndex:返回当前被迭代的元素的索引
BooleanisEven:返回当前被迭代的元素的索引是否是偶数
BooleanisOdd:返回当前被迭代的元素的索引是否是奇数
BooleanisFirst:返回当前被迭代的元素是否是第一个元素
BooleanisLast:返回当前被迭代的元素是否是最后一个元素
说明:因为iterator会把每次迭代的实例放在值栈的栈顶,而<s:property>默认访问的是值栈的栈顶元素。所以如下代码可行:
<s:setvar="list"value="{'第一个','第二个','第三个'}"/>
<!--iterator迭代的特点:会把迭代的对象放到值栈的栈顶-->
<s:iteratorvalue="#list">
<s:property/>
</s:iterator>
如果想用status来实现一些功能,可参下面的代码:
<br/>-------------------奇数红色,偶数蓝色---------------<br/>
<s:iteratorvalue="#list"status="i">
<fontcolor='<s:iftest="#i.even">blue</s:if><s:else>red</s:else>'>
<s:property/>
</font><br/>
</s:iterator>
(4)url标签
<br/><br/>-----------使用url---------------<br/>
<s:setvar="age"value="25"scope="request"/>
<s:urlaction="asm"namespace="/">
<s:paramname="age"value="#request.age"></s:param>
</s:url>
说明:它会根据action及namespace并附加上下文路径构建一个链接。
<br/><!--value的值中一定要用单引号引起,这样才表示它的值是一个字串-->
<s:setvar="bdUrl"value="'http://www.baidu.com'"/>
<s:urlvalue="#bdUrl"/><br/>
<s:urlvalue="%{#bdUrl}"/>
说明:由于url标签的value属性默认不支持ognl,所以我们要使用%{}来表示{}中的#bdUrl是一个ognl表达式。
七、国际化
尽管国际化不是重点内容,但是也有必要了解它的使用。在struts2中国际化有三种级别:分别是针对某个Action的action级别,针对package的package级别,针对webapp的webapp级别。下面我们建立struts2i18n项目来演示国际化在struts2中的使用。
1.action级别下的国际化
步骤一、首先是建立login.jsp及LoginAction,由于它们经常使用,在此省去它们的代码。
步骤二、建立资源文件,由于LoginAction在com.asm包中,所以我们应在com.asm包下我们建立两个资源文件:一个是中文LoginAction_zh_CN.properties、一个是英文LoginAction_zh_CN.properties。注意它们的名字相对固定,前面与Action的名字相同,后面是语言和国家代码。
英文资源文件内容如下:
login_page=loginpage
login_username=userName
login_password=password
login_sex=sex
login_male=male
login_female=female
login_submit=login
login_reset=reset
login_suc=Welcome{0}
中文资源文件,需要特别注意:我们应使用Myeclipse自带的MyEclipsepropertiesEditer编辑器来打开此资源文件,并在properties视图下进行编辑,这样它会把中文进行编码(我们切换到source视图下可以看到经编码后的中文)。这一步非常重要,否则会出现乱码。
步骤三,修改login.jsp中的内容:
<%@pagelanguage="java"pageEncoding="utf-8"%>
<%@tagliburi="/struts-tags"prefix="s"%>
<html>
<body>
<s:textname="login_page"/><br>
<s:labelkey="login_username"/>
<s:formaction="/login.action"method="post">
<!--
<s:textfieldlabel="用户名"name="username"required="true"/>
-->
<s:textfieldlabel="%{getText('login_username')}"name="username"/>
<!--
<s:passwordlabel="密码"name="password"required="true"/>
-->
<s:passwordkey="login_password"name="password"/>
<!--
<s:radiolist="#{1:'男',2:'女'}"value="1"label="性别"name="sex"/>
-->
<s:radiolist="#{1:getText('login_male'),2:getText('login_female')}"value="1"label="%{getText('login_sex')}"name="sex"/>
<s:submitkey="login_submit"/><s:resetkey="login_reset"/>
</s:form>
</body>
</html>
说明:对资源文件的引用,我们采取了两种方式:有的是通过在label中使用%{getText('资源文件中的key')}这样的形式,有的是通过key=资源文件中的key这种形式。需要注意在radio标签中list对资源文件的引用。另外需要注意:
<s:textname="login_page"/><br>
<s:labelkey="login_username"/>
它们的区别:前面是纯文本,后者是一个块。我们可以通过查看login.jsp的源码来证明。
步骤四、当我们直接访问login.jsp时会报错,因为在login.jsp中用到了资源文件,而资源文件又依赖于LoginAction,所以我们只能通过此Action来跳到login.jsp。但是使用包范围、全局范围的资源文件时,可以直接访问login.jsp文件实现国际化。操作步骤如下:
首先在LoginAction中增加一个方法:
publicStringdoGoLogin(){
returnLOGIN;
}
随后再在struts.xml中配置如下内容:
<packagename="i18n"extends="struts-default"namespace="/">
<actionname="login"class="com.asm.LoginAction">
<resultname="success">success.jsp</result>
<resultname="login">login.jsp</result>
</action>
</package>
接着再编写一个link.jsp页面,内容如下:
<ahref="<%=request.getContextPath()%>/login!goLogin.action">登录</a>
直接访问Action中的方法格式:doX(大写)xxx----ActionName!x(小写)xxx.action注意此方法和前面二.7中相关方法的区别。我们通过此Action跳转到login.jsp这样便能成功访问到login.jsp页面。
步骤五、在success.jsp中使用资源文件,主要内容如下:
<s:textname="login_suc">
<s:paramvalue="%{username}"></s:param>
</s:text>
说明:在前面的资源文件中,我们配置了login_suc=Welcome{0},其中{0}表示占位参数,这里我们使用<s:param>来给此参数赋值。
步骤六、测试:在ie的internet选项中改变语言实现国际化的访问。
2.配置package的资源文件
同样在建立com.asm包下建立两个资源文件(package级别的资源文件名必须以package开头):取名为:package_zh_CN.properties,它的内容为:pack=pack属性值和package_en_US.properties,它的内容为:pack=packageAttributeValue
然后再在login.jsp页面中增加如下内容:
<h4>测试包资源文件</h4>
<s:textname="pack"></s:text>
这样便完成了package级别的资源文件配置,最后发布测试。
3.app级别的资源文件
在src目录下建立两个资源文件,取名为myapp_en_US.properties,它的内容为:
app=appAttributeValue和myapp_zh_CN.properties,它的内容为:
然后还需要在strust.xml中增加如下配置:
<constantname="struts.custom.i18n.resources"value="myapp"></constant>
注意:name是固定值,而value来自于这个资源文件的基名。
最后在login.jsp中增加如下内容:
<h4>测试app级别资源文件</h4>
<s:textname="app"></s:text>
这样便完成了app级别的资源文件配置,随后发布测试。
说明:action级的资源文件优先级别最高,app最低。Pack级别的资源文件可作用于同一个包,app级别的资源文件可作用于当前项目。
补充:在jsp页面中直接访问某个资源文件,struts2为我们提供了i18n标签,使用此标签我们可以在类路径下直接从某个资源文件中获取国际化数据,而无需任何配置:
<s:i18nname="XXX">--xxx为类路径下资源文件的基名
<s:textname="">
<s:param></s:param>
</s:text>
</s:i18n>
而如果要访问的资源文件在类路径的某个包下(如action或package级别的资源文件),可以这样访问:
<s:i18nname="com/asm/资源文件基名">--com.asm为包名
4.使用资源文件的原理
我们建立ReadResourceFileTest类,代码如下:
packagecom.asm;
importjava.util.Locale;
importjava.util.ResourceBundle;
publicclassReadResourceFileTest{
publicstaticvoidmain(String[]args){
ResourceBundlerb=ResourceBundle.getBundle("com.asm.LoginAction",Locale.US);
System.out.println(rb.getString("login_suc"));
}
}
补充:在Action类(必须继承自ActionSupport)中获取资源文件的值的方法,可以使用如下代码:
Stringvalue=this.getText("资源文件的键名");
//获取资源文件的对应的值。如果想给资源文件中的占位符赋值,可以使用getText的重载方法。
ActionContext.getContext().put("XXX",value);//存放在request范围,供jsp获取此值
5.选择使用资源文件
其实在我们成功访问到login.jsp页面后,只要在地址栏中增加参数request_locale=en_US便可以正确切换到登录页面为英文。当然我们可以再链接根据此参数写这个资源文件的链接。当然我们也可借助一个新Action来实现,操作步骤如下:在login.jsp中增加如下代码:
<ahref="change.action?request_locale=zh_CN">
<s:textname="chinese"></s:text>
</a>
<ahref="change.action?request_locale=en_US">
<s:textname="english"></s:text>
</a>
change.action对应的配置为:
<actionname="change"class="com.asm.ChangeLangAction">
<result>/login.jsp</result>
</action>
ChangeLangAction的主要代码如下:
packagecom.asm;
publicclassChangeLangActionextendsActionSupport{
publicStringexecute()throwsException{
returnSUCCESS;
}
}
以上是第一种方法,特别要注意,由于使用了不同Action,所以要资源文件这时只有pack级别和app级别的才起作用,所以这时还应把action级别的资源文件内容增加到app级别的资源文件中去。下面使用第二种方法,原理基本和上面一样,只需在此ChangeLangAction中增加一个新的字段Stringlang及相应的get/set方法,再增加一个新的方法changeLang,代码如下:
publicStringchangeLang()throwsException{
Localelocale=null;
System.out.println(lang);
if(lang.equals("zh")){
//显示中文
locale=Locale.CHINA;
System.out.println("======"+lang+locale);
}else{
//显示英文
locale=Locale.US;
}
ActionContext.getContext().setLocale(locale); ServletActionContext.getRequest().getSession().setAttribute("WW_TRANS_I18N_LOCALE",locale);
returnSUCCESS;
}
配置内容为:
<actionname="cl"class="com.asm.ChangeLangAction"method="changeLang">
<result>/login.jsp</result>
</action>
在login.jsp中对应的链接为:
<ahref="cl.action?lang=zh">
<s:textname="chinese"></s:text>
</a>
<ahref="cl.action?lang=en">
<s:textname="english"></s:text>
</a>
这样操作后,当我们成功访问到login.jsp后,便可以点击链接来随意切换访问英文或中文页面。
八、验证机制
注意:要想实现校验,action必须继承自ActionSupport类。
1.基于手工编码的校验
我们建立struts2validate项目,其中reg.jsp页面主要代码如下:
<body>
<s:head/>
<h3>注册页面</h3>
<s:formmethod="post"action="reg">
<s:beanname="com.asm.AgeAction"id="aa"></s:bean>
<s:textfieldname="user.username"label="用户名"/>
<s:propertyvalue="errors.user.username"/>
<s:passwordname="user.password"label="密码"/>
<s:passwordname="user.password2"label="确认密码"/>
<s:selectlist="#aa.ageMap"name="user.age"label="年龄"headerValue="填写真实年龄"headerKey="0"/>
<s:resetvalue="重置"align="left"/>
<s:submitvalue="注册"align="left"/>
</s:form>
</body>
说明:<s:head/>可以用来对验证信息进行一些美化效果处理,另在此页面中我们用到了一个AgeAction用来动态生成“年龄”表单项,在前面的表单标签中已用过类似的做法。AgeAction的代码如下:
packagecom.asm;
publicclassAgeActionextendsActionSupport{
privateMap<Integer,String>ageMap;
publicAgeAction(){
ageMap=newHashMap();
for(inti=1;i<=120;i++){
ageMap.put(newInteger(i),i+"");
}
}
...省略ageMap的get/set方法
}
Regaction的配置如下:
<packagename="validate"extends="struts-default">
<actionname="reg"class="com.asm.RegAndLoginAction"method="reg">
<resultname="success">/regSuc.jsp</result>
<resultname="login">/reg.jsp</result>
</action>
</package>
根据配置,我们来看它的对应Action:RegAndLoginAction,代码如下:
packagecom.asm;
publicclassRegAndLoginActionextendsActionSupport{
privateUseruser;
publicStringreg()throwsException{
if(user.getUsername()==null||user.getUsername().equals("")){
this.addFieldError("user.username","用户名不能为空");
}elseif(!Pattern.matches("^[a-zA-Z][a-zA-Z0-9_]{3,14}$",user.getUsername())){
this.addFieldError("user.username","用户名只能是以字母开头,后面可以跟字母、数字或下滑线,长度只能是4-15位");
}elseif(user.getPassword()==null||user.getPassword().equals("")){
this.addFieldError("user.password","密码不能为空");
}elseif(!user.getPassword().equals(user.getPassword2())){
this.addFieldError("user.password2","两次输入的密码不一致,请重新输入");
}elseif(user.getAge()<16){
this.addFieldError("user.age","未满16岁,不能注册");
}
if(this.hasFieldErrors()){
returnLOGIN;
}
System.out.println("regsuccess....");
returnSUCCESS;
}
...省略user的get/set方法
}
说明:当reg.jsp提交给此Action对应的reg方法处理时,它会调用addFieldError把错误信息加到FiledError中去,关于这点,我们可以在前台reg.jsp页面中用<s:debug>调试时,可以看到值栈中的此Action对象中的fieldErrors对应着我们添加的错误信息,因此这点也就为我们取出验证信息提供一个参考,即是说我们可以取出此验证信息,对它进行美化处理,而不是按struts2默认来显示。后面,我们接着对登录页面用login方法进行了类似的验证(在此省略),所以此action取名为regAndLoginAction.
补充:当我们把login.jsp页面的验证写完后,可以发现reg和login这两个方法显示相当的繁琐,对此我们可以专门把验证分别放在validateReg和validateLogin方法中去。我们新建一个Action来演示,新的RegAndLogin2Action主要代码如下:
packagecom.asm;
publicclassRegAndLogin2ActionextendsActionSupport{
privateUseruser;
@Override
publicvoidvalidate(){
System.out.println("校验的统一出口,对所有方法进行校验:这里可以放一些公共的验证");
}
publicvoidvalidateReg(){
...省略,对reg方法进行验证
}
publicvoidvalidateLogin(){
...省略,对login方法进行验证
}
publicStringreg()throwsException{
System.out.println("regsuccess....");
returnSUCCESS;
}
publicStringlogin()throwsException{
System.out.println("loginsuccess....");
returnSUCCESS;
}
...省略user的get/set方法
}
说明:当reg.jsp提交给此Action对应的reg方法处理时,它会首先调用此reg方法专属的验证方法valiadteReg(注意取名规则:validate+方法名<首字母大写>),此方法验证完成后,会调用validate方法,此方法完成后才会调用reg方法。因此一般情况下,我们会把一些公共的验证放在validate方法中,而这些所有的验证方法也只进行验证处理,并把错误信息封装到fieldError字段中(或者其它字段)。reg这些真正执行的方法只进行一些其它处理(比如把注册信息写进数据库)。测试时需要修改把前面的配置注释掉,写上下面的配置:
<actionname="login"class="com.asm.RegAndLogin2Action" method="login">
<resultname="success">/logSuc.jsp</result>
<resultname="input">/login.jsp</result>
</action>
<actionname="reg"class="com.asm.RegAndLogin2Action"method="reg">
<resultname="success">/regSuc.jsp</result>
<resultname="input">/reg.jsp</result>
</action>
说明:配置中有一个inputresult的配置,因为带有validate的方法进行验证时,如果验证失败,会返回input所对应的result结果集。
简析校验流程:
(1)类型转换器请求参数执行类型转换,并把转换后的值赋给action中属性。
(2)如果在执行类型转换过程中出现异常,系统会将异常信息保存到ActionContext,conversionError拦截器将异常信息添加到fieldErrors里,不管类型转换是否出现异常都会进入第(3)步。
(3)系统通过反射技术调用action中的validateXxx()方法
(4)再调用action中的validate()方法
(5)经过上面4步,如果系统中的fieldErrors存在错误信息(即存放错误信息的集合size大于0),系统自动将请求转发至名为input的视图。如果系统中的fieldErrors没有任何错误信息,系统将执行action中的处理方法。
注意:经过以上过程的分析,可以知道如果类型转换失败,也会到input视图。
2.基于XML配置形式的校验
新建struts2validateXML项目,在此项目中,基本的代码和上面的struts2validate项目相似,只是在上一个项目中我们在Action的具体方法中进行了验证处理,现在先修改RegAndLoginAction的代码如下:
packagecom.asm;
publicclassRegAndLoginActionextendsActionSupport{
privateUseruser;
publicStringreg()throwsException{
System.out.println("regsuccess....");
returnSUCCESS;
}
publicStringlogin()throwsException{
System.out.println("loginsuccess....");
returnSUCCESS;
}
...省略user的get/set方法
}
下面我们在action所在的包下建立一个对此Action进行校验的xml文件,文件名为:RegAndLoginAction-validation.xml,取名原则就是actionClassName-validation.xml。它会对此Action中的所有方法进行校验。主要代码如下:
<?xmlversion="1.0"encoding="UTF-8"?>
<!DOCTYPEvalidatorsPUBLIC
"-//OpenSymphonyGroup//XWorkValidator1.0.3//EN"
"http://www.opensymphony.com/xwork/xwork-validator-1.0.3.dtd">
<validators>
<fieldname="user.username">
<field-validatortype="requiredstring">
<message>用户名不能为空</message>
</field-validator>
<field-validatortype="regex">
<paramname="expression">^[a-zA-Z][a-zA-Z0-9_]{3,14}$</param>
<message>
用户名只能是以字母开头,后面可以跟字母、数字或下滑线,长度只能是4-15位
</message>
</field-validator>
</field>
<fieldname="user.password">
<field-validatortype="requiredstring">
<message>密码不能为空</message>
</field-validator>
</field>
</validators>
进行此配置,相当于在RegAndLoginAciton中增加用validate()方法进行验证。如果我们想对某个方法进行验证,配置文件应取名为actionClassName-ActionName-validation.xml,比如我们对reg方法进行验证,在前面用到validateReg方法,这里只需增加RegAndLoginAction-reg-validation.xml配置文件即可,它的作用和validateReg方法相同,在此省略此配置文件内容。
关于验证的配置文件中用到的验证类型可以参看文档或者叁看压缩包中的配置参照文件,下面对校验器类型进行简单说明:
Required-必须校验器:要求field的值不能为null
Requiredstring-必须字串校验器:不能为null,用长度大于0,默认情况下会对字串去前后空格
int、[long、short、double]:整数值[long型、短整形、double型]型值必须在指定范围。参数min指定最小值,参数max指定最大值
date-日期校验器:日期校验类型,符合日期格式,用可以使用min/max来指定日期范围
expression-OGNL表达式校验器:expression参数指定ognl表达式,该逻辑表达式基于值栈进行求值,返回true时校验通过,否则不通过,该校验器不可用在字段校验器风格的配置中
fieldexpression-字段ognl表达式校验器:要求field满足一个ognl表达式,expression参数指定ognl表达式,该逻辑表达式基于值栈进行求值,返回true校验通过,否则不通过
email-邮件地址校验器:非空用为合法的邮件地址
url-网址校验器:非空用为合法的url地址
visitor-复合属性校验器:它指定一个校验文件用于校验复合属性中的属性
conversion-转换校验器:指定在类型转换失败时,提示的错误信息
stringlength-字符器长度校验器:要求字段必须在指定的范围内,否则校验失败。minLength参数指定最小长度,maxLength参数指定最大长度。Trim参数指定校验field之前是否去除字串前后的空格
regex-正则表达式校验器:校验字段是否与expression参数指定的正则表达式匹配。caseSensitive参数指定进行匹配时是否区分大小写,默认为true,即区分大小写。
补充:基于xml校验的一些特点
当为某个Action提供了ActionClassName-validation.xml和ActionClassName-ActionName-validation.xml两种规则的校验文件时,系统会按下面的顺序寻找校验文件:(1)ActionClassName-validation.xml(2)ActionClassName-ActionName-validation.xml
系统寻找到第一个校验文件时还会继续搜索后面的校验文件,当探索到所有校验文件时,会把校验文件里的所有校验规则汇总,然后全部应用于action方法的校验。如果两个校验文件中指定的校验规则冲突,则会只使用后面文件中的校验规则。
当action继承了另一个action,父类action的校验文件会先被搜索到。
假定UserAction继承BaseAction:
<actionname="user"class="com.asm.UserAction"method="execute">访问上面的action,系统会先搜索父类的校验文件:BaseAction-validation.xml,BaseAction-user-validation.xml,接着搜索子类的校验文件:UserAction-validation.xml,UserAction-user-validation.xml.应用于上面action校验规则为四个文件的总和。
九、文件上传下载(了解)
首先建立struts2UpDownLoad项目,搭建好struts2基本的开发环境。
1.上传实例
2.步骤一:upload.jsp代码如下:
<s:formaction="upload"method="post"enctype="multipart/form-data">
<s:filename="file"label="上传的头像(格式:jpg,gif,bmp)"></s:file>
<s:submitvalue="上传"/> <s:resetvalue="取消"/>
</s:form>
注意:在form标签中我们用到了enctype实体,这是上传时必须用到得。
步骤二,建立struts.xml。对upload.action的配置如下:
<actionname="upload"class="com.asm.UploadAction">
<paramname="savePath">img</param>
<result>/upSuc.jsp</result>
<resultname="input">upload.jsp</result>
<interceptor-refname="defaultStack">
<paramname="fileUpload.maximumSize">1024*1024</param>
<paramname="fileUpload.allowedTypes">
image/bmp,image/pjpeg,image/gif
</param>
</interceptor-ref>
</action>
在这里唯一需要说明的是<interceptor-ref>下的参数问题,在以前如果要为某个特定的拦截器传递参数需要在<interceptor>下配置pararm参数,在此处我们用.形式来配置fileUpload拦截器的参数。这用做即可以保证默认的拦截器栈起作用,也可以向fileUpload拦截器传递参数。第一个参数是限制上传图片的大小(除了可以这样限制图片大小,也可以配置一个常量的方法来限制上传文件的大小,配置的内容为:<constantname="struts.multipart.maxSize"value="文件大小"/>),第二个参数是限制上传图片的格式只能为bmp,pjpeg,gif关于这些参数可以参看fileupload拦截器对应类的api文档。
另还需注意:在action下的“<paramname="savePath">img</param>”代码可以为UploadAction的savePath字段传递值,这样作的好处是在项目正式发布后,我们可以通过修改struts.xml中的配置来灵活给savePath赋值。而如果直接在java源代码中初始化savePath的值,在项目运行后就不能简单修改。这种用法主要是为了给客户提供一个灵活可配的特定初始化方式。
步骤三、编写UploadAction,主要代码如下:
packagecom.asm;
publicclassUploadActionextendsActionSupport{
privateStringsavePath;
privateFilefile;
privateStringfileFileName;
privateStringfileContentType;
publicStringexecute()throwsException{
Stringpath=ServletActionContext.getServletContext().getRealPath(savePath);
StringsavaFileName=path+"\\"+fileFileName;
//System.out.println(savaFileName);
BufferedInputStreambis=null;
BufferedOutputStreambos=null;
try{
bis=newBufferedInputStream(newFileInputStream(file));
bos=newBufferedOutputStream(newFileOutputStream(savaFileName));
byte[]buf=newbyte[(int)file.length()];
intlen=0;
while((len=bis.read(buf))!=-1){
bos.write(buf,0,len);
}}catch(Exceptione){
e.printStackTrace();
}finally{
if(bis!=null)
bis.close();
if(bos!=null)
bos.close();
}
returnSUCCESS;
}
...省略以上四个字段的get/set方法
}
说明:其实上传的难点就是在此action的处理上。首先是从配置文件中读取文件的保存路径,然后联合fileFileName(命名规则是上传的文件对应的字段名+FileName,如果要得到上传文件的类型,固定的写法应是上传的文件对应的字段名+ContentType,比如这里应为fileContentType)来确定完整的保存路径,并最终为创建BufferedOutputStream作准备。BufferedInputStream是通过前台upload.jsp页面传递的file构建。特别要注意处理流,如果书写不当可能会使上传文件循环写入,使得硬盘容量不够。还要注意对流的关闭问题。补充:关于文件的操作可以使用commons-io.jar包的FileUtils类下的copyFile方法来进行文件的拷贝,比如这里调用copyFile方法(file,要保存的目录)
上传成功后,upSuc.jsp的主要内容如下:
<body>
<h4>上传成功,以下是你刚上传的图片:</h4>
<imgsrc="<%=request.getContextPath()%>/<s:propertyvalue="savePath+'/'+fileFileName"/>"> <br>
保存路径为:<s:propertyvalue="savePath+'/'+fileFileName"/>
</body>
说明:当上传成功后,会显示上传的图片。
扩展实例:如果想上传多个文件,可以在Action中使用File[]files来接受上传的文件(jsp中对应的上传文件的参数均应为files)。对应的上传文件的名字,使用String[]fileFileName。然后循环files数组保存File文件对象。
2.下载实例
下载页面的doload.jsp的主要代码如下:
<ahref="download.action?downloadName=img/a.bmp">下载图片</a><br>
<ahref="download.action?downloadName=img/music.rar">下载千千静听</a><br>
对应的downloadaction配置如下:
<actionname="download"class="com.asm.DownloadAction">
<resultname="success"type="stream">
<paramname="inputName">targetFile</param>
<paramname="contentType">
image/bmp,application/x-zip-compressed
</param>
</result>
</action>
说明:type类型指明了结果集为流类型,并且为流类型结果集配置了参数,inputName指定流的来源,这里来源为targetFile,所以在下面的Action中有getTargetFile方法,contentType指明下载时的文件类型。
DownloadAction的主要代码如下:
packagecom.asm;
publicclassDownloadActionextendsActionSupport{
privateStringdownloadName;
publicStringexecute()throwsException{
returnSUCCESS;
}
publicInputStreamgetTargetFile(){
returnServletActionContext.getServletContext().getResourceAsStream(downloadName);
}
publicvoidsetDownloadName(StringdownloadName){
this.downloadName=downloadName;
}
}
说明:下载实例在此略作了解,具体可以借助apache组织提供的上传下载开源项目理解。
十、类型转换
建立struts2conversion项目,并搭建好struts2的基本开发环境
1.基于Action的直接属性转换
建立t.jsp页面,内容如下:
<s:formaction="phone"method="post">
<s:textfieldname="thePhone"label="电话"/>
<s:submitvalue="提交"/>
<s:resetvalue="重置"/>
</s:form>
此action对应的配置如下:
<actionname="phone"class="com.asm.action.PhoneAction">
<resultname="success">tSuc.jsp</result>
<resultname="input">/t.jsp</result>
</action>
对应的PhoneAction类的代码如下:
packagecom.asm.action;
publicclassPhoneActionextendsActionSupport{
privateTelephonethePhone;
publicStringexecute()throwsException{
returnSUCCESS;
}
...省略thePhone的get/set方法
}
说明,如果直接这样执行将会出错,因为前台t.jsp传的String默认是不能被转成这里的Phone对象,所以我们必须使用类型转换,而且我们配置了inputresult就是告诉我们如果类型转换失败,将会停在t.jsp页面,而且会报错。下面接着看怎么类型转换。在这里我们要把010-123456这样的电话换成:区号:010电话:123456这样的形式时。具体的操作步骤如下:
创建类型转换类TelephoneConversion,代码如下:
packagecom.asm.conversion;
publicclassTelephoneConversionextendsStrutsTypeConverter{
publicObjectconvertFromString(Mapcontext,String[]values,ClasstoClass){
System.out.println("执行字串到Telephone对象的转换");
Telephonetp=newTelephone();
String[]tel=values[0].split("-");
tp.setSecNum(tel[0]);
tp.setPhone(tel[1]);
returntp;
}
publicStringconvertToString(Mapcontext,Objectobj){
System.out.println("执行Telephone对象到字串的转换");
Telephonetp=(Telephone)obj;
return"区号:"+tp.getSecNum()+"\t电话:"+tp.getPhone();
}
}
说明:类型转换类必须实现TypeConverter接口,而这里的StrutsTypeConverter类便是TypeConverter接口实现类DefaultTypeConverter的子类。此类中有两个方法,一个方法实现把字串转成其它对象,一个方法实现把其它对象转成字串。在convertFromString方法中,我们实现把客户端传递的字串转成Telephone对象,这样就能让PhoneAction的setThePhone方法得以正确执行。而后面的方法是为了我们要取值时进行的处理,比如在tSuc.jsp中我们要得到此值,需要把Telephone对象转换字串。其实如果没有convertToString方法,只要我们重写Telephone的toString方法也能达到目的。
写完类类型转换类后,我们还应告知struts2,所以我们还需建立一个properties文件。我们在PhoneAction的同包下建立PhoneAction-conversion.properties文件,它的主要代码如下:
thePhone=com.asm.conversion.TelephoneConversion
说明:这句话的意思是说我们要把PhoneAction(通过properties文件名可以知道要转换的是此Action)下的thePhone使用TelephoneConversion进行转换。其实我们也可以配置全局的properties文件说明,比如我们在src目录下建立xwork-conversion.properties文件(名字固定),它的内容如下:
com.asm.vo.Telephone=com.asm.conversion.TelephoneConversion
说明:它的意思是只要遇到Telephone对象,就要用后面的转换器来实行转换。
2.基于Action的间接属性vo转换
t2.jsp主要内容如下:
<s:formaction="up"method="post">
<s:textfieldname="user.thePhone"label="电话"/>
<s:submitvalue="提交"/>
<s:resetvalue="重置"/>
</s:form>
我们建立UserPhoneAction类,它的主要代码如下:
packagecom.asm.action;
publicclassUserPhoneActionextendsActionSupport{
privateUseruser;
publicStringexecute()throwsException{
returnSUCCESS;
}
...省略user的get/set方法
}
User类的代码如下:
packagecom.asm.vo;
publicclassUser{
privateTelephonethePhone;
...省略thePhone的get/set方法。
}
说明:通过这两个类及t2.jsp页面,我们知道,前台传递的thePhone对象不时直接传递,而是采用了vo模式,所以当我们配置类型转换时,要特别注意。因为前面我们使用了全局的类型转换,所以这里不会出错,但是当我们去掉前面的全局转换时,配置类型转换的properties文件就应在User类对应的包下配置User-conversion.properties文件,它的主要内容如下:
thePhone=com.asm.conversion.TelephoneConversion
说明及总结:类型转换的配置文件如果不采用全局的配置时,我们就应以要转换的类型的直接持有类为基准:比如,这里的thePhone的直接持有类为User对象,所以我们就应以User为基准写properties文件名。
十一、注解配置
在此先略去注解配置的实例,具体可以参看官方提供的文档。其实在熟悉struts及相关的一些内容后,再来看文档是比较容易理解得。只是要注意使用注解Annotition时:(1)要多导入一个jar包:struts2-convention-plugin-2.1.6.jar。(2)需要在web.xml中增加如下内容:
<filter>
<filter-name>struts2</filter-name>
<filter-class> org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
</filter-class>
<!--增加了以下内容-->
<init-param>
<param-name>actionPackages</param-name>
<param-value>com.struts2.action</param-value>
</init-param>
</filter>
十二、总结
本教程对struts2的基本知识进行了一些说明,关于struts2的更多详细内容应参看struts2的官方文档及提供的app实例。
下面对struts2的基本执行流程作一简要说明,此流程说明可以结合官方提供的struts2结构图来看:
n客户端提交一个(HttpServletRequest)请求,如上文在浏览器中输入
http://localhost:8080/appName/...就是提交一个(HttpServletRequest)请求。
n请求被提交到一系列(主要是3层)的过滤器(Filter),如(ActionContextCleanUp、其他过滤器(SiteMesh等)、FilterDispatcher)。注意:这里是有顺序的,先ActionContextCleanUp,再其他过滤器(OthterFilters、SiteMesh等),最后到FilterDispatcher。
nFilterDispatcher是控制器的核心,就是MVC的Struts2中实现控制层(Controller)的核心。(有点struts1.x中ActionServlet的感觉)
nFilterDispatcher询问ActionMapper是否需要调用某个Action来处理这个(HttpServletRequest)请求,如果ActionMapper决定需要调用某个Action,FilterDispatcher则把请求的处理交给ActionProxy。
nActionProxy通过ConfigurationManager(struts.xml)询问框架的配置文件,找到需要调用的Action类。例如,用户注册示例将找到UserReg类。
nActionProxy创建一个ActionInvocation实例,同时ActionInvocation通过代理模式调用Action。但在调用之前,ActionInvocation会根据配置加载Action相关的所有Interceptor(拦截器)。关于ActionInvocation的执行过程我们在五、2自定义拦截器最后的补充中已经进行了较详细说明。
n一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果result。
相关推荐
这个“Struts2实例3源码”压缩包很可能是为了帮助开发者理解并实践Struts2框架的用法,通过具体的代码示例来学习其核心概念和功能。 Struts2的核心特性包括: 1. **Action和Result**:在Struts2中,Action是处理...
Struts2是一个强大的MVC(Model-View-Controller)框架,它是Apache...以上就是Struts2实例工程中涉及的关键知识点,通过学习和实践这些内容,开发者可以更好地理解和运用Struts2框架来开发高效、稳定的Java web应用。
本实例“Struts2实例4”旨在深入探讨和展示Struts2框架的实际应用,帮助开发者理解如何在项目中有效地利用其功能。 在Struts2框架中,我们首先需要了解的是其核心概念——Action类。Action类是业务逻辑的载体,负责...
在本实例中,我们将深入探讨如何使用Java来编写一个Struts2应用。 首先,让我们了解Struts2的核心概念: 1. **Action类**:在Struts2中,业务逻辑通常由Action类执行。Action类是Java类,它实现了`...
本实例“Struts2实例5”旨在深入理解和应用Struts2的核心特性,帮助开发者更加熟练地使用该框架进行项目开发。下面我们将详细探讨Struts2的相关知识点。 1. **Struts2框架基础** Struts2是Apache软件基金会的一个...
这个“Struts2实例代码”压缩包提供了具体的实现示例,帮助开发者理解并掌握Struts2框架的使用。 在实际应用中,Struts2框架提供了一种组织应用程序结构的方式,它简化了HTTP请求与业务逻辑之间的映射,使得开发者...
在这个实例中,我们将深入探讨Struts2的核心概念、配置以及如何创建一个简单的应用程序。 首先,理解Struts2的核心理念至关重要。Struts2框架通过提供一系列拦截器(Interceptor)来处理HTTP请求,这些拦截器按照预...
这个实例项目展示了如何在实际应用中结合Struts2、国际化、类型转换以及Struts2自定义标签来实现用户管理功能,包括用户数据的增删改查。 首先,我们来看Struts2的核心概念。Struts2作为MVC框架,它负责处理HTTP...
### Struts2 实例 HelloWorld 知识点解析 #### 一、概述 在学习Struts2框架时,通常会从最简单的“Hello World”实例开始。这种实例不仅可以帮助初学者快速上手,还能让大家对Struts2的工作原理有一个基本的理解。...
在这个“Struts2实例3”的主题中,我们将深入探讨Struts2的核心特性、工作原理以及如何通过实际操作来应用这些概念。下面,我们将详细讨论Struts2的关键知识点。 1. **Struts2架构**: Struts2框架基于拦截器...
Struts2是一个强大的Java web应用程序框架,用于构建和部署可维护、高性能的MVC(Model-View-...文档“struts2struts2实例和详细介绍.doc”应该会提供更详细的指导和示例代码,帮助你深入理解和运用Struts2框架。
通过深入研究这个“struts2实例源码2”,开发者不仅可以理解Struts2框架的基本工作原理,还能学习到如何在实际项目中应用这些概念,提高开发效率和代码质量。同时,这也为进一步探索其他高级特性,如AOP(面向切面...
这个"struts2实例源码1"可能是为了演示如何在实际项目中使用Struts2框架。 在Struts2中,以下是一些核心概念和关键知识点: 1. **Action类**:Action类是处理用户请求的核心组件。每个Action类通常对应于一个特定...
在这个“Struts2实例小程序1”中,你将开始接触并理解Struts2的基本概念和工作流程,这对于初学者来说是一个很好的起点。 1. **Struts2入门**: - **MVC模式**:Struts2遵循MVC设计模式,将业务逻辑、数据和展示...
这个"struts2实例.zip"压缩包包含了Struts2框架的实际应用代码,旨在帮助开发者理解和掌握Struts2的核心功能和工作原理。下面将详细阐述Struts2的关键特性以及在实际开发中的应用。 1. **MVC架构**:Struts2基于...
在本实例"Struts2实例 BBS"中,我们将深入探讨如何使用Struts2实现一个基本的论坛系统,该系统具备增删改查(CRUD)功能,且不包含冗余或无效代码。以下是对这一实例的详细分析: 首先,我们来看看Struts2框架的...
struts2简单应用实例struts2简单应用实例struts2简单应用实例struts2简单应用实例struts2简单应用实例struts2简单应用实例struts2简单应用实例
在这个"PageOffice MVC Struts2实例"中,我们将探讨如何利用PageOffice控件在Struts2框架下实现Office文档的在线编辑与保存功能。 PageOffice是一款强大的Java办公组件,它可以无缝集成到各种Java应用中,支持用户...
这个"struts2实例"很可能是为了展示Struts2框架的实际应用,帮助开发者理解如何在项目中有效地利用它。在SAPSOAWeb这个项目中,我们可以期待看到如何配置和使用Struts2来处理用户请求,以及如何构建动态的、数据驱动...