一、struts2文件上传
1.上传文件的时候要求必须使得表单的enctype属性设置为multipart/form-data,把它的method属性设置为post
2.上传单个文件的时候需要在Action类中添加属性并提供每个属性的标准get/set方法:
private File uploadImage;//上传的文件 private String uploadImageContentType;//上传的文件类型 private String uploadImageFileName;//上传文件的名字 ...... 每个属性的get/set方法略。 ......
定义这些属性的时候必须严格按照要求来,否则不能被识别。upload是表单中name属性的名字。
3.上传文件的一般步骤:
ServletContext sc=ServletActionContext.getServletContext(); String path=sc.getRealPath("/04_fileupload/uploadfiles"); //获取目标文件夹的真实路径 File desc=new File(path,uploadImageFileName);//拼接目标文件名称和目标文件夹 FileUtils.copyFile(uploadImage, desc);//使用FileUtils类完成复制,实际上是完成了上传功能。 uploadImage.delete();//删除临时文件 //这一步在有些版本中并不是必须的,在一些高级版本中会自动删除临时文件,但是在一些低级版本中的myeclipse中并不会自动删除临时文件,所以为了保险起见,还是手动删除最好。 uploadImage实际上是临时文件。
4.上传成功之后的文件应当到tomcat服务器下相应的工程目录下找,而不应当到myeclipse下的对应目录找。
二、struts2上传文件之参数配置
1.设置上传文件的总大小
<constant name="struts.multipart.maxSize" value="1024"></constant>
这里的1024单位是字节。
2.设置允许上传的文件类型、文件后缀名、单个文件上传的大小。
(1)在struts-default.xml文件中,声明了一个文件上传的拦截器
<interceptor name="fileUpload" class="org.apache.struts2.interceptor.FileUploadInterceptor"/>
(2)在给出的拦截器类中,能够找到相关的配置参数:
/* <!-- START SNIPPET: parameters --> * <p/> * <ul> * <p/> * <li>maximumSize (optional) - the maximum size (in bytes) that the interceptor will allow a file reference to be set * on the action. Note, this is <b>not</b> related to the various properties found in struts.properties. * Default to approximately 2MB.</li> * <p/> * <li>allowedTypes (optional) - a comma separated list of content types (ie: text/html) that the interceptor will allow * a file reference to be set on the action. If none is specified allow all types to be uploaded.</li> * <p/> * <li>allowedExtensions (optional) - a comma separated list of file extensions (ie: .html) that the interceptor will allow * a file reference to be set on the action. If none is specified allow all extensions to be uploaded.</li> * </ul> * <p/> * <p/> * <!-- END SNIPPET: parameters --> */
(3)配制方法:在struts.xml文件中,或者在其它自定义struts.xml配置文件中的action标签节点下:
<interceptor-ref name="defaultStack"> <param name="fileUpload.maximumSize">1024000</param> <param name="fileUpload.allowedExtensions">.txt</param> <param name="fileUpload.allowedTypes">text/plain</param> </interceptor-ref>
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE struts PUBLIC 3 "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN" 4 "http://struts.apache.org/dtds/struts-2.3.dtd"> 5 <struts> 6 <constant name="struts.multipart.maxSize" value="1024000"></constant> 7 <!-- 单位是字节 --> 8 <!-- <constant name="struts.multipart.saveDir" value="c:/"></constant> --> 9 <!-- 这句话不写也行吧 --> 10 <package name="fileupload_package" namespace="/fileupload" extends="struts-default"> 11 <action name="fileUploadAction" method="upload" class="com.kdyzm.fileupload.FileUploadAction"> 12 <result name="success"> 13 /04_fileupload/success.jsp 14 </result> 15 <result name="input"> 16 /04_fileupload/fail.jsp 17 </result> 18 <interceptor-ref name="defaultStack"> 19 <param name="fileUpload.maximumSize">1024000</param> 20 <param name="fileUpload.allowedExtensions">.txt,.jpg,.png,.gif,.jpeg</param> 21 <param name="fileUpload.allowedTypes">text/plain</param> 22 </interceptor-ref> 23 </action> 24 </package> 25 </struts>
(4)进行测试
首先,在上传失败的jsp页面中使用 <s:fielderror/>用于提示错误信息。
<%@ page isELIgnored="false" language="java" import="java.util.*" pageEncoding="utf-8" contentType="text/html; charset=utf-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="/struts-tags" prefix="s" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>Insert title here !</title> <meta http-equiv="content-type" content="text/html;charset=utf-8"> </head> <body> 文件上传失败! <!-- 该标签不起作用?? --> <s:fielderror/> </body> </html>
上传了图片文件的时候,错误信息提示如下:
3.错误提示信息国际化
(1)上传错误的提示信息在struts-messages.properties文件中,可以自定义配置文件覆盖该文件中的相关参数配置达到国际化的目的,注意完成之后需要在sruts.xml文件中注册。。
struts.messages.error.uploading=Error uploading: {0}
struts.messages.error.file.too.large=File {0} is too large to be uploaded. Maximum allowed size is {4} bytes!
struts.messages.error.content.type.not.allowed=u6587u4EF6u7C7Bu578Bu4E0Du5141u8BB8uFF1A {0} "{1}" "{2}" {3}
struts.messages.error.file.extension.not.allowed=u6587u4EF6u6269u5C55u540Du4E0Du5141u8BB8uFF1A {0} "{1}" "{2}" {3}
在struts.xml文件中注册:
<constant name="struts.custom.i18n.resources" value="com.kdyzm.fileupload.upload_messages"></constant>
以上文本中的乱码信息是因为是使用了native2ascii对文件进行了编码,再使用myeclipse默认编辑器打开的时候就成了乱码。但是在Jsp页面中显示的是正常的中文。
(2)想要让错误信息显示出来必须对其进行如下配置才行,否则不会在xml文件中显示。
<interceptor-ref name="defaultStack"> <param name="fileUpload.maximumSize">1024000</param> <param name="fileUpload.allowedExtensions">.txt</param> <param name="fileUpload.allowedTypes">text/plain</param> </interceptor-ref>
三、struts2文件上传之多文件上传。
1.上传多个文件的时候需要注意的事项:
在jsp表单中的多个输入框的name属性值应当相同。比如如果是多个图片的上传,应当所有的输入框的name属性都是相同的才行。比如:
1 <%@ page isELIgnored="false" language="java" import="java.util.*" pageEncoding="utf-8" 2 contentType="text/html; charset=utf-8" %> 3 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 4 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 5 <!-- 文件上传请求文件 --> 6 <html> 7 <head> 8 <title>Insert title here !</title> 9 <meta http-equiv="content-type" content="text/html;charset=utf-8"> 10 <style type="text/css"> 11 table{ 12 border: 1px solid black; 13 border-collapse: collapse; 14 } 15 table td{ 16 border: 1px solid black; 17 text-align: center; 18 height:30px; 19 } 20 </style> 21 </head> 22 23 <body> 24 <%-- <form action="<c:url value='/fileupload/fileUploadAction.action'/>" --%> 25 <form action="<c:url value='/fileupload/filesUploadAction.action'/>" 26 enctype="multipart/form-data" method="post"> 27 <table> 28 <tr> 29 <td>请选择上传的文件1:</td> 30 <td><input type="file" name="uploadImage"></td> 31 </tr> 32 <tr> 33 <td>请选择上传的文件2:</td> 34 <td><input type="file" name="uploadImage"></td> 35 </tr> 36 <tr> 37 <td>请选择上传的文件3:</td> 38 <td><input type="file" name="uploadImage"></td> 39 </tr> 40 <tr> 41 <td colspan="2"> 42 <input type="submit" value="上传"> 43 </td> 44 </tr> 45 </table> 46 </form> 47 </body> 48 </html>
在该表单中的所有的输入框中的name属性都是uploadImage。
2.除此之外,在Action中的属性设置:所有的属性都应当是数组,将原来的属性变量全部改成数组就可以了。
但是在上传的时候应当注意,应当遍历数组依次上传单个文件完成上传多个文件的任务。
for(int i=0;i<uploadImage.length;i++) { File file=uploadImage[i]; String fileName=uploadImageFileName[i]; ServletContext sc=ServletActionContext.getServletContext(); String path=sc.getRealPath("/04_fileupload/uploadfiles"); File aimFile=new File(path,fileName); FileUtils.copyFile(file, aimFile); file.delete(); System.out.println(fileName+"已经上传成功!"); }
四、自定义拦截器
1.Struts2拦截器在访问某个Action方法之前或者之后实施拦截,Struts2拦截器是可插拔的,拦截器是AOP的一种实现)
2.拦截器栈:将拦截器按照一定的顺序连接成为一条链。在访问被拦截方法的时候,Stuts2拦截器链中的拦截器就会按照之前定义好的顺序被依次调用。
3.每个拦截器都是实现了com.opensymphony.xwork2.interceptor.Interceptor接口的Java类。
该接口的定义:
package com.opensymphony.xwork2.interceptor; import com.opensymphony.xwork2.ActionInvocation; import java.io.Serializable; public interface Interceptor extends Serializable { void destroy(); void init(); String intercept(ActionInvocation invocation) throws Exception; }
其中init方法和destroy方法在拦截器的生命周期中只会调用一次。intercept方法则是每拦截一个动作请求,就会调用一次该方法,相当于doFilter()方法。
(1)Struts2会依次调用为某个Action而注册的每一个拦截器的intercept方法。
(2)每次调用intercept方法的时候,Struts都会传递一个ActionInvocation接口的实例。
4.ActionInvocation接口
该接口代表一个给定动作的执行状态,拦截器可以从该类的对象中获取与该动作相关联的Action对象和Result对象。在完成拦截器自己的任务之后,拦截器将会调用ActionInvocation对象的Invoke方法前进到Action处理流程的下一个环节。
可以使用ActionInvocation对象的addPreResultListener方法给ActionInvocation对象“挂”上一个或者多个PreResultListener监听器,该监听器对象可以在动作执行完毕之后,开始执行动作结果之前做些事情。
5.AbstractInterceptor类实现了Interceptor接口,并为init方法、destroy方法提供了一个空白的实现。
6.自定义拦截器的步骤
整体上分为两步:首先自定义拦截器,然后在struts.xml文件中注册该拦截器。
功能:拦截没有登陆的用户,如果没有登陆,则将页面转向失败页面,否则转向成功页面。
(1)自定义拦截器,这里实现Interceptor接口。
package com.kdyzm.myinterceptor; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.struts2.ServletActionContext; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.interceptor.Interceptor; public class UserInterceptor implements Interceptor { private static final long serialVersionUID = 1L; @Override public void destroy() { System.out.println("自定义拦截器被销毁!!!!!"); } @Override public void init() { System.out.println("自定义拦截器初始化!!!!!!"); } @Override public String intercept(ActionInvocation invocation) throws Exception { System.out.println(invocation.getAction()); System.out.println(invocation.getResult()); System.out.println(invocation.getResultCode()); System.out.println(invocation.getProxy()); System.out.println(invocation.getStack()); System.out.println(invocation.getInvocationContext()); System.out.println(invocation.getProxy().getActionName()); System.out.println(invocation.getProxy().getNamespace()); // ServletContext sc=ServletActionContext.getServletContext(); HttpServletRequest request=ServletActionContext.getRequest(); // HttpServletResponse response=ServletActionContext.getResponse(); HttpSession session=request.getSession(); Object user=session.getAttribute("user"); if(user==null) return "error"; else return "success"; } }
(2)注册拦截器到struts.xml文件,这里的配置是最重要的,这里得配置方式和struts-default.xml文件中的配置格式几乎完全相同。但是这里应当注意,由于之前使用使用的是默认的defaultStack拦截器栈,如果向要继续使用该栈的话该怎么做呢?一种方法就是将该栈重写一遍,这是最笨的方法,另外一种方法就是直接引用该栈。引用方式:
<interceptor-ref name="defaultStack"></interceptor-ref>
自定义struts.xml中的配置:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN" "http://struts.apache.org/dtds/struts-2.3.dtd"> <struts> <package name="myinterceptor_package" namespace="/myinterceptor" extends="struts-default"> <interceptors> <interceptor name="UserLoginInterceptor" class="com.kdyzm.myinterceptor.UserInterceptor"></interceptor> <interceptor-stack name="userloginInterceptorStack"> <interceptor-ref name="defaultStack"></interceptor-ref> <interceptor-ref name="UserLoginInterceptor"></interceptor-ref> </interceptor-stack> </interceptors> <!-- 配置默认使用的拦截器栈 --> <default-interceptor-ref name="userloginInterceptorStack"></default-interceptor-ref> <action name="userAction" method="save" class="com.kdyzm.myinterceptor.UserAction"> <result name="success"> /05_interceptor/success.jsp </result> <result name="error"> /05_interceptor/fail.jsp </result> </action> </package> </struts>
(3)UserAction.java类中的内容
package com.kdyzm.myinterceptor; import com.opensymphony.xwork2.ActionSupport; public class UserAction extends ActionSupport{ private static final long serialVersionUID = 4422437084143546390L; public String save() throws Exception{ System.out.println("调用了save方法!"); return "success"; } }
(4)测试文件:
1 <%@ page isELIgnored="false" language="java" import="java.util.*" pageEncoding="utf-8" 2 contentType="text/html; charset=utf-8" %> 3 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 4 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 5 <html> 6 <head> 7 <title>Insert title here !</title> 8 <meta http-equiv="content-type" content="text/html;charset=utf-8"> 9 </head> 10 11 <body> 12 <a href="<c:url value='/myinterceptor/userAction.action'/>">测试interceptor!</a> 13 </body> 14 </html>
1 <%@ page isELIgnored="false" language="java" import="java.util.*" pageEncoding="utf-8" 2 contentType="text/html; charset=utf-8" %> 3 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 4 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 5 <html> 6 <head> 7 <title>Insert title here !</title> 8 <meta http-equiv="content-type" content="text/html;charset=utf-8"> 9 </head> 10 11 <body> 12 interceptor通过表示已经登陆! 13 </body> 14 </html>
1 <%@ page isELIgnored="false" language="java" import="java.util.*" pageEncoding="utf-8" 2 contentType="text/html; charset=utf-8" %> 3 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 4 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 5 <html> 6 <head> 7 <title>Insert title here !</title> 8 <meta http-equiv="content-type" content="text/html;charset=utf-8"> 9 </head> 10 11 <body> 12 Interceptor失败表示还没有登陆! 13 </body> 14 </html>
最后一个文件,该文件用于向session中添加一个key值为user的对象,用于检测拦截是否有逻辑上的错误。
1 <%@ page isELIgnored="false" language="java" import="java.util.*" pageEncoding="utf-8" 2 contentType="text/html; charset=utf-8" %> 3 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 4 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 5 <html> 6 <head> 7 <title>Insert title here !</title> 8 <meta http-equiv="content-type" content="text/html;charset=utf-8"> 9 </head> 10 11 <body> 12 <% 13 session.setAttribute("user", "张三"); 14 %> 15 </body> 16 </html>
(5)测试结果:
首先访问query.jsp
单击链接之后:
这时候,直接访问login.jsp文件,然后再次访问query.jsp文件,并单击链接,这时候就会提示已经验证成功
(6)注意事项
在拦截器中不能调用invoke方法,如果调用了该方法,测试就会失败。
五、struts2手动验证
1.struts2验证方法有两种:
(1)手工编写代码实现(基本验证)
(2)基于XML配置文件的方式(验证框架)
2.验证的原理:ActionSupport类实现了Validateable接口,想要进行验证需要实现Validateable接口,所以我们只需要继承ActionSupport类就可以了。
使用的方法是Validateable接口定义的validate方法。当Action初始化的时候,会自动调用该方法,所以,之前的动作和配置都不需要改动,只需要重写validate方法即可。在validate方法中进行一些判断,比如用户名是否已经存在、密码长度是否正确等,如果有错误,使用addFieldError方法添加错误类型即可。
3.小练习:
Action类:
package com.kdyzm.validate; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.opensymphony.xwork2.ActionSupport; public class ValidateAction extends ActionSupport{ private static final long serialVersionUID = -3130043637195994925L; private String username; private String password; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String add() throws Exception{ return "success"; } @Override public void validate() { if(username==null||"".equals(username)) this.addFieldError("error", "用户名不能为空!"); if(password==null||"".equals(password)) this.addFieldError("error", "密码不能为空!"); String regex="^[0-9a-zA-Z]{6,12}$"; Pattern p=Pattern.compile(regex); Matcher m=p.matcher(password); boolean result=m.matches(); if(result==false) this.addFieldError("error", "密码长度为6-12位!"); } public String login()throws Exception{ System.out.println("业务逻辑处理!"); return "success"; } }
其余配置不做解释,直接看结果:
由于密码长度不合法,所以会提示以下的错误:
4.对action的指定方法进行验证:重点
如果仅仅想对Action的某个指定方法进行验证,则只需要这样做就可以了:
如果Action中的指定方法名为:public String add(),则校验方法的方法名为:public void validateAdd(),这样通过反射机制就能够比较轻松的完成这个验证了。
所以在Action中如果有这种以validate开头的方法,一定是某种校验方法。
上述小练习中将validate方法改成validateLogin方法也可以,并不影响任何东西。
5.各个方法执行的顺序:
先执行set方法将变量获取到;然后执行validateXXX方法,校验是否合法;最后执行login等业务逻辑处理的方法。如果校验方法报错了,则最后的login等处理业务逻辑的方法将不能运行。
6.执行addFieldError方法的底层代码是什么?
public synchronized void addFieldError(String fieldName, String errorMessage) { final Map<String, List<String>> errors = internalGetFieldErrors(); List<String> thisFieldErrors = errors.get(fieldName); if (thisFieldErrors == null) { thisFieldErrors = new ArrayList<String>(); errors.put(fieldName, thisFieldErrors); } thisFieldErrors.add(errorMessage); }
7.实际上的输入校验流程是什么?
(1)类型转换器对请求参数执行类型转换,并把转换后的值赋给action中的属性
(2)如果在执行类型转换的过程中出现了异常,系统会将异常信息保存到ActionContext,conversionError拦截器将异常信息封装到fieldErrors中,然后执行第三步。如果类型转换没有出现异常,则直接进入第三步。
(3)系统通过反射技术调用action中的validateXXX()方法,Xxxx是方法名。
(4)调用action中的validate方法。
(5)经过上面的4步,如果系统中的fieldErrors中存在错误信息(存放错误信息的集合的size()>0),系统就会就会自动将请求转发到名称为input的视图。如果系统中的fieldErrors没有任何错误信息,系统就会执行action中的处理方法。