1. 文件下载
1.1传统方式
/**
* TODO用传统的方式实现文件下载
*优点:运用传统的web只是,好理解
*缺点:在Action中引入了Servlet的api,导致struts2和servlet耦合
*/ publicclass DownloadActionextends ActionSupport {
private StringfileName; //要下载的文件的名字
public StringgetFileName() {
returnfileName;
}
publicvoid setFileName(StringfileName)throws Exception {
/*
*因为文件名是可能通过GET方式提交过来的,所以要转码
* PSOT提交乱码问题框架自己会处理
*/
if("GET".equalsIgnoreCase(ServletActionContext.getRequest().getMethod())){
fileName=new String(fileName.getBytes("ISO-8859-1"),"UTF-8");
}
this.fileName = fileName;
}
public String download()throws Exception {
//假设要下载的文件在/WEB-INF/download目录下
HttpServletRequestrequest = ServletActionContext.getRequest();
HttpServletResponseresponse = ServletActionContext.getResponse();
//得到要下载的文件的输入流
InputStreamis = ServletActionContext.getServletContext().getResourceAsStream("/WEB-INF/download/" +fileName);
/*
*判断用户使用的浏览器类型
*分别处理文件下载时名字乱码问题
*/
StringuserAgent = request.getHeader("User-Agent");//获取浏览器的类型 Firefox?IE?
if(userAgent.contains("Firefox")) {//处理火狐下载文件名乱码问题,以UTF-8编码,用ISO-8859-1解码
fileName =new String(fileName.getBytes("UTF-8"),"ISO-8859-1");
}elseif(userAgent.contains("IE")) {//处理IE下载文件名乱码问题,以UTF-8编码
fileName = URLEncoder.encode(fileName,"UTF-8");
}
response.setContentType("application/x-msdownload");//告诉客户端内容是下载类型的,要在获取输出流之前设置(经测试,没有这句也可以)
response.setHeader("Content-Disposition","attachment;filename=" +fileName); //设置下载文件的在客户端显示的名字,中文会乱码(已处理)
OutputStreamos = response.getOutputStream();
byte[] buf =newbyte[1024];
int len = 0;
while((len =is.read(buf)) != -1) {
os.write(buf,0, len);
os.flush();
}
if(os !=null) os.close();
if(is !=null) is.close();
returnnull;//因为下载完成就可以关闭浏览器窗口了,无需显示页面,所以返回null
}
}
1.2结合框架
1)请求的时参数有中文的话要先编码,虽然浏览器可会自动编码中文,但保险起见,自己代码编码。
<%-- 使用jstl的<c:url>可以对get请求中的中文自动编码 --%>
<c:url var="myurl"value="/download">
<c:param name="fileName"value="图片.jpg"/>
</c:url>
<ahref="${myurl}">图片.jpg</a>
2)配置文件struts.xml中配置
<actionname="download"class="com.maple.download.action.DownloadAction"method="execute">
<!--以二进制stream流的形式下载 -->
<resultname="success" type="stream">
<!--下载内容的类型 -->
<paramname="contentType">image/jpg</param>
<!--以下载的形式输出给客户端
这里的${fileName}不是EL表达式,而是OGNL的语法
通过${属性名},可以在xml文件中访问Action的属性
中文下载时会乱码,要返回编码后的名字。
-->
<paramname="contentDisposition">attachment;filename=${fileName}</param>
<!--要下载的文件的输入流
底层调用的是Action中的getImageStream方法,该方法返回一个InputStream流
-->
<paramname="inputName">imageStream</param>
<!--以上参数的名字是固定的,可以参照org.apache.struts2.dispatcher.StreamResult的属性或文档 -->
</result>
</action>
3)Action中的代码
/**
*DownAction.java
* TODO结合框架实现文件下载
*注意:
*Struts2没有提供文件下载拦截器,只用文件上传拦截器,
*所以不是用拦截器实现文件下载的,而是以结果集(stream)
*的形式下载的。
*要在struts.xml中进行相关配置
*该Action的作用主要的返回一个InputStream给框架
*/
publicclass DownloadActionextends ActionSupport {
private StringfileName; //要下载的文件的名字
public StringgetFileName()throws Exception {
//返回用UTF-8编码后的名字,严格处理的话,这里还要判断浏览器的类型
return URLEncoder.encode(fileName,"UTF-8");
}
publicvoid setFileName(StringfileName)throws Exception {
/*
*因为文件名是可能通过GET方式提交过来的,所以要转码
* PSOT提交乱码问题框架自己会处理
*/
if("GET".equalsIgnoreCase(ServletActionContext.getRequest().getMethod())){
fileName=new String(fileName.getBytes("ISO-8859-1"),"UTF-8");
}
this.fileName = fileName;
}
public InputStreamgetImageStream()throws Exception {
//假设要下载的文件在/WEB-INF/download目录下
//得到要下载的文件的输入流
InputStreamis = ServletActionContext.getServletContext().getResourceAsStream("/WEB-INF/download/" +fileName);
return is;//要将得到的输入流返回框架
}
}
2.基于XML文件的声明式验证
1)手工验证执行顺序:validateXxx(争对Action中某个的业务方法验证)->validate(争对Action中所有的业务方法验证)
2)XML声明式验证
validate()------"Action的类名-validation"的xml文件------必须放置在与Action类同目录下。
validateXxxx()--"Action的类名-<action>标签中的name属性值-validation"的xml文件--必须放置在与Action类同目录。
验证指定Action的所有业务方法ValidatorAction-validation.xml:
<?xmlversion="1.0"encoding="UTF-8"?>
<!-- 该申明可以到xwork-core-xxx.jar包中的xwork-validator-1.0.2.dtd文件中拷贝 -->
<!DOCTYPEvalidatorsPUBLIC
"-//ApacheStruts//XWork Validator 1.0.2//EN"
"http://struts.apache.org/dtds/xwork-validator-1.0.2.dtd">
<!-- 该文件在执行ValidatorAction中所有的业务方法时都会起作用,
因为该文件名为ValidatorAction-validator.xml,且和ValidatorAction在同一目录下
相当于代码验证时的validate()方法 -->
<validators>
<!-- 要验证的字段,和表单的字段名一致 -->
<field name="username">
<!-- type指明使用框架内置的验证器
如果一个字段有多个验证器,验证器的作用顺序是从上到下,和配置的
顺序一致,只用前面的验证通过了后面的才能执行。
在com.opensymphony.xwork2.validator.validators包中的default.xml中可以找内置的验证器
requiredstring和required的区别:
requiredstring要求必须是字符串
required要求只要有值就可以
-->
<field-validatortype="requiredstring">
<!--去除字段的空格,默认为true -->
<paramname="trim">true</param>
<!--验证不通过时的提示信息 -->
<message>用户名不能为空</message>
</field-validator>
</field>
<field name="password">
<field-validatortype="requiredstring">
<message>密码不能为空</message>
</field-validator>
</field>
<field name="age">
<field-validatortype="requiredstring">
<message>年龄不能为空</message>
</field-validator>
</field>
<field name="salary">
<field-validatortype="requiredstring">
<message>薪水不能为空</message>
</field-validator>
</field>
<field name="birthday">
<field-validatortype="requiredstring">
<message>生日不能为空</message>
</field-validator>
</field>
</validators>
验证指定Action的某个业务方法ValidatorAction-xxx-validation.xml:
注意:xxx为<action name=”xxx”>标签的name属性的值。和手工(代码)验证有点点区别。
<?xmlversion="1.0"encoding="UTF-8"?>
<!DOCTYPEvalidatorsPUBLIC
"-//ApacheStruts//XWork Validator 1.0.2//EN"
"http://struts.apache.org/dtds/xwork-validator-1.0.2.dtd">
<!-- 该文件在执行ValidatorAction中的某个业务方法起作用,
因为该文件名为ValidatorAction-xxx-validator.xml,且和ValidatorAction在同一目录下
类似于代码验证时的validateXxx()方法 -->
<validators>
<field name="username">
<!--使用框架内置的正则表达式验证器
处理类com.opensymphony.xwork2.validator.validators.RegexFieldValidator
-->
<field-validatortype="regex">
<paramname="expression">[\u4E00-\uFA29]+</param>
<message>用户名必须为中文</message>
</field-validator>
</field>
<field name="password">
<field-validatortype="regex">
<paramname="expression">[0-9]{6}</param>
<message>密码必须为6位数字</message>
</field-validator>
</field>
<field name="age">
<field-validatortype="int">
<paramname="min">1</param>
<paramname="max">120</param>
<message>年龄在1到120之间</message>
</field-validator>
</field>
<field name="salary">
<field-validatortype="double">
<paramname="minInclusive">4000</param>
<paramname="maxInclusive">8000</param>
<message>薪水必须在4000到8000之间,包含4000和8000</message>
</field-validator>
</field>
<field name="birthday">
<field-validatortype="date">
<paramname="min">1970-01-01</param>
<paramname="max">2050-01-01</param>
<message>生日必须在1970-01-01到2050-01-01之间</message>
</field-validator>
</field>
</validators>
当存在二种验证文件时,其结果是二者验证效果之和,先执行ValidatorAction-validation.xml文件->后执行ValidatorAction-xxx-validation.xml文件。
对比:当ValidatorAction中,有validate(),又有validateXxx()的话,会先执行validateXxx()
再执行validate(),二者验证是[叠加]的效果。
3.OGNL(对象图导航语言)
3.1概念
OGNL是Object Graphic Navigation Language(对象图导航语言)的缩写,
它是一个开源项目。Struts2框架使用OGNL作为默认的表达式语言,而不是EL。
OGNL表达式可以完成:
1、访问OGNL上下文(OGNL context)和ActionContext;
2、操作集合对象;
3、Ognl通常和struts2的标签结合使用,不能独立使用。
3.2 Struts2的数据中心(ActionContext对象)
1)ActionContxt对象是整个Struts2的数据中心,包含六个对象,requestMap,sessionMap,applicationMap,parameters,attr,ValueStack(值栈)
注意:pageContext对象不属于数据中心的对象。
3.3某个Action的数据中心(ValueStack对象,即值栈)
ValueStack(值栈)实际是一个接口,在Struts2中利用OGNL时,实际上使用的是实现了该接口的OgnlValueStack类,这个类是Struts2利用OGNL的基础。
ValueStack(值栈): 贯穿整个 Action 的生命周期(每个 Action类的对象实例都拥有一个
ValueStack 对象). 相当于一个数据中心. 在其中保存当前Action对象和其他相关对象.
Struts 框架把 ValueStack 对象保存request中。
Action的对象一旦产生,值栈就产生了,位于request中,Action的对象一旦销毁,值栈也就销毁了,每一次请求都会产生值栈。
在 ValueStack 对象的内部有两个逻辑部分:
• ObjectStack: Struts 把动作和相关对象压入 ObjectStack 中—List。
• ContextMap: Struts 把各种各样的映射关系(一些 Map 类型的对象) 压入 ContextMap 中。
也可以按以下说法理解:
值栈分为二部分(List区域和Map区域)
List: 存放Action产生的实例和Action中所有的属性。
Map:在Action中能够取得的一切map对象都放置Map区域。
当Struts2接受一个请求时,会迅速创建ActionContext,ValueStack,action。然后把action存放进ValueStack,所以action的实例变量可以被OGNL访问。
注意: Struts2中,OGNL表达式需要配合Struts标签才可以使用。如:<s:propertyvalue="name"/>通过ongl获取值的时候,如果没有获取,则返回空字符串,即“”。
3.4 ongl中#的用法
#相当于ActionContext.getContext(),使用#xxx来获取值的时候,会直接去值栈中找,如果要找的值不在值栈中则返回空字符串。此时需要指定查找的范围,才能找到指定的值。
可以存放数据的返回有:request,session,application,parameters,ValueStack五个(即数据中心除attr对象外其他对象都可以存放数据)
如:
#request.username(也可以写为:#request[“username”]) 到requestMap中找key为username对应的值。相当于request.getParameter(“username”).
#username 到值栈中找key为username对应的值,即不写任何范围时,默认到值栈中找。
#parameters.username获取参数中名为username的值,多个同名的话返回数组,指定下标(从0开始)可以获取某个参数值。如#parameters.username[1]获取第二参数值。
特别注意:
attr对象不能存储数据,它主要用于从其他五个范围里和pageContext中找指定key的值。
如:
#attr.username 获取key为username的对应的值,获取的顺序为:
pageContext对象(不是)-requestMap对象-valuestack对象-sessionMap对象-applicationMap对象找到就返回,不再往下找了,找到最后没找到就返回空字符串""。
1)作用于struts2的域对象,而不是普通域对象,不过底层还是操作普通的域对象,struts2对普通的域对象进行了封装。
<%
pageContext.setAttribute("username","pageContext_username");
%>
<!-- 通过ongl中的#可以到指定的域中获取值,获取值栈中的值不用写# -->
值栈ValueStack中的值:<s:propertyvalue="username"/><br/>
requestMap中的值:<s:propertyvalue="#request.username"/><br/>
sessionMap中的值:<s:propertyvalue="#session.username"/><br/>
applicationMap中值:<s:propertyvalue="#application.username"/><br/>
parameters中值:<s:propertyvalue="#parameters.username"/><br/><!--获取参数名为username的值,多个的话返回数组 -->
通过attr对象获取值:<s:propertyvalue="#attr.username"/><!--这里获取到的值是:pageContext_username -->
<!-- attr是struts2数据中心的对象之一,专门用来从struts2的域对象中取值
取值的顺序为:
pageContext对象-requestMap对象-valuestack对象-sessionMap对象-applicationMap对象
找到就返回,不再往下找了,找到最后没找到就返回空字符串""
注意:pageContext对象不是struts2的数据中心之一。 -->
2)作用域JavaBean对象,取出JavaBean对象的属性值。
<%
List<User>users =new ArrayList<User>();
users.add(new User(1,"张三", 20));
users.add(new User(2,"李四", 22));
users.add(new User(3,"王五", 23));
users.add(new User(4,"赵六", 25));
//将users放到pageContext中
pageContext.setAttribute("users", users);
%>
<table border="1">
<tr>
<th>编号</th>
<th>姓名</th>
<th>年龄</th>
</tr>
<%-- 通过<s:iterator>标签遍历取出user --%>
<s:iteratorvalue="#attr.users"var="user">
<tr>
<td><s:propertyvalue="#user.id"/></td>
<td><s:propertyvalue="#user.name"/></td>
<td><s:propertyvalue="#user.age"/></td>
</tr>
</s:iterator>
</table>
3)作用于普通字符串
如果value只是一个字符串的话,不是JavaBean对象,此时加#号,还是不加#,都可以。
<s:property value="#username"/> 等价于<s:property value="username"/>
<%-- 通过<s:iterator>标签遍历取出user的指定的属性值,即集合的投影(过滤) --%>
<s:iteratorvalue="#attr.users.{name}"var="name">
<tr>
<td></td>
<!--当要获取的key对应的值为字符串时,#号可有可无-->
<td><s:propertyvalue="name"/></td>
<td></td>
</tr>
</s:iterator>
4)集合的投影(过滤)
集合的过滤有以下三种方式:
a.“?#”:过滤所有符合条件的集合,如:users.{?# this.age > 19};
b.“^#”:过滤第一个符合条件的元素,如:users.{^# this.age > 19};
c.“$#”:过滤最后一个符合条件的元素,如:users.{$# this.age > 19} 。
this表示集合中的某个元素。
投影(过滤)操作返回的是一个集合,可以使用索引取得集合中指定的
元素,如:users.{?#this.age > 19}[0]。
A. 取出所有年龄大于22岁的所有用户:<s:iterator value="#attr.users.{?#this.age> 22}" var="user" >
B. 取出所有年龄大于22岁的第一个用户:<s:iterator value="#attr.users.{^#this.age> 22}" var="user" >
C. 取出所有年龄大于22岁的最后一个用户:<s:iterator value="#attr.users.{$#this.age> 22}" var="user" >
D. 取出所有年龄大于22岁的第二个[索引从0开始]用户:<s:iteratorvalue="#attr.users.{?#this.age >22}[1]" var="user" >
5)用#构造Map集合
该功能常用在给radio或select、checkbox等标签赋值上。
<%-- 通过#{key:value, key:value}的形式构造,类似json格式 --%>
<s:iterator value="#{'male': '男', 'female' : '女'}">
<!--通过指定其key[固定]和value[固定]取出其对应的键和值 -->
<s:propertyvalue="key"/>=<s:propertyvalue="value"/><br/>
</s:iterator>
<%-- 通过{value1, value2}的形式构造简写 --%>
<s:iterator value="{'男', '女'}">
<s:property/>
</s:iterator>
6)用#构造List集合
创建list与创建map语法很相似,不同的是list前不需要加"#"号.
<s:iterator value="{1,2,3,4}">
<s:property/> <br>
</s:iterator>
<s:iterator value="{'s1','s2','s3','s4'}"var="s">
<s:property value="#s"/> <br>
</s:iterator>
7)%的用法
“%”符号的用途是在标签的属性值被理解为字符串类型时,告诉执行环境%{}里的是OGNL表达式。
<!-- 不使用%{}时,是ongl表达式,通常都这样做 -->
<s:property value="#request.username"/><br/>
<!-- %{}里面的内容是ongl表达式 -->
<s:property value="%{#request.username}"/><br/>
<!-- %{}里面的内容用引号引起来时,不再是ongl表达式,而是普通的字符串,原样输出 -->
<s:propertyvalue="%{'#request.username'}"/><br/>
8)$的用法
在struts2配置文件中引用ognl表达式 ,引用的是值栈的值 。
A.读取xml文件中配置的变量
<field-validatortype="double">
<paramname="minInclusive">4000</param>
<paramname="maxInclusive">8000</param>
<message>薪水必须在${minInclusive}到${maxInclusive}之间,包含${minInclusive}和${maxInclusive}</message>
</field-validator>
B.读取Action类中的实例变量,底层执行getXxxx()方法
<!--
这里的${fileName}不是EL表达式,而是OGNL的表达式,通过${属性名},可以在xml文件中访问Action的属性-->
<param name="contentDisposition">attachment;filename=${fileName}</param>
4.Struts2防止表单重复提交
A.框架提供了token拦截器,主要用于防止表单重复提交。
B.默认栈中无token拦截器,所以在<action>标签中要显示引用token拦截器,此时也要显式引用默认拦截器。
C.开发步骤:
1)在jsp页面中使用<s:token/>,自动产生hidden框,同时在服务端的session中放置一份id值。
<s:formaction="token"method="post"namespace="/">
<!--在表单总添加该标签后,自动产生hidden框,同时在服务端的session中放置一份id值。 -->
<s:token/>
<s:textfieldlabel="用户名"name="username"/>
<s:passwordlabel="密码"name="password"showPassword="true"/>
<s:submitvalue="注册"/>
<s:resetvalue="清空"/>
</s:form>
2)在<action/>标签中,显示引用token拦截器。
<packagename="token"namespace="/"extends="struts-default">
<actionname="token"class="com.maple.token.action.TokenAction"method="execute">
<resultname="success"type="dispatcher">/WEB-INF/token_success.jsp</result>
<!-- 重复提交时返回的结果是invalid.token[固定],可以在源码中查得 -->
<resultname="invalid.token"type="dispatcher">/WEB-INF/token_repeat.jsp</result>
<!-- 显式引用token拦截器,因为在struts.xml文件中只是注册的token拦截器并没有将其放到默认拦截器栈中 -->
<interceptor-refname="token"/>
<!-- 这里必须显式引用默认拦截器栈,因为一旦显式引用了其他拦截器就没有默认拦截器了 -->
<interceptor-ref name="defaultStack"/>
</action>
</package>
3)配置一个重复提交要转到的页面。
<!--重复提交时返回的结果是invalid.token[固定],可以在源码中查得 -->
<resultname="invalid.token"type="dispatcher">/WEB-INF/token_repeat.jsp</result>