1、Struts静态参数封装。什么叫封装呢?其实就是把用户输入的数据获取到,然后输出或者封装到类里面去。
——静态参数,也就是我们写死的数据,不能算是用户动态提交的数据。我们测试一遍。
——新建一个web project,把jar包导入到lib里面,然后在src下面新建一个struts.xml文件,再在web.xml里面配置名字叫struts2的过滤器。我们在struts.xml里面增加如下代码。
<struts>
<constant name="struts.devMode" value="true"></constant>
<package name="p_name_1" extends="struts-default">
<action name="action1" class="com.hello.web.action.MyAction" method="testParams">
<result name="success" type="redirect">/success.jsp</result>
<param name="username">eric</param>
<param name="password">1234</param>
</action>
</package>
</struts>
——根据这个配置,我们新建一个继承自ActionSupport的类,叫MyAction,在里面定义两个变量,再写一个testParams方法。如下:
public class MyAction extends ActionSupport {
private String username;
private String password;
public String testParams(){
System.out.println(username+":"+password);
return "success";
}
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;
}
}
——我们部署后访问http://localhost:8080/Day01_Params/action1.action
,在控制台有输出eric:1234
。
——这里面需要注意的是:在action里面的param里定义的参数name名称一定要和动作类里面的变量名称一致。而且要有set方法。
——因为在我们执行testParams方法的前后,有很多拦截器interceptor做一些工作,其中有一个叫做staticParams的拦截器,它就是专门负责看看你struts.xml里的action里是否有定义param参数,如果有的话,拿到这个参数的name名字,然后去这个action的对应的类class里面的方法method去找setXXX方法,和setXXX中set后面的XXX比较(这里它会自动转换大小写),如果发现有匹配的,就把值复制给类class里面的变量。然后我们在执行这个方法method的时候,这个变量其实就已经有值了,我们输出到控制台的时候就能看到值了。
2、动态参数封装。核心的例子就是表单数据的提交。
——我们创建一个表单,动作类还是那个动作类,不变。
<form action="${pageContext.request.contextPath }/action1.action" method="post">
用户名:<input type="text" name="username" /><br>
密码:<input type="password" name="password" /><br>
<input type="submit" value="提交" />
</form>
——我们在action里面什么都没写,就写了个正常的结果成功后跳转。
<package name="p_name_1" extends="struts-default">
<action name="action1" class="com.hello.web.action.MyAction" method="testParams">
<result name="success">/success.jsp</result>
</action>
</package>
——我们访问http://localhost:8080/Day01_Params/index.jsp
,输入参数后,点击提交,浏览器跳转到成功页面,控制台有输出李四:1234
。这一切的原理又是什么呢?
——这里的一切都是严格遵循规范的,比如form表单里面的name值一定要和动作类里面的变量名字一致(静态参数封装的要求是param的name值一定要和动作类里面的变量名字一致,更进一步应该是setXXX的XXX一致),然后就没有然后了,就这么一个要求。
——原理是因为这里面又使用了一个默认的拦截器,我们说静态参数封装用的是staticParams的拦截器,而这次动态参数封装用的默认拦截器是params。这个拦截器会检查表单里面参数的name值,然后和action对应的类class的变量(其实是setXXX的XXX)匹配。如果匹配上了,那就赋值。说白了,原理和静态参数是类似的,只是用了不同的拦截器来处理。
3、第二种动态参数封装。我们上面的封装把变量和方法都写在动作类里面了,下面的这种封装就更贴近实际开发,也就是我们的参数是封装在一个类里面的,比如User,所以我们在动作类里面使用的不是零散的username和password变量,而是User类。所以我们得先写一个User类,我们把变量封装在User类里面了:
public class User implements Serializable {
private String username;
private String password;
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;
}
}
——然后改变一下动作类里面的变量和方法,变量换成了user实例,注意方法里面获取数据的方式也变成了user.getXXX()
:
public class MyAction extends ActionSupport {
private User user;
public String testParams(){
System.out.println(user.getUsername()+":"+user.getPassword());
return "success";
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
——如果只是到这一步,表单提交过来,表单里面的name和我们动作类里面的setUser根本对应不上,所以还是不行的?那我们就得修改表单里面的name。改成如下:
<form action="${pageContext.request.contextPath }/action1.action" method="post">
用户名:<input type="text" name="user.username" /><br>
密码:<input type="password" name="user.password" /><br>
<input type="submit" value="提交" />
</form>
——上面那种把表单里的name改成user.username
等形式估计还是第一次见吧。正式这种规范把动态参数封装运作起来了。流程是这样的。拿到表单的name一看是user.username
,它就知道是一个封装类,然后拿到name里面user和动作类里面的setXXX匹配(这些步骤还是不变的),匹配到了,如果没有实例就利用getUser创建一个实例user,然后到这个类User里面去找和name后面的username匹配的setXXX,然后匹配到了setUsername就把username赋值给了user.username,最后我们就获得了一个user实例以及里面的数据,可以随心所欲地使用数据了。
4、下面这种就是实际开发中常用的动态参数封装方法。叫模型驱动的动态参数封装方法。我们上面那种方法有个缺点,就是在form表单里面写的name是user.username
,这明显不符合一般习惯啊,我们还是习惯写成username
,但是写成username
的话,在动作类里面它又匹配不到合适的setUsername(这是在User类里面才有的set方法),所以要么修改表单里面的name值,要么修改我们的动作类,上面的一种方法是修改了form表单里的name值。下面这种就是改造了一下动作类。
——实现了ModelDriver接口,泛型就是我们的实体类User。
public class MyAction extends ActionSupport implements ModelDriven<User> {
private User user=new User();
public String testParams(){
System.out.println(user.getUsername()+":"+user.getPassword());
return "success";
}
public User getModel() {
return user;
}
}
——我们可以看到上面的动作类里面,连getUser和setUser方法也都没有了,只有一个getModel方法,返回了一个User实例。
——这里的原理是:ModelDriver接口里面的有一个intercept方法,这个方法是判断我们的动作类是否属于(instanceof)ModelDriver,因为我们实现了这个接口,所以答案为是,如果是的话,它就把我们的动作类转换成ModelDriver类型的类,然后调用我们动作类的getModel,也就是我们实现的接口方法,得到返回的实例化对象user。如果这个user不等于null的话,它就把我们的实例化对象user做了stack.push()操作,压到栈里面去了,进行了一系列操作。也就是说,我们的动作类只要实现了ModelDriver接口并实现里面的getModel方法,返回我们自己实例化的User对象,它就会自动获得我们的实例化对象,然后做一系列操作。
——注意,必须我们自己实例化对象。
private User user=new User();
——注意:
- 别忘了把form表单里的name改成正常的样子usernamehe password。
- 虽然上面我们利用了ModelDriver后可以不写user的set和get方法,但是还是建议写一下。
5、数据类型转换。我们从表单过来的数据要么是String要么是String[],但是类里面的变量可能是其他类型,所以需要转换。
——struts2中基本数据类型会自动转换;日期类型只认识默认的yyyy-MM-dd,结果存在数据库中也是以yyy-MM-dd的形式存储;字符串数组是以逗号+空格
隔开存储的,比如吃饭, 睡觉, 打豆豆
。
——那问题来了。如果我们在表单中输入的日期String是2016-09-30的话,它能够自动处理。但是如果我们输入09/30/2016的话就会报错。如果我们需要纠正的话,就需要重写类型转换器。
——类型转换器的写法是我们定义一个类,继承自StrutsTypeConverter,并且实现convertFromString和convertToString,也就是把获取到的不是默认格式的String转换成日期格式,需要的时候把数据库中的默认的格式转换成用户习惯的格式。也就是在这两个方法里面利用SimpleDateFormat。
——写了这个类型转换器后,需要配置。有两种配置方法,一种是局部类型转换器的配置,也就是给某个特性的javabean使用:在这个bean同级下方新建一个javabean-conversion.properties的文件,在里面写一个key-value,key是需要转换的属性,value就是我们自定义类型转换器类的全路径名称。比如:User-conversion.properties:
birthday=com.hello.web.converter.MyTypeConverter
还有一种配置方法是全局类型转换器的配置。在src下面新建一个xwork-conversion.properties文件,在里面写key-value,key就不是一个属性了,因为是全局的大家都能用,所以不能固定是某个属性,而是某个数据类型,value不变。比如:
jav.util.Date=com.hello.web.converter.MyTypeConverter
6、我们其实更关心的是如果数据类型错误,怎么处理?给struts.xml里的action增加一个结果视图,类型是input,意思是如果有字段值错误,返回到index.jsp页面,这种解决办法叫做回显视图来提示。
<result name="input">/index.jsp</result>
——然后我们就请出我们用到的struts标签。先在jsp页面添加库。
——然后使用,在任何地方增加一个字段错误的标签。如果出现input错误然后返回index.jsp页面的时候,这个<s:fielderror />
会自动条用一句话,就是报错的信息。
<s:fielderror />
<form action="${pageContext.request.contextPath }/action1.action" method="post">
用户名:<input type="text" name="username" /><br>
密码:<input type="password" name="password" /><br>
<input type="submit" value="提交" />
</form>
——但,其实我们可以用标签重写我们整个form表单,如下。它默认method就是post所以省略,而action不需要contextpath,所以直接写action1.action即可。里面有很多标签,比如必须填写、回显密码等。这时候我们就不需要单独写<s:fielderror />
了,因为我们这样写表单的时候已经内置了这样的功能,它会在对应错误字段的上面显示错误信息。
<s:form action="action.action">
<s:textfield name="username" label="用户名" requiredLabel="true"></s:textfield>
<s:password name="password" label="密码" showPassword="true"></s:password>
<s:submit value="提交"></s:submit>
</s:form>
——但是,到这里还有一个问题,就是显示的错误信息,是系统默认的。如果我们需要修改成我们自己的信息,就需要配置一个文件。与表单对应的一般都有一个javabean实体类,比如User,我们在于User同级的下方新建一个User.properties文件(在属性比如birthday所在类的同级下方新建一个名字与所在类User相同的properties文件),在里面写如下代码即可:
invalid.fieldvalue.birthday=格式错误,请输入yyyy-MM-dd格式
7、编程式验证。因为我们的ActionSupport实现了Validateable和ValidationAware等接口,Validateable里面有个validate方法。
public void validate(){
if(StringUtils.isEmpty(user.getUsername())){
addFieldError("username", "用户名没有哦!");
}
}
——这个validate方法会在action的动作方法之前执行。
—— 它会拦截所有的action在它们之前进行验证,也就是说所有的如果你不配置的话,写一个就相当于是给所有的action都添加了相同的验证。解决的办法有2种,一种是在其他不需要验证的action对应的方法之前增加一个修饰符叫@SkipValidation
。
@SkipValidation
public String testParams()
return "success";
}
——还有一种方式不要写一个统一的validate方法,而是遵循一种规范为每一个action对应的方法写一个验证方法,规范是validate +首字母大写的动作方法名
,如validateTestParams
。
8、与上面编程式验证对应的就是不编程,只利用配置文件进行配置,叫做声明式验证。
——新建一个与动作类名称对应的xml文件,名字规范是动作类名-validation.xml
。
——里面的约束文档类型去源码里面找去。
——里面写的就是验证表单的什么name?然后是验证哪一种,比如例子中的是否为空?然后是出错时候的提醒信息。一层套一层。
——但遇到了同样的问题,就是这种方式对所有的action都做了验证。解决的话还是2种方式,第一种和之前说过的一样,就是在其他不需要验证的action对应的方法前面增加@SkipValidation
。
——第二种方式就是改这个xml,把名字变成动作类名字-动作action名字-validation.xml
。比如我们这里变成MyAction-action1-validation.xml
。注意这里的第二个是action名字,而不是对应的方法名。
——下面第一种是基于一个个字段来验证的,第二种是基于验证器来验证的。
<validators>
<field name="username">
<field-validator type="requiredstring">
<message>用户名飞走了!</message>
</field-validator>
</field>
<validator type="requiredstring">
<param name="filedName">username</param>
<message>用户名呢?</message>
</validator>
</validators>
——关于验证器的type,按照下列路径在struts源码里面找,可以看到有很多验证的type。
9、验证器这一块,有很多小的使用技巧。其中有包含正则和表达式的一些用法。这里有一个用户注册结合验证器的案例。