1、在struts框架中,被调用的Action属性设置完毕后,框架自动调用execute()方法,在这个方法中在调用其他的service完成业务逻辑,最后返回SUCCESS等,但是,我们也可以自己指定要执行哪个方法。具体方法为:
(1)在Action中定义一个方法,这个方法要与execute()方法除名字外其他都相同,方法声明相同,例如定义了一个public String myExecute(){ return SUCCESS;}
(2)在struts.xml中对<action>标签进行修改,增加一个method属性,属性值就是方法的名字,如:
<action name="aas" class="com.cdtax.struts2.LoginAction" method="myExecute">
这样就指定了执行我们自己的方法。但不推荐使用这种方式,因为容易导致Action代码混乱。
2、后台对输入进行验证(又发现一问题,在action中名字为register也出错)
我们写的Action之所以能校验,是因为我们写的Action继承了ActionSupport,ActionSupport中有validate()方法。
struts框架先执行validate(),在执行execute();在我们自己Action中需要重写validate方法,就是添加我们自己的校验规则
实例:
(1)首先写一个输入的页面,register.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@ taglib prefix="s" uri="/struts-tags" %> <% String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <base href="<%=basePath%>"> <title>My JSP 'register.jsp' starting page</title> <meta http-equiv="pragma" content="no-cache"> <meta http-equiv="cache-control" content="no-cache"> <meta http-equiv="expires" content="0"> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> <meta http-equiv="description" content="This is my page"> <!-- <link rel="stylesheet" type="text/css" href="styles.css"> --> </head> <body> <h2>用户注册信息</h2> <hr> <form action="register1.action"> username:<input type="text" name="username" size="20"><br> password:<input type="password" name="password" size="20"><br> repassword:<input type="password" name="repassword" size="20"><br> age:<input type="text" name="age" size="20"><br> birthday:<input type="text" name="birthday" size="20"><br> graduation:<input type="text" name="graduation" size="20"><br> <input type="submit" value="submit"> </form> </body> </html>
(2)一个Action:RegisterAction.java
package com.cdtax.struts2; import java.util.Calendar; import java.util.Date; import com.opensymphony.xwork2.ActionSupport; public class RegisterAction extends ActionSupport { private String username; private String password; private String repassword; private int age; private Date birthday; private Date graduation; 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 getRepassword() { return repassword; } public void setRepassword(String repassword) { this.repassword = repassword; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public Date getGraduation() { return graduation; } public void setGraduation(Date graduation) { this.graduation = graduation; } @Override public String execute() throws Exception { System.out.println("execute invoke"); return SUCCESS; } @Override public void validate() { if(null == username || username.length() < 4 || username.length() > 6) { this.addActionError("username invalid"); this.addFieldError("username", "username invalid in field"); } if(null == password || password.length() < 4 || password.length() > 6) { this.addActionError("password invalid"); } else if(null == repassword || repassword.length() < 4 || repassword.length() > 6) { this.addActionError("repassword invalid"); } else if(!password.equals(repassword)) { this.addActionError("two password is not same"); } if(age < 10 || age >50) { this.addActionError("age invalid"); } if(null == birthday) { this.addActionError("birthday invalid"); } if(null == graduation) { this.addActionError("graduation invalid"); } if(null != birthday && null != graduation) { Calendar c1 = Calendar.getInstance(); c1.setTime(birthday); Calendar c2 = Calendar.getInstance(); c2.setTime(graduation); if(!c1.before(c2)) { this.addActionError("birthday should defore graduation"); } } } }
这个Action先对成员变量进行赋值,然后执行validate()进行值的校验,当然,在进行赋值前是要进行类型转换的,由于定义的都是原生数据类型和Date类型,struts自动进行类型转换,所以不用我们自己写converter。在这个validate中,如我输入值不符合我们的要求,就调用this.addActionError(String)方法,增加相应的错误提示信息,这是Action级别的错误信息,还可以调用this.addFieldError(String,String),这是Field级别错误信息。
(3)如果有输入有错误,那么就不执行execute(),而是转向struts.xml中该Action的名为input的result所对应的页面,这里是到输入页面register.jsp,相应的struts.xml中action描述为:
<action name="register1" class="com.cdtax.struts2.RegisterAction"> <result name="success">/registerResult.jsp</result> <result name="input">/register.jsp</result> </action>
(4)这里的问题是,转向到register.jsp后错误信息没有显示出来,而且我们之前在输入框中的信息也没有保留下来,这个问题可以使用struts提供的标签来解决,将register.jsp修改如下:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@ taglib prefix="s" uri="/struts-tags" %> <% String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <base href="<%=basePath%>"> <title>My JSP 'register.jsp' starting page</title> <meta http-equiv="pragma" content="no-cache"> <meta http-equiv="cache-control" content="no-cache"> <meta http-equiv="expires" content="0"> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> <meta http-equiv="description" content="This is my page"> <!-- <link rel="stylesheet" type="text/css" href="styles.css"> --> </head> <body> <h2>用户注册信息</h2> <s:actionerror cssStyle="color:red" /> <hr> <s:fielderror cssStyle="color:blue"></s:fielderror> <!-- <form action="register1.action"> username:<input type="text" name="username" size="20"><br> password:<input type="password" name="password" size="20"><br> repassword:<input type="password" name="repassword" size="20"><br> age:<input type="text" name="age" size="20"><br> birthday:<input type="text" name="birthday" size="20"><br> graduation:<input type="text" name="graduation" size="20"><br> <input type="submit" value="submit"> </form> --> <s:form action="register1.action"> <s:textfield name="username" label="username"></s:textfield><br/> <s:password name="password" label="password"></s:password><br/> <s:password name="repassword" label="repassword"></s:password><br/> <s:textfield name="age" label="age"></s:textfield><br/> <s:textfield name="birthday" label="birthday"></s:textfield><br/> <s:textfield name="graduation" label="graduation"></s:textfield><br/> <s:submit value="submit"></s:submit> </s:form> </body> </html>
先是引入struts标签库:<%@ taglib prefix="s" uri="/struts-tags" %>,然后添加如下标签显示错误信息:
<s:actionerror cssStyle="color:red" /> ——显示action级别错误,就是使用addActionError()方法添加的错误信息
<hr>
<s:fielderror cssStyle="color:blue"></s:fielderror> ——显示field级别错误,就是使用addFieldError()方法添加的错误信息
使用struts的form标签,可以显示错误及保留以前输入信息,但是field级别信息会出现两次,原因是使用struts标签的form会自动输出Field级别错误,这时我们可以设置struts的form标签,使其为简单类型,如下:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@ taglib prefix="s" uri="/struts-tags" %> <% String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <base href="<%=basePath%>"> <title>My JSP 'register.jsp' starting page</title> <meta http-equiv="pragma" content="no-cache"> <meta http-equiv="cache-control" content="no-cache"> <meta http-equiv="expires" content="0"> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> <meta http-equiv="description" content="This is my page"> <!-- <link rel="stylesheet" type="text/css" href="styles.css"> --> </head> <body> <h2>用户注册信息</h2> <s:actionerror cssStyle="color:red" /> <hr> <s:fielderror cssStyle="color:blue"></s:fielderror> <!-- <form action="register1.action"> username:<input type="text" name="username" size="20"><br> password:<input type="password" name="password" size="20"><br> repassword:<input type="password" name="repassword" size="20"><br> age:<input type="text" name="age" size="20"><br> birthday:<input type="text" name="birthday" size="20"><br> graduation:<input type="text" name="graduation" size="20"><br> <input type="submit" value="submit"> </form> --> <s:form action="register1.action" theme="simple"> username:<s:textfield name="username" label="username"></s:textfield><br/> password:<s:password name="password" label="password"></s:password><br/> repassword:<s:password name="repassword" label="repassword"></s:password><br/> age:<s:textfield name="age" label="age"></s:textfield><br/> birthday:<s:textfield name="birthday" label="birthday"></s:textfield><br/> graduation:<s:textfield name="graduation" label="graduation"></s:textfield><br/> <s:submit value="submit"></s:submit> </s:form> </body> </html>
这句:<s:form action="register1.action" theme="simple">,可以禁止其自动输出field级别错误
(5)如果我们在输入框age,birthday和graduation中输入不合法的内容,如输入aaa,bbb,ccc字符串,输出时Field级别错误会多出一些,这些多出来的错误信息是struts在类型转换时调用addFieldError()添加的
执行流程:
1)首先进行类型转换
2)然后进行输入校验(执行validate方法)
3)如果在上述过程中出现了任何错误,都不会再去执行execute方法,会转向struts.xml中该Action的名为input的result所对应的页面
在类型转换过程中出现的错误,struts会自动添加到field级别错误中,就是自动调用addFieldError(),相应的还用action级别错误,这个错误是调用addActionError()添加的,是我们手工执行的。
3、我们的猜测,调用addActionError和addFieldError方法增加错误信息,如果我们在validate方法最后将actionError和fieldError删除,是不是就会转到成功页面,即执行execute方法呢:使用:this.getFieldErrors().clear();和this.getActionErrors().clear();
结果不行!!!!
4、分析addActionError()源代码
public void addActionError(String anErrorMessage) { validationAware.addActionError(anErrorMessage); }
再看validationAware.addActionError
public synchronized void addActionError(String anErrorMessage) { internalGetActionErrors().add(anErrorMessage); }
再看internalGetActionErrors()方法
private Collection<String> internalGetActionErrors() { if (actionErrors == null) { actionErrors = new ArrayList<String>(); } return actionErrors; }
这个方法是一个私有方法,他返回一个ArrayList<String>对象,也就是说,错误消息是保存在一个叫actionErrors变量的集合中,
对于this.getActionErrors()
public Collection<String> getActionErrors() { return validationAware.getActionErrors(); }
validationAware.getActionErrors():
public synchronized Collection<String> getActionErrors() { return new ArrayList<String>(internalGetActionErrors()); }
通过validationAware.getActionErrors(),我们知道他返回的是actionErrors的一个拷贝,即调用getActionErrors()方法返回Action级别的错误信息列表时,返回的实际上是集合的一个副本而不是集合本身,对于副本的清除clear()方法,对集合本身无影响。换句话说,Action级别的错误信息列表对开发者来说是只读的。
对于addFieldError(),
public void addFieldError(String fieldName, String errorMessage) { validationAware.addFieldError(fieldName, errorMessage); } 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); }
private Map<String, List<String>> internalGetFieldErrors() { if (fieldErrors == null) { fieldErrors = new LinkedHashMap<String, List<String>>(); } return fieldErrors; }
FieldError级别的错误底层使用的是Map——fieldErrors = new LinkedHashMap<String, List<String>>();该map的key是String类型,value是List<String>类型,这就表示一个Field Name可以对应多条错误信息。
对于this.getFieldErrors()
public Map<String, List<String>> getFieldErrors() { return validationAware.getFieldErrors(); } public synchronized Map<String, List<String>> getFieldErrors() { return new LinkedHashMap<String, List<String>>(internalGetFieldErrors()); }
如果我们的程序调用这样的方法:
this.clearActionErrors()
this.clearFieldErrors()
这两个方法则是真正清楚底层集合和Map中的错误信息,可以用this.clearErrors()代替上两句。
clearActionErrors:
public void clearActionErrors() { validationAware.clearActionErrors(); } public synchronized void clearActionErrors() { internalGetActionErrors().clear(); }
clearFieldErrors:
public void clearFieldErrors() { validationAware.clearFieldErrors(); } public synchronized void clearFieldErrors() { internalGetFieldErrors().clear(); }
clearErrors:
public void clearErrors() { validationAware.clearErrors(); } public synchronized void clearErrors() { internalGetFieldErrors().clear(); internalGetActionErrors().clear(); }
5、action中自定义方法的输入校验
对于通过action的method属性所指定的自定义方法,其对应的自定义输入校验方法名为valiadateMyExecute(假设自定义的方法名为myExecute)。底层是通过反射来调用的。
如果既有validate,又有自定义输入校验方法,那么先执行自定义输入校验方法,在执行validate方法,执行完毕后如果出现了任何错误都不会再去执行自定义的execute方法,流程转向了input这个名字所对应的页面上。
6、对于类型转换中出现的错误是struts2框架自己添加的field级别错误,是默认的,我们可以自定义
自定义Field级别的错误提示消息:
1)新建一个以Action名命名的properties文件,如RegisterAction.properties文件(action包下)
2)然后在该属性文件中指定每一个出错字段的错误信息,如
invalid.fieldvalue.birthday=birthday invalid!!
红色是不变的,绿色是字段名,等号后面是具体错误信息。
3)可以使用汉字,但要转换为UTF-8,使用的是jdk下的native2ascii
7、struts2的校验框架(有效的xml文件)。跟待校验的action在同一个包下。具体来说分为字段优先校验器与校验器优先校验器。文件名为Action的名字+-validation.xml,如Register-validation.xml。
相应的DTD文件:xwork-validator-1.0.2.dtd
<?xml version="1.0" encoding="UTF-8"?> <!-- XWork Validators DTD. Used the following DOCTYPE. <!DOCTYPE validators PUBLIC "-//Apache Struts//XWork Validator 1.0.2//EN" "http://struts.apache.org/dtds/xwork-validator-1.0.2.dtd"> --> <!ELEMENT validators (field|validator)+> <!ELEMENT field (field-validator+)> <!ATTLIST field name CDATA #REQUIRED > <!ELEMENT field-validator (param*, message)> <!ATTLIST field-validator type CDATA #REQUIRED short-circuit (true|false) "false" > <!ELEMENT validator (param*, message)> <!ATTLIST validator type CDATA #REQUIRED short-circuit (true|false) "false" > <!ELEMENT param (#PCDATA)> <!ATTLIST param name CDATA #REQUIRED > <!ELEMENT message (#PCDATA)> <!ATTLIST message key CDATA #IMPLIED >
1)字段优先的校验器:
针对RegisterAction的struts2校验框架校验器RegisterAction-validator.xml:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE validators PUBLIC "-//Apache Struts//XWork Validator 1.0.2//EN" "http://struts.apache.org/dtds/xwork-validator-1.0.2.dtd"> <validators> <field name="username"> <field-validator type="requiredstring"> <message>username can't blank!</message> </field-validator> <field-validator type="stringlength"> <param name="minLength">4</param> <param name="maxLength">6</param> <message>length of username should be between 4 /${minLength} and 6</message> </field-validator> </field> <field name="password"> <field-validator type="requiredstring"> <message>passord can't be blank!</message> </field-validator> <field-validator type="stringlength"> <param name="minLength">4</param> <param name="maxLength">6</param> <message>length of password should be between ${minLength} and ${maxLength}</message> </field-validator> </field> <field name="age"> <field-validator type="required"> <message>age can't be blank!</message> </field-validator> <field-validator type="int"> <param name="min">10</param> <param name="max">40</param> <message>age should be between 10 to 40</message> </field-validator> </field> </validators>
将RegisterAction的validate方法注释掉,重新启动服务器,现在的校验就是使用上面的xml进行校验,field标签的属性name就是Action中的成员变量的名字,field-validator标签的属性type值,是已定义好的校验器的名字,具体是在xwork核心代码xwork-core-2.3.14.jar的com.opensymphony.xwork2.validator.validators包下,这包里面包含很多以validator结尾的类,如RequiredStringValidator.class,还有一个default.xml,内容如下:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE validators PUBLIC "-//Apache Struts//XWork Validator Definition 1.0//EN" "http://struts.apache.org/dtds/xwork-validator-definition-1.0.dtd"> <!-- START SNIPPET: validators-default --> <validators> <validator name="required" class="com.opensymphony.xwork2.validator.validators.RequiredFieldValidator"/> <validator name="requiredstring" class="com.opensymphony.xwork2.validator.validators.RequiredStringValidator"/> <validator name="int" class="com.opensymphony.xwork2.validator.validators.IntRangeFieldValidator"/> <validator name="long" class="com.opensymphony.xwork2.validator.validators.LongRangeFieldValidator"/> <validator name="short" class="com.opensymphony.xwork2.validator.validators.ShortRangeFieldValidator"/> <validator name="double" class="com.opensymphony.xwork2.validator.validators.DoubleRangeFieldValidator"/> <validator name="date" class="com.opensymphony.xwork2.validator.validators.DateRangeFieldValidator"/> <validator name="expression" class="com.opensymphony.xwork2.validator.validators.ExpressionValidator"/> <validator name="fieldexpression" class="com.opensymphony.xwork2.validator.validators.FieldExpressionValidator"/> <validator name="email" class="com.opensymphony.xwork2.validator.validators.EmailValidator"/> <validator name="url" class="com.opensymphony.xwork2.validator.validators.URLValidator"/> <validator name="visitor" class="com.opensymphony.xwork2.validator.validators.VisitorFieldValidator"/> <validator name="conversion" class="com.opensymphony.xwork2.validator.validators.ConversionErrorFieldValidator"/> <validator name="stringlength" class="com.opensymphony.xwork2.validator.validators.StringLengthFieldValidator"/> <validator name="regex" class="com.opensymphony.xwork2.validator.validators.RegexFieldValidator"/> <validator name="conditionalvisitor" class="com.opensymphony.xwork2.validator.validators.ConditionalVisitorFieldValidator"/> </validators> <!-- END SNIPPET: validators-default -->
从这里可以看到,field-validator的type就是指出校验所使用的类。
一个具体的校验器xml:RegisterAction-validator.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE validators PUBLIC "-//Apache Struts//XWork Validator 1.0.2//EN" "http://struts.apache.org/dtds/xwork-validator-1.0.2.dtd"> <validators> <field name="username"> <field-validator type="requiredstring"> <message>username can't blank!</message> </field-validator> <field-validator type="stringlength"> <param name="minLength">4</param> <param name="maxLength">6</param> <message>length of username should be between 4 /${minLength} and 6</message> </field-validator> </field> <field name="password"> <field-validator type="requiredstring"> <message>passord can't be blank!</message> </field-validator> <field-validator type="stringlength"> <param name="minLength">4</param> <param name="maxLength">6</param> <message>length of password should be between ${minLength} and ${maxLength}</message> </field-validator> </field> <field name="age"> <field-validator type="required"> <message>age can't be blank!</message> </field-validator> <field-validator type="int"> <param name="min">10</param> <param name="max">40</param> <message>age should be between 10 to 40</message> </field-validator> </field> <field name="birthday"> <field-validator type="required"> <message>birthday can't be blank!</message> </field-validator> <field-validator type="date"> <param name="min">2005-1-1</param> <param name="max">2007-12-31</param> <message>birthday should be between ${min} and ${max}</message> </field-validator> </field> </validators>
对于<message>标签来说,可以使用key属性来指定错误的信息,具体如下:
<field-validator type="stringlength"> <param name="minLength">4</param> <param name="maxLength">6</param> <message key="username.invalid"></message> </field-validator>
这里key的值是在特定的属性文件中定义的,这个属性文件是一组属性文件,位于Action相同的包下面,文件名是如下形式:package_语言_国家.properties,其中语言是小写,如en,zh等,国家是大写,如US,CN等,具体的文件如package_en_US.properties、package_zh_CN.properties,文件的内容:
package_en_US.properties:
username.invalid=username invalid
package_zh_CN.properties:
username.invalid=\u7528\u6237\u540D\u4E0D\u5408\u6CD5
中文使用的是unicode编码。这样struts2框架会根据浏览器使用的语言来相应的使用哪一个属性文件中的值,属性文件中的key就是我们在message标签中属性key的值,如这里的username.invalid
这就是国际化,针对不同的语言,使用不同的提示信息。i18n——internationalization
上述文件就叫做国际化资源文件,其命名规则:package_语言名_国家名.propertiies
8、国际化
JDK的ResourceBundle类
关于Locale类:
package com.cdtax.i18n; import java.util.Locale; public class I18Ntest1 { public static void main(String[] args) { Locale[] locales = Locale.getAvailableLocales(); for(Locale locale : locales) { System.out.println(locale.getDisplayCountry() + " : " + locale.getCountry()); System.out.println(locale.getDisplayLanguage() + " : " + locale.getLanguage()); } } }
ResourceBundle:
package com.cdtax.i18n; import java.util.Locale; import java.util.ResourceBundle; public class I18NTest2 { public static void main(String[] args) { System.out.println(Locale.getDefault()); ResourceBundle bundle = ResourceBundle.getBundle("cdtax",Locale.US); String value = bundle.getString("hello"); System.out.println(value); } }
我们在src目录下建立cdtax_en_US.properties和cdtax_zh_CN.properties文件,里面放置键值对,key都是hello,值为hello!和你好,这样当使用的环境为英文(en_US)时,就使用cdtax_en_US.properties中的key的值,如果为中文(zh_CN)环境,就使用cdtax_zh_CN.properties中的key的值,这里就是中文的“你好”,如果是其他环境,我们可以给出一个默认的资源文件,就是cdtax.properties,文件名不带语言名和国家名。同样道理,对于校验框架xml文件中的message标签的key属性所对应的资源文件,也有一个默认的文件,是package.properties。
在资源文件中可以使用占位符,如下:
hello=hello {0}
在文件中使用:
package com.cdtax.i18n; import java.text.MessageFormat; import java.util.Locale; import java.util.ResourceBundle; public class I18NTest3 { public static void main(String[] args) { Locale locale = Locale.CHINA; ResourceBundle bundle = ResourceBundle.getBundle("cdtax",locale); String value = bundle.getString("hello"); String result = MessageFormat.format(value, new Object[]{"cdtax"}); System.out.println(result); } }
关键是MessageFormat.format(value, new Object[]{"cdtax"});这个类。
9、校验器优先校验器
RegisterAction-validation.xml:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE validators PUBLIC "-//Apache Struts//XWork Validator 1.0.2//EN" "http://struts.apache.org/dtds/xwork-validator-1.0.2.dtd"> <validators> <validator type="requiredstring"> <param name="fieldName">username</param> <message>username can't be blank!!!!!</message> </validator> <validator type="stringlength"> <param name="fieldName">username</param> <param name="minLength">4</param> <param name="maxLength">6</param> <message>length of username should be between ${minLength} to ${maxLength}</message> </validator> </validators>
10、struts2框架校验执行先后顺序:
先执行类型转换校验,然后
1)首先执行校验框架(xml文件)
2)执行自定义方法的校验方法(validateMyExecute())
3)执行valiadte()方法。