可以使用ctrl+f对标题查询 标题如下
●开发准备
●第一个例子程序
●Action
●result
●ognl表达式(valuestack理解)
●Struts2标签
●<s:token/>标签防止表单重复提交
●对一些默认配置的修改(default.properties)
●struts Exception handling—声明式异常处理
●i18n:支持程序国际化
●文件上传
●类型转换
●拦截器 单实例运行
●表单验证
●开发准备
1.引入类库 直接从struts\apps\struts2-blank\WEB-INF\lib下拷贝
除junit和spring-test之外的所有jar包 还可以来个commons-logging.jar
2.复制一个struts.xml
直接从struts\apps\struts2-blank\WEB-INF\classes下拷贝struts.xml
3.对web.xml配置 同样从
struts\apps\struts2-blank\WEB-INF\web.xml中拷贝
<filter></filter>和<filter-mapping></filter-mapping>以及其中的全部内容
●第一个例子程序
* 创建web工程
* 引入struts2需要的jar包
struts2-core-2.1.8.1.jar :Struts 2框架的核心类库
xwork-core-2.1.6.jar :XWork类库,Struts 2在其上构建
ognl-2.7.3.jar :对象图导航语言(Object Graph Navigation Language),
struts2框架通过其读写对象的属性
freemarker-2.3.15.jar :Struts 2的UI标签的模板使用FreeMarker编写
commons-logging-1.1.x.jar :ASF出品的日志包,Struts 2框架使用这个日志
包来支持Log4J和JDK 1.4+的日志记录。
commons-fileupload-1.2.1.jar 文件上传组件,2.1.6版本后需要加入此文件
commons-io-1.3.2.jar,上传文件依赖的jar包
* 创建jsp页面
<a href="${pageContext.request.contextPath}/primer/helloWorldAction.action">helloWorld</a><br>
* 创建HelloWorldAction
/*
* 在struts中所有的action都要实现Action这个接口
*/
public class HelloWorldAction implements Action {
public String execute() throws Exception {
System.out.println("HelloWorldAction execute");
return "success";
}
}
* 配置struts.xml文件
* 在src下创建struts.xml
* 该文件的dtd规范在struts2-core-2.1.8.1.jar/struts-2.1.7.dtd文件中
* 配置该xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN"
"http://struts.apache.org/dtds/struts-2.1.7.dtd">
<struts>
<!-- 请求路径/primer/helloWorldAction.action -->
<!--
package:包(给java中的包类似),规范管理
* name:包的名称:唯一,主要作用用于继承
* namespace:命名空间 唯一,相当于房间号
* extends="struts-default":继承
* 在struts2-core-2.1.8.1.jar/struts-default.xml文件存在如下的包
底层代码<package name="struts-default" abstract="true">
* 为什么要继承struts-default包呢,让struts2运行时执行default中定义的拦截器
拦截器将请求封装到action的变量中
-->
<package name="primer" namespace="/primer" extends="struts-default">
<!--
action:配置action的
* name:客户端请求访问的action的唯一名称(在包中唯一)
* class:要访问的action的完整类路径
-->
<action name="helloWorldAction" class="cn.itcast.primer.HelloWorldAction">
<!--
result:处理action中execute()方法的返回值
* name:该属性的值要和execute()方法的返回值对应
* result标签的文本内容是要转向的路径
public String execute() throws Exception {
System.out.println("HelloWorldAction execute");
return "success";
}
-->
<result name="success">/primer/success.jsp</result>
<result name="error">/primer/error.jsp</result>
</action>
</package>
</struts>
* 配置struts的过滤器,解析struts.xml文件
<filter>
<filter-name>StrutsPrepareAndExecuteFilter</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>StrutsPrepareAndExecuteFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
* 测试
*在地址栏输入如下路径
http://localhost:808/itcast0706struts2/primer/test.jsp
●Action
1.struts2的作用就是让用户请求和返回视图(servlet api)分离。
<action name="helloWorldAction" class="cn.itcast.primer.HelloWorldAction">
如果没有为action指定class,则默认的class为struts-default.xml中的<default-class-ref>配置的class
如果请求路径找不到action则执行默认配置的action <default-action-ref name="index"></default-action-ref>
默认的请求后缀为.action和没有后缀 如果需要更改则在配置文件中写 逗号隔开<constant name="struts.action.extension" value="do,go,action"></constant>
2.namespace
namespace决定了action的访问路径,默认为"",可以接收所有路径的action
namespace可以写为/,或者/xxx,或者/xxx/yyy,对应的action访问路径为/xxx.action
/xxx/xxx.action,或者/xxx/yyy/xxx.action
namespace最好也用模块来进行命名 比如用户模块 就用user
如果找不到命名空间,就去父级找。如/xxx/xxx/xxx/a.action找不到就找xxx/xxx/a.action以此类推
3.编写action
具体视图的返回可以由用户自己定义的Action来决定
具体的手段是根据返回的字符串找到对应的配置项,来决定视图的内容
具体Action的实现可以是一个普通的java类,里面有public String execute方法即可
或者实现Action接口
不过最常用的是从ActionSupport继承,好处在于可以直接使用Struts2封装好的方法
4.struts2中的路径问题是根据action的路径而不是jsp路径来确定,所以尽量不要使用相对路径。
虽然可以用redirect方式解决,但redirect方式并非必要。
解决办法非常简单,统一使用绝对路径。(在jsp中用request.getContextRoot方式来拿到webapp的路径)
或者使用myeclipse经常用的,指定basePath
5.方法配置
Action执行的时候并不一定要执行execute方法
可以在配置文件中配置Action的时候用method=来指定执行哪个方法(默认execute方法)
add方法 返回值是"add"
<action name="helloWorldAction" class="cn.itcast.primer.HelloWorldAction" method="add">
<result name="add">/alksdjf/asdf.jsp</result>
</action>
5.1动态方法调用
以前会产生太多的action,所以不推荐使用
也可以在url地址中动态指定(动态方法调用DMI)(推荐)
如http://localhost:80/webapp/XXaction!methodname.action
注意.action写在最后
禁用动态方法调用<constant name="struts.enable.DynamicMethodInvocation" value="false" /> 默认为true
5.2如果没有为action指定class,则默认的class为struts-default.xml中的<default-class-ref>配置的class
6.动态action配置
使用通配符,将配置量降到最低,不过,一定要遵守"约定优于配置"的原则,即命名规则统一。
(在实际开发中action确定而method由动态方法调用指定)
<action name="Student*" class="com.bjsxt.struts2.action.StudentAction" method="{1}">
<result>/Student{1}_success.jsp</result>
*代替字符串,{1}代表第一个*所匹配的字符串。
<action name="*_*" class="com.bjsxt.struts2.action.{1}Action" method="{2}">
<result>/{1}_{2}_success.jsp</result>
</action>
7.使用action属性接收参数
在URL地址中传的参数和action里的成员变量可以是一一对应的,
action初始化时,会自动将成员变量的值初始化成参数的值。(struts调set方法,所以set方法名
和url参数的名必须匹配而真正的成员变量名不一定非要匹配)
8.使用Domainmodel(域模型)接收参数
action类中可以不写具体的属性而将其写入领域模型中,
然后action类中持有一个领域模型的对象的引用,在url中同样可以传参数直接对领域模型对象的属性赋值,
写法为 namespace/actionname!method?domainmodel.parameter=XXX&domainmodel.parameter=XXX,
(也可能是method?domainmodel.parameter.parameter.parameter=XXX,此时domainmodel成员变量为另一个对象,
另一个对象的成员变量为另另一个对象)
在post的表单中<input type="text" name="domainmodel.parameter">
当领域模型与实际参数的属性不同时,如,password往往有确认password,则新建一个dataTransferObject类来接收参数并判断,再初始化领域模型。此时action类中持有的则是DTO的引用。
8.1集合接受批量参数
在action放一个集合 Collection<User> users;
则在表单中
<input type="text" name="users[0].username"/>
<input type="text" name="users[1].username"/>
9.我们也可以使action类实现ModelDriven<User>接口,使用接口中的getmodle方法来使action自动获得域模型,这样即使在action类中包含了领域模型的引用,同样可以在url中直接写 namespace/actionname!method?parameter=XXX¶meter=XXX.使用这个方法时,在action中要自己new一个模型。
9.1利用模型驱动回写
模型驱动ModelDriven的原理
1.当用户调用action实现了ModelDriven接口的类时,ModelDriven 拦截器将调用这个action对象的getModel()方法,并把返回的模型对象的引用压入到ValueStack栈顶,该对象的各属性均为空,这步在最开始就会做.
2.接下来 Parameters 拦截器(把参数封装到栈定的对象的拦截器)将把表单字段映射到 ValueStack 栈的栈顶对象的各个属性中.因为此时 ValueStack 栈的栈顶元素是刚被压入的模型对象, 所以该模型各属性将被填充.
3.如果某个字段在模型里没有匹配的属性, Param 拦截器将尝试 ValueStack 栈中的下一个对象.
4.一个模型类必须有一个不带任何参数的构造器.
综上所述,假如页面提交了表单,参数拦截器就会对valuestack栈顶对象赋值,由于栈定对象实际也就是action中的对象(引用),所以action中的对象也会取得值。
则当action转发回jsp时,由于value存于request中,request是同一个,所以valuestack也是同一个。
5.当我们使用struts2的标签时,标签会自动根据name属性的值去值栈中栈首对象中找到并做回显,即使是隐藏字段,只要name一样,也会被赋值。
10. <constant name="struts.i18n.encoding" value="GBK" /> struts.xml解决中文问题的标签,这些标签可以在struts2-core-2.1.6.jar中的org.apache.struts2中的default.properties中查询默认值。
在2.1.6之前,中文问题是个BUG,在2.1.7中已修正。
11.使用struts的标签来将程序中对用户的提示信息打印到前台页面:
在后台 使用this.addFieldError("name","name is error");其中name为action中的属性,
"name is error"为错误信息的提示
<s:fielderror fieldName="name" />将带有简单格式的提示信息打印到前台
首先,在前台页面中包含<%@taglib uri="/struts-tags" prefix="s" %>
在META-INF下struts-tag.tld中有标签详细信息 页面中使用<s:debug></s:debug>
可以观察ValueStack里保存的action中的属性。
属性都是存在map中的,有值,有名,而 值,是个数组,
所以我们用<s:property value="errors.name[0]">可以取到错误信息的具体字符串。
12.action中得到request、session、application(servletContext)
一、
后台程序可以通过ActionSupport中的方法
private Map<String, Object> request;
private Map<String, Object> session;
private Map<String, Object> application;
request = (Map)ActionContext.getContext().get("request");
session = ActionContext.getContext().getSession();
application = ActionContext.getContext().getApplication();
或者
ServletActionContext.getContext().getRequest()
ServletActionContext.getContext().getSession()
ServletActionContext.getContext().getApplication()
得到map类型的 request,session,application.
还可以通过put方法对这些名值对进行赋值。
在前台页面,可以通过
<s:property value="#request.r1"/> | <%=request.getAttribute("r1") %> <br />
<s:property value="#session.s1"/> | <%=session.getAttribute("s1") %> <br />
<s:property value="#application.a1"/> | <%=application.getAttribute("a1") %> <br />
来拿到所设置的request、session、application.既用#key来访问或者用最原始的jsp语言来访问。
二(常用)、
实现RequestAware,SessionAware,ApplicationAware
同样设置三个成员变量 但使用范型 这些map是struts进行封装的,将request、session、application中的内容取出放到了map中
private HttpServletRequest request;
private Map<String, Object> session;
private ServletContext application;
然后设置set方法(接口中的方法),由struts将页面元素对象自动设置进去(实现RequestAware,SessionAware,ApplicationAware),
然后调用put方法,往进写东西。
三、
同样设置三个成员变量 但类型不同
private HttpServletRequest request;
private HttpSession session;
private ServletContext application;
然后初始化
request = ServletActionContext.getRequest();
session = request.getSession();
application = session.getServletContext();
然后由request.setAttribute("r1", "r1")往进设置值。
四、
同样设置三个成员变量
private HttpServletRequest request;
private HttpSession session;
private ServletContext application;
初始化
public void setServletRequest(HttpServletRequest request) {
this.request = request;
this.session = request.getSession();
this.application = session.getServletContext();
}
然后由request.setAttribute("r1", "r1")设值。
13.模块包含
在struts.xml中可以将其他xml文档包含
<include file="XXXX.xml" />
这样的好处是,多个模块可以同时开发,然后包含就可以协作工作。
或者将配置xml放到一个xml中,再在struts.xml中引入
14.默认action
在url的namespace后什么都不敲入或敲入不存在的action时,访问默认的action
<default-action-ref name="index"></default-action-ref>
然后再jsp中使用<s:property value="r"/>接受参数。
●result
如果没有为result指定name属性,则默认name属性为success
1.result的type(类型)属性
·默认为dispatcher 运用jsp请求转发(forward)到结果页面 不能跳转到action
<result name="success" type="dispatcher">
<param name="location">/adfadf/asdf.jsp</param>
</result>
此时 在result类中的location属性就会变成/adfadf/asdf.jsp
·redirect 重定向到结果页面 不能跳转到action
<result name="success" type="redirect">
<param name="location">/adfadf/asdf.jsp</param>
</result>
·chain forward转发到一个action
<result type="chain">
<param name="actionName">adfadf</param>
<param name="namespace">/XXXXXX</param>
</reuslt>
·redirectAction 重定向到一个action
<result type="redirectAction">
<param name="actionName">adfadf</param>
<param name="namespace">/XXXXXX</param>
</reuslt>
·freemarker
·httpheader 发一个http的头信息
·stream 下载
·velocity
·xslt xml的修饰
·plaintext 页面源码
·tiles 页面分块后动态指定
假如chain forward跳转的action在某包内
则 <result type="chain">
<param name="actionName">adfadf</param>
<param name="namespace">/XXXXXX</param>
</reuslt>
此时 在result类中的actionName属性和namespace属性就会变成adfadf和XXXXX
在struts-2.2.1.1-docs/struts-2.2.1.1/docs/chain-result.html可查
2.global-results全局结果集在,同一个package中大家可以共用的结果视图 应放在action前面 通过包的继承可以通用
<global-results>
<result name="mainpage">/main.jsp</result>
</global-results>
其他package中也想使用此package的result,则extends="packagename"
<package name="admin" namespace="/admin" extends="user">
则此包内拥有user包的action
3.动态结果集 即<result>${r}</result>,此时r为action的成员变量 xml中${}可以读取valuestack里的内容
r的值可以动态指定为一个字符串,例如"/user_success.jsp"。
4.带参数的结果集。
由于forward服务器内跳转共享一个值栈,所以参数也共享,即没有必要传。
而再redirect客户端重定向的时候,值栈不共享,是两次request过程,
此时希望action向新定向到的页面传参数需要使用?paramname=${paramname}
即<result type="redirect">/user_success.jsp?t=${type}</result>
此时如果在user_success.jsp使用struts的<s:property value="t">
标签取值取不出,因为不是action没有值栈actionstack。
只能用<s:property value="#parameters.t">从actioncontext中取。
●ognl表达式
0.值栈的理解
值栈保存在request中,底层由一个list和一个map组成。
list中保存了action中的成员变量。map中保存了request、session、application的引用还有对象
0.1action中获取值栈的方法
//从request中获得需要强转
ServletActionContext.getRequest().getAttrbute("struts.valueStack");
//从actionContext中获得
ServletActionContext.getContext().getValueStack();
valueStack.getContext()
// 值栈可以直接赋值,set方法放置对象到map中,map再放入到栈(List集合)上。即list中存有一个map存在0位置
valueStack.set("student", new Student());
0.2从值栈中获得list和map的方法
//获得list 没啥用 因为action中的成员变量都是自动放进去的
valueStack.getRoot();
//获得map 没啥用 因为reuqest、session、application还有对象都是自动放进去的
valueStack.getContext();
0.3值栈的作用范围
valuestack中即使存于request中,其中的map里存的request、session、app的作用范围不变,因为存的是对象的引用。
由于list中存的也是对象的引用,该对象真实存在于action中,所以作用范围是action,也就是说当访问同一个action的时候,共享valuestack的list中的内容。
但是action是多实例的,即每次不同的请求访问会创建新的action,所以只有当由struts2的过滤器执行完action转发到的页面才可以共享valuestack的内容。
0.4获得map(actionContext)并放入东西 用#可以取
ActionContext.getContext().put("roleList",roleList);
1.action中的成员变量为domainmodel对象引用时,可以在前台传参数来初始化这个对象,不传不构造,
也可以自己new。但是,如果要使用传参来初始化的时候domainmodel必须有一个空的构造方法。。
2.访问普通属性时从栈顶开始找此属性,找到就不往下找了
访问值栈中的action的普通属性 <s:property value="username"/> 直接写属性名
访问值栈中对象的普通属性<s:property value="cat.friend.name"/> 对象名.成员变量名(是对象).成员变量名
访问值栈中对象的普通方法<s:property value="password.length()"/> 对象名.方法名()
访问值栈中action的普通方法<s:property value="m()"/> 直接写方法名()
3.
访问静态方法 <s:property value="@com.bjsxt.struts.ognl.S@s()"/> @类名@方法名()
在struts中要有一个设置<constant name="struts.ognl.allowStaticMethodAccess" value="true"><constant>
设置后才允许静态方法访问,这些在default.properties配置文件中可以查到
访问静态属性 <s:property value="@com.bjsxt.struts.ognl.S@STR"/> @类名@属性名
访问Math类的静态方法:<s:property value="@@max(2,3)" /> @@方法名
4.
访问普通类的构造方法:<s:property value="new com.bjsxt.struts2.ognl.User(8)"/> new 全类名(参数)
访问List中某个元素:<s:property value="users[1]"/> List名[下标]
访问List中元素某个属性的集合:<s:property value="users.{age}"/> 把users中每个user拿出来,
再把每个user的age拿出来组成一个新的List。 list名.{属性名}
访问List中元素某个属性的集合中的特定值:<s:property value="users.{age}[0]"/> 把users中每个user拿出来,
再把每个user的age拿出来组成一个新的List,取第0个。 list名.{属性名}[下标]
访问Set:<s:property value="dogs"/>
访问Set中某个元素:<s:property value="dogs[1]"/> 由于set是无序的所以取不到
访问Map:<s:property value="dogMap"/>
访问Map中某个元素:<s:property value="dogMap.dog101"/> map名.key名 或者dogMap['dog101']或者dogMap[\"dog101\"]
访问Map中所有的key:<s:property value="dogMap.keys"/> 拿到了key的集合
访问Map中所有的value:<s:property value="dogMap.values"/> 拿到了value的集合
访问容器的大小:<s:property value="dogMap.size()"/> | <s:property value="users.size"/>不加()也可以,
ognl认为它是个属性。
5.
投影(过滤):<s:property value="users.{?#this.age==1}[0]"/>
从users(list)中拿出age为1的user并将其放入集合,取第0个。
投影:<s:property value="users.{^#this.age>1}.{age}"/>
从users中拿出age大于1的 取开头那个 把年龄输出
投影:<s:property value="users.{$#this.age>1}.{age}"/>
从users中拿出age大于1的 取结尾那个 把年龄输出
投影:<s:property value="users.{$#this.age>1}.{age} == null"/>
从users中拿出age大于1的 取结尾那个的年龄 看是否为空
6.
用[]访问valuestack里面的元素 action永远在栈顶
[]:<s:property value="[0].username"/> 此时访问的是从0位置开始也就是从action的位置
开始往下的元素直到栈底。如果用到服务器端跳转,用到的action会挨个被压入valuestack中。
7.#的用法 访问context中(map栈)的属性 #相当于actionContext.getContext
·确定域的话用
<s:property value="#request.username"/>
或者#request['userName']相当于request.getAttrbute("adminName");
·所有域
<s:property value="#attr.username"/>
·#可以构造map集合
<s:radio list="#('male'):'男','famale':'女'">
8.%的用法
告诉环境%{}中是ognl表达式 下式中的request.username会当做ognl解析
<s:textfield value="%{request.username}"></s:textfield>
告诉环境%{}中的值用单引号引起来就是字符串 下式中的request.username会当做字符串解析
<s:textfield value="%{'request.username'}"></s:textfield>
9.$的用法
在配置文件.xml文件中使用ognl表达式
<result>${r}</result>
10.ognl操作集合和数组
数组长度:<s:property value="#request.persons.size">
创建数组:<s:iterator value="{'aaa', 'bbb', 'ccc'}" var="x">
<s:property value="#x.toUpperCase()"/> |
</s:iterator>
操作map:<s:iterator value="{'key01':'value01', 'key02':'value02'">
<s:property/> | <s:property value="key"/>|<s:property value="value"/>
</s:iterator>
<s:iterator value="{'key01':'value01', 'key02':'value02'" var="map">
<s:property/> | <s:property value="#map.key"/>|<s:property value="#map.value"/>
</s:iterator>
●Struts2标签
首先,在前台页面中包含<%@taglib uri="/struts-tags" prefix="s" %>
·非UI标签
1.property
<s:property value="username"/>
将username属性的值打印出来 如果没有写value 则默认输出ValueStack栈顶的值。
<s:property value="'username'"/>
将username字符串打印出来
<s:property value="admin" default="管理员"/>
为admin属性设置默认值,如果valuestack中没有则打印默认值
<s:property value="'<hr/>'" escape="false"/>
默认为true,将标签转译成字符显示,而不是直接显示标签。
false时,不转译,即浏览器直接显示标签样式。
2.set 往指定或非指定范围内设定变量并指定值 value中默认解析ognl
常用于给actioncontext中的param中的变量命名
<s:set var="adminName" value="username" />
将名为adminName的变量设置到valueStack的list中和map中各有一份
如果不写value则将ValueStack栈顶的值赋给变量。
值为username的值,此时
<s:property value="#request.adminName" />
<s:property value="#adminName" />
都可以将adminName的值显示 #相当于actionContext.getContext
<s:set name="adminPassword" value="password" scope="page"/>
为adminPassword变量设定范围,name=这种写法已经被废弃
<s:set var="adminPassword" value="password" scope="session"/>
上面代码显示,
当scope(范围)为session时,
在debug标签中看到actionContext中的session有值,
<s:property value="#session.adminPassword"/>
所以必须从session中取而不能直接取值。
3.bean 创建name指定的类的对象并用var命名
不用var命名,会只在值栈中压入创建的对象,
标签结束时消除对象,所以标签外访问不到。
反之,则在stackcontext中,标签结束后同样可以取到。
<s:bean name="com.bjsxt.struts2.tags.Dog" var="myDog">
<s:param name="name" value="'oudy'"></s:param>
</s:bean>
param属性为对象的成员变量设置值,name为名,value为值。
注意value为ognl所以字符串要加单引号。
4.include 包含英文文件(中文会出问题)
<s:include value="/_include1.html"></s:include>
或者
<s:set var="incPage" value="'/_include1.html'" />
<s:include value="%{#incPage}"></s:include>
%{}的意思为将大括号中的内容强制当成ognl表达式
5.if elseif
url传参数后在actionContext中的parameters中会有对应参数和值
<s:property value="#parameters.age[0]" />可以查看参数age的值
<s:if test="#parameters.age[0] < 0">wrong age!</s:if>
<s:elseif test="#parameters.age[0] < 20">too young!</s:elseif>
<s:else>yeah!</s:else>
6.iterator
<s:iterator value="{1,2,3}">
<s:property/>|
</s:iterator>
ognl{}内为集合,iterator标签将集合中的内容遍历
如果没有指定var 默认将值(或对象)放入对象栈顶(循环完从栈顶移除)
指定了var 将对象栈顶和map栈同时放入一份
由<s:property/>依次打印出来
<s:iterator value="{'aaa', 'bbb', 'ccc'}" var="x">
<s:property value="#x.toUpperCase()"/> |
</s:iterator>
每次遍历将集合的元素赋予x,
在property标签中调用其转换大写方法转换为大写
<s:iterator value="{'aaa', 'bbb', 'ccc'}" status="status">
<s:property/> |
遍历过的元素总数:<s:property value="#status.count"/> |
遍历过的元素索引:<s:property value="#status.index"/> |
当前是偶数?:<s:property value="#status.even"/> |
当前是奇数?:<s:property value="#status.odd"/> |
是第一个元素吗?:<s:property value="#status.first"/> |
是最后一个元素吗?:<s:property value="#status.last"/>
</s:iterator>
在iterator中有个状态标签status,有许多属性,如元素总数,索引,奇偶等,
status其实是个对象 有isFirst、isodd等方法,保存在map栈中
<s:iterator value="#{1:'a', 2:'b', 3:'c'}" >
<s:property value="key"/> | <s:property value="value"/>
</s:iterator>
ognl中的map写法为 键:值
用<s:property value="key">取键,value="value"取值
<s:iterator value="#{1:'a', 2:'b', 3:'c'}" var="x">
<s:property value="#x.key"/> | <s:property value="#x.value"/>
</s:iterator>
用var=x代表其中一个条目,value=#x.key取这个条目的键,#x.value取这个条目的值
7.fielderror
当action类中
this.addFieldError("fielderror.test", "wrong!");
前台可以用fielderror将其显示
<s:fielderror fieldName="fielderror.test" theme="simple"></s:fielderror>
8.push标签
<s:push value=>
9.action标签 访问某action
<s:action name="XXXaction" namespace="xxxnamespace" executeresult="true"></s:action>
name:action名字(不包括后缀,如.action)
namespace:action所在命名空间
executeResult:Action的result是否需要被执行,默认值是false不执行
10.url标签 产生路径
<s:url value="ognlTagAction_test.action" namespace="/ognl" var="myurl">
<s:param name="id" value="12"></s:param>
<s:param name="cnname" value="%{'zhang'}"></s:param>
</s:url>
value:如果不提供就用当前action,使用value后缀必须加.action
action:用来生成url的action,如果没有则使用value
namespace :命名空间
var:引用变量的名称
中间可以加参数,参数会自动进行url编码
生成的url为http://主机名/namespace/value¶m
使用:<a href='<s:property value="#myurl" />' >xxxx</a><br>
----------------------------------------------
·UI标签
11.form标签
<body>
<s:form id="form1" name="form1" action="uiTagAction_save" namespace="/ui" method="post">
姓名:<s:textfield id="username" name="username" /> <br>
电话:<s:textfield id="tel" name="tel"/><br>
密码:<s:password name="psw" value="888888" showPassword="true"/><br>
<s:hidden name="hid" value="1"></s:hidden>
描述:<s:textarea name="des" value="xxx" rows="10" cols="10"></s:textarea>
<br><br><br>
<!--
爱好:<input type="checkbox" name="love1" value="true" id="form1_love1"/>
<input type="hidden" id="__checkbox_form1_love1" name="__checkbox_love1" value="true" />
fieldValue:属性为转化为checkbox标签时,value属性的值
如果没有fieldValue,则转化为checkbox标签,value的值为true/false
-->
爱好:<s:checkbox name="love1" fieldValue="梦游"/><br>
---------------------------------------------------------------------------------------------------<br>
<!--
如果集合是list
<input type="checkbox" name="love2" value="Java" id="love2-1" checked="checked"/>Java
<input type="checkbox" name="love2" value=".Net" id="love2-2" checked="checked"/>.Net
<input type="checkbox" name="love2" value="RoR" id="love2-3"/>RoR
<input type="checkbox" name="love2" value="PHP" id="love2-4"/>PHP
s:checkboxlist name="love2" list="{'Java','.Net','RoR','PHP'}" value="{'Java','.Net'}"
* 本例以'Java'为例
* 该标签生成的type="checkbox"的value值是java
* 该标签生成的type="checkbox"标签外的值是java
-->
爱好:<s:checkboxlist name="love2" list="{'Java','.Net','RoR','PHP'}" value="{'Java','.Net'}">
</s:checkboxlist>
<br>
<!--
如果集合是Map
{1:'瑜珈用品',2:'户外用品',3:'球类',4:'自行车'}"
<input type="checkbox" name="love3" value="1" id="love3-1" checked="checked"/>瑜珈用品
<input type="checkbox" name="love3" value="2" id="love3-2"/>户外用品
<input type="checkbox" name="love3" value="3" id="love3-3" checked="checked"/>球类
<input type="checkbox" name="love3" value="4" id="love3-4"/>自行车
s:checkboxlist name="love3" list="{1:'瑜珈用品',2:'户外用品',3:'球类',4:'自行车'}" value="{1,3}"
* 本例以 1:'瑜珈用品' 为例
* 该标签生成的type="checkbox"的value值是map中的key 这里值为1
* 该标签生成的type="checkbox"标签外的值是map中的value 这里值为瑜珈用品
-->
爱好:<s:checkboxlist name="love3" list="#{1:'瑜珈用品',2:'户外用品',3:'球类',4:'自行车'}" value="{1,3}">
</s:checkboxlist>
<%
List<Person> list=new ArrayList<Person>();
list.add(new Person(1,"tom"));
list.add(new Person(2,"mike"));
list.add(new Person(3,"jack"));
request.setAttribute("persons",list);
%>
<br>
<!-- 如果集合里存放的是javabean
<input type="checkbox" name="love4" value="1" id="love4-1" checked="checked"/>tom
<input type="checkbox" name="love4" value="2" id="love4-2"/>mike
<input type="checkbox" name="love4" value="3" id="love4-3" checked="checked"/>jack
s:checkboxlist name="love4" list="%{#request.persons}" listKey="id" listValue="name"
value="{1,3}"
* list="%{#request.persons}" 集合,集合中存放的是Person对象(属性id,name)
* listKey:对应的是person对象的id属性,转化后是type="checkbox"标签中value属性的值
* listValue:对应的是person对象的name属性,转化后是type="checkbox"标签外的值
-->
爱好:<s:checkboxlist name="love4" list="%{#request.persons}" listKey="id" listValue="name"
value="{1,3}">
</s:checkboxlist>
<br>
---------------------------------------------------------------------------------------------------<br>
<!--
如果集合里存放的是javabean(id和name为Person的属性)
<input type="radio" name="love5" id="form1_love51" checked="checked" value="1"/>tom
<input type="radio" name="love5" id="form1_love52" value="2"/>mike
<input type="radio" name="love5" id="form1_love53" value="3"/>jack
s:radio name="love5" list="%{#request.persons}" listKey="id" listValue="name"
value="1"
* list="%{#request.persons}" 集合,集合中存放的是Person对象(属性id,name)
* listKey:对应的是person对象的id属性,转化后是type="radio"标签中value属性的值
* listValue:对应的是person对象的name属性,转化后是type="radio"标签外的值
-->
爱好:<s:radio name="love5" list="%{#request.persons}" listKey="id" listValue="name"
value="1">
</s:radio>
<br>
<!--
如果集合为MAP
<input type="radio" name="love5" id="form1_love51" value="1"/>瑜珈用品
<input type="radio" name="love5" id="form1_love52" value="2"/><label for="form1_love52">户外用品</label>
<input type="radio" name="love5" id="form1_love53" checked="checked" value="3"/><label for="form1_love53">球类</label>
<input type="radio" name="love5" id="form1_love54" value="4"/><label for="form1_love54">自行车</label>
s:radio name="love6" list="{1:'瑜珈用品',2:'户外用品',3:'球类',4:'自行车'}" listKey="key" listValue="value"
* 本例以 1:'瑜珈用品' 为例
* 该标签生成的type="radio"的value值是map中的key 这里值为1
* 该标签生成的type="radio"标签外的值是map中的value 这里值为瑜珈用品
-->
爱好:<s:radio name="love6" list="#{1:'瑜珈用品',2:'户外用品',3:'球类',4:'自行车'}" listKey="key" listValue="value"
value="3">
</s:radio>
<br>
<!--
如果集合是list
<input type="radio" name="love7" id="form1_love7Java" checked="checked" value="Java"/>Java
<input type="radio" name="love7" id="form1_love7.Net" value=".Net"/><label for="form1_love7.Net">.Net</label>
<input type="radio" name="love7" id="form1_love7RoR" value="RoR"/><label for="form1_love7RoR">RoR</label>
<input type="radio" name="love7" id="form1_love7PHP" value="PHP"/><label for="form1_love7PHP">PHP</label>
s:radio name="love7" list="{'Java','.Net','RoR','PHP'}" value="'Java'"
* 本例以'Java'为例
* 该标签生成的type="radio"的value值是java
* 该标签生成的type="radio"标签外的值是java
-->
爱好:<s:radio name="love7" list="{'Java','.Net','RoR','PHP'}" value="'Java'">
</s:radio>
---------------------------------------------------------------------------------------------------<br>
<!--
如果集合是list
<select name="love8" id="form1_love8">
<option value="Java">Java</option>
<option value=".Net">.Net</option>
<option value="RoR">RoR</option>
<option value="PHP">PHP</option>
</select>
s:select name="love8" list="{'Java','.Net','RoR','PHP'}"
* 本例以'Java'为例
* 该标签生成的option标签中value的是java
* 该标签生成的option标签文本值的也是java
-->
<s:select name="love8" list="{'Java','.Net','RoR','PHP'}">
</s:select>
<br>
<!-- 如果集合里存放的是javabean(id和name为Person的属性)
<select name="love9" id="form1_love9">
<option value="1">tom</option>
<option value="2">mike</option>
<option value="3">jack</option>
</select>
s:select name="love9" list="%{#request.persons}" listKey="id" listValue="name"
* list="%{#request.persons}" 集合,集合中存放的是Person对象(属性id,name)
* listKey:对应的是person对象的id属性,转化后是option"标签中value属性的值
* listValue:对应的是person对象的name属性,转化后是option标签的文本值
-->
<s:select name="love9" list="%{#request.persons}" listKey="id" listValue="name">
</s:select>
<br>
<!--
如果集合为MAP
<select name="love10" id="form1_love10">
<option value=" ">请选择</option>
<option value="1">瑜珈用品</option>
<option value="2">户外用品</option>
<option value="3">球类</option>
<option value="4">自行车</option>
</select>
s:select name="love10" list="{1:'瑜珈用品',2:'户外用品',3:'球类',4:'自行车'}" listKey="key" listValue="value"
* 本例以 1:'瑜珈用品' 为例
* listKey:对应的是map集合中的key属性的值,转化后对应的是option标签中value属性的值
* listValue:对应的是map集合中的value属性的值,转化后对应的是option标签中的文本值
-->
<s:select name="love10" list="#{1:'瑜珈用品',2:'户外用品',3:'球类',4:'自行车'}" listKey="key" listValue="value"
headerKey="" headerValue="请选择">
</s:select>
<br>
<br>
---------------------------------------------------------------------------------------------------<br>
<s:submit type="submit" value="submit"></s:submit><br>
<s:submit type="button" value="button"></s:submit><br>
<s:submit type="image" value="image"></s:submit><br>
<s:reset type="reset" value="重置"></s:reset><br>
<s:reset type="button" value="重置"></s:reset><br>
</s:form>
</body>
12.主题控制
主题: 为了让所有的 UI 标签能够产生同样的视觉效果而归集到一起的一组模板. 即风格相近的模板被打包为一个主题
1、simple: 把 UI 标签翻译成最简单的 HTML 对应元素, 而且会忽视行标属性
2、xhtml: xhtml 是默认的主题. 这个主题的模板通过使用一个布局表格提供了一种自动化的排版机制. (默认值)
3、css_xhtml: 这个主题里的模板与 xhtml 主题里的模板很相似, 但它们将使用 css 来进行布局和排版
4、ajax: 这个主题里的模板以 xhtml 主题里德模板为基础, 但增加了一些 Ajax 功能.
修改主题:
A、通过 UI 标签的 theme属性(只适用于当前的标签)
<s:textfield name="username" label="用户名“
theme="simple"></s:textfield>
B、在一个表单里, 若没有给出某个 UI 标签的 theme 属性, 它将使用这个表单的主题
(适用于整个form标签)
<s:form action="" method="post" namespace="/ui“ theme="simple">
C、修改 struts.properties 文件中的 struts.ui.theme 属性. (适用整个环境)
<!-- 设置ui标签的主题 -->在sturts2.xml中写
<constant name="struts.ui.theme" value="simple"></constant>
优先级:A>B>C
●<s:token/>标签防止表单重复提交
javaWEB中如何处理表单重复提交
* 显示添加信息的jsp页面
* 在页面增加一个隐藏域
<input type="hidden" value="xxx145678990000000"/>
* 同时把生成的唯一值,放置到session中一份
session.setAttribute("token.session","xxx145678990000000")
* 提交信息给servlet
* 获取表单中隐藏域的值 <input type="hidden" value="xxx145678990000000"/>
* 获取sesison的值 session.getAttribute("token.session")
* 比对session的值和表单中隐藏域的值
* 如果相等:提交表单
* 不相等:表单重复提交了
* struts2中如何实现表单重复提交
* 在添加信息页面的<s:form>表单中增加 <s:token></s:token>
<s:form name="form1" namespace="/model" action="userAction_save" method="post">
<s:token></s:token>
用户名:<s:textfield name="username"/><br>
电话:<s:textfield name="tel" /><br>
描述:<s:textfield name="des" /><br>
<s:submit type="submit" value="保存"></s:submit>
</s:form>
* 配置拦截器栈,由于令牌拦截器不在defaultStack栈中,需要重新配置
在package中增加
<interceptors>
<interceptor-stack name="tokenStack">
<interceptor-ref name="defaultStack"/>
<interceptor-ref name="token">
<!-- 配置令牌拦截器拦截哪些方法,如果有多个方法,用","隔开 -->
<param name="includeMethods">save,edit</param>
</interceptor-ref>
</interceptor-stack>
</interceptors>
<default-interceptor-ref name="tokenStack"/>
* 配置result
<!-- 当表单重复提交时,转向invalid.token指向的页面 -->
<result name="invalid.token">/model/error.jsp</result>
* 在jsp页面使用 <s:actionerror/>显示错误信息
* 默认的错误提示信息是英文的,在struts2-core-2.1.8.1.jar\org\apache\struts2\struts-messages.properties中
内容如下:
struts.messages.invalid.token=The form has already been processed or no token was supplied, please try again.
* 修改为中文
* 在于当前的action下创建token.properties文件
增加如下内容:
struts.messages.invalid.token=您的表单已经重复提交了,请返回添加页面,刷新重试.
* 加载资源文件
<!-- 配置加载自定义的国际化资源文件,有多个的话,用","隔开
* cn.itcast.converter.converter:加载cn.itcast.converter.converter.properties资源文件
* cn.itcast.upload.fileupload:加载cn.itcast.upload.fileupload.properties资源文件
* cn.itcast.i18n.resource:加载国际化资源文件
* cn.itcast.model.token:处理表单重复提交的资源文件
-->
<constant name="struts.custom.i18n.resources"
value="cn.itcast.converter.converter,
cn.itcast.upload.fileupload,
cn.itcast.i18n.resource,
cn.itcast.model.token"></constant>
●对一些默认情况的修改(default.properties)
疑问 struts.xml中的constant标签有哪些属性
default.properties中所有的属性都可以用constant进行修改
<constant name="struts.devMode" value="true"></constant> 开启热部署(开发模式)包括i18n加载和struts.xml修改加载
<constant name="struts.ognl.allowStaticMethodAccess" value="true"><constant> 允许访问静态方法
<constant name="struts.i18n.encoding" value="GBK" />
<constant name="struts.configuration.xml.reload" value="true" />当struts.xml修改时自动加载
解决中文问题 国际化全局配置
<constant name="struts.custom.i18n.resource" value="XXXX">
</constant>
<constant name="struts.enable.DynamicMethodInvocation" value="false" />禁用动态方法调用
如果找不到命名空间,就去父级找。如/xxx/xxx/xxx/a.action找不到就找xxx/xxx/a.action以此类推
<default-action-ref name="index"></default-action-ref>如果请求路径找不到action则执行默认配置的action
<constant name="struts.action.extension" value="do,go,action"></constant>默认的请求后缀为.action和没有后缀 如果需要更改则在配置文件中写 逗号隔开
●struts Exception handling—声明式异常处理
在struts中 class中的方法可以在捕获异常后将其抛出,
这样可以支持struts的声明式处理任何异常。
在struts.xml中
<global-results>
<result name="error">/error.jsp</result>
</global-results>
<global-exception-mappings>
<exception-mapping result="error"exception="java.lang.Exception">
</exception-mapping>
</global-exception-mappings>
必须把results写前面!
对异常做出映射,当发生java.lang.Exception时,返回error这个result.
10.struts <default-action-ref >的bug问题
该设置不会解析action的方法只会简单的将result返回BUG。
要想设置首页,目前只能在web.xml中配置
<welcome-file-list>
<welcome-file>index</welcome-file>
</welcome-file-list>
●i18n:支持程序国际化
1.i18n原理
a)ResourceBundle和Locale的概念
ResourceBundle res = ResourceBundle.getBundle("app", Locale.CHINA);
System.out.println(res.getString("welcome.msg" ));
b)资源文件
app_en_US.properties app_英语缩写_美国.properties
内容: welcome.msg=hello,sir
app_zh_CH.properties app_中华缩写_中国.properties
c)native2ascii jdk/bin/native2ascii 可以将中文转换成iso8859-1
2.Struts的资源文件
a)app级 见原理
b)propertiesEditor插件
i. 解压
ii. features plugin 覆盖到myeclipse中的eclipse目录里
3.Action级别国际化
<constant name="struts.i18n.reload" value="true"></constant> 此配置为当国际化文件修改时自动加载
a)jsp中
<s:property value="getText('login.username')"/>
或者<s:text name="login.username" />
这里可以直接调用方法说明action中有这个方法
这个方法其实是Actionsupport中的方法
b)LoginAction_en_US.properties中
login.username=username
login.password=password
login.login=login
4.package级别国际化 在包内
package_en_US.propertiess
5.全局级别国际化 (常用)
XXXX_en_US.properties建立在src目录内
<constant name="struts.custom.i18n.resource" value="cn.itcast.converter.XXXX_en_US">
</constant>
6.动态字符的国际化
properties中
welcome.msg=welcome:{0}
jsp文件中
<s:text name="welcome.msg">
<s:param value="username"></s:param>
</s:text>
7.动态语言切换
struts中可以直接在url后传参数实现动态语言切换
"admin/lang?request_locale=en_US"
●i18n:支持程序国际化2
如何实现软件的国际化
* 针对不同的国家,定义不同的资源文件
* 资源文件的格式:基名_语言_国家.properties
* 默认的资源文件(当其他的资源文件找不到时,执行默认的资源文件):格式:基名.properties
* 针对中文的资源文件
* 文件的名称 resource_zh_CN.properties
* 内容
item.username=用户名
item.password=密码
item.login=登陆
* 针对英文的资源文件
* 文件的名称 resource_en_US.properties
* 内容
item.username=username_en
item.password=password_en
item.login=login_en
* 默认的资源文件
* 文件的名称 resource.properties
* 内容
item.username=username
item.password=password
item.login=login
* 加载资源文件
* 在struts.xml文件增加如下配置
<!-- 配置加载自定义的国际化资源文件,有多个的话,用","隔开
* cn.itcast.converter.converter:加载cn.itcast.converter.converter.properties资源文件
* cn.itcast.upload.fileupload:加载cn.itcast.upload.fileupload.properties资源文件
* cn.itcast.i18n.resource:加载国际化资源文件
-->
<constant name="struts.custom.i18n.resources"
value="cn.itcast.converter.converter,
cn.itcast.upload.fileupload,
cn.itcast.i18n.resource"></constant>
* 在jsp页面使用
* 引入struts2的标签库
<%@ taglib uri="/struts-tags" prefix="s" %>
* 使用<s:text name="item.username">获取
* name属性的值应该是资源文件中的key
●类型转换
·当类型转换失败时 会跳转到由input属性所指向的页面 在页面给出提示信息
<result name="input">/xxxx/error.jsp</result>
·已经提供的转换器
由字符串到整形、字符串到字符串、字符串到util.date类型的格式必须为yyyy-mm-dd
·自定义转换器(局部)
接口TypeConverter为所有转换器的父接口
1)在实际开发中可以自定义类继承DefaultTypeConverter类或者子类StrutsTypeConverter
2)重写converValue(Map<String,Object> context,Object value,Class toType)方法
/*首先明确该转换器要完成java.lang.String---->java.util.Date类型的转换
* 例如要把20111212---->Date(默认的转换器只能转换yyyy-mm-dd所以自定义一个接受yyyymmdd)
* 方法的返回值就是action中属性brithday接收到的值
* context为ognl上下文
* value是要转换的值(是个参数数组因为rerequest.getParameterValues("birthday")取到是数组)
* toType是要转换的类型
*/
a.在方法中判断各项参数是否为空 如果为空就返回空
b.然后判断传来的toType是否为我预先设定要的类型 比如你传来一个java.util.date那我这个转换器实现不了 返回空
c.判断value instanceof java.lang.String
d.判断value[0]是否为空
e.判断都通过了就进行转换SimpleDateFormat sf = new SimpleDateFormat("yyyyMMdd"); return sf.parse(String[0]);
f.如果出转换异常了就抓住 打印信息后再抛一个运行时异常 否则类型转换拦截器不能捕获到异常 就不会跳转到异常页面
g.在异常页面<s:fielderror/>显示错误信息(引入<%@ taglib uri="/struts-tags" prefix="s" >)
struts2的错误拦截器把错误信息放到一个集合中 由以上标签遍历
默认的显示错误信息为英文 改成中文则需要建立配置文件xxx.priperties 增加内容
xwork.default.invalid.fieldvalue=类型转换出现了错误"{0}".
增加具体的对每个action的提示
invalid.fieldvalue.xxx=提示信息 其中xxx为action的属性名称
<constant name="struts.custom.i18n.resource" value="cn.itcast.converter.xxx">
</constant>
3)配置转换器
a.创建一个xxxAction-conversion.properties 与action放在同一个目录内
b.在此文件中写 属性名称=类型转换器的全类名 如 brithday=cn.itcast.Converter
·自定义类型转换(全局)
1)前两步与自定义局部转换器相同
2)前两步与自定义局部转换器相同
3)配置转换器
a.创建一个xwork-conversion.properties 放在src目录下
b.在此文件中写 属性名称=类型转换器的全类名 如 brithday=cn.itcast.Converter
●文件上传
·导包
commons-fileupload-1.2.1.jar、commons-io-1.3.2.jar
·前台页面
<form action="${pageContext.request.contextPath}/upload/uploadAction_saveFile.action"
name="form1" method="post" enctype="multipart/form-data" >
上传文件名称:<input type="file" name="uploadImage">
<input type="submit" value="上传">
</form>
·后台程序
action
public class UploadAction extends ActionSupport {
/*
* * 上传文件名称:<input type="file" name="uploadImage"> 对应的是file上传组件name属性的值
* * 上传的文件先保存为临时文件,目录和名称如下
* E:\\apache-tomcat-6.0.18\\work\\Catalina\\localhost\\itcast0706struts2\\upload__24bfe9c0_1311be9e985__8000_00000000.tmp
* * 当程序处理完毕是,struts2会删除该临时文件
* Removing file uploadImage
* E:\\apache-tomcat-6.0.18\\work\\Catalina\\localhost\\itcast0706struts2\\upload__24bfe9c0_1311be9e985__8000_00000000.tmp
*/
private File uploadImage;
//上传文件的类型[格式:上传组件name属性的值+ContentType]
private String uploadImageContentType ;
//上传文件的真实名称[格式:上传组件name属性的值+FileName]
private String uploadImageFileName;
public String saveFile(){
System.out.println(uploadImageContentType+" "+uploadImageFileName);
ServletContext sc=ServletActionContext.getServletContext();
String realPath=sc.getRealPath("/pic");
try {
File destFile=new File(realPath,uploadImageFileName);
FileUtils.copyFile(uploadImage, destFile);
} catch (IOException e) {
e.printStackTrace();
}
return "success";
}
}
·配置 创建strurs_upload.xml文件
<struts>
<!--配置上传文件的总开关,控制上传文件的大小(默认值是2M),是上传组件自带的
org.apache.commons.fileupload.FileUploadBase$SizeLimitExceededException,
抛出的异常是上传组件自带的,
-->
<constant name="struts.multipart.maxSize" value="222097152"></constant>
<package name="upload" namespace="/upload" extends="struts-default">
<action name="uploadAction_*" class="cn.itcast.upload.UploadAction" method="{1}">
<!-- 修改默认栈中上传拦截器的属性值-->
<interceptor-ref name="defaultStack">
<!-- 控制上传文件的大小,分开关,这个开关是struts2提供的 -->
<param name="fileUpload.maximumSize">12097152</param>
<!-- 配置允许上传文件的类型,如果有多个用","隔开 -->
<param name="fileUpload.allowedTypes">text/plain,application/vnd.ms-powerpoint</param>
<!-- 配置允许上传文件的扩展名,如果有多个用","隔开-->
<param name="fileUpload.allowedExtensions ">ppt</param>
</interceptor-ref>
<result name="success">/upload/success.jsp</result>
<!--当文件上传失败时,要转到由input属性所指向的页面,在该页面给出提示信息-->
<result name="input">/upload/error.jsp</result>
</action>
</package>
</struts>
·处理错误信息
* 在处理错误信息的jsp页面(error.jsp)
* 引入<%@ taglib uri="/struts-tags" prefix="s"%>
* 使用<s:fielderror></s:fielderror>显示错误信息
* 默认显示的错误信息是英文的,该英文信息存在struts2-core-2.1.8.1.jar\org\apache\struts2\struts-messages.properties文件中
内容如下
struts.messages.error.uploading=Error uploading: {0}
struts.messages.error.file.too.large=File too large: {0} "{1}" "{2}" {3}
struts.messages.error.content.type.not.allowed=Content-Type not allowed: {0} "{1}" "{2}" {3}
struts.messages.error.file.extension.not.allowed=File extension not allowed: {0} "{1}" "{2}" {3}
注:{0}:html页面上传组件的name属性的值:<input type="file" name="uploadImage">
{1}:上传文件的真实名称
{2}:上传的临时文件(保存到临时目录的临时名称)
{3}:上传文件的类型(对struts.messages.error.file.too.large来说表示上传文件的大小)
* 修改上传文件的错误信息为中文
* 在于当前action的同级目录(其他目录也可),创建fileupload.properties文件,文件名称自定义
内容如下
struts.messages.error.uploading=上传错误: {0}
struts.messages.error.file.too.large=文件太大: {0} "{1}" "{2}" {3}
struts.messages.error.content.type.not.allowed=类型不允许: {0} "{1}" "{2}" {3}
struts.messages.error.file.extension.not.allowed=文件扩展名不允许: {0} "{1}" "{2}" {3}
* 在struts.xml文件中加载该资源文件
<!-- 配置加载自定义的国际化资源文件,有多个的话,用","隔开
* cn.itcast.converter.converter:加载cn.itcast.converter.converter.properties资源文件
* cn.itcast.upload.fileupload:加载cn.itcast.upload.fileupload.properties资源文件
-->
<constant name="struts.custom.i18n.resources"
value="cn.itcast.converter.converter,
cn.itcast.upload.fileupload"></constant>
·多文件上传的action
public class UploadsAction extends ActionSupport {
private File[] uploadImages;
private String[] uploadImagesContentType ;
private String[] uploadImagesFileName;
public String saveFiles(){
ServletContext sc=ServletActionContext.getServletContext();
String realPath=sc.getRealPath("/pic");
try {
if(uploadImages!=null&&uploadImages.length>0){
for(int i=0;i<uploadImages.length;i++){
File destFile=new File(realPath,uploadImagesFileName[i]);
FileUtils.copyFile(uploadImages[i], destFile);
}
}
} catch (IOException e) {
e.printStackTrace();
}
return "success";
}
public File[] getUploadImages() {
return uploadImages;
}
public void setUploadImages(File[] uploadImages) {
this.uploadImages = uploadImages;
}
public String[] getUploadImagesContentType() {
return uploadImagesContentType;
}
public void setUploadImagesContentType(String[] uploadImagesContentType) {
this.uploadImagesContentType = uploadImagesContentType;
}
public String[] getUploadImagesFileName() {
return uploadImagesFileName;
}
public void setUploadImagesFileName(String[] uploadImagesFileName) {
this.uploadImagesFileName = uploadImagesFileName;
}
·多文件上传的配置
<action name="uploadsAction_*" class="cn.itcast.upload.UploadsAction" method="{1}">
<result name="success">/upload/success.jsp</result>
<!--当文件上传失败时,要转到由input属性所指向的页面,在该页面给出提示信息-->
<result name="input">/upload/error.jsp</result>
</action>
●拦截器 单实例运行
·理解原理
链接中访问Useraction中的save方法
被过struts2滤器拦截
被struts2拦截器挨个拦截
拦截器通过你的访问请求发现你访问的方法(可以通过配置设置哪些方法需要被拦截)
当你有权限访问(session中有值的时候)就通过产生的代理对象调用action的你要访问的方法
(拦截器的放行方法执行过程:当拦截器栈不为空时,拿出下一个拦截器,调用下一个拦截器,该方法等待调用方法的返回,在下一个拦截器内部也执行放行方法,从而方法栈中压入了一堆方法等待返回,最终如果调用了
action则返回action执行的结果的字符串。)
从源码中看出,如果拦截器没有执行放行而直接返回,则返回字符串有效,当拦截器执行了放行则返回的字符串无效,最终执行的action的返回值有效。
当你没有权限访问的时候 返回字符串 然后从xml中找到result返回视图
·自定义拦截器
1)拦截器类
public class PerssionInterceptor implements Interceptor {
public PerssionInterceptor(){
System.out.println("PerssionInterceptor 拦截器的构造方法");
}
//初始化方法,在拦截器的声明周期中,该方法执行一次
public void init() {
System.out.println("PerssionInterceptor init");
}
/**
* 在拦截器的声明周期中,该方法执行多次
* ActionInvocation: 代表一个给定动作的执行状态, 拦截器可以从该类的对象里获得与该动作相关联的 Action 对象和 Result 对象
* * 由struts2创建,在运行时注入
*/
public String intercept(ActionInvocation invocation) throws Exception {
//请求的UserAction对象
System.out.println("invocation.getAction() "+invocation.getAction());
//请求的UserAction对象的代理对象
System.out.println("invocation.getProxy().getAction() "+invocation.getProxy().getAction());
//访问的action中的方法 svae
System.out.println("invocation.getProxy().getMethod() "+invocation.getProxy().getMethod());
//访问的action所在的命名空间 /aop
System.out.println("invocation.getProxy().getNamespace() "+invocation.getProxy().getNamespace());
//获取session
Map sessionMap=invocation.getInvocationContext().getSession();
//从session中获取user
Object user=sessionMap.get("user");
//如果user==null
if(user==null){
//转到没有权限的页面
return "error";
}
//真正调用action的save方法
String resultValue=invocation.invoke();
System.out.println("resultValue "+resultValue);
return resultValue;
}
//销毁方法,在拦截器的声明周期中,该方法执行一次
public void destroy() {
System.out.println("PerssionInterceptor destroy");
}
}
2)配置
在struts_aop.xml文件配置拦截器
全局拦截器配置:
<interceptors>
<!-- 声明自定义的拦截器 -->
<interceptor name="perssionInterceptor" class="cn.itcast.aop.PerssionInterceptor" />
<!-- 定义新的拦截器栈 -->
<interceptor-stack name="perssionStack">
<!-- 引用默认栈-->
<interceptor-ref name="defaultStack"/>
<!-- 增加自定义的拦截器 -->
<interceptor-ref name="perssionInterceptor"/>
</interceptor-stack>
</interceptors>
<!--struts2执行时,真正要执行的拦截器栈-->
<default-interceptor-ref name="perssionStack"/>
action级别拦截器配置:
<package name="test" namespace="/" extends="struts-default">
<interceptors>
<interceptor name="perssionInterceptor" class="cn.itcast.aop.PerssionInterceptor" />
</interceptors>
<action name="test" class="cn.itcast.aop.TestAction">
<result>/test.jsp</result>
<interceptor-ref name="perssionInterceptor"></interceptor-ref>
<interceptor-ref name="defaultStack"></interceptor-ref><!--这句话必须写否则没有系统自带的interceptor -->
</action>
</package>
·针对特定的方法进行拦截的拦截器 继承MethodFilterInterceptor 重写doIntercept方法
public class Copy_2_of_PerssionInterceptor extends MethodFilterInterceptor {
public String doIntercept(ActionInvocation invocation) throws Exception {
//请求的UserAction对象
System.out.println("invocation.getAction() "+invocation.getAction());
//请求的UserAction对象的代理对象
System.out.println("invocation.getProxy().getAction() "+invocation.getProxy().getAction());
//访问的action中的方法 svae
System.out.println("invocation.getProxy().getMethod() "+invocation.getProxy().getMethod());
//访问的action所在的命名空间 /aop
System.out.println("invocation.getProxy().getNamespace() "+invocation.getProxy().getNamespace());
//获取session
Map sessionMap=invocation.getInvocationContext().getSession();
//从session中获取user
Object user=sessionMap.get("user");
//如果user==null
if(user==null){
//转到没有权限的页面
return "error";
}
//真正调用action的save方法
String resultValue=invocation.invoke();
System.out.println("resultValue "+resultValue);
return resultValue;
}
}
·针对特定的方法进行拦截的拦截器的配置 为拦截器增加参数
<interceptors>
<!-- 声明自定义的拦截器 -->
<interceptor name="perssionInterceptor" class="cn.itcast.aop.PerssionInterceptor" />
<!-- 定义新的拦截器栈 -->
<interceptor-stack name="perssionStack">
<!-- 引用默认栈-->
<interceptor-ref name="defaultStack"/>
<!-- 增加自定义的拦截器 -->
<interceptor-ref name="perssionInterceptor">
<!-- 此配置可以让拦截器只对save方法进行拦截 多个方法用“,”隔开 -->
<param name="includeMethod">save</param>
<interceptor-ref/>
</interceptor-stack>
</interceptors>
<!--struts2执行时,真正要执行的拦截器栈-->
<default-interceptor-ref name="perssionStack"/>
·使用struts的token拦截器
<action>
<interceptor-ref name="defaultStack"></interceptor-ref>
<interceptor-ref name="token"></interceptor-ref>
<result name="invalid.token">/error.jsp</result>
<action>
最后一句写明当返回为invalid.token时的结果页面
●表单验证
·手工编写代码实现
1)前台表单页面
<body>
<br>
<s:fielderror fieldName="username"></s:fielderror>
<s:fielderror fieldName="psw"></s:fielderror>
<s:form id="loginForm" name="loginForm" method="post" namespace="/validate"
action="validateAction_login.action" theme="simple">
<table border="1">
<tr>
<td>用户名</td>
<td> <s:textfield name="username" /></td>
</tr>
<tr>
<td>密码</td>
<td><s:password name="psw" /></td>
</tr>
<tr>
<td> </td>
<td><s:submit value="登陆"/></td>
</tr>
</table>
</s:form>
</body>
2)验证action
//说白了就是拿到你给我注入的参数 在复写的方法里进行验证 验证通过也就是不往错误列表加信息 struts就给你调你要调的方法
//验证不通过 也就是你往错误列表加信息了 struts就返回你配置好的错误页面了
public class ValidateAction extends ActionSupport {
private String username;
private String psw;
public void setUsername(String username) {
System.out.println("setUsername方法");
this.username = username;
}
/**
* 需求:
* * 用户名不能为null ,""
* * 密码不能为null, "" 并且密码的长度6-12之间
*
* struts2中如何进行验证:
* * action必须要继承ActionSupport或者实现Validateable接口
* * action必须要重写validate()方法
* * setUsername(接收客户端值) validate(验证方法) login(业务方法)的执行顺序
* * setUsername(接收客户端值)
* * validate(验证方法)
* * login(业务方法)
*
* * 定义验证出错要转向的错误页面
* <result name="input">/validate/login.jsp</result>
*
* * 什么表示验证出错呢
* * 什么时候表示验证出错
* * 放置错误信息的集合中!=null,并且集合中存在错误信息,转到错误处理页面
*
* * 什么时候表示验证通过
* * 放置错误信息的集合中==null //Map==null
* * 放置错误信息的集合中!=null,但是集合中没有错误信息
* * 表示验证通过,执行业务方法login
*
* * <s:fielderror></s:fielderror>:遍历集合,显示错误信息
*
* 底层:
* addFieldError("username", "用户名不能为空");
*
* public synchronized void addFieldError(String fieldName, String errorMessage) {
Map<String, List<String>> errors = internalGetFieldErrors(); //获取map集合 key为fieldName value为错误信息list
List<String> thisFieldErrors = errors.get(fieldName); //获取list集合
if (thisFieldErrors == null) { //list集合==null
thisFieldErrors = new ArrayList<String>(); //创建新的list集合
//errors.put("username",list);
errors.put(fieldName, thisFieldErrors); //放入list和到map中
}
//thisFieldErrors.add("用户名不能为空")
thisFieldErrors.add(errorMessage); //放入错误信息到list集合
}
*
*
*
* * validate对action中的所有方法都进行验证
* * 如果对action中执行指定的方法进行验证呢?
* * 格式 validate+要访问的业务方法(首字母大写) ---validateLogin
*
*/
public void validateLogin() {
System.out.println("username ="+username +" psw="+psw);
System.out.println("validate方法");
if(username==null||"".equals(username.trim())){
this.addFieldError("username", "用户名不能为空");
}
if(psw==null||"".equals(psw.trim())){
this.addFieldError("psw", "密码不能为空");
}else{
Pattern pattern=Pattern.compile("^[0-9a-zA-Z]{6,12}$");
Matcher matcher=pattern.matcher(psw);
if(!matcher.matches()){
this.addFieldError("psw", "密码的长度应该在6-12之间");
}
}
}
public void validateTest() {
System.out.println("test username ="+username +" psw="+psw);
}
public String login(){
System.out.println("login方法");
return "success";
}
public String test(){
System.out.println("test方法");
return "success";
}
public String getUsername() {
return username;
}
public String getPsw() {
return psw;
}
public void setPsw(String psw) {
this.psw = psw;
}
}
3)xml配置
<struts>
<package name="validate" namespace="/validate" extends="struts-default">
<action name="validateAction_*" class="cn.itcast.validate.ValidateAction" method="{1}">
<result name="success">/validate/success.jsp</result>
<!-- 验证出错要转向的错误页面 -->
<result name="input">/validate/login.jsp</result>
</action>
<action name="validateXmlAction_*" class="cn.itcast.validate.ValidateXmlAction" method="{1}">
<result name="success">/validate/success.jsp</result>
<!-- 验证出错要转向的错误页面 -->
<result name="input">/validate/loginxml.jsp</result>
</action>
</package>
</struts>
·基于xml使用struts2自带的验证
理解 struts会根据你的类名找到你这个类的验证配置文件 对类中的字段进行验证
1)表单页面
<body>xml页面
<br>
<s:fielderror/>
<s:form name="loginForm" method="post" namespace="/validate"
action="validateXmlAction_login.action" theme="simple" >
<table border="1">
<tr>
<td>用户名</td>
<td> <s:textfield name="username" /></td>
</tr>
<tr>
<td>密码</td>
<td><s:password name="psw" /></td>
</tr>
<tr>
<td>年龄[年龄不能小于0]</td>
<td><s:textfield name="age"/></td>
</tr>
<tr>
<td> </td>
<td><s:submit value="登陆"/></td>
</tr>
</table>
</s:form>
<a href="${pageContext.request.contextPath}/validate/validateXmlAction_test.action">test其他的方法</a>
</body>
2)action类
public class ValidateXmlAction extends ActionSupport {
private String username;
private String psw;
public void setUsername(String username) {
System.out.println("setUsername方法");
this.username = username;
}
public String login(){
System.out.println("login方法");
return "success";
}
public String test(){
System.out.println("test方法");
return "success";
}
}
3)对struts.xml配置
<action name="validateXmlAction_*" class="cn.itcast.validate.ValidateXmlAction" method="{1}">
<result name="success">/validate/success.jsp</result>
<!-- 验证出错要转向的错误页面 -->
<result name="input">/validate/loginxml.jsp</result>
</action>
4)对验证器的xml的配置
* 验证的规范规范在xwork-core-2.1.6.jar\xwork-validator-1.0.3.dtd
* 验证规则的配置文件:xwork-core-2.1.6,jar\com\opensymphony\xwork2\validator\validators\default.xml
a.对action中所有的方法进行验证
* xml文件名称定义的格式: actionClassName-validation.xml
* actionClassName是请求的action的简单类名
* -validation.xml固定写法
* 该xml文件要和action放置在同一个目录下
* 本例为:ValidateXmlAction-validation.xml
* 配置如下 :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.3//EN"
"http://www.opensymphony.com/xwork/xwork-validator-1.0.3.dtd">
<validators>
<!--
field:字段验证
* name="username":要验证的action中的属性,这里是ValidateXmlAction的username属性
-->
<field name="username">
<!--
这里验证用户名不能为空
field-validator:指定验证器
type:验证规则:validator name="requiredstring"
class="com.opensymphony.xwork2.validator.validators.RequiredStringValidator"
-->
<field-validator type="requiredstring">
<!-- 调用RequiredStringValidator中的setTrim(true) -->
<param name="trim">true</param>
<!-- 验证出错的提示信息 -->
<message><![CDATA[用户名不能为空]]></message>
</field-validator>
</field>
<field name="psw">
<!--
这里验证用户名不能为空
field-validator:指定验证器
type:验证规则:validator name="requiredstring"
class="com.opensymphony.xwork2.validator.validators.RequiredStringValidator"
-->
<field-validator type="requiredstring">
<!-- 调用RequiredStringValidator中的setTrim(true) -->
<param name="trim">true</param>
<!-- 验证出错的提示信息 -->
<message><![CDATA[密码不能为空]]></message>
</field-validator>
<!-- 验证密码的长度在6-12之间
validator name="regex"
class="com.opensymphony.xwork2.validator.validators.RegexFieldValidator" -->
<field-validator type="regex">
<param name="expression"><![CDATA[^[a-zA-Z0-9]{6,12}$]]></param>
<message><![CDATA[密码的长度应该在6-12之间]]></message>
</field-validator>
</field>
</validators>
b.对action指定的方法进行验证
* xml文件名称定义的格式: actionClassName-actionName-validation.xml
* actionClassName是请求的action的简单类名
* actionName:请求的action的名称
* -validation.xml固定写法
* 该xml文件要和action放置在同一个目录下
* 本例为:ValidateXmlAction-validateXmlAction_login-validation.xml
* 配置与对action中所有的方法进行验证的相同
·自定义验证规则
1)验证规则类
public class AgeValidate extends FieldValidatorSupport {
public void validate(Object object) throws ValidationException {
//object cn.itcast.validate.ValidateXmlAction 就是那个action类 因为action类继承了actionSupport所以里面封装了addFieldError
//在这个类的addFieldError方法中肯定先从配置文件中取出错误信息然后调用actionSupport的addFieldError方法
System.out.println("object "+object);
//获取字段的名称
String fieldName=this.getFieldName();
//获取字段的值
Object fieldValue=this.getFieldValue(fieldName, object);//struts从配置文件中读取出字段的名字注入到了这个变量中
//判断字段的值是否是Integer类型
if(fieldValue instanceof java.lang.Integer){
//转化为Integer类型
Integer age=(Integer)fieldValue;
//和0比对
//如果小于0,增加错误提示信息
if(age<0){
this.addFieldError(fieldName, object);
}
}
}
}
2)注册验证规则
* 在当前工程的src下创建validators.xml文件
* 该文件的规范在xwork-core-2.1.6.jar\xwork-validator-config-1.0.dtd
* 配置如下
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator Config 1.0//EN"
"http://www.opensymphony.com/xwork/xwork-validator-config-1.0.dtd">
<validators>
<!--validator声明验证规则
* name:验证规则的名称(唯一)
* class验证规则对应的类
-->
<validator name="ageValidate" class="cn.itcast.validate.AgeValidate"></validator>
</validators>
3)在ValidateXmlAction-validateXmlAction_login-validation.xml增加如下配置
<!-- 验证年龄字段 -->
<field name="age">
<field-validator type="ageValidate">
<message><![CDATA[年龄不能为负数]]></message>
</field-validator>
</field>