1 <context-param> 2 <param-name>pattern</param-name> 3 <param-value>yyyy-MM-dd hh:mm:ss</param-value> 4 </context-param> 5 //获取当前 WEB 应用的初始化参数 pattern 6 ServletContext servletContext = ServletActionContext.getServletContext(); 7 System.out.println(servletContext); 8 String pattern = servletContext.getInitParameter("pattern");
Struts2 中, HTML 表单将被直接映射到一个 POJO,通过params拦截器,类中定义对应属性,及对应set方法即可。
params拦截器会把请求参数的值赋给栈顶对象对应的各个属性,如果栈顶对象没有对应属性,则往下找下一个对象对应的属性。
如果已经有了一个javabean,表单传过来了bean的属性,就不需要再action中再写一遍bean的每个属性来赋值了,可以直接写一个bean的变量,通过ModelDriven拦截器,如果 Action 类实现了 ModelDriven 接口,该拦截器将把 ModelDriven 接口的 getModel() 方法返回的对象置于栈顶,然后params拦截器就会将表单属性赋给栈顶的空的bean对象了。继承接口后这样写(可以没有setEmployee方法)
1 private Employee employee; 2 @Override 3 public Employee getModel() { 4 employee = new Employee(); 5 return employee; 6 }
代码里不能直接return new Employee();因为和成员变量employee没关系,所以其他用到employee对象的时候,它是空。
每次求情,只要有ModelDriven的getModel()方法,一般栈顶对象都会是该方法返回的对象!
用class.hashcode()方法,可以看对象是不是同一个。
关于回显:
从值栈站顶开始查找匹配的属性,若找到,就添加到value属性中,就会自动给赋上。
下图29。18
通常情况下,用ModelDriven拦截器要和Preparable拦截器一起用。
Struts 2.0 中的 modelDriven 拦截器负责把 Action 类以外的一个对象压入到值栈栈顶
而 prepare 拦截器负责准备为 getModel() 方法准备 model
1 5). 存在的问题: 2 3 getModel 方法 4 5 public Employee getModel() { 6 if(employeeId == null) 7 employee = new Employee(); 8 else 9 employee = dao.get(employeeId); 10 11 return employee; 12 } 13 14 I. 在执行删除的时候, employeeId 不为 null, 但 getModel 方法却从数据库加载了一个对象. 不该加载! 15 II. 指向查询全部信息时, 也 new Employee() 对象. 浪费! 16 17 6). 解决方案: 使用 PrepareInterceptor 和 Preparable 接口. 18 19 7). 关于 PrepareInterceptor 20 21 [分析后得到的结论] 22 23 若 Action 实现了 Preparable 接口, 则 Struts 将尝试执行 prepare+ActionMethodName 方法, 24 若 prepare+ActionMethodName 不存在, 则将尝试执行 prepareDo+ActionMethodName 方法. 25 若都不存在, 就都不执行. 26 27 若 PrepareInterceptor 的 alwaysInvokePrepare 属性为 false, 28 则 Struts2 将不会调用实现了 Preparable 接口的 Action 的 prepare() 方法 29 30 [能解决 5) 的问题的方案] 31 32 可以为每一个 ActionMethod 准备 prepare[ActionMethdName] 方法, 而抛弃掉原来的 prepare() 方法 33 将 PrepareInterceptor 的 alwaysInvokePrepare 属性置为 false, 以避免 Struts2 框架再调用 prepare() 方法. 34 35 如何在配置文件中为拦截器栈的属性赋值: 参看 /struts-2.3.15.3/docs/WW/docs/interceptors.html 36 一共有三种方法,有的可以为一个action修改值,有的可以为所有action修改至,下面是为所有action修改属性 37 <interceptors> 38 <interceptor-stack name="parentStack"> 39 <interceptor-ref name="defaultStack"> 40 <param name="params.excludeParams">token</param> 41 </interceptor-ref> 42 </interceptor-stack> 43 </interceptors> 44 45 <default-interceptor-ref name="parentStack"/>
如图,私人订制,为需要的方法,添加上prepareMethod方法,来自自定义
修改拦截器中部分属性的配置如下:
paramsPrepareParmsStack拦截器栈,执行顺序:
params -> prepare -> modelDriven -> params
//***************************************类型转换*********
Parameters拦截器:把请求参数映射到action属性,能自动完成字符串和基本数据类型的转换。
如果类型转换失败:
若 Action 类没有实现 ValidationAware 接口: Struts 在遇到类型转换错误时仍会继续调用其 Action 方法, 就好像什么都没发生一样.
若 Action 类实现 ValidationAware 接口:Struts 在遇到类型转换错误时将不会继续调用其 Action 方法: Struts 将检查相关 action 元素的声明是否包含着一个 name=input 的
result(一般情况下,继承ActionSupport类就好了,然后,都是将表单输入页面当做该result,好比,后台age需要int,你输入个string,输入的不合法的值,我还给你返回这个界面). 如果有, Struts 将把控制权转交给那个 result 元素; 若没有 input 结果, Struts 将抛出一个异常
此时返回到input的result页面后,输入框上面还有错误消息提示,输入不合法之类的(xhtml主题下)
覆盖默认的出错消息
在对应的 Action 类所在的包中新建 ActionClassName.properties 文件, ClassName 即为包含着输入字段的 Action 类的类名
在属性文件中添加如下键值对: invalid.fieldvalue.fieldName=xxx
这样就可以了,但是在simple主题下,没用。这个时候需要用标签来处理,值栈中有错误信息(${fieldErrors.arg[0]}、<s:fielderror fieldName="age"></s:fielderror>放在输入框后面即可)
1 <body> 2 3 <!-- 4 问题1: 如何覆盖默认的错误消息? 5 1). 在对应的 Action 类所在的包中新建 6 ActionClassName.properties 文件, ActionClassName 即为包含着输入字段的 Action 类的类名 7 2). 在属性文件中添加如下键值对: invalid.fieldvalue.fieldName=xxx 8 9 10 问题2: 如果是 simple 主题, 还会自动显示错误消息吗? 如果不会显示, 怎么办 ? 11 1). 通过 debug 标签, 可知若转换出错, 则在值栈的 Action(实现了 ValidationAware 接口) 对象中有一个 fieldErrors 属性. 12 该属性的类型为 Map<String, List<String>> 键: 字段(属性名), 值: 错误消息组成的 List. 所以可以使用 LE 或 OGNL 的方式 13 来显示错误消息: ${fieldErrors.age[0]} 14 15 2). 还可以使用 s:fielderror 标签来显示. 可以通过 fieldName 属性显示指定字段的错误. 16 17 问题3. 若是 simple 主题, 且使用 <s:fielderror fieldName="age"></s:fielderror> 来显示错误消息, 则该消息在一个 18 ul, li, span 中. 如何去除 ul, li, span 呢 ? 19 在 template.simple 下面的 fielderror.ftl 定义了 simple 主题下, s:fielderror 标签显示错误消息的样式. 所以修改该 20 配置文件即可. 在 src 下新建 template.simple 包, 新建 fielderror.ftl 文件, 把原生的 fielderror.ftl 中的内容 21 复制到新建的 fielderror.ftl 中, 然后剔除 ul, li, span 部分即可. 22 23 问题4. 如何自定义类型转换器 ? 24 1). 为什么需要自定义的类型转换器 ? 因为 Struts 不能自动完成 字符串 到 引用类型 的 转换. 25 2). 如何定义类型转换器: 26 I. 开发类型转换器的类: 扩展 StrutsTypeConverter 类. 27 II. 配置类型转换器: 28 有两种方式 29 ①. 基于字段的配置: 30 > 在字段所在的 Model(可能是 Action, 可能是一个 JavaBean) 的包下, 新建一个 ModelClassName-conversion.properties 文件 31 > 在该文件中输入键值对: fieldName=类型转换器的全类名. 32 > 第一次使用该转换器时创建实例. 33 > 类型转换器是单实例的! 34 35 ②. 基于类型的配置: 36 > 在 src 下新建 xwork-conversion.properties 37 > 键入: 待转换的类型=类型转换器的全类名. 38 > 在当前 Struts2 应用被加载时创建实例. 39 40 --> 41 42 <s:debug></s:debug> 43 44 <s:form action="testConversion" theme="simple"> 45 Age: <s:textfield name="age" label="Age"></s:textfield> 46 ${fieldErrors.age[0] } 47 ^<s:fielderror fieldName="age"></s:fielderror> 48 <br><br> 49 50 Birth: <s:textfield name="birth"></s:textfield> 51 <s:fielderror fieldName="birth"></s:fielderror> 52 <br><br> 53 54 <s:submit></s:submit> 55 </s:form> 56 57 </body>
以上是字符串到基本数据类型的类型转换,下面是字符串到引用类型的类型转换(如日期,这个时候需要自定义类型转换器)
自定义类型转换器必须实现 ongl.TypeConverter 接口或对这个接口的某种具体实现做扩展
通常情况下,自定义类型转换器的类对StrutsTypeConverter进行扩展就好了
1. Department 是模型, 实际录入的 Department. deptName 可以直接写到
s:textfield 的 name 属性中. 那 mgr 属性如何处理呢 ?
struts2 表单标签的 name 值可以被赋为 属性的属性: name=mgr.name, name=mgr.birth
2. mgr 中有一个 Date 类型的 birth 属性, Struts2 可以完成自动的类型转换吗 ?
全局的类型转换器可以正常工作!
1 public class DateConverter extends StrutsTypeConverter { 2 3 private DateFormat dateFormat; 4 5 public DateConverter() { 6 System.out.println("DateConverter's constructor..."); 7 } 8 9 public DateFormat getDateFormat(){ 10 if(dateFormat == null){ 11 //获取当前 WEB 应用的初始化参数 pattern 12 ServletContext servletContext = ServletActionContext.getServletContext(); 13 System.out.println(servletContext); 14 String pattern = servletContext.getInitParameter("pattern"); 15 dateFormat = new SimpleDateFormat(pattern); 16 } 17 18 return dateFormat; 19 } 20 21 @Override 22 public Object convertFromString(Map context, String[] values, Class toClass) { 23 24 System.out.println("convertFromString..."); 25 26 if(toClass == Date.class){ 27 if(values != null && values.length > 0){ 28 String value = values[0]; 29 try { 30 return getDateFormat().parseObject(value); 31 } catch (ParseException e) { 32 e.printStackTrace(); 33 } 34 } 35 } 36 37 //若没有转换成功, 则返回 values 38 return values; 39 } 40 41 @Override 42 public String convertToString(Map context, Object o) { 43 44 System.out.println("convertToString..."); 45 46 if(o instanceof Date){ 47 Date date = (Date) o; 48 return getDateFormat().format(date); 49 } 50 51 //若转换失败返回 null 52 return null; 53 } 54 55 }
//**************************************国际化**************************************
1 1. 国际化的目标 2 3 1). 如何配置国际化资源文件 4 5 I. Action 范围资源文件: 在Action类文件所在的路径建立名为 ActionName_language_country.properties 的文件 6 II. 包范围资源文件: 在包的根路径下建立文件名为 package_language_country.properties 的属性文件, 7 一旦建立,处于该包下的所有 Action 都可以访问该资源文件。注意:包范围资源文件的 baseName 就是package,不是Action所在的包名。 8 III. 全局资源文件 9 > 命名方式: basename_language_country.properties 10 > struts.xml <constant name="struts.custom.i18n.resources" value="baseName"/> 11 12 IV. 国际化资源文件加载的顺序如何呢 ? 离当前 Action 较近的将被优先加载. 13 14 假设我们在某个 ChildAction 中调用了getText("username"): 15 16 (1) 加载和 ChildAction 的类文件在同一个包下的系列资源文件 ChildAction.properties 17 (2) 加载 ChildAction 实现的接口 IChild,且和 IChildn 在同一个包下 IChild.properties 系列资源文件。 18 (3) 加载 ChildAction 父类 Parent,且和 Parent 在同一个包下的 baseName 为 Parent.properties 系列资源文件。 19 (4) 若 ChildAction 实现 ModelDriven 接口,则对于getModel()方法返回的model 对象,重新执行第(1)步操作。 20 (5) 查找当前包下 package.properties 系列资源文件。 21 (6) 沿着当前包上溯,直到最顶层包来查找 package.properties 的系列资源文件。 22 (7) 查找 struts.custom.i18n.resources 常量指定 baseName 的系列资源文件。 23 (8) 直接输出该key的字符串值。 24 25 26 2). 如何在页面上 和 Action 类中访问国际化资源文件的 value 值 27 28 I. 在 Action 类中. 若 Action 实现了 TextProvider 接口, 则可以调用其 getText() 方法获取 value 值 29 > 通过继承 ActionSupport 的方式。 30 1.在action中访问国际化资源文件的value值 31 String username = getText("username"); 32 2.带占位符的 33 String time = getText("time",Arrays.asList(new Date())); 34 syso,就可以打印出来 35 36 II. 页面上可以使用 s:text 标签; 对于表单标签可以使用表单标签的 key 属性值 37 > 若有占位符, 则可以使用 s:text 标签的 s:param 子标签来填充占位符 38 > 可以利用标签和 OGNL 表达式直接访问值栈中的属性值(对象栈 和 Map 栈) 39 40 time=Time:{0}(这个是在i18n.properties中写的) 41 42 <s:text name="time"> 43 <s:param value="date"></s:param> 44 </s:text> 45 46 ------------------------------------ 47 48 time2=Time:${date}(类似一个ongl表达式的情况,在i18n.properties中直接获取值) 49 50 <s:text name="time2"></s:text> 51 52 53 3). 实现通过超链接切换语言. 54 55 I. 关键之处在于知道 Struts2 框架是如何确定 Local 对象的 ! 56 II. 可以通过阅读 I18N 拦截器知道. 57 III. 具体确定 Locale 对象的过程: 58 59 > Struts2 使用 i18n 拦截器 处理国际化,并且将其注册在默认的拦截器栈中 60 > i18n拦截器在执行Action方法前,自动查找请求中一个名为 request_locale 的参数。 61 如果该参数存在,拦截器就将其作为参数,转换成Locale对象,并将其设为用户默认的Locale(代表国家/语言环境)。 62 并把其设置为 session 的 WW_TRANS_I18N_LOCALE 属性 63 > 若 request 没有名为request_locale 的参数,则 i18n 拦截器会从 Session 中获取 WW_TRANS_I18N_LOCALE 的属性值, 64 若该值不为空,则将该属性值设置为浏览者的默认Locale 65 > 若 session 中的 WW_TRANS_I18N_LOCALE 的属性值为空,则从 ActionContext 中获取 Locale 对象。 66 67 IV. 具体实现: 只需要在超连接的后面附着 request_locale 的请求参数, 值是 语言国家 代码. 68 <a href="testI18n.action?request_locale=en_US">English</a> 69 <a href="testI18n.action?request_locale=zh_CN">中文</a> 70 71 > 注意: 超链接必须是一个 Struts2 的请求, 即使 i18n 拦截器工作! 72 73 74
在页面上使用国际化(xhtml主题):
在页面上使用国际化(simple主题):
国际化资源文件:
**************************************国际化**************************************//
//**************************************动态加载国际化**************************************
i18n拦截器
具体实现代码:
**************************************动态加载国际化**************************************//