Java
Java Web框架

Struts2(2)

简介:Struts2是一个基于MVC设计模式的Web应用框架,它本质上相当于一个Servlet,在MVC设计模式中,Struts2作为控制器(Controller)来建立模型与视图的数据交互。Struts2是Struts的下一代产品,是在Struts1和WebWork的技术基础上进行了合并的全新的Struts2框架。

1. 自定义转发类型

从Struts源码可以知道,所有的转发类型都是继承自org.apache.struts2.dispatcher.StrutsResultSupport的类,因此我们可以自己定义一个转发类型。新建一个类继承自org.apache.struts2.dispatcher.StrutsResultSupport,代码如下:

  • package com.coderap.resulttype;
  • import javax.servlet.http.HttpServletRequest;
  • import javax.servlet.http.HttpServletResponse;
  • import org.apache.struts2.ServletActionContext;
  • import org.apache.struts2.dispatcher.StrutsResultSupport;
  • import com.opensymphony.xwork2.ActionInvocation;
  • public class MyDispatcher extends StrutsResultSupport{
  • @Override
  • // 参数1 要转发的路径
  • // 参数2 ActionInvocation
  • protected void doExecute(String path, ActionInvocation ai)
  • throws Exception {
  • // 1 获得request对象
  • HttpServletRequest req = ServletActionContext.getRequest();
  • HttpServletResponse resp = ServletActionContext.getResponse();
  • // 2 调用request转发方法
  • req.getRequestDispatcher(path).forward(req, resp);
  • }
  • }

以上代码可以通过struts.xml文件进行配置使用:

  • <result-types>
  • <!-- 自定义结果处理类 -->
  • <result-type name="dispatcher2" class="com.coderap.resulttype.MyDispatcher"></result-type>
  • </result-types>

这样以来就可以将<result>中配置结果类型的参数改为dispatcher2

  • <action name="Demo1Action" class="com.coderap.resulttype.Demo1Action" method="execute" >
  • <result name="success" type="dispatcher2" >/index.jsp</result>
  • </action>

另外我们可以通过参数注入为转发的页面注入一些有用的参数:

  • <action name="Demo2Action" class="com.coderap.resulttype.Demo2Action" method="execute" >
  • <result name="success" type="plainText" >
  • <!-- 参数注入 -->
  • <param name="charSet">UTF-8</param>
  • <param name="location">/index.jsp</param>
  • </result>
  • </action>

在上述的配置中,我们配置了result的类型为plainText,这样可以让服务器直接将网页的内容进行返回,不会解析JSP语法。而在result中还进行了参数的注入,设置了charSetlocation参数。

2. 文件下载

使用stream类型的result的可以进行文件下载的操作,具体的Action的配置如下:

  • <action name="Demo3Action" class="com.coderap.resulttype.Demo3Action" method="execute" >
  • <result name="success" type="stream">
  • <param name="contentType">application/zip</param>
  • <param name="inputName">downloadFileIS</param>
  • <param name="contentDisposition">attachment;filename="DownloadFile.zip"</param>
  • <param name="bufferSize">10240</param>
  • </result>
  • </action>

在上述的配置中,通过给result注入一些配置参数,实现了下载的配置,但是需要注意的是,虽然配置信息详细地说明了下载文件的类型、缓冲区大小等信息,但是其中的inputName需要我们手动指定一个Action中提供的InputStream进行注入,具体代码如下:

  • package com.coderap.resulttype;
  • import java.io.InputStream;
  • import java.util.Map;
  • import javax.servlet.ServletContext;
  • import javax.servlet.http.HttpServletRequest;
  • import javax.servlet.http.HttpServletResponse;
  • import org.apache.struts2.ServletActionContext;
  • import org.apache.struts2.interceptor.ServletRequestAware;
  • import org.apache.struts2.interceptor.ServletResponseAware;
  • import org.apache.struts2.interceptor.SessionAware;
  • import com.opensymphony.xwork2.ActionSupport;
  • public class Demo4Action extends ActionSupport{
  • public InputStream getDownloadFileIS(){
  • // 1读取文件流
  • // 需要servletContext
  • ServletContext sc = ServletActionContext.getServletContext();
  • InputStream is = sc.getResourceAsStream("/WEB-INF/DownloadFile.zip");
  • //2 返回
  • return is;
  • }
  • public String execute(){
  • return SUCCESS;
  • }
  • }

在上述代码中,getDownloadFileIS()方法创建了相应的输入流,并将需要提供给下载的文件装入其中,然后将该流返回;这样以来,Struts会通过上面配置信息中的<param name="inputName">downloadFileIS</param>默认寻找Action中的getDownloadFileIS()的方法获取输入流,然后提供下载文件。

3. 获取表单参数

开发中最常见的场景就是获取表单中的数据了,在此分别介绍几种获取表单数据的参数。首先我们需要搭建一个JSP页面用于提交表单,核心代码如下:

  • <form action="/struts2-day02/param/Demo1Action.do" method="post" >
  • <input type="text" name="name" /><input type="submit" value="gogo"/>
  • </form>

