概述
从一个 HTML 表单到一个 Action 对象, 类型转换是从字符串到非字符串.
HTTP 没有 “类型” 的概念. 每一项表单输入只可能是一个字符串或一个字符串数组. 在服务器端, 必须把 String 转换为特定的数据类型
在 struts2 中, 把请求参数映射到 action 属性的工作由 Parameters 拦截器负责, 它是默认的 defaultStack 拦截器中的一员**. Parameters** 拦截器可以自动完成字符串和基本数据类型之间转换.
类型转换错误
若 Action 类没有实现 ValidationAware 接口: Struts 在遇到类型转换错误时仍会继续调用其 Action 方法, 就好像什么都没发生一样.
若 Action 类实现 ValidationAware 接口:Struts 在遇到类型转换错误时将不会继续调用其 Action 方法: Struts 将检查相关 action 元素的声明是否包含着一个 name=input 的 result. 如果有, Struts 将把控制权转交给那个 result 元素; 若没有 input 结果, Struts 将抛出一个异常
例子:
//index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<s:form action="testConversion">
<s:textfield name="age" label="Age"></s:textfield>
<s:submit></s:submit>
</s:form>
</body>
</html>
//success.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
Insert title here
</body>
</html>
testConversion.java//
package product;
import com.opensymphony.xwork2.ActionSupport;
public class testConversion extends ActionSupport{
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String execute(){
System.out.println("age"+age);
return "success";
}
}
//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="hello" extends="struts-default" >
<action name="testConversion" class="product.testConversion">
<result>/success.jsp</result>
<result name="input">/index.jsp</result>
</action>
</package>
</struts>
类型转换错误消息的定制
作为默认的 default 拦截器的一员, ConversionError 拦截器负责添加与类型转换有关的出错消息(前提: Action 类必须实现了 ValidationAware 接口)和保存各请求参数的原始值.
若字段标签使用的不是 simple 主题, 则非法输入字段将导致一条有着以下格式的出错消息:
覆盖默认的出错消息
在对应的 Action 类所在的包中新建 ActionClassName.properties 文件, ClassName 即为包含着输入字段的 Action 类的类名
在属性文件中添加如下键值对:
如果是theme=“simple”的话,index.jsp改善
<s:debug></s:debug>
<s:form action="testConversion" theme="simple">
<s:textfield name="age" label="Age"></s:textfield>
${fieldErrors.age[0] }
<s:fielderror name="age"></s:fielderror>
<s:submit></s:submit>
</s:form>
问题:
若是 simple 主题, 且使用 <s:fielderror fieldName=“age”></s:fielderror> 来显示错误消息, 则该消息在一个 ul, li, span 中. 如何去除 ul, li, span 呢 ?
在struts-2.3.37-all\struts-2.3.37\apps\struts2-blank\WEB-INF\lib\struts2-core-2.3.37.jar\template.simple 下面的 fielderror.ftl 定义了 simple 主题下, s:fielderror 标签显示错误消息的样式. 所以修改该配置文件即可. 在 src 下新建 template.simple 包, 新建 fielderror.ftl 文件, 把原生的 fielderror.ftl 中的内容
复制到新建的 fielderror.ftl 中, 然后剔除 ul, li, span 部分即可.
具体操作28:00
定制出错消息的样式:
每一条出错消息都被打包在一个 HTML span 元素里, 可以通过覆盖其行标为 errorMessage 的那个 css 样式来改变出错消息的格式.
显示错误消息: 如果是 simple 主题, 可以通过 <s:fielderror fieldName=“filedname”></s:fielderror> 标签显示错误消息
定制类型转换器
index.jap改进
<s:form action="testConversion" theme="simple">
Age: <s:textfield name="age" label="Age"></s:textfield>
${fieldErrors.age[0] }
^<s:fielderror fieldName="age"></s:fielderror>
<br><br>
Birth: <s:textfield name="date"></s:textfield>
<s:fielderror fieldName="date"></s:fielderror>
<br><br>
<s:submit></s:submit>
</s:form>
自定义类型转换器必须实现 ongl.TypeConverter 接口或对这个接口的某种具体实现做扩展
I. 开发类型转换器的类: 扩展 StrutsTypeConverter 类.
package product;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import javax.servlet.ServletContext;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.util.StrutsTypeConverter;
import org.apache.struts2.util.StrutsTypeConverter;
public class DateConverter extends StrutsTypeConverter {
private DateFormat format1;
public DateConverter() {
System.out.println(1);
format1=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
}
@Override
public Object convertFromString(Map arg0, String[] arg1, Class arg2) {
System.out.println(2);
// TODO Auto-generated method stub
if(arg2==Date.class)
if(arg1!=null&&arg1.length>0)
try {String a=arg1[0];
return format1.parseObject(a);
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return arg1;
}
@Override
public String convertToString(Map arg0, Object arg1) {
// TODO Auto-generated method stub
System.out.println(3);
if(arg1 instanceof Date){
Date date = (Date) arg1;
return format1.format(date);
}
//若转换失败返回 null
return null;
}
}
II. 配置类型转换器:
有两种方式
①. 基于字段的配置:
> 在字段所在的 Model(可能是 Action, 可能是一个 JavaBean) 的包下, 新建一个 ModelClassName-conversion.properties 文件
> 在该文件中输入键值对: fieldName=类型转换器的全类名.
> 第一次使**用该转换器时创建实例**.
> 类型转换器是单实例的!
如果model分开
名字改Customer-conversion.properties并在一个包中
②. 基于类型的配置:
> 在 src 下新建 xwork-conversion.properties
> 键入: 待转换的类型=类型转换器的全类名.
> 在当前 **Struts2 应用被加载时创建实例**.
类型转换与复杂对象配合使用
form 标签的 name 属性可以被映射到一个属性的属性.
在上面的基础上修改
index.jsp
<s:form action="testConversion" theme="simple">
Age: <s:textfield name="age" label="Age"></s:textfield>
${fieldErrors.age[0] }
^<s:fielderror fieldName="age"></s:fielderror>
<br><br>
Birth: <s:textfield name="he.date"></s:textfield>
<s:fielderror fieldName="he.date"></s:fielderror>
<br><br>
<s:submit></s:submit>
</s:form>
////////////////////////////////////////////////////////////
package product;
import java.util.Date;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;
public class testConversion extends ActionSupport implements ModelDriven<xin>{
public String execute(){
System.out.println("xin"+x);
return "success";
}
private xin x;
@Override
public xin getModel() {
// TODO Auto-generated method stub
x=new xin();
return x;
}
}
/////////////////////////////////////////////////////
package product;
import java.util.Date;
public class xin {
private int age;
private He he;
public int getAge() {
return age;
}
public He getHe() {
return he;
}
public void setHe(He he) {
this.he = he;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "xin [age=" + age + ", he=" + he + "]";
}
}
////////////////////////////////////////////////////
package product;
import java.util.Date;
public class He {
private Date date;
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public He(Date date) {
super();
this.date = date;
}
@Override
public String toString() {
return "He [date=" + date + "]";
}
public He() {
super();
}
}
消息处理与国际化
在程序设计领域, 把在无需改写源代码即可让开发出来的应用程序能够支持多种语言和数据格式的技术称为国际化.
与国际化对应的是本地化, 指让一个具备国际化支持的应用程序支持某个特定的地区
Struts2 国际化是建立在 Java 国际化基础上的:为不同国家/语言提供对应的消息资源文件,Struts2 框架会根据请求中包含的 Locale 加载对应的资源文件通过程序代码取得该资源文件中 指定 key 对应的消息
配置国际化资源文件
1.Action 范围资源文件:
在Action类文件所在的路径建立名为ActionName_language_country.properties 的文件
2.包范围资源文件:
在包的根路径下建立文件名为package_language_country.properties 的属性文件,一旦建立,处于该包下的所有 Action 都可以访问该资源文件。注意:包范围资源文件的 baseName 就是package,不是Action所在的包名。
3.全局资源文件
命名方式: basename_language_country.properties
struts.xml
struts.propertiesstruts.custom.i18n.resources=baseName
4.临时指定资源文件:
<s:i18n…/> 标签的 name 属性指定临时的国际化资源文件
国际化资源文件加载的顺序如何呢 ? 离当前 Action 较近的将被优先加载.
假设我们在某个 ChildAction 中调用了getText(“username”):
(1)加载和 ChildAction 的类文件在同一个包下的系列资源文件 ChildAction.properties
(2)加载 ChildAction 实现的接口 IChild,且和 IChildn 在同一个包下 IChild.properties 系列资源文件。
(3)加载 ChildAction 父类 Parent,且和 Parent 在同一个包下的 baseName 为 Parent.properties 系列资源文件。
(4) 若 ChildAction 实现 ModelDriven 接口,则对于getModel()方法返回的model 对象,重新执行第(1)步操作。
(5) 查找当前包下 package.properties 系列资源文件。
(6) 沿着当前包上溯,直到最顶层包来查找 package.properties 的系列资源文件。
(7) 查找 struts.custom.i18n.resources 常量指定 baseName 的系列资源文件。
(8) 直接输出该key的字符串值。
如何在页面上 和 Action 类中访问国际化资源文件的 value 值
I. 在 Action 类中. 若 Action 实现了 TextProvider 接口, 则可以调用其 getText() 方法获取 value 值
> 通过继承 ActionSupport 的方式。
II. 页面上可以使用 s:text 标签; 对于表单标签可以使用表单标签的 key 属性值
> 若有占位符, 则可以使用 s:text 标签的 s:param 子标签来填充占位符
> 可以利用标签和 OGNL 表达式直接访问值栈中的属性值(对象栈 和 Map 栈)
总之
JSP 页面访问国际化消息:
不带占位符:
<s:text name=“key”/>
表单元素的 label 属性:可替换为 key 或使用 getText() 方法,并对其进行强制 OGNL 解析
带占位符:
在 <s:text…/> 标签中使用多个 <s:param…/> 标签来填充消息中的占位符。
Struts2 直接在国际化消息资源文件中通过 “${}” 使用表达式,该表达式将从值栈中获取对应的属性值
Action 访问国际化消息:
若 Action 类继承了 ActionSupport ,则可调用 TextProvider 接口的 getText 方法。
例子:
//index.jsp
<%@page import="java.util.Date"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<s:debug></s:debug>
<s:form action="">
<!--
若 label 标签使用 %{getText('username')} 的方式就也可以从国际化资源文件中获取 value 值了
因为此时在对象栈中有 DefaultTextProvider 的一个实例, 该对象中提供了访问国际化资源文件的 getText() 方法
同时还需要通知 struts2 框架 label 中放入的不再是一个普通的字符串, 而是一个 OGNL 表达式. 所以使用 %{} 把 getText()
包装起来, 以强制进行 OGNL 解析.
-->
<s:textfield name="username" label="%{getText('username')}"></s:textfield>
<!-- key 的方式是直接上资源文件中获取 value 值 -->
<s:textfield name="password" key="password"></s:textfield>
<s:submit name="submit" key="submit"></s:submit>
</s:form>
</body>
</html>
//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>
<constant name="struts.custom.i18n.resources" value="i18n"></constant>
<package name="hello" extends="struts-default" >
</package>
</struts>
资源文件
如果主题是simple的话,i18n.jsp改成
<s:form action="" theme="simple">
<!--
页面上可以直接使用 <s:text name="" /> 标签来访问国际化资源文件里的 value 值.
-->
<s:text name="username"></s:text>
<s:textfield name="username" label="%{getText('username')}"></s:textfield><br/>
<s:text name="password"></s:text><s:textfield name="password" key="password"></s:textfield><br/>
<s:submit name="submit" key="submit" value="%{getText('submit')}"></s:submit>
</s:form>
Action 类中访问国际化资源文件的 value 值
package product;
import java.util.Arrays;
import java.util.Date;
import com.opensymphony.xwork2.ActionSupport;
public class TestI18nAction extends ActionSupport {
/**
*
*/
private static final long serialVersionUID = 1L;
private Date date = null;
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
@Override
public String execute() throws Exception {
date = new Date();
//1. 在 Action 中访问国际化资源文件的 value 值
String username = getText("username");
System.out.println(username);
//2. 带占位符的
String time = getText("time", Arrays.asList(date));
System.out.println(time);
return SUCCESS;
}
}
在i18n.jsp加入
<s:text name="time"><s:param value="date"></s:param>
</s:text>
<s:text name="time2"></s:text>
利用超链接实现动态加载国际化资源文件
Struts2 使用 i18n 拦截器处理国际化,并且将其注册在默认的拦截器中
i18n拦截器在执行Action方法前,自动查找请求中一个名为request_locale 的参数。如果该参数存在,拦截器就将其作为参数,转换成Locale对象,并将其设为用户默认的Locale(代表国家/语言环境)。并把其设置为 session 的 WW_TRANS_I18N_LOCALE 属性
若 request 没有名为request_locale 的参数,则 i18n 拦截器会从 Session 中获取 WW_TRANS_I18N_LOCALE 的属性值,若该值不为空,则将该属性值设置为浏览者的默认Locale
若 session 中的 WW_TRANS_I18N_LOCALE 的属性值为空,则从 ActionContext 中获取 Locale 对象。
例子:
i18n.jsp加入以下
<a href="testI18n.action?request_locale=en_US">English</a>
<a href="testI18n.action?request_locale=zh_CN">中文</a>