Struts2中的输入校验
使用Annotation取代struts.xml配置
Struts2与AJAX交互
u 驾驭XML数据
u 驾驭JSON数据
第一个Struts2程序
当我们在一个新的工作空间中创建一个Web项目的时候首先要将我们自己的Tomcat绑定到MyEclipse中。
接下来我们可以使用自己安装的JDK,Windows→Preferences→输入Installed JREs
建一个Web Project
拷struts.xml(可从下载的struts目录下的示例中拷)。将<struts>标签下的内容都注释掉,这样自己写的时候可供参考。
拷struts2所需要的lib,直接从示例中拷,只需要8个(首字母以c、c、c、f、j、o、s、x开头,其中的javaassist包一定不能少)
查看项目目录在磁盘上的结构:Window→Show View→Navigator
在web.xml文件中配置Struts2的核心控制器(直接从示例中拷)
下面我们修改struts.xml文件,添加以下配置:
<package name="default"namespace="/" extends="struts-default">
<actionname="hello">
<result>
/Hello.jsp
</result>
</action>
</package>
我们将原来的index.jsp改名为Hello.jsp,打开该JSP文件(使用MyEclipse JSP Editor打开,不要使用默认的图形编辑器打开,这样速度很慢),适量修改一下页面信息。
执行该应用程序:
右击项目名→Debug As→MyEclipse Server Application→选择你配置好的服务器
Tomcat正常启动后,在url地址栏中输入:http://localhost:8080/循序渐进地调试
接下来输入项目路径名,如果名字太长,可以右键项目名→Copy Qualified Name。
接下来url地址栏中的信息变为:http://localhost:8080/Struts2_0100_Introduction,如果我们就直接回车,会发现报错了,它会在项目路径之后的namespace为/的package下面找指定的action name,这里它是空字符串。
我们还要在应用程序路径后面输入指定的namespace下面的指定的action name才能将请求提交给相应的Action来处理。如:http://localhost:8080/Struts2_0100_Introduction/hello
有必要在struts.xml文件中增加如下常量配置:
<constantname="struts.devMode" value="true" />,该配置使得每次修改struts.xml文件之后都能自动加载。
在struts.xml文件中还可以使用模块包含的方式实现团队开发的协同配置。就是每个人负责的模块的配置可以写在一个单独的xml文件中,最后由项目经理将所有的xml文件包含进struts.xml文件。每个人写的xml文件其内容和struts.xml文件相同。如:
<?xml version="1.0"encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<constant name="struts.devMode" value="true"/>
<constant name="struts.i18n.encoding"value="GBK"></constant>
<package name="default" namespace="/"extends="struts-default">
<action name="action"class="MyAction">
<result>/test.jsp</result>
</action>
</package>
</struts>
当然,上面的配置必须要遵循团队的一个统一的规范,包括命名规范和是否使用通配符等等。
在struts.xml中使用模块包含的语句是:
<includefile="my.xml"></include>
查看某个jar文件中类的源代码:
右击jar文件→Properties→Java Source Attachment→External Folder…→选择到struts2目录中的相应路径(我这里的目录是:E:/…/struts-2.2.3/src/core/src/main/java)
E:\temp\IT\北京尚学堂\Struts2 05
查看Struts2的API文档的方法:
右击jar文件→Properties→Javadoc Location→Browse Javadoc location path→
Struts2中用户请求的namespace
默认namespace指的是:namespace=””或不指定。
默认namespace和根namespace的作用差不多,如果actionname在根namespace下找不到才会到默认namespace下找。
根namespace指的是:namespace=”/”。
如果action name(一定是请求路径的最后一个/后的内容)在指定的namespace下找不到,就会到根namespace中找。
指定namespace指的是:namespace=”/xxx”
要被指定namespace下的Action访问到,则请求路径一定要这样写:
http://localhost:8080/project_path/xxx/.../action_name
注意可以在namespace和action name之间再添加其他路径,但通常不建议这样做
总结:注意一个问题,就是如果请求的路径是:http://localhost:8080/project_path/,那么首先会读取web.xml文件中的<welcome-file-list>,即向客户端响应欢迎页面,若找不到这样的页面,就会交给Struts2的请求处理机制来处理(即匹配namespace下的action name)。
Action详解
我们自己定义的Action最好就是直接继承自ActionSupport类。
JSP开发中必定会遇到的一个路径问题!
当我们不考虑在JSP页面中使用MyEclipse自动给我们生成的basepath的情况下,我们在url地址栏中输入一个路径(比如:http://localhost:8080/project_name/user/login.action),将请求交给相应的Action处理,之后Action再向我们返回一个页面welcome.jsp,这时候我们发现url地址栏中的路径根本就没有变化。如果welcome.jsp中有一个链接:<a href=”index.jsp”>返回首页</a>,当我们点这个链接后,页面并没有跳转回首页,url地址栏中的路径变为:http://localhost:8080/project_name/user/index.jsp,即仅仅将请求路径的最后的/action_name替换成了/index.jsp,这肯定不是我们想要的结果。要向避免这一问题除了使用MyEclipse给我们生成的basepath之外,还可以对所有的超链接使用如下形式的路径:<a href="<%=request.getContextPath() %>页面在应用程序中的路径">back</a>,可以举个例子,如:<a href="<%=request.getContextPath()%>/jsp/default.jsp">back</a>
Action中方法调用:method调用和动态方法调用
第一种方法直接在<action>标签最后加一个属性method,即交给相应的Action处理后调用Action中的哪个方法。
第二种方法,在url地址栏中的/action_name!method_name。这种方式要比第一种方式灵活,这是动态方法调用。
Action中通配符的使用简化配置
最最常见的使用情况是:
<package name="actions"namespace="/actions" extends="struts-default">
<actionname="*_*" class="com.cn.action.{1}Action"method="{2}">
<result>/{1}_{2}_success.jsp</result>
</action>
</package>
我们只需要按照配置的约定来命名Action、Action中的方法以及结果页面就行了。不过这也会带来一个问题,有可能用户请求路径符合action name,但却找不到Action类,这时候服务器就会报错。还需要使用拦截器判断用户请求路径是否合法,这以后再说。
默认Action配置
如果请求匹配一个包下的namespace,而不匹配该包下指定的action name,就会交给该包下的默认Action处理。看下面的配置:
<package name="default"namespace="/" extends="struts-default">
<default-action-refname="default"></default-action-ref>
<actionname="action" class="MyAction">
<result>/test.jsp</result>
</action>
<actionname="My*" class="My{1}">
<result>/index.jsp</result>
</action>
<actionname="default">
<result>/test.html</result>
</action>
</package>
这里要注意一个问题,一旦请求匹配到某包下的namespace,且在包下面配置了默认action,那么请求中的action name,在本包中的指定action name找不到,就会交给默认Action处理,而不会再到根namespace或默认namespace中去找了!还要注意一个问题,在使用通配符的情况下,匹配到了action name,有可能找不到Action类,这时候就会报错,就像我们上个例子中的My*,这个是默认Action处理不了的。
result type详解
常用的结果类型有:dispatcher、redirect、chain和redirectAction
默认情况下我们使用的是dispatcher。redirect是重定向到指定的页面。dispatcher和redirect的区别就是之前action中保存的信息有没有丢失。前者没有,后者丢失了。chain表示的是Action链,即请求会经过多个Action的处理。每次请求转发给一个Action处理的时候,就会把该Action放到值栈的最顶端。最终的结果页面可以访问到所有Action中的内容。在本包中的跳转使用:<result type="chain">action_name</result>,跨包跳转使用:
<resulttype="chain">
<paramname="actionName">action_name</param>
<paramname="namespace">/ns</param>
</result>
redirectAction的配置和chain类似,只是之前一个请求中的所有信息都丢失掉了,包括前一个Action中的所有信息都丢失了,现在值栈中就只包含了重定向的action。
全局结果类型
当一个包下的多个action在处理请求后都可能返回通一个页面,这时候就没有必要在每个action中都配置该结果页面,而将这个工作统一交给该包下的一个全局结果来完成。全局结果的配置如下:
<package name="default"namespace="/ " extends="struts-default">
<global-results>
<result name="home">/home.jsp</result>
</global-results>
…
</package>
动态结果类型
有时候为了优化struts.xml文件中的action配置,我们无需将<result>中的内容限定死了,而可以这样指定:<result>${result_page}</result>,这里的result_page是action中的一个属性,我们可以根据请求处理的结果动态的指定。
给结果类型传参
这种用法通常是考虑到结果类型是redirect的情况,因为之前请求及Action中的所有信息都丢失了,只能通过url传参的方式给重定向的页面传递信息,如:
<result type=”redirect”>login_success.jsp?username=${name}</result>,这里使用了ognl表达式来获取前一个action中的信息。
我们之前写的Action扮演了两种角色,既用于封装用户请求参数又处理控制逻辑。一般情况下用户的请求参数和action的处理结果都应该封装在model对象中。这就是模型驱动模式。这里要说说属性驱动和模型驱动,前者是以Action作为信息的携带者来封装用户请求参数和处理结果的,后者是使用Model示例取而代之的。就单单从Action的层面上来讲,模型驱动实现了Model和Controller的分离。下面看一个简单的例子:
首先定义一个Model类(其实就是JavaBean),用其封装用户请求参数和处理结果。
public class UserBean {
private String username;
private String password;
private String tip;
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 getTip() {
return tip;
}
public void setTip(String tip) {
this.tip = tip;
}
}
接下来我们就要定义模型驱动的Action,该Action要实现一个ModelDrivern<T>接口。代码实现如下:
public class LoginAction implementsAction, ModelDriven<UserBean> {
private UserBean model = new UserBean();
public String execute() throws Exception {
return SUCCESS;
}
public UserBean getModel() {
return model;
}
}
结果页面如下:
<body>
${model.username }<br />
${model.password }
</body>
我们就很纳闷儿,为什么我们在Action中实例化了一个UserBean对象后,用户请求参数值就自动赋给该对象的相应属性了呢?其实Struts2中有内置的model-driven拦截器,它会拦截用户请求参数,将其赋给Action中的Model对象的属性。
其实在结果页面中获取action中Model对象的属性还可以使用下面的表达式语句。
${username }<br />
${password }<br />
因为此时我们通过<s:debug />标签会发现我们在Action中定义的Model对象已经被置于值栈的最顶端了。因此可以直接访问其属性。
在Struts2中可以通过配置异常类型所对应的逻辑视图页面来实现发生异常时页面的跳转。这需要我们在struts.xml文件中配置<exception-mapping>,看下面的一个例子:
public class ExceptionAction extendsActionSupport {
privateString username;
privateString password;
privateString tip;
省略了Getters和Setters方法
publicString execute() throws Exception{
if(getUsername().equals("user")){
thrownew MyException("user name can't be user");
}
System.out.println("nocustom exception");
if(getUsername().equals("sql")){
thrownew java.sql.SQLException("user name can't be sql");
}
System.out.println("noSQL exception");
if(getUsername().equals("jack")&& getPassword().equals("123")){
setTip("haha,loginsuccess");
returnSUCCESS;
}else{
setTip("huhu,loginfailed");
returnERROR;
}
}
}
MyException的代码:
public class MyException extends Exception{
publicMyException(String msg){
super(msg);
}
}
这里我们选择了直接在execute方法中年抛出异常的办法,将异常交给Struts2框架来处理,它会根据抛出的异常类型,直接到struts.xml文件中找相关了<exception-mapping>配置,而不会再往下执行execute方法中接下来的代码了。看一下struts.xml文件中的配置。
<package name="default"namespace="/" extends="struts-default">
<global-results>
<result name="sql">/error.jsp</result>
<result name="root">/error.jsp</result>
</global-results>
<global-exception-mappings>
<exception-mapping result="sql"exception="java.sql.SQLException" />
<exception-mapping result="root"exception="java.lang.Exception" />
</global-exception-mappings>
<action name="exception"class="ExceptionAction">
<exception-mapping result="my"exception="MyException" />
<result name="my">/error.jsp</result>
<resultname="success">/index.jsp</result>
<resultname="error">/index.jsp</result>
</action>
</package>
而我们的显示页面如下:
<body>
${exception.message }<br />
<s:debug />
</body>
使用Annotation取代struts.xml配置
我们可以不再创建struts.xml文件了,而引入一个jar包——struts2-convention-plugin-2.2.3.jar
这样我们可以直接在action中使用Annotation来实现actin的配置。
其实说到Struts2中的乱码问题,本质上还是Servlet中获取用户请求参数的值为中文而可能引起的中文乱码问题。
而要处理中文乱码问题无非就要考虑用户提交请求的两种方式:GET和POST。
GET:首先修改Tomcat中的server.xml文件:
<Connector port="8080"protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"URIEncoding="GBK"/>
接下来我们可以统一一些编码规范:
Html页面:
<head>
<metahttp-equiv="content-type" content="text/html;charset=GBK">
…
</head>
JSP页面:
<%@ page language="java" import="java.util.*"pageEncoding="GBK"
contentType="text/html;charset=GBK"%>
POST:页面的编码风格不变,不要忘了还有关键一步——在struts.xml文件中配置如下的常量:<constant name="struts.i18n.encoding"value="GBK"></constant>
这样之后中文乱码问题就一劳永逸了。
Struts2简单表单验证
举个简单的例子,用户填写一个注册表单后提交给一个RegisterAction处理,在相关方法中验证用户输入信息,如果验证失败后,可以通过以下代码将错误信息保存起来:
this.addFieldError(String name,String msg);
注意使用上述方法时,我们写的Action一定要继承自ActionSupport。可以给一个表单域添加多个错误信息。所有错误信息其实是封装在一个Map集合中,一般下key是用户请求参数名,value是一个字符串数组类型的有关这个表单域错误信息的数组。我们在结果页面中获取错误信息的方法有好几种:
<s:fielderrorname="name"></s:fielderror>
${errors['name'] }
第一种方式是按照Struts2实现的CSS风格来显示一个表单域的错误信息的。一般我们会根据实际需要来设置错误信息的CSS样式,这种方法不是很好。
我是倾向于使用第二种方法,以上获取到的是一个String数组,而我们要遍历每个元素可以使用Struts2控制标签,现在先不介绍,后面再说。
正常情况下,我们不需要写自己的类型转换器,因为Struts2有内置的类型转换器,可以帮我们把用户请求参数值转化为常用的数据类型。只是请求参数名的书写要符合一些规范,比如ognl表达式。
一般情况下如果用户请求参数是如下数据
http://....action?name=kobe&name=bryant,而在Action中定义的属性是:
private String name;
getters和setter方法省略
最终我们给Action的name属性赋的值是什么呢?
你也许想不到Action中name的属性是kobe, bryant。也就是说如果请求参数名name传了多个值,那么Action中相应的name属性最终赋的值却是请求参数值的拼接(中间用英文逗号和空格连接)
JSP页面中获取Struts2中存储变量的方式——ognl表达式
在结果页面中如何获取Action中的属性值?
Action属性是简单数据类型(包括String、Date):
给请求参数赋值的方式:http://....action?name=value&birth=1989-06-26
获取属性:${name} ${birth}
Action属性是字符串数组:
给请求参数赋值的方式:http://....action?hobbies=music&hobbies=painting&hobbies=sleeping
Action属性是字符串集合:
给请求参数赋值的方式:
http://....action?names[0]=jack&names[1]=tom&names[2]=mark
获取数组或集合元素的值:${names[index]}
Action属性是User(属性有name、age等)类型
给请求参数赋值的方式:http://....action?user.name=jack&user.age=12
获取属性:${user.name}
Action属性是User集合类型(List<User>或Map<String,User>)
给请求参数赋值的方式:http://....action?users[0].name=jack&users2[‘0012’].name=tom
获取属性:${users[0].name} ${users2[‘0012’].name}
总结:
访问List中某个元素:${users[0]}
访问List中元素的某个属性的集合:${users.{age}}
访问Map同上
访问Map中某个元素(对象):${cats.tom}或${cats[‘tom’]}
访问Map中所有key:<s:propertyvalue="dogs.keys"/>
访问Map中所有value:<s:propertyvalue="dogs.values"/>
访问Map中元素的某个属性的集合:<s:propertyvalue="dogs.values.{age}"/>
访问容器的大小:<s:propertyvalue="dogs.size()"/>
获取值栈中的集合对象时使用投影
访问Map中所有满足条件的元素:<s:propertyvalue="dogs.values.{?#this.age > 3}" />
如何遍历上述获取到的集合呢?
在结果页面中如何获取用户请求参数值?
<s:property value="#parameters.参数名[索引]"/>,这样获取到的是一个字符串数组。
在结果页面中如何获取request(session或application)范围中存放的对象?
<s:property value="#request.key"/>,这样获取到的就是我们塞进request范围中的对象,它的类型是确定的,session和application同上。
Struts2标签
在JSP页面中调用Action
<s:action name="在Action配置中的ActionName"namespace="action所在的包命名空间" executeResult="是否将Action转向的结果页面包含进来"ignoreContextParams="是否将本页面中的请求参数传给被调用的Action" />
<s:property />
获取属性为空时使用默认值:<s:propertyvalue="name" default="haha" />
输出HTML内容:<s:propertyvalue="'<hr />'" escape="false" />
<s:set />
<s:setvar=”name” value=”ognl_expression” />
将value获取的值放到var定义的变量中,默认是把该变量放到request范围和Stack Context中。可以使用<s:property value=”#name || #request.name” />获取。
设置变量的范围:
<s:setvar=”name” value=”ognl_expression” scope=”page || request || session ||application”/>
当我们指定了一个范围之后只能在指定的范围中取。如page范围:
<%=pageContext.getAttribute("name")%>,而其他范围:
<s:propertyvalue="#request.name"/>
<s:set/>最常用的情况就是在一个JSP页面中存储获取的变量值,以备以后使用。
<s:bean>
<s:beanname="Dog" var="dog">
<s:param name="name" value="'oudy'" />
<s:param name="age"value="3" />
</s:bean>
<s:propertyvalue="#dog"/><br />
<s:propertyvalue="#request.dog"/>
该标签实例化一个对象,并给其属性赋值,默认将其放在request和Stack Context范围中。
其实在<s:bean>标签内部,我们可以直接访问生成对象的属性,如:
<s:beanname="Dog" var="dog">
<s:param name="name" value="'oudy'" />
<s:param name="age" value="3" />
${name }
</s:bean>
因为这时候生成的对象被置入值栈栈顶。而出了<s:bean>标签,对象就不能这样访问了。
<s:include>
在结果页面中再包含其他的页面:<s:includevalue=”1.jsp” />
可以在<s:include>标签内使用<s:param>标签来给被包含的页面传参数
<s:date>日期格式化标签
一般情况下只要这样使用:
<s:datename="date" format="yyyy/MM/dd" />就会将指定的日期类型格式化
而
<s:datename="date" nice="true"/>就是计算被格式化的日期和当前时间的时间差
页面输出的时间差是这种格式的:2 years,36 days ago
nice属性和format属性不要同时指定!!!
<s:param>给Struts2中的组件传参数
它有两个参数:name和value,注意如果要给value指定String类型的值,必须要这样指定:
value=”’thisi is a string’”
<s:push>将变量推入到valueStack中
如:
<s:push value="表达式获取到的变量">
<s:property/>
</s:push>
直接在<s:push>标签中使用<s:property/>来获取值
<s:url>指定url路径字符串
1. <s:urlvalue="haha"></s:url>
代表haha字符串
2. <s:url action="haha">
<s:param name="username">zhangsan</s:param>
</s:url>
代表字符串:/tag_test/haha.action?username=zhangsan
3. 如果既不指定action也不指定value,如:
<s:url>
<s:param name="username">zhangsan</s:param>
</s:url>
就会使用我们url地址栏中的action路径,代表的字符串是:
/tag_test/mm.action?username=zhangsan
4. 如果我们要包含url地址栏中原有的请求参数,可以这样:
<s:url includeParams="get">
<s:param name="username">zhangsan</s:param>
</s:url>
输出内容就是:
/tag_test/mm.action?date=2010-01-01&username=zhangsan
5. 同时指定action和value:
那么action属性将会失效
控制标签
<s:if>
<s:if test=”条件表达式”>输出内容</s:if>
<s:else if test=”条件表达式”>输出内容</s:elseif>
<s:else>输出内容</s:else>
条件表达式可以使用比较运算符判断,也可以是用于判断数组或集合中是否存在指定元素的in、not in,如’jack’ in #request.names
<s:iterator />
下面我们就用该标签来解决上面留下来的一个问题:如何在结果页面中遍历Action中验证表单域时添加的错误信息。
<s:iteratorvalue="errors.username">
<s:property/><br/>
</s:iterator>
也可以这样写:
<s:iteratorvalue="errors.username" var="e">
<s:propertyvalue="#e"/><br />
</s:iterator>
我们也可以自己组织错误消息的显示风格。
再举一个例子:
假如Action中有一个属性是Map<String,Dog>类型的,Dog类中定义了name和age属性。现在要求把该集合中age大于1的对象一个个遍历出来,如何做呢?
<s:iteratorvalue="dogs.values.{?#this.age > 1}" var="dog"status="status">
<s:iftest="#status.even">
<font color="red">
<s:property value="#status.count"/>
<s:property value="#dog.name"/><br />
</font>
</s:if>
<s:else>
<s:property value="#status.count"/>
<s:property value="#dog.name"/><br />
</s:else>
</s:iterator>
以上是一个完整的例子,并对奇数行和偶数行的显示进行了处理。
使用<s:iterator>来遍历所有用户请求参数值的示例:
<s:iterator value="#parameters"var="parameter">
<s:property value="#parameter.key"/>
<s:iterator value="#parameter.value">
<s:property />
</s:iterator><br />
</s:iterator>
<s:append>将多个集合拼接起来
拼接时对集合的类型没有限制
下面是一个例子,功能是将Action中的fielderror错误集合和parameters封装的用户请求参数集合拼接起来,再将所有信息遍历出来。
<s:append var="infos">
<s:param value="#parameters"/>
<s:param value="errors"/>
</s:append>
<s:iterator value="#infos"var="info">
<s:property value="#info.key"/>
<font color="red">
<s:iterator value="#info.value">
<s:property />
</s:iterator>
</font><br />
</s:iterator>
页面输出信息如下:
<s:generator>将一个字符串转成一个集合
<s:generator separator=","val="'11,22,33,44,55'" var="nums"count="3">
<s:iterator value="#nums">
<s:property /><br/>
</s:iterator>
</s:generator>
这里要注意val属性值一定要是字符串类型的,var指定的变量存放的是转换后的集合对象,该变量存放在request范围和stack context中,如果不指定val那么该变量就被放在值栈栈顶(记住出了标签就找不到了),count指定转换集合中的前几个元素,还可以指定converter属性,即指定要使用的转换器。
<s:merge>拼接多个集合
注意和<s:append>的区别:如果有三个集合,分别是:a1, b1, c1, d1, e1 a2, b2.c2, d2, e2
a3, b3, c3, d3, e3
如果用<s:append>拼接后的结果是:a1,b1,c1,d1,e1,a2,b2,c2,d2,e2,a3,b3,c3,d3,e3
而使用<s:merge>拼接后的结果 a1,a2,a3,b1,b2,b3,c1,c2,c3,d1,d2,d3,e1,e2,e3
他们的区别仅是拼接后元素的顺序,其他都一样
<s:subset>截取集合的子集
<s:subset source="{'1','2','3','6','5'}" start="1" count="3">
<s:iterator>
<s:property />
</s:iterator>
</s:subset>
source指定被截取的集合,start表示从第几个元素开始截取(索引从0开始),count表示截取的长度,这里试了一下,发现使用var属性无效
截取子集时实现过滤元素,要实现Decider接口中的decide方法,如下:
publicclass MyDecider implements Decider{
publicboolean decide(Object element) throws Exception {
int e = Integer.parseInt(element.toString());
return e%2 == 0;
}
}
此时JSP页面中的标签变为:
<s:bean name="MyDecider" id="d"></s:bean>
<s:subset source="{'7','2','3','6','5'}"decider="#d">
<s:iterator>
<s:property />
</s:iterator>
</s:subset>
我们指定了decider属性为MyDecider的一个实例,这样的话,对于目标集合中的每个元素在遍历的时候都会交给我们写的MyDecider去过滤,集合中元素被封装成decide方法的Object类型的参数。根据我们的判断逻辑,只要该方法返回true就表示该元素被成功添加到子集中。
<s:sort>对集合元素实现定制排序
首先我们要实现一个类实现Comparator接口,如下:
publicclass MyComparator implements Comparator<String>{
publicint compare(Stringstr1, String str2) {
if(str1.compareTo(str2) >= 0){
return 1;
}else{
return -1;
}
}
}
此时JSP页面中的标签变为:
<s:bean name="MyComparator" id="cc"></s:bean>
<s:sort comparator="#cc"source="{'haha','gaga','aa','dd','cc','mm'}">
<s:iterator>
<s:property />
</s:iterator>
</s:sort>
这里指定了一个comparator属性,会按我们在MyComparator类中实现的排序逻辑来对集合中的元素来排序。
关于ui标签,我相信在公司里绝大部分时候都不会使用,Struts2将页面显示风格作了封装,限定死了。而为了配合美工的工作,前台页面显示风格的显示应该交给美工实现。Struts2中的数据校验机制这个我也不知道会不会用,还得问问前辈。目前培训的时间有限,我们应该在有限的时间内,尽量多的掌握比较实用的技术,其他旁枝末节的东西,业余有时间可以看看。
查看Struts2中的默认主题:
打开项目中的org.apache.struts2 jar包→default.properties文件→Ctrl+F查找theme→会发现default.properties文件中的如下配置:
### Standard UI theme
### Change this to reflect which pathshould be used for JSP control tag templates by default
struts.ui.theme=xhtml
struts.ui.templateDir=template
#sets the default template type. Eitherftl, vm, or jsp
struts.ui.templateSuffix=ftl
我们可以在struts.xml文件中覆盖以上配置。
只要添加上这样一句话:
<constantname="struts.ui.theme" value=" "></constant>
value可指定为xhtml、simple、css_xhtml和ajax
如果我们在struts.xml文件中没有指定,默认使用的是xhtml布局
xhtml主题:<form>中使用的是表格布局
simple主题:<form>中无布局
css_xhtml主题:<form>中使用的是div布局
我们可以查看源代码来查看struts2中ui布局的实现。
在使用默认主题即xhtml的情况下,如果我们在页面中使用下面的标签输出错误信息:
<s:fielderror></s:fielderror>
那么页面最后显示的内容将会使用一些既定的CSS样式,如:
窗体顶端
窗体底端
- error1
查看页面源代码会发现上面的标签将被以下内容代替:
<ul class="errorMessage">
<li><span>error1</span></li>
</ul>
经过实验,发现不管我们使用何种主题,错误信息的显示样式始终都是如上所示。
现在我们想要使用自己的显示风格,比较困难,首先就将计就计,修改<ul>标签的显示风格。
ul.errorMessage{
list-style-type:none;
margin: 0;
padding: 0;
}
这种方法治标不治本,接下来使用Struts2推荐使用的方法。
方法一
从前面的default.properties文件中的主题目录的配置我们知道,存放主题的目录叫做template,该目录我们可以在本应用程序中的src目录下新建一个,再在template目录下新建一个表示主题的文件夹(叫它simple好了),该目录下的模板文件就是我们要按自己的意愿修改的。这样我们就覆盖了原来jar包中的相关主题下的指定模板文件。看具体操作:
我们只是修改了fielderror.ftl文件中的一些freemarker语句,就可以修改<s:fielderror>的显示风格了,不过这要求我们熟悉freemarker语句,有一定的学习成本。
方法二
定义自己的主题
我们可以定义自己的主题,定义自己的主题的时候最好是从simple主题中拷贝所有的模板文件,然后再对模板文件修改。
注意这里mytheme文件夹中的所有模板文件都是从simple中拷贝的。对其中的某些模板文件可以适当修改,如fielderror.ftl文件。然后再在struts.xml文件中引入我们的主题:<constant name="struts.ui.theme"value="mytheme"></constant>,这里我个人不喜欢使用默认主题(xhtml)和css_xhtml主题,因为它擅自对html元素作了封装,虽然这样一来页面显示风格统一了,并且还支持数据校验失败后跳到原表单页时在相应的表单域中显示错误信息,但这真的不是我想要的。我希望自己按照自己的风格来实现。
从已有的主题进行扩展
我们从xhtml拓展,就修改一下<s:textfield>UI标签所对应的xhtml模板文件吧。
1. 先创建一个自己的主题
在src目录下建一个包,包名为template.mytheme
然后我们把xhtml主题目录下的有关模板文件拷到该包下,对模板文件作相应修改
修改完成后还要在该包下创建一个theme.properties文件,内容为:parent=xhtml
表示我们创建的主题是从哪个主题扩展来的。
2. 在使用Struts2 UI标签的时候可以指定theme属性为我们的自定义主题
表单标签
常用的两个属性:name和value
value的值我们需要使用%{表达式语言}来获取值显示在文本框中
<s:checkboxlist>
name 该属性一定要指定!!!
list 表示的是一个集合对象,如果是Map类型的,listKey就是key,listValue就是value,如果是User类型的集合,那么listKey和listValue可以指定为User类的任意属性
listKey
listValue
<s:combobox>
name 必须要的
maxlength 手动输入的最大长度
list 指定的数组或集合
size 下来列表上面文本框的大小
<s:doubleselect>
级联选择器
<s:set var="haha" value="#{'a':{'a1','a2'},'b':{'b1','b2'},'c':{'c1','c2'}}"></s:set>
<s:doubleselect doubleList="#haha[top]" size = '20' doubleSize= '20'list="#haha.keySet()"doubleName="mmm"></s:doubleselect>
size 表示父下拉框的高度
doubleSize 子下拉框的高度
list 父下拉框要绑定的集合
<s:optiontransferselect>列表左移右移框
<s:optiontransferselect
label="Favourite Cartoons Characters"
name="leftSideCartoonCharacters"
leftTitle="Left Title"
rightTitle="Right Title"
list="{'Popeye', 'He-Man', 'Spiderman'}"
multiple="true"
headerKey="headerKey"
headerValue="--- Please Select ---"
emptyOption="false"
doubleList="{'Superman', 'Mickey Mouse', 'Donald Duck'}"
doubleName="rightSideCartoonCharacters"
doubleHeaderKey="doubleHeaderKey"
doubleHeaderValue="--- Please Select ---"
doubleEmptyOption="false"
doubleMultiple="true"
/>
<s:select>
<!--
list collection
listKey <option value=
listValue <option>value</option>
multiple select multi items
size initialize how many items to show
required show * to indicate required
value initialize item to be selected, itmust referenced listKey
-->
<s:select label="Pets"
name="petIds"
list="dogs"
listKey="id"
listValue="name"
multiple="true"
size="3"
required="true"
value="dogs[2].id"
/>
<s:radio>
<s:radio name="haha"label="select" list="{'male','female'}"/><br />
<s:radio name="gege" label="mm"list="#{'11':'aa','22':'bb','33':'cc'}"
listKey="key" listValue="value"/>
<s:optgroup>
<s:select name="mm" list="#{'11':'aa','22':'bb','33':'cc'}" listKey="key"listValue="value">
<s:optgroup label="dd" list="#{'44':'d1' }"listKey="key" listValue="value"/>
<s:optgroup label="ee" list="#{'55':'e1' }"listKey="key" listValue="value"/>
</s:select>
<s:token>防止页面重复提交
防止页面重复提交,指的是用户点击提交按钮服务器的处理比较慢页面没有立刻跳转,这是如果用户再次点击提交按钮,那么服务器端会再次执行,势必会向数据库中插入重复的记录,导致破坏数据的完整性。
处理方式1:将表单的提交方式设为POST
struts.xml文件:
<interceptor-ref name="token"></interceptor-ref>
<interceptor-ref name="defaultStack"></interceptor-ref>
<result>/suc.jsp</result>
<result name="invalid.token">/error.jsp</result>
提交页面:
<s:form action="my">
<s:token />
<s:submit></s:submit>
</s:form>
<s:updownselect>下拉列表的上下移动
<s:updownselect name="mm" moveUpLabel="move up"moveDownLabel="move down"
list="#{'11':'aa','22':'bb','33':'cc'}" listKey="key" listValue="value"
emptyOption="true"></s:updownselect>
emptyOption 表示的是第一项是空
非表单标签
获取actionErrorActionMessage中的信息
这些信息都是在action中调用addActionError和addActionMessage方法来设置的。
<s:actionerror /><br/>
<s:actionmessage />
Action与Web元素交互
如何在Action中获取用户请求参数值并设置用户请求参数值?
首先获取request对象:ActionContext request = ActionContext.getContext();
获取parameters对象:Map<String,Object> parameters = request.getParameters();
设置用户请求参数值:parameters.put(String key, String[] value);
获取用户请求参数值:((String[])parameters.get(String key))[index]
因为一个请求参数名可以对应多个值,所有根据参数名获取的值是字符串数组类型。
如何在Action中取得request对象,并在request中存放东西和获取东西?
ActionContext request =ActionContext.getContext();
request.put(String key, Object value);
request.get(String key);获取到的是一个对象
以上的代码都是在Action的方法中写的。
如何在Action中获取session和application对象,并在其范围中存放东西和获取东西?
在获取到request对象的情况下,即可获取session和application对象。
Map<String,Object> session =request.getSession();
session.put(Stringkey, Object value);
session.get(Stringkey);获取到的是一个对象
application对象同上。
在Action中获取request、session和application等Web元素的最常用方式:
public classMyAction1 implements RequestAware,SessionAware,ApplicationAware {
privateMap<String,Object> request;
privateMap<String,Object> session;
privateMap<String,Object> application;
public voidsetRequest(Map<String, Object> request) {
this.request = request;
}
public voidsetSession(Map<String, Object> session) {
this.session =session;
}
public voidsetApplication(Map<String, Object> application) {
this.application =application;
}
}
Struts2与AJAX交互
首先要向我们的项目中引入几个重要的文件,它们是jquery-1.7.1.js 、dom4j-1.6.1.jar、gson-1.6.jar和struts2-json-plugin-2.2.3.jar。
在Struts2中使用XML格式包装的数据无非就在JSP页面和Action之间进行传递。实现步骤:
1. 导入jquery库和dom4j jar包
2. 触发JSP页面的脚本事件:如
<select id="name">
<option value="zhangsan">张三</option>
<option value="lisi">李四</option>
</select>
<input type="submit" value="提交"onclick="getInfo();">
3. 执行getinfo()方法,方法体中指定请求的路径、要传的参数及响应回来的数据,如
function getInfo(){
$.post("getXMLAction.action",{
name: $("#name").val()
},function(returnedData,status){
var id =$(returnedData).find("id").text();
var name =$(returnedData).find("name").text();
var age =$(returnedData).find("age").text();
var address= $(returnedData).find("address").text();
var html ="<table width='60%' border='1' align='center'>"
+"<tr><th>id</th><th>name</th><th>age</th><th>address</th>"
+"<tr align='center'><td>" + id +"</td><td>" + name
+"</td><td>"+age+"</td><td>"+address+"</td></tr></table>";
$("#theBodytable:eq(0)").remove();
$("#theBody").append(html);
});
}
4. 在页面中显示取回的XML数据的时候肯定要在<body>中添加一个节点,而在每次执行异步提交响应数据之前先要把原来添加的节点先删掉,这里我们是以<body>作为父节点来添加新节点的。如:
<body id="theBody">
…
</body>
5. 而在处理异步提交请求的服务器端(Action)中,首先要定义一个属性来接收异步提交传过来的参数(属性一定要用Getters和Setters方法封装),
6. 构建基于XML的DOM节点树,并在节点中插入数据,如
Person p1 = newPerson();
p1.setId(1);
p1.setName("zhangsan");
p1.setAge(20);
p1.setAddress("nantong");
Person p2 = newPerson();
p2.setId(12);
p2.setName("lisi");
p2.setAge(30);
p2.setAddress("nanjing");
Document document =DocumentHelper.createDocument();
Element rootElement= document.addElement("persons");
rootElement.addComment("Thisis comment!!");
Element e =rootElement.addElement("person");
Element idElement =e.addElement("id");
Element nameElement= e.addElement("name");
Element ageElement =e.addElement("age");
ElementaddressElement = e.addElement("address");
if("zhangsan".equals(name)){
idElement.setText(p1.getId()+ "");
nameElement.setText(p1.getName());
ageElement.setText(p1.getAge()+ "");
addressElement.setText(p1.getAddress());
}else{
idElement.setText(p2.getId()+ "");
nameElement.setText(p2.getName());
ageElement.setText(p2.getAge()+ "");
addressElement.setText(p2.getAddress());
}
7. 设置XML文件如何在服务器端响应。
HttpServletResponseresponse = ServletActionContext.getResponse();
response.setContentType("text/xml;charset=GBK");
response.setHeader("cache-control","no-cache");
PrintWriter out =response.getWriter();
//设置XML文件的编码
OutputFormat format= OutputFormat.createPrettyPrint();
format.setEncoding("GBK");
//获取XMLWriter对象
XMLWriter writer =new XMLWriter(out, format);
writer.write(document);
out.flush();
out.close();
returnnull;
8. struts.xml文件中action的配置很简单,不要配置结果类型。
在Struts2中读写Json数据有两种方法,一种是和读写XML数据一样的方法还有一种是使用Struts2的json插件的方法。先说第一种方法。
1. 首先要引入jquery插件和gson jar包。
2. JSP页面几乎和XML的一样,只是获取响应数据的时候略有不同:
var people = returnedData;
var id =people.id;
var name= people.name;
var age =people.age;
varaddress = people.address;
3. 服务器端的处理也基本和XML相同,首先定义属性接收异步请求参数,接下来设置响应:
Person people = newPerson();
people.setId(1);
people.setName(name);
people.setAge(30);
people.setAddress("beijing");
Gson gson = new Gson();
String result =gson.toJson(people);
HttpServletResponseresponse = ServletActionContext.getResponse();
response.setContentType("application/json;charset=utf-8");
response.setHeader("cache-control","no-cache");
PrintWriter out =response.getWriter();
out.print(result);
out.flush();
out.close();
returnnull;
4. 最后的struts.xml文件中action的配置也不要配结果类型。
第二种方法是使用Struts2提供的json插件,自然先要引入相关的jar包,我们直接在action中定义与响应数据相对应的属性,比如:
private Stringname;
private int age;
private int id;
private String address;
…省略Getters和Setters方法
而在页面中获取响应数据则是:
var people =returnedData;
var id =people.id;
var name =people.name;
var age =people.age;
var address =people.address;
属性都是对应的。
这时候的struts.xml文件的配置就要稍稍注意了,package要继承自json-default包,action的结果类型要写完整,结果类型是json。
这里要注意一个问题,就是json对象的属性一定要和action中定义的属性相对应。当然我们也可以排除action中的属性使其不再作为json对象的属性,如:
<resultname="success" type="json">
<paramname="excludeProperties">address</param>
</result>
<param>标签中的内容就是我们要指定的action中的属性,多个属性之间可以用英文逗号隔开。
当然我们也可以使用Annotation(标注)技术来修改action中属性所对应的json对象的属性名,如:
@JSON(name="myId")
public int getId(){
return id;
}