该表单将以POST的方式提交数据到Demo1Action.doAction中。

3.1. 方式一

Demo1Action.doAction的类中,编写以下代码:

  • package com.coderap.param;
  • import java.util.Arrays;
  • import java.util.Map;
  • import com.opensymphony.xwork2.ActionContext;
  • import com.opensymphony.xwork2.ActionSupport;
  • import com.sun.net.httpserver.Authenticator.Success;
  • public class Demo1Action extends ActionSupport{
  • public String execute(){
  • // 获得参数方式1
  • // 1 获得封装参数的map
  • Map<String, Object> params = ActionContext.getContext().getParameters();
  • // 2 获得参数
  • String[] names = (String[]) params.get("name");
  • System.out.println(Arrays.toString(names));
  • return SUCCESS;
  • }
  • }

同时,struts.xml配置文件Action核心代码如下:

  • <action name="Demo1Action" class="com.coderap.param.Demo1Action" method="execute" >
  • <result name="success" type="dispatcher" >/param/demo1.jsp</result>
  • </action>

访问相应的Action地址,提交表单,可以顺利的获取表单数据。这就是获取参数的最简单的一种方式。

3.2. 方式二

第二种方式只需要在Action中生成表单对应的属性名的属性和其Getter和Setter方法即可:

  • package cn.itcast.d_param;
  • import java.util.Arrays;
  • import java.util.Map;
  • import com.opensymphony.xwork2.ActionContext;
  • import com.opensymphony.xwork2.ActionSupport;
  • import com.sun.net.httpserver.Authenticator.Success;
  • // action可以作为封装数据的JavaBean来使用。
  • // servlet可以吗?不可以。因为只有一个Servlet实例
  • // action为什么可以?因为一个线程对应一个Action对象
  • public class Demo2Action extends ActionSupport{
  • private String name;
  • public String getName() {
  • return name;
  • }
  • // 接收表单提交参数,只需要在Action中准备与提交键相同的属性,即可。
  • // struts2 会自动将提交的值赋值到属性中
  • public void setName(String name) {
  • this.name = name;
  • }
  • // 第2种获得参数方式
  • public String execute(){
  • System.out.println(name);
  • return SUCCESS;
  • }
  • }

同时,struts.xml配置文件Action核心代码如下:

  • <action name="Demo2Action" class="com.coderap.param.Demo2Action" method="execute" >
  • <result name="success" type="dispatcher" >/param/demo2.jsp</result>
  • </action>

访问相应的Action地址,提交表单,可以顺利的获取表单数据。这就是获取参数的最简单的第二种方式。

注1:action可以作为封装数据的JavaBean来使用,因为一个线程对应一个Action对象;但是servlet不可以,因为只有一个Servlet实例。
注2:struts会自动转换八大基本数据类型和对应包装类,以及Date类型,其中Date类型对数据提交格式有要求是yyyy-MM-dd
注3:struts提供了相应的标签,可以用于简化开发:

  • <s:form action="Demo3Action" namespace="/param" >
  • <s:textfield name="age" label="年龄" ></s:textfield>
  • <s:textfield name="money" label="工资" ></s:textfield>
  • <s:textfield name="married" label="婚否" ></s:textfield>
  • <s:textfield name="sex" label="性别" ></s:textfield>
  • <s:textfield name="birthday" label="生日" ></s:textfield>
  • <s:submit value="gogo" ></s:submit>
  • </s:form>

4. 自定义类型转换器

在开发中,我们经常会碰到用户输入数据和规定格式不相同的情况,这种情况下提交的数据Struts将无法进行自动数据转换,比如,Struts要求Date数据的规定格式为yyyy-MM-dd,但是有时候我们会碰到输入数据格式为yyyy/MM/dd的情况,这种情况下将无法进行正确的数据类型转换,这个时候我们就需要用到自定义类型转换器了。

4.1. 实现类

实现自定义转换器的方式有以下几种:

  • 方案1:实现TypeConverter接口,有一个方法,但参数过多。

  • 方法2:继承DefaultTypeConverter默认实现类,提供简洁方法convertValue(Object , Class)convertValue(Object value , Class toType)方法的缩回碰到转换的两种具体情况解释如下:

  1. 表单提交,浏览器发送到服务器。浏览器发送的肯定字符串String,需要转换成指定的类型。例如Date类型,这个时候有:

参数value表示浏览器发送的数据。类型是String[],底层使用request.getParameterValues("...");参数toType表示需要转换的类型,java.uilt.Date类型。具体代码操作如下:

  • // 如果toType是 Date类型,表示希望将 字符串转成 时间
  • if(toType == java.util.Date.class){
  • //获得数据
  • String[] params = (String[])value;
  • //转成成时间
  • }
  1. 标签回显,服务器发送给浏览器,类型之前已经从字符串转成时间,现在希望将时间再转换成字符串在,这个时候有

