Struts2 day04
本节内容
1. struts2的数据类型转换器
2.拦截器
3.表单数据验证
4.防止表单重复提交
5.struts2文件的上传与下载
6.ajax异步请求
7.struts2注解式开发(了解)
一、struts2的数据类型转换器
我们在学习web的时候,我们知道我们表单提交通过HTTP协议,他们相互传递的都是字符串,也就是说服务器接收到的来自用户的数据只能是字符串或者是字符数组而在Web应用的对象中,往往使用了多种不同的类型,如整数(int)、浮点数(float)、日期(Date)或者是自定义数据类型等。因此在服务器端必须将字符串转换成合适的数据类型。Struts2框架中为我们提供了一些简单类型的转换器,比如转换为int、float等简单数据类型是不需要我们自己定义转换器去转换的,struts2内部本身就为我们提供了转换的方法,但像一些复杂的类型和我们自定义的数据类型还是需要我们自己去写转换器去转换的。在转换工程中,如果在类型转换中出现异常,类型转换器开发者无需关心异常处理逻辑,Struts2的conversionError拦截器会自动处理该异常,并且提示在页面上生成提示信息。
下面我们就来看一下自定义的类型转换器。
实现类型转换器一般有2种方式:
1.实现OGNL提供的TypeConvert接口以及实现了TypeConvert接口的DefaultTypeConvert类来实现自定义的类型转换器
2.基于Struts2的类型转换器Struts 2提供了一个StrutsTypeConverter的抽象类,这个抽象类是DefaultTypeConverter类的子类。开发时可以直接继承这个类来进行转换器的构建。通过继承该类来构建类型转换器,可以不用对转换的类型进行判断(和第一种方式的区别)
1.
package com.struts.conversion; import com.opensymphony.xwork2.conversion.impl.DefaultTypeConverter; import com.struts.action.Print; import java.util.Map; /** * 类型转换器 */ public class LoginActionConversion extends DefaultTypeConverter { @Override public Object convertValue(Map<String, Object> context, Object value, Class toType) { System.out.println ("自定义类型转换器"); if (toType== Print.class){ Print print = new Print (); String[] strs =(String[]) value; String[] xy = strs[0].split (","); print.setX (Integer.parseInt (xy[0])); print.setY (Integer.parseInt (xy[1])); return print; } return super.convertValue (context, value, toType); } }
注册局部类型转换器。
局部类型转换器仅仅对某个Action起作用。局部类型转换器非常简单,只需要在相应的Action目录下新建一个资源文件。该资源文件名格式如下。ActionName-conversion.properties。其中ActionName表示需要进行转换的Action的类名,“-conversion.properties”字符串则是固定格式的。该文件也是一个典型Properties文件,文件由键值对组成:propertyName = 类型转换器类
如:name=util.NameConvert
name:表示要进行转换的属性
util.NameConvert:表示要进行转换的自定义类型转换器。
注意:该属性文件应该与ActionName.class放在相同位置。
print =com.struts.conversion.LoginActionConversion
还有一种叫做全局类型转换器 对所有Action的特定类型的属性都会生效。
全局类型转换器,必须提供一个xwork-conversion.properties文件。文件必须保存在classes目录下。该资源文件名格式如下:
复合类型=对应的类型转换器
复合类型:指定需要完成类型转换的复合类
对应的类型转换器:指定所指定类型转换的转换器。
如:注册User类的全局类型转换器为:UserConverter
cn.wjz.bean.User = cn.wjz.util.UserConverter
注意:如果局部类型转换和全局类型转换同时存在的话,局部类型转换具有较高的优先级,也就是以局部类型转换器为主。
2.
package com.struts.conversion; import com.struts.action.Print; import org.apache.struts2.util.StrutsTypeConverter; import java.util.Map; /** * struts2的类型转换器 */ public class LocationActionConversion extends StrutsTypeConverter { @Override public Object convertFromString(Map map, String[] strings, Class aClass) { Print print =null; System.out.println ("全局类型转换"); if (aClass== Print.class){ print = new Print (); String[] xy= strings[0].split (","); print.setX (Integer.parseInt (xy[0])); print.setY (Integer.parseInt (xy[1])); } return print; } @Override public String convertToString(Map map, Object o) { return null; } }
com.struts.action.Print=com.struts.conversion.LocationActionConversion
二、拦截器
拦截器可谓struts2的核心了,最基本的bean的注入就是通过默认的拦截器实现的,一般在struts2.xml的配置中,package内直接或间接继承了struts-default.xml,这样struts2默认的拦截器就会作用.下面详细的说明一下:
Interceptor拦截器类似于过滤器,是可以在action执行前后执行的代码。是我们做web开发时经常用的技术。比如:权限控制、日志等。我们也可以将多个Interceptor连在一起组成Interceptor栈。
Struts2拦截器,每个拦截器类只有一个对象实例,即采用单例模式,所以引用这个拦截器的Action都共享这一拦截器类的实例,因此,在拦截器中如果使用类变量,要注意同步问题。
实现原理 : Struts2拦截器的实现原理相对简单,当请求struts2的action时,Struts 2会查找配置文件,并根据其配置实例化相对应拦截器对象,然后串成一个列表,最后一个一个地调用列表中的拦截器。
如果要实现拦截器,需要实现interceptor接口或者继承AbstractInterceptor抽象类,后者用的更多。
定义一个拦截器:
package com.interceptor; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.interceptor.AbstractInterceptor; public class InterceptorDemo extends AbstractInterceptor{ @Override public String intercept(ActionInvocation invocation) throws Exception { String user=(String) ActionContext.getContext().getSession().get("user"); if (user==null||"".equals(user)) { return "fail"; } return invocation.invoke(); } }
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN" "http://struts.apache.org/dtds/struts-2.5.dtd"> <struts> <package name="root" namespace="/" extends="struts-default"> <!-- 配置拦截器 --> <interceptors> <interceptor name="interceptor1" class="com.interceptor.InterceptorDemo"></interceptor> </interceptors> <action name="log*" class="com.action.LogAction" method="log{1}"> <result >/log{1}.jsp</result> <allowed-methods>login,logout</allowed-methods> </action> <action name="add" class="com.action.UserAction"> <result>/addUser.jsp</result> <result name="fail">/login.jsp</result> <!-- 注意:在使用自定义的拦截器后,默认拦截器是不生效的,需要添加上默认拦截器 --> <interceptor-ref name="interceptor1"></interceptor-ref> <interceptor-ref name="defaultStack"></interceptor-ref> </action> </package> </struts>
通过以上代码发现,action拦截只能拦截部分拦截请求,如果需要大面积拦截,就会出现很多重读代码,最好的办法就是将指定的部分action统一一起拦截掉,可以使用全局拦截。
通过以上代码发现,action拦截只能拦截部分拦截请求,如果需要大面积拦截,就会出现很多重读代码,最好的办法就是将指定的部分action统一一起拦截掉,可以使用全局拦截。
全局拦截器:
全局拦截与Action拦截,区别就在于配置文件,Action代码没任何区别。
问题1:
返回值问题,因为全局拦截器将包中所有Action都拦截掉,蛋拦截器中的返回值只能固定一个,那么此时,在所有拦截器的Action中,都需要配置与拦截器返回值相同的result,可以将返回值配置成全局结果集。
问题2:
包中某些Action又不希望被拦截,比如:登录 ,登出 ,注册 等,两种解决方案:
①将这些功能直接放到jsp界面中,不使用Action。但是,不要轻易暴露实际访问的jsp页面地址。
②将这些不需要拦截的放到另一个包中。
③使用方法拦截。
<interceptors> <!-- 关联自己写的拦截器 --> <interceptor name="myInterceptor" class="com.interceptor.InterceptorDemo"></interceptor> <!-- 把自己的拦截器加载到拦截器栈中--> <interceptor-stack name="myStack"> <interceptor-ref name="myInterceptor"></interceptor-ref> <interceptor-ref name="defaultStack"></interceptor-ref> </interceptor-stack> </interceptors> <!-- 为当前包所有Action配置默认拦截器 --> <default-interceptor-ref name="myStack"></default-interceptor-ref> <!--全局结果集--> <global-results> <result name="fail">/login.jsp</result> </global-results>
三、表单数据验证
我们在做web项目的时候通常会做数据校验,我们常写的是在网页中使用js进行校验,但是客户端的校验是不安全的,如果软件安全性要求如果高的话,那就需要我们进行服务器验证,我们的表单数据验证有3种方式,:客户端验证,一般就是我们的js验证,服务器验证,数据库验证。
传统验证,我们可以在我们的action中使用代码验证。
使用action继承ActionSupport实现验证
- 在Action类的业务方法中直接验证
- 重写Validate()方法
- 使用validateXxx()方法
在Action类的业务方法中直接验证
package com.struts.action; import com.opensymphony.xwork2.ActionSupport; public class LoginAction extends ActionSupport { private String username; private String pwd; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } public String login(){ if (username==null||"".equals (username)){ this.addFieldError ("username","用户名不能为空"); return "input"; } if (pwd==null||"".equals (pwd)){ this.addFieldError ("pwd","密码不能为空"); return "input"; } return "success"; } }
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>$Title$</title> </head> <body> ${fieldErrors.username} ${fieldErrors.pwd} <form action="login" method="post"> 用户名: <input type="text" name="username"/> <br/> 密码: <input type="password" name="pwd"/> <br/> <input type="submit" value="登录"/> </form> </body> </html>
重写Validate()方法
@Override public void validate() { if (username==null||"".equals (username)){ this.addFieldError ("username","用户名不能为空"); } if (pwd==null||"".equals (pwd)){ this.addFieldError ("pwd","密码不能为空"); } }
如果Action中有validate()方法,会先执行验证,验证后才执行业务方法,不通过不执行业务方法
使用validateXxx()方法
使用validateXxx()方法
- Struts2支持validateXxx()方法针对xxx()方法进行数据验证
- 使用validateRegister()方法实现针对register()方法的数据验证
- 注册时通过validateRegister()方法验证年龄和邮箱等其它字段
- 注册和登录时在validate()方法中验证用户名和密码非空等其它验证
validate()方法和validateXxx()方法同时存在时都会起作用
ValidateXxx()方法的调用要先于validate()方法
public void validateLogin(){ System.out.println ("进行了"); if (username==null||"".equals (username)){ this.addFieldError ("username","用户名不能为空"); } if (pwd==null||"".equals (pwd)){ this.addFieldError ("pwd","密码不能为空"); } }
基于xml验证:基于配置文件的验证,可以减少java代码的编写。
创建配置文件,配置文件名:ActionName-validation.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE validators PUBLIC "-//Apache Struts//XWork Validator 1.0.3//EN" "http://struts.apache.org/dtds/xwork-validator-1.0.3.dtd"> <validators> <field name="username"> <field-validator type="requiredstring"> <message>用户名不能为空</message> </field-validator> </field> <field name="pwd"> <field-validator type="requiredstring"> <message>密码不能为空</message> </field-validator> </field> </validators>
package com.struts.action; import com.opensymphony.xwork2.ActionSupport; public class RegisterAction extends ActionSupport { private String username; private String pwd; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } public String register(){ System.out.println ("运行 了"); return "success"; } }
以上代码也是对action中的所有方法验证,但是通常不需要全部验证,需要指定方法验证,xml的指定方法验证需要将配置文件的文件名改为如下格式:
ActionClassName-ActionMethodName-validation.xml
四、防止表单重复提交
由于用户网速原因,用户可能重复点击提交按钮,,或者刷新提交页面,已经提交成功,但是用户后退后,恶意刷新页面形成重复提交。
令牌机制:
在struts2中使用令牌机制解决表单重复提交问题,产生一个随机字符串,在服务器与客户端同时保留副本,在客户端想服务器发送请求的过程中,会自动提交副本,服务端接收到请求后,判断本地的令牌与提交的令牌是否相符,如果相同,就执行提交,完成业务逻辑操作,完成后,服务器端的副本会立马生产一个新的令牌,而客户端的字符串,还是原来的字符串,那么,此时如果客户端再提交,服务端与客户端令牌已经不一致,则判定为重复提交。
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@taglib prefix="s" uri="/struts-tags" %> <html> <head> <title>$Title$</title> </head> <body> ${fieldErrors.username} ${fieldErrors.pwd} <form action="register" method="post"> <s:token></s:token> 用户名: <input type="text" name="username"/> <br/> 密码: <input type="password" name="pwd"/> <br/> <input type="submit" value="登录"/> </form> </body> </html>
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN" "http://struts.apache.org/dtds/struts-2.5.dtd"> <struts> <package name="user" namespace="/" extends="struts-default"> <interceptors> <interceptor-stack name="myStack"> <interceptor-ref name="token"></interceptor-ref> <interceptor-ref name="defaultStack"></interceptor-ref> </interceptor-stack> </interceptors> <default-interceptor-ref name="myStack"></default-interceptor-ref> <action name="login" class="com.struts.action.LoginAction" method="login"> <result name="input">/index.jsp</result> <result name="success" type="redirect">/index.jsp</result> </action> <action name="register" class="com.struts.action.RegisterAction" method="register"> <result name="success" type="redirect">/index.jsp</result> <result name="invalid.token">/error.jsp</result> </action> </package> </struts>
五、Struts2的文件上传与下载
Struts2的文件上传
文件上传需要的jar包:commons-fileupload-1.3.1.jar与commons-io-2.5.jar2个支持包
在struts2中已经提供了上传的拦截器:
在文件中,我们需要设置请求的数据类型:以及请求方式,我们知道get请求提交的数据量只有1kb,有大小限制,post没有大小限制,
文件上传中设置表单的enctype="application/x-www-form-urlencoded"这是普通数据提交类型,文件上传我们需要将表单的enctype="multipart/form-data"这是2进制数据提交
package com.struts.action; import com.opensymphony.xwork2.ActionSupport; import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; /** * 文件上传Action */ public class UpLoadAction extends ActionSupport { private File file; // input中的name+ FileName private String fileFileName; //input中的name+ContentType private String fileContentType; public File getFile() { return file; } public void setFile(File file) { this.file = file; } public String getFileFileName() { return fileFileName; } public void setFileFileName(String fileFileName) { this.fileFileName = fileFileName; } public String getFileContentType() { return fileContentType; } public void setFileContentType(String fileContentType) { this.fileContentType = fileContentType; } public String upload(){ System.out.println(this.fileFileName); System.out.println(this.fileContentType); File file =new File("H://"+fileFileName); try { // 把临时文件夹中的文件复制出来 FileUtils.copyFile (this.file,file); } catch (IOException e) { e.printStackTrace (); } return "success"; } }
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>$Title$</title> </head> <body> <form action="upload" method="post" enctype="multipart/form-data"> <input type="file" name="file"> <input type="submit" value="上传"/> </form> </body> </html>
多个文件上传,我们只需要将与文件相关的属性修改成数组形式,上传的方法也修改成数组形式
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>$Title$</title> </head> <body> <form action="upload" method="post" enctype="multipart/form-data"> <input type="file" name="file"> <input type="file" name="file"> <input type="file" name="file"> <input type="file" name="file"> <input type="submit" value="上传"/> </form> </body> </html>
package com.struts.action; import com.opensymphony.xwork2.ActionSupport; import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; /** * 文件上传Action */ public class UpLoadAction extends ActionSupport { private File[] file; // input中的name+ FileName private String[] fileFileName; public File[] getFile() { return file; } public void setFile(File[] file) { this.file = file; } public String[] getFileFileName() { return fileFileName; } public void setFileFileName(String[] fileFileName) { this.fileFileName = fileFileName; } public String upload(){ for (int i=0;i<file.length;i++){ File files = new File ("H://"+fileFileName[i]); try { FileUtils.copyFile (this.file[i],files); } catch (IOException e) { e.printStackTrace (); } } return "success"; } }
限制文件名与文件大小,以及文件类型
限制文件名:在我们上传后我们可以重新对文件的文件名进行设置。可以使用随机数:UUID.randomUUID().toString()
限制文件类型以及文件大小
我们需要设置文件的格式,可以直接通过上传的拦截器进行设置,但是文件大小需要注意,如果文件不上传,服务器是不知道文件的大小的,也就意味着,如果要控制文件的大小,需要先将文件上传,避免经过上传组件,实现上传功能,实现上传,会触发默认大小限制2M,所以需要将默认的文件大小限制修改成实际的文件大小限制
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN" "http://struts.apache.org/dtds/struts-2.5.dtd"> <struts> <!--设置上传的文件大小--> <constant name="struts.multipart.maxSize" value="100000000"></constant> <package name="user" namespace="/" extends="struts-default"> <!--<interceptors>--> <!--<interceptor-stack name="myStack">--> <!--<interceptor-ref name="token"></interceptor-ref>--> <!--<interceptor-ref name="defaultStack"></interceptor-ref>--> <!--</interceptor-stack>--> <!--</interceptors>--> <!--<default-interceptor-ref name="myStack"></default-interceptor-ref>--> <!--<action name="login" class="com.struts.action.LoginAction" method="login">--> <!--<result name="input">/index.jsp</result>--> <!--<result name="success" type="redirect">/index.jsp</result>--> <!--</action>--> <!--<action name="register" class="com.struts.action.RegisterAction" method="register">--> <!----> <!--<result name="success" type="redirect">/index.jsp</result>--> <!--<result name="invalid.token">/error.jsp</result>--> <!--</action>--> <action name="upload" class="com.struts.action.UpLoadAction" method="upload"> <result name="success">/index.jsp</result> <interceptor-ref name="fileUpload"> <!--设置文件类型--> <param name="allowedTypes"> text/plain </param> <param name="maximumSize"> 100000000 </param> </interceptor-ref> <interceptor-ref name="defaultStack"></interceptor-ref> </action> </package> </struts>
文件下载:
<a href="<%=path%>/downloadFile?download=UploadFile/readme.doc">点击链接下载文件</a>
文件下载:其实就是让服务器以流的形式进行给客户端响应
package com.struts.action; import com.opensymphony.xwork2.ActionSupport; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.net.URLEncoder; public class DownLoadAction extends ActionSupport{ private String filename; private InputStream inputStream; public String getFilename() { return filename; } public void setFilename(String filename) { this.filename = filename; } public InputStream getInputStream() { return inputStream; } public void setInputStream(InputStream inputStream) { this.inputStream = inputStream; } public String downLoad(){ File file = new File("H:"+ File.separatorChar+"upload"); File file1 = new File (file,filename); if(file1.exists ()){ //下载 try { inputStream = new FileInputStream (file1); } catch (FileNotFoundException e) { e.printStackTrace (); } }else{ //文件不存在 } return "success"; } }
<action name="download" class="com.struts.action.DownLoadAction" method="downLoad"> <result name="success" type="stream">/index.jsp <param name="inputName">inputStream</param> <param name="contentDisposition"> attachment;filename=${filename} </param> </result> </action>
六、ajax异步请求返回Json
struts2中使用ajax请求返回json数据
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <script type="text/javascript" src="js/jquery-3.3.1.min.js"></script> <script type="text/javascript"> $(function(){ $.getJSON("ajax",funtion(data){ alert(data.result) }); }); </script> </body> </html>
package com.ajax.action; import com.google.gson.Gson; import com.opensymphony.xwork2.ActionSupport; import pojo.User; public class AjaxAction extends ActionSupport { private String result; public String getResult() { return result; } public void setResult(String result) { this.result = result; } public String ajaxJson()throws Exception { System.out.println ("ajax请求"); User user = new User (); user.setUsername ("小明"); user.setPwd ("132345"); Gson gson = new Gson (); result = gson.toJson (user); System.out.println (result); return "success"; } }
七、struts2注解开发(了解即可)
在我们的struts2中提供了很多的注解,用来替换struts.xml的复杂配置,通常可以用来替换package、action等元素,这就意味着么哦们在struts2时,可以不使用struts.xml文件来开发了。但是事务都是有利有弊的,使用方便,维护起来就没那么方便了。
要使用注解方式,我们必须添加一个额外包:struts2-convention-plugin-2.x.x.jar。
package com.tjcyjd.web.action; import org.apache.struts2.convention.annotation.Action; import org.apache.struts2.convention.annotation.ExceptionMapping; import org.apache.struts2.convention.annotation.ExceptionMappings; import org.apache.struts2.convention.annotation.Namespace; import org.apache.struts2.convention.annotation.ParentPackage; import org.apache.struts2.convention.annotation.Result; import org.apache.struts2.convention.annotation.Results; import com.opensymphony.xwork2.ActionSupport; /** * Struts2基于注解的Action配置 * */ @ParentPackage("struts-default") @Namespace("/annotation_test") @Results( { @Result(name = "success", location = "/main.jsp"), @Result(name = "error", location = "/error.jsp") }) @ExceptionMappings( { @ExceptionMapping(exception = "java.lange.RuntimeException", result = "error") }) public class LoginAction extends ActionSupport { private static final long serialVersionUID = 2730268055700929183L; private String loginName; private String password; @Action("login") //或者写成 @Action(value = "login") public String login() throws Exception { if ("yjd".equals(loginName) && "yjd".equals(password)) { return SUCCESS; } else { return ERROR; } } @Action(value = "add", results = { @Result(name = "success", location = "/index.jsp") }) public String add() throws Exception { return SUCCESS; } <br> public String getLoginName() { return loginName; } public void setLoginName(String loginName) { this.loginName = loginName; } public String getPassword() { return password; } public void setPassword(String password) { this.password= password; } }
常用注解:
Namespace:指定命名空间。
ParentPackage:指定父包。
Result:提供了Action结果的映射。(一个结果的映射)
Results:“Result”注解列表
ResultPath:指定结果页面的基路径。
Action:指定Action的访问URL。
Actions:“Action”注解列表。
ExceptionMapping:指定异常映射。(映射一个声明异常)
ExceptionMappings:一级声明异常的数组。
InterceptorRef:拦截器引用。
InterceptorRefs:拦截器引用组。
实际上struts2中最主要的概念就是package、action以及Interceptor等等概念,所以只要明白这些注解就可以了。