参数value表示服务器已经转成好的时间,类型为Date;参数toType表示需要转换的类型,类型为String。具体代码操作如下:

  • if(toType == String.class){
  • // 将数据强转时间
  • Date date = (Date)value;
  • // 格式化
  • }

转换器的所有代码如下:

  • package com.coderap.param;
  • import java.text.ParseException;
  • import java.text.SimpleDateFormat;
  • import java.util.Date;
  • import com.opensymphony.xwork2.conversion.impl.DefaultTypeConverter;
  • public class MyDateConverter extends DefaultTypeConverter {
  • @Override
  • // 参数1: 待转换的对象
  • // 参数2: 需要被转换成的类型
  • public Object convertValue(Object value, Class toType) {
  • try {
  • //1 创建sdf 格式化
  • SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
  • //2 判断参数2的类型
  • if(toType == Date.class){
  • // Date.class=> 字符串转日期
  • // 2012/12/12 => Date.class
  • String[] params = (String[]) value;
  • return sdf.parse(params[0]);
  • }
  • if(toType == String.class){
  • // String.class = > 日期转字符串
  • // Date => 2012/12/12
  • Date date = (Date) value;
  • return sdf.format(date);
  • }
  • } catch (ParseException e) {
  • e.printStackTrace();
  • throw new RuntimeException(e);
  • }
  • throw new RuntimeException("不可用");
  • }
  • }

配置转换器的方式有两种:单独为某个Action进行配置,以及全局配置。

  1. 单独配置

使用单独配置时,只对当前Action类有效,需要在配置对应的Action的类文件目录下,创建一个简单类名--conversion.properties的文件,并在该文件中写入转换器的对应信息。如:

Demo3Action-conversion.properties文件:

  • birthday=com.coderap.param.MyDateConverter
  1. 全局配置

使用全局配置时,对所有的Action类有效,需要在SRC目录下,创建一个固定名为xwork-conversion.properties的文件,并在该文件中写入转换器的对应信息。如:

xwork-conversion.properties文件:

  • java.util.Date=com.coderap.param.MyDateConverter

这样以来,Struts在做数据处理的时候就会自动加载配置文件进行配置了。

注:自定义转换器很少使用,一般情况使用默认就可以。

5. 数据校验

校验分为浏览器端校验,以JavaScript实现,但是但不安全;服务器端校验可以使用struts校验。

5.1. struts校验介绍

  • 手动校验:编写代码;适用于需要与数据库交互。
  • xml校验:编写配置文件;通用校验,逻辑简单。例如:不能为空,长度10,是否相等。

5.1.1. 手动校验

如果需要手动校验,必须实现Validateable接口,提供一个validate()方法。如果需要对action的所有方法校验,则需要实现接口,并实现方法validate();如果只需要实现action单个方法校验,则只需编写方法validate方法() , 此处“方法”表示执行的方法名称,首字母大写。例如:add()执行前需要校验,必须编写validateAdd()方法。

注:两个校验同时存在时,先执行“单个方法”校验,再执行“所有方法”校验。

实例代码如下:

  • @Override
  • public class Demo1Action extends ActionSupport{
  • private String name;
  • public String execute(){
  • System.out.println(name);
  • return SUCCESS;
  • }
  • @Override
  • public void validate() {
  • // 验证代码放入该方法
  • // 判断name参数合法性
  • if(name ==null || "".equals(name)){
  • // 不合法,调用addFieldError添加错误信息
  • addFieldError("name", "用户名不能为空!");
  • }
  • // 添加非表单字段错误信息
  • addActionError("用户名已经重复!");
  • // 添加提示信息
  • addActionMessage("哈哈,这就是提示没别的意思!");
  • }
  • public String getName() {
  • return name;
  • }
  • public void setName(String name) {
  • this.name = name;
  • }
  • }

数据校验提供错误提示,阻止目标方法的执行,一共有以下几种:

  • // 给指定的字段设置提示信息
  • `this.addFieldError("", "")`
  • // Jsp显示错误
  • <s:fielderror>
  • // action提示提示信息
  • this.addActionMessage(aMessage)
  • // Jsp显示错误
  • <s:actionmessage/>
  • // action错误
  • this.addActionError(anErrorMessage)
  • // Jsp显示错误
  • <s:actionerror/>

5.1.2. 定制错误信息

我们发现Struts2打印的类型转换错误信息是英文的,这说明我们需要自定义错误信息。自定义错误信息需要在Action所在目录下创建ActionName.properties文件(与Action同名的properties文件),然后在该文件中给出invalid.fieldvalue.属性名=错误信息,其中invalid.fieldvalue是固定的。例如:invalid.fieldvalue.person=无法将请求参数转换成Person类型!

  • // PersonAction.proeprties
  • invalid.fieldvalue.person=无法把表单参数转换成Person类型