标签:struts web应用开发 web框架 编程语言 开源
Struts2
1.目录
2.MVC
3.STRUTS2解析
4.标签
5.OGNL
6.国际化
7.类型转换
8.校验
9. 拦截器
10.上传与下载
11.STRUTS2与对JSON的支持
把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。MVC模式最早由Trygve Reenskaug在1978年提出,在20世纪80年代为程序语言Smalltalk发明的一种软件设计模式。MVC模式的目的是实现一种动态的程式设计,使后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。除此之外,此模式通过对复杂度的简化,使程序结构更加直观。
视图是用户看到并与之交互的界面。对Web应用程序来说,视图就是由HTML元素组成的界面,在新式的Web应用程序中,HTML依旧在视图中扮演着重要的角色。
用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法。“模型”有对数据直接访问的权力,例如对数据库的访问。“模型”不依赖“视图”和“控制器”,也就是说,模型不关心它会被如何显示或是如何被操作。
控制器接受用户的输入并调用模型和视图去完成用户的需求,所以当单击Web页面中的超链接和发送HTML表单时,控制器本身不输出任何东西和做任何处理。它只是接收请求并决定调用哪个模型构件去处理请求,然后再确定用哪个视图来显示返回的数据。
1. 多个视图可以对应一个模型,可以减少代码的量以及维护,即使模型发生改变,也易于维护。
2. 模型返回的数据域显示逻辑分离,模型可以用多种技术来实现,比如JSP,HTML。
3. 应用被分割成三层,降低各层之间的耦合。
4. MVC更加符合软件工程化管理的精神, 不同的层各司其职,每层的组件具有相同的特征。
Struts2由Struts1与WebWork两个经典的MVC框架发展而来,Struts2无论是从设计的角度来看,以及实际使用来说,它都是一个非常优秀的MVC框架。常见的MVC框架有:Struts1, Struts2, WebWork, Spring MVCWeb。
1. WebWork是由OpenSymphony组织开发的基于MVC架构模式的J2EE Web框架。
2. Tapestry是一个开源的基于组件的Web应用开发框架,它使用组件对象模型来创建动态的,交互的Web应用。
3. Spring MVC Web框架是整个Spring集成框架的一部分。
一个请求在Struts2框架中的处理大概分为以下几个步骤:
1. 客户端初始化一个指向Servlet容器(例如Tomcat)的请求。
2. 这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,例如:SiteMesh Plugin)。
3. 接着FilterDispatcher被调用,FilterDispatcher询问ActionMapper来决定这个请求是否需要调用某个Action。
4. 如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy。
5. ActionProxy根据ActionMapper和Configuration Manager询问框架的配置文件,找到需要调用的Action类。
6. ActionProxy创建一个ActionInvocation的实例。
7. ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用。
8. 一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果。返回结果通常是(但不总是,也可能是另外的一个Action链)一个需要被表示的JSP或者FreeMarker的模版。在表示的过程中可以使用Struts2框架中继承的标签。在这个过程中需要涉及到ActionMapper。
FilterDispatcher是控制器的核心,就是mvc中c控制层的核心。下面粗略的分析下我理解的FilterDispatcher工作流程和原理:FilterDispatcher进行初始化并启用核心doFilter。
介绍一下ActionMapper,ActionProxy,ActionInvocation.
org.apache.struts2.dispatcher.mapper.ActionMapper这个类的介绍可以通过查看API来了解,用一句话解释“ActionMapper其实是HttpServletRequest和Action调用请求的一个映射”。
The ActionMapper interface provides a mapping between HTTP requests and action invocation requests and vice-versa. When given an HttpServletRequest, the ActionMapper may return null if no action invocation request matches, or it may return anActionMapping that describes an action invocation for the framework to try. |
通过actionName获取ActionMapper
ActionMapping getMappingFromActionName(String actionName) |
Action的一个代理,由ActionProxyFactory创建,它本身不包括Action实例,默认实现DefaultActionProxy是由ActionInvocation持有Action实例。ActionProxy作用是如何取得Action(Object getAction()),无论是本地,还是远程。而ActionInvocation的作用是如何执行Action。
Object getAction() // Gets the Action instance for this Proxy. |
是指struts2的配置文件解析器。
就是说,一般情况下,如果你要用SiteMesh或者其他过滤器,一般是放在FilterDispatcher或者是现在的StrutsPrepareAndExecuteFilter之前。在调用完所有过滤器的doFilter方法后,核心过滤器FilterDispatcher或者StrutsPrepareAndExecuteFilter会清空ActionContext,如果其他过滤器要一直使用value stack等struts的特性时,如果不用ActionContextCleanUp的话,便得不到想要的值。
已经过期。
StrutsPrepareAndExecuteFilter和XXXAction共同构成了Struts2的控制器,通常我们把StrutsPrepardAndExecuteFilter称为核心控制器,把XXXAction称为业务控制器。
使用struts2编写一个最简单的helloworld程序,来看下strut2到底是什么。
1. 使用myeclipse建立一个WEB工程。
2. 拷贝所需的struts2依赖包,如果不知道需要哪些包的话,可以去struts2中实例包中去拷贝。
3. 配置struts2的核心拦截器” StrutsPrepareAndExecuteFilter”
<filter> <filter-name>StrutsPrepareAndExecuteFilter</filter-name> <filter-class> org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter </filter-class> </filter> <filter-mapping> <filter-name>StrutsPrepareAndExecuteFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> |
//也可以是这个,但是不推荐使用。 org.apache.struts2.dispatcher.FilterDispatcher |
4. 配置struts.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <constant name="struts.devMode" value="true" /> <constant name="struts.action.extension" value="do" /> <package name="default" namespace="/login" extends="struts-default"> <action name="hello"> <result>/hello.jsp</result> </action> </package> </struts> |
Struts2的默认配置文件是struts.xml,该路径应该放在WEB应用程序的classpath路径下。这个配置文件主要是配置Action和请求之间的对应关系,并配置逻辑试图和物理试图之间的关系。它还有一些额外的功能:Bean配置,配置常量,导入其它配置文件。
常量的配置可以使用struts.properties配置,但是还是推荐使用XML的。至于这些常量配置类有哪些,可以从struts2-core-2.2.1.jar的解压后的目录中找到,下面的default.properties就是我所需要的,struts中的所有常量配置都可以从这里面找到。
常量名 |
作用 |
struts.i18n.encoding |
指定WEB应用的默认编码集。 |
struts.multipart.parser |
处理multipart/form-data的MIME类型,主要是文件上传,可以配置,cos,pell,jakarta,默认是jakarta |
struts.multipart.saveDir |
指定上传文件的零时目录,默认值是javax.servlet.context.tempdir |
struts.multipart.maxSize |
指定上传文件时候允许的最大字节数 |
struts.action.extension |
指定处理请求的后缀,默认值是action,如果是多个的后缀用英文逗号隔开。 |
struts.serve.static.browserCache |
设置浏览器是否缓存静态内容,在开发阶段使用这个选项,每次请求都获得服务器最新的内容,默认值是false |
struts.devMode |
设置开发者模式,如果设置常量为true,则可以是应用出错时显示更多,更友好的错误提示,默认值是false。开发的时候推荐使用这个配置。 把这个值设置成true意味着Struts.i18n.reload和Struts.configuration.xml.reload都设置成true |
struts.i18n.reload |
设置是否每次HTTP请求到达时,系统都重新加载配置文件。 |
struts.configuration.xml.reload |
设置当struts.xml文件发生变化的时候,系统是否自动重新加载该配置文件,默认值是false。 |
struts.custom.i18n.resources |
指定应用所需的国际化资源文件,如果有多个用英文逗号隔开。 |
Struts2按照下面的搜索顺序加载常量
1. Struts-default.xml:在struts2-core-2.2.1.jar文件中。
2. Struts-plugin.xml:会在struts的插件jar文件中。
3. struts.xml:WEB应用的struts2默认配置文件
4. struts.properties: struts2默认配置文件
5. web.xml:WEB应用配置文件
<constant name="struts.devMode" value="true" /> <constant name="struts.action.extension" value="do" /> |
当系统比较庞大,且业务比较复杂的时候,把所有的配置信息放置在struts.xml中话,会导致这个配置文件过于庞大,与臃肿,不便于开发与维护。
可以通過在struts.xml中使用<include>手动导入一个配置文件,在使用struts开发项目的时候可以把每一个模块对应一个配置文件。
struts.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <constant name="struts.devMode" value="true" /> <constant name="struts.action.extension" value="do" /> <include file="struts_part1.xml" /> <package name="default" namespace="/login" extends="struts-default"> <action name="hello"> <result>/hello.jsp</result> </action> </package> </struts> |
struts_part1.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <package name="part1" namespace="/login" extends="struts-default"> <action name="second"> <result>/hello.jsp</result> </action> </package> </struts> |
这里要注意的是这个配置文件中package的name属性一定不要与默认的配置文件的package的name属性一样。
开发Struts2应用来说,开发Action是核心,开发者需要提供大量的Action类,并在struts.xml中配置。Action包含了对用户请求的处理逻辑。
Struts2采用了低侵入式的设计,Struts2不要求Action继承或实现任何Struts2中的接口与基类。这种设计方式下,Struts2的Action类可以是一个普通的POJO类(包含一个无参的execute()方法)。
Struts2通常直接使用Action来封装HTTP请求参数,因此Action类里还应该包含请求参数对应的属性(一定要一一对应),且为这些属性提供相应的setter, getter方法。
Action不仅可以封装请求参数,还可以用于封装处理结果,默认的情况下可以在request范围内获取到这个属性的值。
package com.csuinfosoft.struts2.action;
public class LoginAction {
private String username;
private String password; ……
public String execute() throws Exception { if ("test".equals(username) && "123456".equals(password)) { return “success”; } else { return “input”; } } } |
为了让用户开发Action更加规范,Struts2提供了一个Action接口,这个接口定义了Struts2的Action处理类应该实现的规范。
package com.opensymphony.xwork2;
public interface Action { public static final String SUCCESS = "success"; public static final String NONE = "none"; public static final String ERROR = "error"; public static final String INPUT = "input"; public static final String LOGIN = "login";
public String execute() throws Exception; } |
该接口的规范定义了Action类应该包含一个execute()方法,该方法返回一个字符串。而在Action中定义了五个字符串常量:ERROR, NONE, INPUT, LOGIN,SUCCESS,这几个字符串代表了Action中的多个视图。
package com.csuinfosoft.struts2.action;
import com.opensymphony.xwork2.Action;
public class LoginAction implements Action {
private static final long serialVersionUID = 6376824426094079890L;
private String username;
private String password;
……
public String execute() throws Exception { if ("test".equals(username) && "123456".equals(password)) { return SUCCESS; } else { return INPUT; } } } |
ActionSupport是一个默认的Action的默认实现类,该类里已经提供了许多默认的方法,这些默认方法包括获取国际化信息的方法,数据校验的方法。在实际开发过程中,开发者通过继承ActionSupport类来开发自己的Action。
注意:我们的第一个Struts2的实例中没有定义Action类,也就是<action>属性没有配置class属性,这种情况下系统会自动使用和ActionSupport类作为Action处理类。
package com.csuinfosoft.struts2.action;
import com.opensymphony.xwork2.ActionSupport;
public class LoginAction extends ActionSupport {
private static final long serialVersionUID = 6376824426094079890L;
private String username;
private String password;
……
public String execute() throws Exception { if ("test".equals(username) && "123456".equals(password)) { return SUCCESS; } else { return INPUT; } } } |
Struts2的Action没有与任何Servlet API耦合,这是Struts2的一个改良之处。由于与Servlet API解耦,又便于轻松测试Action。
但是对于WEB应用的控制器而言,不访问ServletAPI是不可能的,Struts2提供了更加方便的访问Servlet API,比如访问HttpServletRequest,HttpSession, ServletContext。
Struts2提供一个ActionContext类,访问Servlet API。
get(key) |
request.getAttribute() |
getContext() |
获取系统的ActionContext实例 |
getParameters() |
request.getParameterMap() |
getSession() |
获取HttpSession实例 |
setApplication() |
在application范围内设置属性名与属性值 |
setSession() |
在session范围内设置属性名与属性值 |
实例,下面展示的是通过ActionContext操作application,session,request
ActionContext ac = ActionContext.getContext(); // application Map<String, Object> application = ac.getApplication(); application.put("application", "application范围"); ac.setApplication(application); // session Map<String, Object> session = ac.getSession(); session.put("session", "session范围"); ac.setSession(session); // 从request范围内获取属性 ac.get("key"); ac.put("request", "request范围"); |
然后在页面访问action里面设置的属性。
<%=application.getAttribute("application")%><br /> <%=session.getAttribute("session")%><br /> <%=request.getAttribute("request ")%><br /> |
Map<String, Object> parameters = ac.getParameters(); for (Iterator<String> iter = parameters.keySet().iterator(); iter.hasNext();) { String key = iter.next(); System.out.println("key=" + key + ", value=" + ((String[]) parameters.get(key))[0]); } |
Struts2提供了ActionContext来访问Servlet API,但是并不是直接访问Servlet API,Struts2提供了直接来访问Servlet API的接口。
package com.csuinfosoft.struts2.action;
import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
import org.apache.struts2.interceptor.ServletRequestAware; import org.apache.struts2.interceptor.ServletResponseAware; import org.apache.struts2.util.ServletContextAware;
import com.opensymphony.xwork2.ActionSupport;
public class LoginActionWithServlet extends ActionSupport implements ServletContextAware, ServletRequestAware, ServletResponseAware {
private HttpServletRequest request;
private HttpServletResponse response;
private ServletContext context;
private String username;
private String password;
public void setServletRequest(HttpServletRequest request) { this.request = request; }
public void setServletResponse(HttpServletResponse response) { this.response = response; }
public void setServletContext(ServletContext context) { this.context = context; }
@Override public String execute() throws Exception { if ("test".equals(username) && "123456".equals(password)) { return SUCCESS; } else { return INPUT; } } } |
注意:在Action中可以获取HttpServletResponse对象,也不要尝试直接在Action中对客户端生成响应。
问题:有ServletContextAware,ServletRequestAware, ServletResponseAware有application,request, response为什么没有session???
除了3.6.4上面所说的方式,还可以通过ServletActionContext工具类来访问,虽然它可以是我们不用去实现上面提到的接口,但是,这样是Action与Servlet API高度耦合,不利于解耦。
package com.csuinfosoft.struts2.action;
import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
import org.apache.struts2.ServletActionContext;
import com.opensymphony.xwork2.ActionSupport;
public class LoginActionWithServletActionContext extends ActionSupport { private String username;
private String password;
@Override public String execute() throws Exception { if ("test".equals(username) && "123456".equals(password)) { // 获取request HttpServletRequest request = ServletActionContext.getRequest(); // 获取response HttpServletResponse response = ServletActionContext.getResponse(); // 获取application ServletContext application = ServletActionContext.getServletContext(); return SUCCESS; } else { return INPUT; } } } |
使用STRUTS2做一个猜数字游戏,要求:随机产生一个0 ~ 50的数字,然后让用户猜,当猜小了,或者猜大了,都做相关提示。直到猜对为止。当猜对以后把所产生的随机数更换,以便下次猜的时候使用。最后按猜的次数来评级,比如:5次以下是优,6~10次为良,10次以上为差,在页面做显示。 |
Struts2框架的核心是Action,拦截器,而Struts2使用包来管理这些Action,拦截器,一个包可以有多个Action和拦截器。就像java中使用包来管理类。
在strutx.xml文件中<package>元素用于定义包配置,<package>元素有以下属性:
属性 |
作用 |
name |
此属性必须属性,该属性指定包的名字。 |
extends |
可选,该属性指定该包继承其它包,如果继承其它包,可以继承其它包中的Action与拦截器。 |
namespace |
可选,该属性指定该包的命名空间。 |
abstract |
可选,该属性说明包是抽象包,抽象包不能包含Action与拦截器。 |
struts-default.xml配置文件,从下面的配置文件可以看出我们写的struts.xml的package都是继承这个配置文件的。
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN" "http://struts.apache.org/dtds/struts-2.1.7.dtd"> <struts> <package name="struts-default" abstract="true"> <default-class-ref class="com.opensymphony.xwork2.ActionSupport" /> </package> </struts> |
这里要强调的是namespace属性,Struts2之所以提供这个属性的原因在于在以后的开发应用中同名Action是很有可能的,通过应用命名空间可以避免这个问题,用它来管理Action,同一个命名空间不能有同名Action;而不同的命名空间可以有同名的Action。
当包指定了命名空间后,该包下所以的Action的访问URL是“命名空间+Action名”。
如果namespace不写的情况下,它是等于namespace=””这种情况下,会有个问题:下面三个URL都能访问Action。他会处理其他package处理不了的URL。
http://127.0.0.1:8888/s2/loginAndReigist http://127.0.0.1:8888/s2/aa/loginAndReigist http://127.0.0.1:8888/s2/aa/bb/loginAndReigist |
注意:如果在一个配置文件中有多个包,就必须是子包在父包的后面,因为Struts2的配置文件是从上到下的处理。
定义Action的时候,至少需要指定该Action的name属性,这个那么属性是Action的名字,也是访问这个Action的URL。
通常情况下还需要指定一个class属性,表明该Action的实现类,注意class属性是可选的,不写,系统默认是ActionSupport类。
Action是一个逻辑控制器,它并不直接对浏览器生成响应。因此Action处理完用户请求后,Action需要将指定的试图呈现给用户,因此配置Action是应该配置逻辑试图和物理试图之间的关系,配置逻辑试图也物理试图的关系是通过<resultname=”” />来实现。如果是success的时候可以不写,默认就是它。
注意:Action的name属性比较灵活,但是不要使用”.”, “-”,这种情况可能发生一些未知异常。
这里的意思是指,使用Action的不同的方法来处理用户的请求,这会使得一个Action包含多个控制处理器。例如页面的“登陆”和“注册”可以交由一个Action来处理,这时候可以采用DMI(Dynamic Method Invocation动态方法调用)来处理,这种处理方式使用的URL并不是使用Action的name属性,而是以下形式来实现:
<from action=”actionName!methodName”> |
LoginAndRegistAction.java
package com.csuinfosoft.struts2.action;
import com.opensymphony.xwork2.ActionSupport;
public class LoginAndRegistAction extends ActionSupport {
private String username;
private String password;
/** * DMI * @return * @throws Exception */ public String regist() throws Exception { System.out.println("恭喜:" + this.getUsername() + " 注册成功!"); return SUCCESS; }
@Override public String execute() throws Exception { if ("test".equals(username) && "123456".equals(password)) { System.out.println(this.getUsername() + " 登陆成功!"); return SUCCESS; } else { return INPUT; } } } |
struts.xml
<action name="loginAndReigist" class="com.csuinfosoft.struts2.action.LoginAndRegistAction"> <result name="success">/hello.jsp</result> <result name="input">/index.jsp</result> </action> |
login_regist.jsp
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <base href="<%=basePath%>"> <title>登陆与注册</title> <script type="text/javascript"> function regist() { alert("注册!!!"); var form = document.getElementById("form"); // 使用默认的.do, 且不要用在URL中使用.do form.action= "<%=path%>" + "/loginAndReigist!regist"; form.submit(); } </script> </head> <body> <form id="form" action="<%=path%>/loginAndReigist.do"> 用户名: <input type="text" name="username" /> <br /> 密码: <input type="password" name="password" /> <br /> <input type="button" value="注册" onclick="regist()" /> <input type="submit" value="登陆" /> <br /> </form> </body> </html> |
注意:
使用DMI的时候action的请求后缀用默认的.do,在写URL的时候不用在URL中带.do,这样做会报404。
关于DMI的使用,系统有一个配置来控制它的使用,配置项如下:
struts.enable.DynamicMethodInvocation = true |
如果配置成false,在调用会报如下错:
There is no Action mapped for namespace / and action name loginAndReigist!regist. - [unknown location] |
上面的动态方法调用可以减少Action,但是其写法有点不好理解,这里的method方式也是一种方式,但是这种方式会导致struts.xml中会有多个Action的配置。
这种方式使用method属性来实现,也就是一个action的话通过多个method完成不同的业务。
<action name="loginAndReigist" class="com.csuinfosoft.struts2.action.LoginAndRegistAction" method=="login"> <result name="success">/hello.jsp</result> <result name="input">/index.jsp</result> </action> |
<action name="loginAndReigist" class="com.csuinfosoft.struts2.action.LoginAndRegistAction" method=="regist"> <result name="success">/hello.jsp</result> <result name="input">/index.jsp</result> </action> |
在配置<action>元素的时候,允许在指定name属性时使用模式字符串(*代表一个或者多个任意字符),然后就可以在class, method, result中使用{N}的形式来代表当前第N个星号所匹配的子串。
这个Action,会根据mehtod的值来进行调用,而method又是使用通配符,它来源与那么属性。
使用LoginAndRegistAction类来完成登录与注册的功能,在配置中使用"*Action"的方式,然后通过由method="{1}"来决定调用哪个方法来执行。
LoginAndRegistAction.java
public class LoginAndRegistAction extends ActionSupport { private String username; private String password;
public String login() throws Exception { if ("test".equals(username) && "123456".equals(password)) { System.out.println("登陆成功"); return SUCCESS; } else { System.out.println("登陆失败"); return INPUT; } }
public String regist() throws Exception { if ("test".equals(username) && "123456".equals(password)) { System.out.println("註冊成功"); return SUCCESS; } else { System.out.println("註冊失败"); return INPUT; } } } |
struts.xml
<action name="*Action" class="com.struts2.action.LoginAndRegistAction" method="{1}"> <result name="success">/hello.jsp</result> <result name="input">/index.jsp</result> </action> |
上面的action的name属性是"*Action",这种定义不是一个普通的Action,而是定义了一系列的逻辑Action,只要用户的URL是*Action.aciton模式,都可以使用这个Action来处理。配置该Action的时候,method属性使用{1},表示该该表达式的值是name属性值中的第一个*的值。
index.jsp
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <base href="<%=basePath%>"> <title>My JSP ‘index.jsp‘ starting page</title> <script type="text/javascript"> function regist() { var form = document.getElementById("form"); form.action = "<%=path%>/registAction.action"; form.submit(); } </script> </head> <body> <form id="form" action="<%=path%>/loginAction.action" method="post"> 用户名: <input type="text" name="username" value="test" /> <br /> 密码: <input type="password" name="password" value="123456" /> <br /> <input type="submit" value="登陆" /> <input type="button" value="注册" onclick="regist()" /> <br /> </form> </body> </html> |
通过配置"*Action"来匹配多个Action,然后使用下面的方式"com.struts2.action.{1}Action"来对应到多个Action类 "。
LoginAction.java
package com.struts2.action; import com.opensymphony.xwork2.ActionSupport;
public class LoginAction extends ActionSupport { private String username; private String password; public String execute() throws Exception { if ("test".equals(username) && "123456".equals(password)) { System.out.println("登陆成功......"); return SUCCESS; } else { System.out.println("登陆失败......"); return INPUT; } } } |
RegistAction.java
package com.struts2.action;
import com.opensymphony.xwork2.ActionSupport;
public class RegistAction extends ActionSupport { private String username; private String password; public String execute() throws Exception { if ("test".equals(username) && "123456".equals(password)) { System.out.println("註冊成功......"); return SUCCESS; } else { System.out.println("註冊失败......"); return INPUT; } } } |
这里Action所对应的class是来至于action的name属性。
struts.xml
<action name="*Action" class="com.struts2.action.{1}Action"> <result name="success">/hello.jsp</result> <result name="input">/index.jsp</result> </action> |
index.jsp
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <base href="<%=basePath%>"> <title>My JSP ‘index.jsp‘ starting page</title> <script type="text/javascript"> function regist() { var form = document.getElementById("form"); form.action = "<%=path%>/RegistAction.action"; form.submit(); } </script> </head> <body> <form id="form" action="<%=path%>/LoginAction.action" method="post"> 用户名: <input type="text" name="username" value="test" /> <br /> 密码: <input type="password" name="password" value="123456" /> <br /> <input type="submit" value="登陆" /> <input type="button" value="注册" onclick="regist()" /> <br /> </form> </body> </html> |
使用两个通配符来实现,其中第一个就代表Action的名字,第二个代表method的名字。<action name="*_*"class="com.struts2.action.{1}Action" method="{2}" >。
RegistAction.java
public class RegistAction extends ActionSupport { private String username; private String password; public String regist() throws Exception { if ("test".equals(username) && "123456".equals(password)) { System.out.println("註冊成功............"); return SUCCESS; } else { System.out.println("註冊失败............"); return INPUT; } } } |
LoginAction.java
public class LoginAction extends ActionSupport { private String username; private String password;
public String login() throws Exception { if ("test".equals(username) && "123456".equals(password)) { System.out.println("登陆成功............"); return SUCCESS; } else { System.out.println("登陆失败............"); return INPUT; } } } |
struts.xml
<action name="*_*" class="com.struts2.action.{1}Action" method="{2}"> <result name="success">/hello.jsp</result> <result name="input">/index.jsp</result> </action> |
index.jsp
<%@ page language="java" pageEncoding="UTF-8"%> <% 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 ‘index.jsp‘ starting page</title> <script type="text/javascript"> function regist1() { var form = document.getElementById("form1"); form.action = "<%=path%>/Regist_regist.action"; form.submit(); } </script> </head> <body> <form id="form1" action="<%=path%>/Login_login.action" method="post"> 用户名: <input type="text" name="username" value="test" /> <br /> 密码: <input type="password" name="password" value="123456" /> <br /> <input type="submit" value="登陆" /> <input type="button" value="注册" onclick="regist1()" /> <br /> </form> </body> </html> |
不但class属性,method属性中可以使用表达式,还可以在<result>中也使用{ N },我们在上面的的实例的基础上修改一下。
下面的配置中class,action,method,result都是通过name属性的通配符来实现的。
<action name="*_*" class="com.struts2.action.{1}Action" method="{2}"> <result name="success">/{2}.jsp</result> <result name="input">/index.jsp</result> </action> |
注意:一定要刷新页面
<action name="*"> <result>/{1}.jsp</result> </action> |
这个配置所代表的意思是:它配置所以的Action,所以用户的请求都有Action来处理,Action没有class属性,所以交由ActionSupport来处理,且ActionSupport类的execute()方法返回一个success字符串,所以这么处理当访问的abcAction的话会出现会访问abc.jsp,就相当于一个超链接一样。
由于出现了通配符会出现到底是由哪个action来处理,规律总结为:如果请求为abcAction.action如果配置文件中有名为abcAction的Action就由它处理,如果没有则搜寻能匹配abcAction.action的Action,例如:“*Action, *”都可以,但是这里要注意的是这两个没有优先级,先找到哪个就是那个!也就是按它们在配置文件中的顺序。
ExactAction.java
public class ExactAction extends ActionSupport { private String message; @Override public String execute() throws Exception { System.out.println(this.getClass().getName() + " executing"); this.setMessage(this.getClass().getName() + " executing"); return SUCCESS; } } |
MatchingAction.java
public class MatchingAction extends ActionSupport { private String message; @Override public String execute() throws Exception { System.out.println(this.getClass().getName() + " executing"); this.setMessage(this.getClass().getName() + " executing"); return SUCCESS; } } |
AllAction.java
public class AllAction extends ActionSupport { private String message; @Override public String execute() throws Exception { System.out.println(this.getClass().getName() + " executing"); this.setMessage(this.getClass().getName() + " executing"); return SUCCESS; } } |
struts.xml
<package name="part1" namespace="/priority" extends="default"> <action name="exactAction" class="com.csuinofsoft.priority.ExactAction"> <result>/priority.jsp</result> </action> <action name="*Action" class="com.csuinofsoft.priority.MatchingAction"> <result>/priority.jsp</result> </action> <action name="*" class="com.csuinofsoft.priority.AllAction"> <result>/priority.jsp</result> </action> </package> |
测试:
URL |
处理类 |
ExactAction |
|
MatchingAction |
|
AllAction |
注意:除非请求URL与Action的name属性绝对相同,否则按照先后顺序来决定由哪个Action处理用户的请求,因此我们应该将name="*"放在最后。
上面使用name="*"来处理用户的所有请求,Struts2还支持配置默认的Action。也就是当请求找不到对应的Action的时候,由系统默认的Action来处理用户的请求。
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <constant name="struts.devMode" value="true" /> <package name="default" namespace="/" extends="struts-default"> <default-action-ref name="defaultAction" /> <action name="defaultAction" class="com.struts2.action.DefaultAction"> <result>/404.jsp</result> </action> </package> </struts> |
注意:<default-action-ref>要写在package标签的最上面。
package com.struts2.action;
import javax.servlet.http.HttpServletRequest; import org.apache.struts2.ServletActionContext; import com.opensymphony.xwork2.ActionSupport;
public class DefaultAction extends ActionSupport { private String url; ……… @Override public String execute() throws Exception { HttpServletRequest request = ServletActionContext.getRequest(); String requestURI = request.getRequestURI(); this.setUrl(requestURI + " 找不到!"); System.out.println("默认Action处理"); return SUCCESS; } } |
通常我们把StrutsPrepardAndExecuteFilter称为核心控制器,把XXXAction称为业务控制器。这个业务控制器不能直接生产客户端的响应。Action则负责处理请求,负责生成响应的试图组件,通常由JSP来充当,Action会为JSP提供要显示的数据。当Action处理完后,控制器应该使用哪个试图资源来生成响应,这时候就需要<result>元素来指定,该元素定义逻辑试图也物理试图之间的映射关系。
<result>可以分为局部结果,全局结果。
局部结果 |
<result>作为<action>的元素。 |
全局结果 |
<result>作为<global-results>的子元素。 |
<result>元素的属性
name |
配置逻辑试图名 |
type |
指定结果类型 |
这里要说明的是<result>元素的name,不配置的话,name属性默认是success。
结果类型也就是<result>元素的type属性,它的默认值是dispatcher,它有多种的支持。Struts2所支持的结构类型可以查看struts-default.xml。
类型 |
作用 |
chain |
Action链式处理的结构类型 |
dispatcher |
用于指定JSP作为试图的结构类型(转发) |
freemaker |
指定freemaker模板作为结构类型 |
httpheader |
用于特殊的HTTP行为的结构类型 |
redirect |
重定向到其它URL |
redirectAction |
重定向到其它的Action |
stream |
用于向浏览器返回一个InputStream,用于文件下载 |
velocity |
用于velocity模板作为试图 |
xslt |
XML/XSLT |
plainText |
用于显示页面的原始代码 |
这个使用场景很少,它用于显示VIEW的纯文本,下面有两种配置方法,第二种的配置支持设置编码集。
<action name="plainTextAction" class="com.struts2.action.PlainTextAction"> <result name="success" type="plainText">/hello.jsp</result> </action> |
<action name="plainText" class="com.struts2.action.PlainTextAction"> <result name="success" type="plainText"> <param name="location">/result.jsp</param> <param name="charSet">UTF-8</param> </result> </action> |
这个就类使用servlet中的重定向,它与dispatcher(转发)相对应。它与dispatcher的区别和servlet一样,它会丢失request中的数据。
下面的例子和以前一样,只是换成了重定向,到页面会发现放在request范围内的username已经无法读取了。
public class LoginAction extends ActionSupport { …… public String execute() throws Exception { if ("test".equals(username) && "123456".equals(password)) { System.out.println("登陆成功............"); return SUCCESS; } else { System.out.println("登陆失败............"); return INPUT; } } } |
<action name="login" class="com.struts2.action.LoginAction"> <result name="success" type="redirect">reLogin</result> </action> |
public class ReLoginAction extends ActionSupport { …… public String execute() throws Exception { if ("test".equals(username) && "123456".equals(password)) { System.out.println("登陆成功............"); return SUCCESS; } else { System.out.println("登陆失败............"); return INPUT; } } } |
由于重定向的时候,是无法保存request里面的属性的,但是如果非得要传递属性的话,可以使用下面的方式,使用类似于URL重写的方式来实现。
<action name="login" class="com.csuinfosoft.struts2.action.LoginAction"> <!-- 使用重定向,并且传递参数 --> <result type="redirect"> <![CDATA[reLogin.action?userName=${userName}&password=${password}]]> </result> </action> |
如果出现传递中文的时候出现乱码的情况可以考虑添加下面的两种属性
<param name="charSet">UTF-8</param> |
<param name="encode">UTF-8</param> |
注意:
1. redirectAction中可以通过下面的方式来传递参数,但是这种方式在redirect中好像不行。
<param name="userName">${userName}</param> <param name="password">${password}</param> |
2. redirect要重定向到其它命名空间的action好像是不行的,要使用redirectAction。
它与redirect比较相似,一样会重新生成一个新的请求,但与redirect不同的是redirectAction的实现方式是不同的,它是以ActionMapperFactory提供的ActionMapper来重定向,前面一个Action的处理结果,请求参数都会丢失。
下面我从下面几种方式来讨论redirectAction:重定向当前命名空间下面的action,重定向到其它命名空间下面的action。
下面的配置中从LoginAction重定向到ReLoginAction中,且这两个action都是在同一个namespace中。
<action name="login" class="com.struts2.action.LoginAction"> <result name="success" type="redirectAction"> reLogin </result> </action> <action name="reLogin" class="com.struts2.action.ReLoginAction"> <result name="success">/result.jsp</result> <result name="input">/index.jsp</result> </action> |
由于重定向的话,request里面的参数会丢失,如果想把参数也带上的可以使用下面两种方式,通过类似于重写URL,和使用<param>。
<result name="success" type="redirectAction"> <![CDATA[reLogin.action?username=${username}&password=${password}]]> </result> |
<result name="success" type="redirectAction"> <param name="actionName"> reLogin </param> <param name="username">${username}</param> <param name="password">${password}</param> </result> |
这里从LoginAction跳往namespace为"/other"下面的OtherAction,且调用的是other方法。
<package name="default" namespace="/" extends="struts-default"> <action name="login" class="com.struts2.action.LoginAction"> <result name="success" type="redirectAction"> <param name="actionName">reLogin</param> <param name="namespace">/other</param> <param name="method">other</param> </result> </action> </package> <package name="other" namespace="/other" extends="struts-default"> <action name="other" class="com.struts2.action.OtherAction"> <result name="success">/result.jsp</result> <result name="input">/index.jsp</result> </action> </package> |
如果要也要传递参数的话可以使用3.8.1.3.1中使用的那两种方式来传递。
当不指定结果类型的时候默认就是dispatcher,也就是以前servlet里面的转发,只是这种转发只能是页面,或者模板,而不能是action。
3.8.1.4说了dispatcher只能转发页面,不能是action,那action用哪个呢?答案是chain。
如果使用dispatcher转发到另一个action的话会报错,这里也印证了dispatcher只能转发页面。
type=“chain”时 result标签的参数可以有下面3个:
actionName |
要转发的action的名称 |
namespace |
要转发的action的namespace |
method |
要转发action里面的method |
使用chain来实现action的转发方式一:
<action name="login" class="com.struts2.action.LoginAction"> <result name="success" type="chain">reLogin</result> </action> |
<action name="reLogin" class="com.struts2.action.ReLoginAction"> <result name="success">/result.jsp</result> </action> |
这种方式的话不指定namespace也不指定method。
使用chain来实现action的转发方式二:
<action name="login1" class="com.struts2.action.LoginAction"> <result name="success" type="chain"> <param name="actionName">other</param> <param name="namespace">/other</param> <param name="method">other</param> </result> </action> |
<package name="other" namespace="/other" extends="struts-default"> <action name="other" class="com.struts2.action.OtherAction"> <result name="success">/result.jsp</result> </action> </package> |
这里我们转发的是other namespace空间下面的OtherAction,且执行的是里面other()。
注意:不能写成/reLogin,会报错。
到目前为止<result>都是配置在<action>中,其实<result>还可以配置在<global-result>中,这个叫全局结果,它对所以的Action都有效。
使用<global-results>的原因是,假如有许多的action,他们都有一个共同的结果,如果你配在每一个action里面,就太多太麻烦了,与其这样,不如把它写在一个地方,这个地方就叫< global-result >,在packagename="user"下面大家可以共用的这样的结果集。
<global-results> <result name="success">/success.jsp</result> </global-results> |
如果Action里面的结果名与全局结果名一样,这时候Action里面的局部结构会覆盖全局的结果,也就是说只有在Action里面的的结果找不到的时候才会去全局结果里面去寻找。
注意:如果要在一个package里面使用另一个package的里面的全局结果的话,这个时候可以考虑使用extends继承那个package,如果直接写会报异常。
我们前面所写的Action都是在Action定义属性,然后提供getter,setter方法,这种方式在Struts2中叫“属性驱动”,其实还有一种方式叫“模型驱动”的方式也可以。
LoginAction1.java
package com.struts2.action;
import com.opensymphony.xwork2.ActionSupport; import com.opensymphony.xwork2.ModelDriven; import com.struts2.vo.UserVo;
public class LoginAction1 extends ActionSupport implements ModelDriven<UserVo> {
private static final long serialVersionUID = 632788809727830237L;
//一定要实例化 private UserVo user = new UserVo();
public UserVo getUser() { return user; }
public void setUser(UserVo user) { this.user = user; }
@Override public UserVo getModel() { return user; }
public String execute() throws Exception { if ("test".equals(user.getUsername()) && "123456".equals(user.getPassword())) { System.out.println("使用模型驱动登陆成功............"); return SUCCESS; } else { System.out.println("使用模型驱动登陆失败............"); return INPUT; } } } |
UserVo.java
package com.struts2.vo;
import java.io.Serializable;
public class UserVo implements Serializable { private String username; private String password; ……… } |
struts.xml
<action name="login4" class="com.struts2.action.LoginAction1"> <result name="success">/result.jsp</result> <result name="input">/index.jsp</result> </action> |
Struts2是可以进行异常处理的,可以在Action的execute()方法中添加try,catch语句,来捕捉异常,然后在返回特定的逻辑试图。这种方式比较繁琐,需要在execute()方法中添加try,catch语句块,这个的缺点还有异常的代码与代码耦合,不利于修改异常的处理。
Struts2的异常处理机制通过在struts.xml文件中配置<exception-mapping>元素完成的,配置该元素时,需要指定两个属性:
exception:此属性指定该异常映射所设置的异常类型。
result:此属性指定Action出现该异常时,系统转入result属性所指向的结果。
异常映射也分为两种:
局部异常映射:通过把﹤exception-mapping﹥元素作为﹤action﹥元素的子元素配置。
全局异常映射:通过把﹤exception-mapping﹥元素作为﹤global-exception-mappings﹥元素的子元素配置。
利用struts2的异常处理机制和拦截器机制可以很方便的实现异常处理功能,你不再需要在Action中捕获异常,并抛出相关的异常了,这些都交给拦截器来帮你做了。
输出异常信息:
使用Struts2的标签来输出异常信息:
﹤s:propertyvalue="exception.message"/﹥:输出异常对象本身。
﹤s:property value="exceptionStack"/﹥:输出异常堆栈信息。
下面分别测试空指针异常,与用户自定义异常
会发生异常的Action ExceptionAction.java,分别会出现空指针,和用户自定义异常。
package com.struts2.exception;
import com.opensymphony.xwork2.ActionSupport;
public class ExceptionAction extends ActionSupport { private static final long serialVersionUID = 4178837153895695645L; @Override public String execute() throws Exception { String str = null; // System.out.println(str.length()); if (str == null) { throw new MyException("用户自定义异常!!!"); } return SUCCESS; } } |
<global-results> <result name="success">/success.jsp</result> <result name="error">/exception.jsp</result> </global-results>
<global-exception-mappings> <exception-mapping result="error" exception="java.lang.NullPointerException"> </exception-mapping> <exception-mapping result="error" exception="com.struts2.exception.MyException"> </exception-mapping> </global-exception-mappings> |
异常处理页面
<%@ 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>异常处理页面</title> </head> <body> 异常信息 <s:property value="exception.message" /> </body> </html> |
通过把﹤exception-mapping﹥元素作为﹤action﹥元素的子元素配置。这种异常配置只在当前Action里面有用。
<action name="exceptionAction" class="com.struts2.exception.ExceptionAction"> <result name="success">/result.jsp</result> <exception-mapping result="error" exception="java.lang.ArithmeticException" /> </action> |
MVC框架着重实现V,C这两部分,Struts2也把重点放在V,C上面。V由Action来实现,而V的功能可以由标签来实现。
Struts2标签使用OGNL表达式作为基础,对集合与对象的访问功能非常的强大。
Struts2的标签可以分成如下三类:
1. UI标签,主要是生成HTML元素的标签。 |
2. 非UI标签,主要是用于数据访问,逻辑控制的标签。 |
3. AJAX标签,用于AJAX支持的标签。 |
对于UI标签的,可以分成如下两类:
1. 表单标签:用于生成HTML的form表单。 |
2. 非表单标签:比如用于生成页面的树。 |
对于非UI标签可以分成如下两类:
1. 流程控制标签:包含实现分支,循环等。 |
2. 数据访问标签:主要是用于输出值栈里面的值,以及国际化。 |
Struts2的标签文件
struts2-core-2.2.1.jar\META-INF\ struts-tags.tld |
如果要在JSP中使用Struts2的标签使用下面的代码来导入Struts2标签。
<%@ taglib prefix=“s” uri=“/struts-tags” %> |
注意:struts2标签中不能使用EL表达式。
控制标签用于对输出流程的控制,例如:分支,循环,也可以实现对集合的合并与排序,控制标签有如下九个:
1. if:用于控制选择输出的标签。
2. elseif/elesIf:与if标签一起使用,用于控制选择输出的标签。
3. else:与if标签一起使用,用于控制选择输出的标签。
4. append:用于将多个集合合并成一个集合。
5. generator:一个字符解析器,将字符串解析成集合。
6. iterator:用于迭代集合。
7. merge:用于将多个集合合并成一个集合。
8. sort:用于对集合进行排序。
9. subset:用于截取集合的部分元素,生成子集合。
if/elseif/else这三个标签用于进行分支流程控制,它们都根据一个boolean值来决定是否显执行。 这三个标签可以组合使用,只有if可以单独使用,elseif/else这两个标签要与if一起使用。
<pre> age =<s:property value="age"/><br /> #age =<s:property value="#age"/><br /> #parameters.age[0]=<s:property value="#parameters.age[0]"/><br /> %{age} =<s:property value="%{ age }"/><br /> </pre> <s:if test="age < 18"> 小屁孩<br /> </s:if> <s:elseif test="age >= 18 && age <=50"> 成年人<br /> </s:elseif> <s:else> 老喽!<br /> </s:else> |
在value stack里面的值不能用#来取值。
Iterator标签用于集合进行迭代输出,集合包括Array,List, Set, Map,它的使用与JSTL中的<forEach>标签使用基本上差不多。
属性名 |
类型 |
描叙 |
var |
String |
迭代的参数名称 |
value |
数组或者集合类型 |
要迭代的集合 |
status |
String |
迭代的状态,可以访问迭代自己的信息 |
begin |
Int |
集合开始迭代的位置,位置从零开始 |
end |
Int |
集合结束迭代的位置,位置从零开始 |
step |
Int |
迭代的步长 |
<s:iterator value="{‘hibernate‘,‘struts2‘,‘spring‘}" status="st" var="iter"> <s:property value="#st.index" />.<s:property value="iter" /> <br /> <s:property value="#st.count" />.<s:property value="iter" /> <br /> </s:iterator> |
迭代从数据库里面查询出来呃数据,然后在页面显示。
<s:iterator var="book" value="books" status="st" step="1" begin="0" end="19"> <s:if test="#st.first"> <div>第一本</div> </s:if> <s:if test="#st.odd"> <div class="odd"> index:<s:property value="#st.index" /> count:<s:property value="#st.count" /> 书名:<s:property value="#book.bookName" /> </div> </s:if> <s:if test="#st.even"> <div class="even"> index:<s:property value="#st.index" /> count:<s:property value="#st.count" /> 书名:<s:property value="#book.bookName" /> </div> </s:if> <s:if test="#st.last"> <div>最后一本</div> </s:if> </s:iterator> |
<s:iterator var="map" value="#{1:‘星期一‘, 2:‘星期二‘, 3:‘星期三‘, 4:‘星期四‘, ‘5‘:‘星期五‘, 6:‘星期六‘, 7:‘星期天‘ }"> <div> <s:property value="#map.key" />=<s:property value="#map.value" /> <br /> </div> </s:iterator> |
数据标签用于提供各种数据访问相关的功能。
1. action:用于在JSP中直接调用action。
2. bean:该标签用于创建一个javabean的实例。
3. date:用于对日期的格式化输出。
4. debug:在页面生成一个调试连接,用于查看value stack和stack context中的内容。
5. i18n:用于指定国际化配置文件的baseName。
6. include:用于在页面中包含其它页面。
7. param:用于设置参数,与url,bean一起使用。
8. push:将某个值放入value stack的栈顶。
9. set:用于设置新的变量,然后将这个变量放入相关的范围之内。
10.text:用于输出国际化信息。
11.url:用于输出一个URL。
12.property:用于输出某个值。
该标签用于输出指定的值,指定的值是指value属性所指定的值,value属性支持OGNL表达式,它有如下属性:
value: 指定要输出的属性值。 default:可选,如果value所指定的值不存在,就显示default属性的值。 escapeHtml:指定是否转义html代码。 escapeCvs:指定是否转义cvs。 escapeJavascript:指定是否转义javascript。 escapeXml:指定是否转义xml。 |
<s:property value="‘<hr />‘" escapeHtml="false" /> |
<s:set name="test" value="‘value‘"></s:set> <s:property value="test" default="null..." /> |
注意:这两个实例里面value属性如果是字符串都要加单引号,不然当做OGNL表达式。
用于设置新的变量,然后将这个变量放入相关的范围之内。
var |
生成变量时候的变量名。 |
value |
设置变量的值,注意:当值是字符串的时候加单引号,不然当做OGNL表达式来取。 |
scope |
变量的范围:page, request, scope, application, aciton,默认的时候放在request,和aciton中。 |
<s:set name="scope" value="‘default scope‘"></s:set> #request.scope= <s:property value="#request.scope" /> <br /> #scope= <s:property value="#scope" /> <hr /> |
<s:set name="sessionScope" value="‘session scope‘" scope="session" /> <%=session.getAttribute("sessionScope")%> |
该标签用于创建一个javabean的实例。
<s:bean name="com.struts2.vo.UserVo" var="u"> <s:param name="username" value="‘test‘" /> <s:param name="password" value="‘password‘" /> </s:bean> #u.username=<s:property value="#u.username" /><br /> #u.password=<s:property value="#u.password" /><br /> |
包含页面,它的include属性,值是string,如果这个属性的值是在set里面,这个时候如果想在include中引用的话,要强制把String转成OGNL表达式,通过这种写法:%{#param}
<s:include value="/included.html" /> |
<s:set var="include" value="‘/included.html‘" /> <s:include value="%{#include}" /> |
$ |
在国际化配置文件,struts.xml配置文件。 |
# |
取ActionContext中的值 |
% |
将文本信息解析成OGNL,如果是OGNL则不起作用。 |
<s:url var="url" action="login" namespace="/"> <!-- 使用用户名,与密码 --> <s:param name="username">jiangyang</s:param> <s:param name="password">123456</s:param> </s:url> |
<!-- 当引用超链接的时候使用 --> <s:a href="%{#url}">LOGIN</s:a> |
用的不多,不好用,改样式,与javascript的整合麻烦。
复选框标签
name |
复选框的名称 |
list |
复选框要显示的是数据,可以是list,也可以是map |
value |
表示复选框默认选择的数据。 |
listKey |
|
listValue |
|
|
|
后台处理类用于准备数据。
public class DataAction extends ActionSupport { private List<String> list; private List<String> value; private Map<Integer, String> map; private List<Integer> mapValue;
@Override public String execute() throws Exception {
list = new ArrayList<String>(); value = new ArrayList<String>(); list.add("语文"); list.add("英语"); list.add("数学"); list.add("音乐");
value.add("语文"); value.add("音乐");
map = new HashMap<Integer, String>(); map.put(1, "语文"); map.put(2, "英语"); map.put(3, "数学"); map.put(4, "音乐");
mapValue = new ArrayList<Integer>(); mapValue.add(1); mapValue.add(3); return SUCCESS; } } |
前天页面展示。
<s:checkboxlist name="list" list="{‘java‘, ‘c‘, ‘c++‘, ‘.net‘}" value="{‘java‘, ‘c‘}"></s:checkboxlist> <hr />
<s:checkboxlist name="list1" list="list" value="value"></s:checkboxlist>
<hr /> <s:checkboxlist name="map" list="#{1:‘java‘, 2:‘c‘, 3:‘c++‘, 4:‘.net‘}" listKey="key" listValue="value" value="{1, 4}"></s:checkboxlist> <hr />
<s:checkboxlist name="map1" list="map" listKey="key" listValue="value" value="mapValue"></s:checkboxlist> |
单选按钮
private List<String> sex; private String defaultSex; sex = new ArrayList<String>(); sex.add("男"); sex.add("女"); sex.add("未知"); defaultSex = "男"; |
<s:radio name="sex1" list="{‘男‘, ‘女‘, ‘未知‘}" value="‘女‘"></s:radio> <hr /> <s:radio name="sex2" list="#{0:‘男‘, 1:‘女‘, -1:‘未知‘}" value="-1" listKey="key" listValue="value"></s:radio> <hr /> <s:radio name="sex3" list="sex" value="defaultSex"></s:radio> <hr /> |
下拉框
private List<String> provice; private String defaultProvice; provice = new ArrayList<String>(); provice.add("湖南"); provice.add("湖北"); provice.add("廣東"); defaultProvice = "湖北"; |
<s:select name="provice1" list="{‘湖南‘, ‘湖北‘, ‘广东‘}" value="‘广东‘"></s:select> <hr /> <s:select name="provice2" list="#{‘hn‘:‘湖南‘, ‘hb‘:‘湖北‘, ‘gd‘:‘广东‘}" value="‘hb‘" listKey="key" listValue="value"></s:select> <hr /> <s:select name="provice3" list="provice" value="defaultProvice"></s:select> <hr /> |
<s:form> <s:textfield name="userName" label="用户名"></s:textfield> <s:password name="password" label="密码"></s:password> <s:file name="file" label="头像"></s:file> <s:textarea name="desc" rows="3" cols="20" label="简介"></s:textarea> <s:submit value="提交"></s:submit> <s:reset value="重设"></s:reset> </s:form> |
Form标签获取action里面的值,一般用于修改。
<s:form action="updateById" method="get"> <s:hidden name="s.id" value="%{ s.id }"></s:hidden> <s:textfield name="s.name" value="%{ s.name }" label="姓名"></s:textfield> <s:select name="s.sex" list="{‘男‘, ‘女‘}" value="s.sex" label="性别"></s:select> <s:textfield name="s.age" value="%{ s.age }" label="年龄"></s:textfield> <s:textfield name="s.qq" value="%{ s.qq }" label="QQ"></s:textfield> <s:submit value="修改"></s:submit> </s:form> |
注意:上面的单选按钮,或者多项按钮,下拉框时候设置它的默认值,时候使用的情况不同。
由于STRUTS2会自动把日期字符串转成Date型,但是有种情况下需要注意,在页面使用<s:textfield>时候来显示action里面的Date内型所对应的字符串数据,会有点麻烦。可以这么处理,有点麻烦。
步骤是这样的,先使用日期标签<s:date>来把action里面的date转换成
然后在<s:textfield>中引用刚刚的日期标签。
<s:date id="gg" name="student.graduation" format="yyyy-MM-dd" /> |
<s:textfield name="student.graduation" key="graduation" value="%{ #request.gg }"onclick="calendar.show(this)"></s:textfield> |
Struts2使用OGNL(ObjectGraph Navigation Language)表达式语言,用来获取和设置Java对象的属性,它旨在提供一个更高的更抽象的层次来对Java对象图进行导航。
大大的增强了Struts2的数据访问能力,它是在XWork原有的基础上,增加了对ValueStack的支持。
OGNL类似于我们以前学的EL表达式,它可以使得我们在页面通过标签就可以访问Action的数据。它不是一个真正的编程语言,只是一种数据访问语言。
OGNL表达式的基本单位是"导航链",一般导航链由如下几个部分组成:
1. 属性名称(property)
2. 方法调用(method invoke)
3. 数组元素
OGNL表达式的计算都是围绕OGNL上下文来进行的,OGNL上下文实际上就是一个Map对象,由ognl.OgnlContext类(实现了java.util.Map接口)来表示。OGNL上下文可以包含一个或多个JavaBean对象,在这些对象中有一个是特殊的,这个对象就是上下文的根(root)对象。如果在写表达式的时候,没有指定使用上下文中的哪一个对象,那么”根对象”将被假定为表达式所依据的对象。
在Ognl上下文中,只能有一个根对象,如果你访问根对象,那么在写表达式的时候,直接写对象属性(property)就可以了;否则,你需要使用”#key”前缀,例如表达式:#p2.name。
OGNL三要素
表达式:(Expression)表达式是整个OGNL的核心,所有的OGNL操作都是针对表达式的解析后进行的。表达式会规定此次OGNL操作到底要干什么。 |
根对象:(Root Object)“根对象”可以理解为OGNL的操作对象。在表达式规定了“干什么”以后,你还需要指定到底“对谁干”。 |
上下文:(context)简单来说上下文就是环境,表达式求值的环境!有了表达式和根对象,我们实际上已经可以使用OGNL的基本功能。 |
例如,根据表达式对根对象进行取值或者设值工作。
不过实际上,在OGNL的内部,所有的操作都会在一个特定的环境中运行,这个环境就是OGNL的上下文环境(Context)。说得再明白一些,就是这个上下文环境(Context),将规定OGNL的操作“在哪里干”。
OGNL的上下文环境是一个Map结构,称之为OgnlContext。上面我们提到的根对象(Root Object),事实上也会被加入到上下文环境中去(这里要注意下Context中并没有root对象),并且这将作为一个特殊的变量进行处理,具体就表现为针对根对象(Root Object)的存取操作的表达式是不需要增加#符号进行区分的。
OgnlContext不仅提供了OGNL的运行环境。在这其中,我们还能设置一些自定义的parameter到Context中,以便我们在进行OGNL操作的时候能够方便的使用这些parameter。不过正如我们上面反复强调的,我们在访问这些parameter时,需要使用#作为前缀才能进行。
public class OGNL1 { public static void main(String[] args) { /* 创建一个Person对象 */ Person person = new Person(); person.setName("zhangsan"); try { /* 从person对象中获取name属性的值 */ Object value = Ognl.getValue("name", person); System.out.println(value); } catch (OgnlException e) { e.printStackTrace(); } } } |
public class Person { private String name; getter(),setter()…… } |
对于使用上下文的OGNL,若不指定从哪一个对象中查找"name"属性,则OGNL直接从根对象(root)查找,若指定查找对象(使用‘#‘号指定,如#person1),则从指定的对象中查找,若指定对象不在上下文中则会抛出异常,换句话说就是说#person1.name形式指定查找对象则必须要保证指定对象在上下文环境中。
public class OGNL2 { public static void main(String[] args) { /* 创建一个上下文Context对象,它是用保存多个对象一个环境 对象 */ Map<String, Object> context = new HashMap<String, Object>(); Person person1 = new Person(); person1.setName("zhangsan"); Person person2 = new Person(); person2.setName("lisi"); Person person3 = new Person(); person3.setName("wangwu");
/* person4不放入到上下文环境中 */ Person person4 = new Person(); person4.setName("zhaoliu");
/* 将person1、person2、person3添加到环境中(上下文中) */ context.put("person1", person1); context.put("person2", person2); context.put("person3", person3); // context.put("person4", person4);
try { /* 获取根对象的"name"属性值 */ Object value = Ognl.getValue("name", context, person2); System.out.println("ognl expression \"name\" evaluation is : " + value);
/* 获取根对象的"name"属性值 */ Object value2 = Ognl.getValue("#person2.name", context, person2); System.out.println("ognl expression \"#person2.name\" evaluation is : " + value2);
/* 获取person1对象的"name"属性值 */ Object value3 = Ognl.getValue("#person1.name", context, person2); System.out.println("ognl expression \"#person1.name\" evaluation is : " + value3);
/* 将person4指定为root对象,获取person4对象的"name"属性,注意person4对象不在上下文中 */ Object value4 = Ognl.getValue("name", context, person4); System.out.println("ognl expression \"name\" evaluation is : " + value4);
/* 将person4指定为root对象,获取person4对象的"name"属性,注意person4对象不在上下文中 */ Object value5 = Ognl.getValue("#person4.name", context, person4); System.out.println("ognl expression \"person4.name\" evaluation is : " + value5); } catch (OgnlException e) { e.printStackTrace(); } } } |
输出:由于注意person4对象不在上下文中导致报错
ognl expression "name" evaluation is : lisi ognl expression "#person2.name" evaluation is : lisi ognl expression "#person1.name" evaluation is : zhangsan ognl expression "name" evaluation is : zhaoliu ognl.OgnlException: source is null for getProperty(null, "name") |
使用OGNL调用方法也十分简单,对于成员方法调用,只需要给出方法的名称+(),若有参数,直接写在括号内,与一般调用Java方法一致。对于静态方法的调用,需要使用如下格式:@ClassName@method,对于静态变量需要使用如下格式:@ClassName@field。
public class OGNL3 {
public static void main(String[] args) {
/* OGNL提供的一个上下文类,它实现了Map接口 */ OgnlContext context = new OgnlContext();
Person p1 = new Person(); p1.setName("zhangsan");
Person p2 = new Person(); p2.setName("lisi");
Person p3 = new Person(); p3.setName("wangwu");
context.put("p1", p1); context.put("p2", p2); context.put("p3", p3);
context.setRoot(p1);
try { /* 调用 成员方法 */ Object value = Ognl.getValue("name.length()", context, context.getRoot()); System.out.println("p1 name length is :" + value);
Object upperCase = Ognl.getValue("#p2.name.toUpperCase()", context, context.getRoot()); System.out.println("p2 name upperCase is :" + upperCase);
Object invokeWithArgs = Ognl.getValue("name.charAt(5)", context, context.getRoot()); System.out.println("p1 name.charAt(5) is :" + invokeWithArgs);
/* 调用静态方法 */ Object min = Ognl.getValue("@java.lang.Math@min(4,10)", context, context.getRoot()); System.out.println("min(4,10) is :" + min);
/* 调用静态变量 */ Object e = Ognl.getValue("@@E", context, context.getRoot()); System.out.println("E is :" + e); } catch (OgnlException e) { e.printStackTrace(); } } } |
輸出結果
p1 name length is :8 p2 name upperCase is :LISI p1 name.charAt(5) is :s min(4,10) is :4 E is :2.718281828459045 |
OGNL不仅可以操作集合对象,还可以创建集合对象,对集合操作与对属性的操作没什么不同,需要注意的是OGNL认为List与Array是一样的。使用OGNL创建List集合时使用{},创建Map对象时使用#{}。
public class OGNL4 {
public static void main(String[] args) throws Exception { OgnlContext context = new OgnlContext();
Classroom classroom = new Classroom(); classroom.getStudents().add("zhangsan"); classroom.getStudents().add("lisi"); classroom.getStudents().add("wangwu"); classroom.getStudents().add("zhaoliu"); classroom.getStudents().add("qianqi");
Student student = new Student(); student.getContactWays().put("homeNumber", "110"); student.getContactWays().put("companyNumber", "119"); student.getContactWays().put("mobilePhone", "112");
context.put("classroom", classroom); context.put("student", student); context.setRoot(classroom);
/* 获得classroom的students集合 */ Object collection = Ognl.getValue("students", context, context.getRoot()); System.out.println("students collection is :" + collection);
/* 获得classroom的students集合 */ Object firstStudent = Ognl.getValue("students[0]", context, context.getRoot()); System.out.println("first student is : " + firstStudent);
/* 调用集合的方法 */ Object size = Ognl.getValue("students.size()", context, context.getRoot()); System.out.println("students collection size is :" + size);
System.out.println("--------------------------飘逸的分割线--------------------------");
Object mapCollection = Ognl.getValue("#student.contactWays", context, context.getRoot()); System.out.println("mapCollection is :" + mapCollection);
Object firstElement = Ognl.getValue("#student.contactWays[‘homeNumber‘]", context, context.getRoot()); System.out.println("the first element of contactWays is :" + firstElement);
System.out.println("--------------------------飘逸的分割线--------------------------");
/* 创建集合 */ Object createCollection = Ognl.getValue("{‘aa‘,‘bb‘,‘cc‘,‘dd‘}", context, context.getRoot()); System.out.println(createCollection);
/* 创建Map集合 */ Object createMapCollection = Ognl.getValue("#{‘key1‘:‘value1‘,‘key2‘:‘value2‘}", context, context.getRoot()); System.out.println(createMapCollection); } } |
public class Classroom {
private List<String> students = new ArrayList<String>();
public List<String> getStudents() { return students; }
public void setStudents(List<String> students) { this.students = students; } } |
public class Student {
private Map<String, Object> contactWays = new HashMap<String, Object>();
public Map<String, Object> getContactWays() { return contactWays; }
public void setContactWays(Map<String, Object> contactWays) { this.contactWays = contactWays; } } |
輸出:
students collection is :[zhangsan, lisi, wangwu, zhaoliu, qianqi] first student is : zhangsan students collection size is :5 --------------------------飘逸的分割线-------------------------- mapCollection is :{homeNumber=110, mobilePhone=112, companyNumber=119} the first element of contactWays is :110 --------------------------飘逸的分割线-------------------------- [aa, bb, cc, dd] {key1=value1, key2=value2} |
OGNL支持类似数据库中的投影(projection)和选择(selection)。
投影就是选出集合中每个元素的相同属性组成新的集合,类似于关系数据库的字段操作。投影操作语法为collection.{XXX},其中XXX 是这个集合中每个元素的公共属性。例如:group.userList.{username}将获得某个group中的所有user的name的列表。
选择就是过滤满足selection 条件的集合元素,类似于关系数据库的纪录操作。选择操作的语法为:collection.{X YYY},其中X 是一个选择操作符,后面则是选择用的逻辑表达式。而选择操作符有三种:
? |
选择满足条件的所有元素 |
^ |
选择满足条件的第一个元素 |
$ |
选择满足条件的最后一个元素 |
OGNL可以对集合进行过滤与投影操作,过滤的语法为collection.{? expression},其中使用"#this"表示集合当前对象(可以与for-each循环比较)。
投影的语法为collection.{expression}。投影和过滤可以看做是数据库中对表取列和取行的操作。
public class OGNL5 {
public static void main(String[] args) throws OgnlException { OgnlContext context = new OgnlContext();
Humen humen = new Humen(); humen.setName("qiuyi"); humen.setSex("n"); humen.setAge(22); humen.getFriends().add(new Humen("zhangsan", "n", 22)); humen.getFriends().add(new Humen("lisi", "f", 21)); humen.getFriends().add(new Humen("wangwu", "n", 23)); humen.getFriends().add(new Humen("zhaoliu", "n", 22)); humen.getFriends().add(new Humen("qianqi", "n", 22)); humen.getFriends().add(new Humen("sunba", "f", 20)); humen.getFriends().add(new Humen("yangqiu", "f", 25));
context.put("humen", humen); context.setRoot(humen);
/* OGNL过滤集合的语法为:collection.{? expression} */ Object filterCollection = Ognl.getValue("friends.{? #this.name.length() > 7}", context, context.getRoot()); System.out.println("filterCollection is :" + filterCollection);
System.out.println("--------------------------飘逸的分割线--------------------------");
/* OGNL投影集合的语法为:collection.{expression} */ Object projectionCollection = Ognl.getValue("friends.{name}", context, context.getRoot()); System.out.println("projectionCollection is :" + projectionCollection); } } |
public class Humen {
private String name; private String sex; private int age; private List<Humen> friends = new ArrayList<Humen>();
public Humen() { }
public Humen(String name, String sex, int age) { this.name = name; this.sex = sex; this.age = age; } ……… @Override public String toString() { return "Humen [name=" + name + ", sex=" + sex + ", age=" + age + "]"; } } |
输出:
filterCollection is :[Humen [name=zhangsan, sex=n, age=22]] --------------------------飘逸的分割线-------------------------- projectionCollection is :[zhangsan, lisi, wangwu, zhaoliu, qianqi, sunba, yangqiu] |
在Struts2(XWork)中,不仅把Action作为OGNL操作的根对象,作为对OGNL的扩展,它还引入了一个ValueStack的概念。
ValueStack是一个堆栈结构,堆栈中的每个元素对于OGNL操作来说,都被看作是根对象。
由于ValueStack是一个堆栈结构,所以其中的元素都是有序的,对于某个OGNL表达式来说,OGNL将自堆栈顶部开始查找,并返回第一个符合条件的对象元素。
在使用值栈时,我们无须关心目标对象的作用域。如果要使用名为“name”的属性,直接从值栈中进行查询就可以了。值栈中的每一个元素,都会按照排列顺序依次检查是否拥有该属性。如果有的话,那么就返回对应的值,查询结束。如果没有的话,那么下一个元素就会被访问,直到到达值栈的末尾。
那么对于值栈中的对象该如何访问呢?Struts2提供了一个特殊的OGNLPropertyAccessor,它可以自动查找栈内的所有对象(从栈顶到栈底),直接找到一个具有你所查找的属性的对象。也就是说,对于值栈中的任何对象都可以直接访问,而不需要使用“#”。
Struts2框架总是把Action实例放在栈顶。因为Action在值栈中,而值栈又是OGNL中的根,所以引用Action的属性可以省略“#”标记,这也是为什么我们在结果页面中可以直接访问Action的属性的原因。
ActionContext是Struts2中OGNL的上下文环境。它维护着一个Map的结构,下面是这个结构的图示:
其中,ValueStack是这个上下文环境中的根对象,而除了这个根对象以外,Struts2还在这个上下文环境中放了许多额外的变量,而这些变量多数都是被XWork封装过的Servlet对象,例如request,session,servletContext(application)等,这些对象都被封装成Map对象,随着ActionContext作用于整个Action执行的生命周期中。
对于保存在ActionContext中的对象,访问时需要使用“#”标记。这些命名对象都是Map类型。
parameters |
用于访问请求参数。如:#parameters[‘id‘]或#parameters.id,相当于调用了HttpServletRequest对象的getParameter()方法。 |
request |
用于访问请求属性。如:#request[‘user‘]或#request.user,相当于调用了HttpServletRequest对象的getAttribute()方法。 |
session |
用于访问session属性。如:#session[‘user‘]或#session.user,相当于调用了HttpSession对象的getAttribute()方法。 |
application |
用于访问application属性。如:#application[‘user‘]或#application.user,相当于调用了ServletContext的getAttribute()方法。 |
attr |
如果PageContext可用,则访问PageContext,否则依次搜索request、session和application对象。 |
测试OGNL的使用,这里会牵涉到一些STRUTS2的标签。
这里我们在request,session里面分别存放了两个属性,然后使用<s:property>标签来测试ognl表达式。
<%@ page language="java" pageEncoding="UTF-8"%> <%@ taglib prefix="s" uri="/struts-tags"%> <% String path = request.getContextPath(); String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; request.setAttribute("user", "this is a user."); request.getSession().setAttribute("test", "test session scope."); %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <base href="<%=basePath%>"> <title>test ognl</title> </head> <body> <s:property value="#request.user" /> <br /> <s:property value="#session.test" /> </body> </html> |
由于Action已经放入ValueStack,且ValueStack是一个根元素,所以获得action的属性不需要使用#。
下面的代码直接使用属性名就可以使用了,这个时候其实我们也可以EL表达式来获取action里面属性。
userName1=${ requestScope.userName } |
userName2=${ userName } |
<% request.getAttribute(“userName”) %> |
userName3= <s:property value="#request[‘userName‘]" /> |
userName4= <s:property value="#parameters[‘userName‘]" /> |
userName6=<%=request.getParameter("userName")%> |
<!-- 这种方式不可以 --> ${ #request[‘userName‘] } <!-- action里面的属性是可以直接获取,而不用加# --> |
userName5= <s:property value="userName" /> |
如果要调用静态方法的话得配置下面的配置:
<constant name="struts.devMode" value="true" /> |
下面测试了,获取字符串长度,获取字符,转成大写,或者小写,取最大值,以及获取静态常量
userName.length()=<s:property value="userName.length()" /> <br /> userName.charAt(1)=<s:property value="userName.charAt(1)" /> <br /> userName.toUpperCase()=<s:property value="userName.toUpperCase()" /> <br /> userName.toLowerCase()=<s:property value="userName.toLowerCase()" /> <br /> min(4,10)=<s:property value="@java.lang.Math@min(4,10)" /> <br /> min(4,10)=<s:property value="@@min(4,10)" /> <br /> E=<s:property value="@@E" /> |
这里需要把hibernate整合进来,这里不在累赘。
BookDao从数据查询数据。
public class BookDao { @SuppressWarnings("unchecked") public List<Book> findBook() { List<Book> books; Session session = null; Transaction t = null; try { session = HibernateSessionFactory.getSession(); t = session.beginTransaction(); String hql = "from Book as b"; books = session.createQuery(hql).setFirstResult(0).setMaxResults(20).list(); t.commit();
return books; } catch (Exception e) { e.printStackTrace(); } return null; } } |
BookAction为action
public class BookAction extends ActionSupport {
private static final long serialVersionUID = 1L;
private List<Book> books;
public List<Book> getBooks() { return books; }
public void setBooks(List<Book> books) { this.books = books; }
@Override public String execute() throws Exception { BookDao dao = new BookDao(); books = dao.findBook();
return SUCCESS; } } |
book.jsp
<s:iterator value="books" var="book"> <s:property value="#book.bookName" /> <s:property value="#book.bookPrice" /> <br /> </s:iterator> |
只显示价格大于20的书本信息 <s:iterator value="books.{?#this.bookPrice>20}"> <s:property value="bookName" /> <s:property value="bookPrice" /> <br /> </s:iterator> |
程序的国际化是商业系统的一个基本要求,今天的软件系统不再是一个简单的单机程序,往往都是一个开发系统。需要面对来至于时间各地的浏览者,如果我们针对每一个国家都开发一套页面的话,这回导致维护的成本很高。所以必须有一个国际化的实现来解决这个问题。
Strut2的国际化是建立在java的国际化之上的,通过提供不同“国家/语言”信息资源,然后通过java.util.ResourceBundle加载指定Locale对应的资源文件,在通过资源文件指定的KEY来获取对应的信息,这样java国际化完全一样,只是Strut2框架对java国际化做了进一步的封装,从而简化了国际化的开发。
package com.struts2.i18n; import java.util.Locale; public class TestLocale {
public static void main(String[] args) { Locale[] locales = Locale.getAvailableLocales(); for (Locale l : locales) { System.out.println(l.getLanguage() + ", " + l.getCountry()); } } } |
TestI18n.java
package com.struts2.i18n; import java.util.Locale; import java.util.ResourceBundle; public class TestI18n {
public static void main(String[] args) { ResourceBundle rbChina = ResourceBundle.getBundle("i18n", Locale.CHINA); String resultChina = rbChina.getString("msg.welcome"); String result1 = MessageFormat.format(resultChina, new Object[] { "测试" }); System.out.println(result1);
ResourceBundle rbUs = ResourceBundle.getBundle("i18n", Locale.US); String resultUs = rbUs.getString("msg.welcome"); String result2= MessageFormat.format(resultUs, new Object[] { "test" }); System.out.println(result2);
} } |
i18n_en_US.properties
msg.welcome.=hello {0} |
i18n_zh_CN.properties
welcome.msg=\u4F60\u597D {0} |
Strut2提供多种加载国际化资源文件的方式,其中最简单,最常用的是通过配置文件来实现,这里以struts.xml为例。
<constant name="struts.custom.i18n.resources" value="i18n" /> |
这样配置后我们的国际化资源文件就必须以”i18n”开头。
注意:还action级别的国际化,以及包级别的国际化,这种太少见不讲解,只讲全局的。
LoginAction_en_US.properties |
LoginAction_zh_CN.properties |
package_zh_CN.properties |
Properties文件里面是不可以使用中文的得转换才行,转发可以通过如下方式:
1. 使用native2ascii命令
2. 使用myeclipse工具
可以通过下面的方式在,action里面获取到国际化信息,且还可以带占位符。
this.getText("test", new String[] { "STRUTS2" }) |
Key就是国际化的键值。
<s:textfield name="test" key="username"></s:textfield> |
使用国际化标签<s:text>做国际化,name属性的值,就是国际化的键值。
<s:text name="reg"></s:text> |
i18n_en_US.properties
test=\u6D4B\u8BD5{0}\uFF01 username=\u7528\u6237\u540D reg=\u6CE8\u518C |
i18n_zh_CN.properties
test=test{0}\! username=Username reg=Register |
这两个全局国际化配置文件一定要放在src下。
i18n_en_US.properties
username=username password=password login=login welcome=welcome {0} |
i18n_zh_CN.properties
username=\u8D26\u53F7 password=\u5BC6\u7801 login=\u767B\u9646 welcome=\u6B22\u8FCE {0} |
国际化这里会接触到几个struts2的标签,<s:textfield>,<s:submit>,<s:text>。
index.jsp
<s:form action="login.action" method="get"> <table> <tr> <s:textfield name="username" key="username" value="test" /> </tr> <tr> <s:textfield name="password" key="password" value="123456" /> </tr> <tr> <s:submit key="login" /> </tr> </table> </s:form> |
result.jsp
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <base href="<%=basePath%>"> <title></title> </head> <body> <s:text name="welcome"> <s:param>${username}</s:param> </s:text> </body> </html> |
通过修改浏览器的语言来测试国际化。下面的是火狐的语言设置。
struts2的自定义类型转换机制为复杂类型的输入输出处理提供了便捷.struts2已经为我们提供了几乎所有的primitive类型以及常用类型(如Date)的类型转换器,我们也可以为我们自定义类添加自定义类型转化器。
struts2为我们提供了一个类型转化器的入口: DefaultTypeConverter,或继承StrutsTypeConverter,由于StrutsTypeConverter提供了更好的封装,所以建议大家在写转换器时通常采用继承StrutsTypeConverter方式来实现。
DefaultTypeConverter在两个包里面都要,这里推荐使用第二个。
ognl.DefaultTypeConverter; |
com.opensymphony.xwork2.conversion.impl.DefaultTypeConverter; |
DefaultTypeConverter是默认的类型转换类,它有两个方法只介绍第一个,第一个是第二个的简化。
Object convertValue(Map context, Object value, Class toType) |
Object convertValue(Map context, Object target, Member member, String propertyName, Object value, Class toType) |
考虑下面这一种情况,输入的是一个坐标,这种情况下默认的转换就不行了,就需要自己进行转换了。如果把坐标中的X,Y作为两个字段来用的话,我们以前的做法是可用的。
PointAction中就一个字段PointVo,而PointVo中有两个字段分别代表X坐标,Y坐标,也就是用这个Action来接收页面的坐标,然后进行处理。
package com.struts2.action; import com.opensymphony.xwork2.ActionSupport; import com.struts2.vo.PointVo; public class PointAction extends ActionSupport { private static final long serialVersionUID = -2400620549484987525L; private PointVo point; public PointVo getPoint() { return point; } public void setPoint(PointVo point) { this.point = point; } @Override public String execute() throws Exception { System.out.print(">>>>>>>>>" + this.getPoint().toString()); return SUCCESS; } } |
这个类就两个字段x坐标,y坐标。
package com.struts2.vo; import java.io.Serializable; /** * 坐标类 * @author sinoyang */ public class PointVo implements Serializable { private static final long serialVersionUID = -6501123839564353734L; private int x; private int y; ……… public String toString() { return "x=" + this.getX() + ", y=" + this.getY(); } } |
这个类是转换的核心,通过重写convertValue方法来达到转换的目的。
context |
一个上下文对象。 |
value |
要转换的值,这个其实是一个数组,接收客户端提交过来的表单数据,当重名的时候接收的就是一个数组。 |
toType |
要转换成的类型。 |
这个类的编写在代码中有注释作为解释。
注意:从PointVo到String的转换可以不讲。
package com.struts2.converter; import java.util.Map; import ognl.DefaultTypeConverter; import com.struts2.vo.PointVo;
/** * 坐标转换类 * @author sinoyang */ @SuppressWarnings("unchecked") public class PointConverter extends DefaultTypeConverter { /** * @param context 上下文对象 * @param value 要转换的值,是一个数组 * @param toType 转换后的对象 */ @Override public Object convertValue(Map context, Object value, Class toType) { // 从客户端到string到PointVo的转换 if (PointVo.class == toType) { PointVo vo = new PointVo(); // 转换成字符串数组,可能有多個同名的,所以是數組 String str[] = (String[]) value; if (str != null && str.length > 0) { // 取数组第一个值,按照逗号分隔 String params[] = str[0].split(","); if (params != null && params.length > 1) { int x = Integer.parseInt(params[0].trim()); int y = Integer.parseInt(params[1].trim()); vo.setX(x); vo.setY(y); } } return vo; } // 从PointVo到String的转换 if (String.class == toType) { PointVo vo = (PointVo) value; return "[x=" + vo.getX() + ", y=" + vo.getY() + "]"; } return null; } |
自己写了转换类了,要怎么用呢?我们要告诉Struts2我们某个Action里面有自定义转换类,这时候需要一个配置文件告诉它,这个配置文件有一下几个要求。
1. 名字必须是“Action name” + “-conversion.properties”。
2. 这个配置文件要放在Action同一个包下。
3. 在这个配置文件中配置要转换的字段所对应的自定义转换类。
PointAction-conversion.properties
point=com.struts2.converter.PointConverter |
point.jsp结果页,就只是输出point信息。
<s:form action="point" method="post"> <s:textfield name="test" label="test" value="test"></s:textfield> <s:textfield name="point" label="坐标" value="10,10"></s:textfield> <s:submit value="提交"></s:submit> </s:form> |
注意:只有使用<s:property value="point"/>时候才会调用String 到PointVo的转换。
这里对某个action的指定字段进行转换,这个叫局部类型转换,如果多个action有这个字段的话,会要配置多个,在下面的章节中会讲解全局的。
如果有多个Action里面都要进行Point转换怎么办呢?假如是10个Action,按照上面的写法需要给10个Action建立转换配置文件,基于这种需求Struts2里面有全局配置转换,全局的意思是指,应用中所有的Action都可以使用这个转换类。
与7.1.1.1一样。
与7.1.1.2一样。
与7.1.1.3一样。
全局的转换配置文件与局部的不同,它遵循以下几点要求。
1. 这个文件的存放路径与struts.xml一样,在classpath下。
2. 文件名字叫 xwork-conversion.properites。
3. 配置的规则是 “要转换的类=自定义转换处理类”。
xwork-conversion.properties
com.struts2.vo.PointVo=com.struts2.converter.PointConverter |
和上面一样,省略。
它对所以的action转换都起作用。
org.apache.struts2.util.StrutsTypeConverter是一个抽象类,它继承了DefaultTypeConverter接口,一般情况下我们使用这种方式来开发自定义转换类。
从源码可以发现它实现了DefaultTypeConverter的convertValue()。
public abstract class StrutsTypeConverter extends DefaultTypeConverter { public Object convertValue(Map context, Object o, Class toClass) { if (toClass.equals(String.class)) { return convertToString(context, o); } else if (o instanceof String[]) { return convertFromString(context, (String[]) o, toClass); } else if (o instanceof String) { return convertFromString(context, new String[]{(String) o}, toClass); } else { return performFallbackConversion(context, o, toClass); } } |
我们通过实现它的两个方法来实现自定义转换类。convertFromString()表示从String转成Object,convertToString()表示把Object转成String。
还是以上面的坐标为实例来测试StrutsTypeConverter类。这里就不测试局部类型转换,直接测试全局的,且不写上面那么详细,除了开发转换类不同,其它的开发都一样。
package com.struts2.converter; import java.util.Map; import org.apache.struts2.util.StrutsTypeConverter; import com.struts2.vo.PointVo; @SuppressWarnings("unchecked") public class PointConverter1 extends StrutsTypeConverter {
@Override public Object convertFromString(Map context, String[] value, Class toType) { PointVo p = new PointVo(); if (value != null && value.length > 0) { // 取数组第一个值,按照逗号分隔 String params[] = value[0].split(","); if (params != null && params.length > 1) { int x = Integer.parseInt(params[0].trim()); int y = Integer.parseInt(params[1].trim()); p.setX(x); p.setY(y); } } return p; }
@Override public String convertToString(Map context, Object value) { PointVo vo = (PointVo) value; return "[x=" + vo.getX() + ", y=" + vo.getY() + "]"; } } |
xwork-conversion.properties
#com.struts2.vo.PointVo=com.struts2.converter.PointConverter com.struts2.vo.PointVo=com.struts2.converter.PointConverter1 |
Struts2的校验可以分为手工校验,和框架校验,还有基于注解的校验,这里我们介绍手工校验和框架校验。
讲正则表达式之前必须先讲一下正则表达式的使用。
用户名的规则是:以大小写字母开头的,字母下划线,数字组成的字符串,长度为6-12位。
String regex = "^[a-zA-Z][a-zA-Z_0-9]{5,11}$"; |
String regex = "^[a-zA-Z][\\w]{5,11}$"; |
这里使用了JUNIT测试各种可能的情况
String regex = "^[a-zA-Z][a-zA-Z_0-9]{5,11}$"; Assert.assertEquals(true, Pattern.matches(regex, "jiangyang")); Assert.assertEquals(true, Pattern.matches(regex, "Jiangyang")); Assert.assertEquals(true, Pattern.matches(regex, "jiang_yang")); Assert.assertEquals(true, Pattern.matches(regex, "jiang_yang88")); Assert.assertEquals(false, Pattern.matches(regex, "aaaaa")); Assert.assertEquals(false, Pattern.matches(regex, "aaaaaaaaaaaaa")); Assert.assertEquals(false, Pattern.matches(regex, "111111111")); Assert.assertEquals(false, Pattern.matches(regex, "_111111111")); Assert.assertEquals(false, Pattern.matches(regex, "jiangyang000000")); |
密码规则为:6~12任意非空白字符
String regex = "^\\S{6,12}$"; |
这里使用了JUNIT测试各种可能的情况
String regex = "^\\S{6,12}$"; Assert.assertEquals(true, Pattern.matches(regex, "jiangyang")); Assert.assertEquals(true, Pattern.matches(regex, "JIANGYANG")); Assert.assertEquals(true, Pattern.matches(regex, "jiang_yang")); Assert.assertEquals(true, Pattern.matches(regex, "jiang.yang")); Assert.assertEquals(true, Pattern.matches(regex, "jiang@yang")); Assert.assertEquals(true, Pattern.matches(regex, "jiang_yang88")); Assert.assertEquals(true, Pattern.matches(regex, "666666")); Assert.assertEquals(false, Pattern.matches(regex, "jiang yang")); |
年龄规则为:1,3位的数字
String regex = "^\\d{1,3}$"; |
String regex = "^\\+?\\d{1,3}$";// 这里考虑前面加一个“+”的情况 |
这里使用了JUNIT测试各种可能的情况
Assert.assertEquals(true, Pattern.matches(regex, "9")); Assert.assertEquals(true, Pattern.matches(regex, "21")); Assert.assertEquals(true, Pattern.matches(regex, "100")); Assert.assertEquals(true, Pattern.matches(regex, "+23")); Assert.assertEquals(false, Pattern.matches(regex, "1000")); Assert.assertEquals(false, Pattern.matches(regex, "-1")); |
手机号码规则 以1开头长度为11位的数字
String regex = "^1\\d{10}$"; |
这里使用了JUNIT测试各种可能的情况
Assert.assertEquals(true, Pattern.matches(regex, "18664306011")); Assert.assertEquals(true, Pattern.matches(regex, "15664306011")); Assert.assertEquals(false, Pattern.matches(regex, "1566430601")); Assert.assertEquals(false, Pattern.matches(regex, "186643060110")); |
email規則為:xx.xx@xx.xx
String regex = "^(\\w+\\.)?\\w+@\\w+\\w+\\.\\w+$"; |
这里使用了JUNIT测试各种可能的情况
Assert.assertEquals(true, Pattern.matches(regex, "xx.xx@xx.xx")); Assert.assertEquals(true, Pattern.matches(regex, "xx@xx.xx")); Assert.assertEquals(false, Pattern.matches(regex, "@xx.xx")); Assert.assertEquals(false, Pattern.matches(regex, "xx.xx")); Assert.assertEquals(false, Pattern.matches(regex, "xx.xx@xx")); |
价格为整数,或者带小数点后带一位或者两位的浮点型。
String regex = "^\\d+(\\.\\d{1,2})?$"; |
String regex = "^(\\+)?\\d+(\\.\\d{1,2})?$"; // 前面带"+" |
这里使用了JUNIT测试各种可能的情况
String regex = "^(\\+)?\\d+(\\.\\d{1,2})?$"; // 前面带"+" Assert.assertEquals(true, Pattern.matches(regex, "20")); Assert.assertEquals(true, Pattern.matches(regex, "20.12")); Assert.assertEquals(true, Pattern.matches(regex, "20.0")); Assert.assertEquals(false, Pattern.matches(regex, "20.012")); Assert.assertEquals(true, Pattern.matches(regex, "+20")); |
String regex = "^[\u4E00-\u9FA5]+$"; |
这里使用了JUNIT测试各种可能的情况
String regex = "^[\u4E00-\u9FA5]+$"; Assert.assertEquals(true, Pattern.matches(regex, "汉字")); Assert.assertEquals(false, Pattern.matches(regex, "1111")); Assert.assertEquals(false, Pattern.matches(regex, "dsfdsafad")); Assert.assertEquals(false, Pattern.matches(regex, "~!@#$%^&*()_+<>?:\"{}|,./;‘[]\‘")); |
所谓手工校验就是通过重写ActionSupport里面的的validate()来实现,如果Action里面不是执行的execute()方法就编写validateXxx()方法,其中xxx代表的时候action里面的method名。
考虑如下需求,一个注册需求,包括:用户名,密码,重复密码,性别,年龄,生日这几个字段。
以前我们的校验是通过javascript进行校验,或者叫做前端校验,这种校验是有问题的,某些不怀好意的人,是可以绕过前端校验的,如果使用以前的校验来说我我们的代码是不够健壮的,我们这里讲的校验可以避免这种情况。
package com.struts2.action; import java.util.Date; import java.util.regex.Pattern; import com.opensymphony.xwork2.ActionSupport; public class RegistAction extends ActionSupport { private static final long serialVersionUID = -7689263209084049054L; private String username; private String password; private String repassword; private int age; private String sex; private Date birthday; …… @Override public String execute() throws Exception { System.out.println("注册成功............"); return SUCCESS; } } |
注意:int,或者float作为字段做校验的时候,如果页面的值为空的话,会报错,这时候考虑封装内型。
在RegistAction重新validate()方法,如果有错误的话使用addFieldError()方法来提示,这个方法的第一个参数是对应页面的字段名称,第二个是错误信息。
@Override public void validate() { } |
String userNameRegex = "^[a-zA-Z]\\w{5,11}$"; if (!Pattern.matches(userNameRegex, this.getUser().getUsername().trim())) { logger.error("用户名非法!"); // 调用字段校验出错方法,保存错误信息,用于在页面显示。 addFieldError("username", "用户名非法!"); } |
密码,与确认密码校验,以及两次输入的密码是否一致。
String pwdRegex = "^\\S{6,12}$"; if (!Pattern.matches(pwdRegex, this.getUser().getPassword().trim())) { logger.error("密码非法!"); addFieldError("password", "密码非法!"); } |
if (!Pattern.matches(pwdRegex, this.getUser().getRepassword().trim())) { logger.error("确认密码非法!"); addFieldError("repassword", "确认密码非法!"); } |
if (!this.getUser().getPassword().trim().equals(this.getUser().getRepassword().trim())) { logger.error("两次密码不一致!"); addFieldError("password", "两次密码不一致!"); } |
String ageRegex = "^\\+?\\d{1,3}$"; if (!Pattern.matches(ageRegex, String.valueOf(this.getUser().getAge()))) { logger.error("年龄非法!"); addFieldError("age", "年龄非法!"); } |
String emailRegex = "^(\\w+\\.)?\\w+@\\w+\\.\\w+$"; if (!Pattern.matches(emailRegex, this.getUser().getEmail().trim())) { logger.error("邮箱非法!"); addFieldError("email", "邮箱非法!"); } |
String telRegex = "^1\\d{10}$"; if (!Pattern.matches(telRegex, this.getUser().getTelephone().trim())) { logger.error("电话非法!"); addFieldError("telephone", "电话非法!"); } |
if (this.getUser().getBirthday() != null && this.getUser().getGraduation() != null) { Calendar calendar1 = Calendar.getInstance(); calendar1.setTime(this.getUser().getBirthday());
Calendar calendar2 = Calendar.getInstance(); calendar2.setTime(this.getUser().getGraduation());
if (!calendar1.before(calendar2)) { this.addFieldError("birthday", "birthday must before graduation!"); } } |
不使用Struts2的标签
<form action="<%=path%>/regist.action" method="post"> <center> <s:fielderror></s:fielderror> </center> <table align="center" width="40%" border="1"> <tr> <td>用户名:</td> <td> <input type="text" name="username" value="${requestScope.username}" /> </td> </tr> <tr> <td> 密码: </td> <td> <input type="password" name="password" /> </td> </tr> <tr> <td> 重复密码: </td> <td> <input type="password" name="repassword" /> </td> </tr> <tr> <td> 性别: </td> <td> <input type="text" name="sex" value="${requestScope.sex}" /> </td> </tr> <tr> <td> 年龄: </td> <td> <input type="text" name="age" value="${requestScope.age}" /> </td> </tr> <tr> <td> 生日: </td> <td> <input type="text" name="birthday" value="1985-03-13" /> </td> </tr> <tr> <td> <input type="submit" value="注册" /> </td> <td> <input type="reset" value="重置" /> </td> </tr> </table> </form> |
使用Strut2的标签
<s:form action="regist.action" method="post"> <s:textfield name="username" label="用户名"></s:textfield> <s:password name="password" label="密码"></s:password> <s:password name="repassword" label="重复密码"></s:password> <s:textfield name="sex" label="性别"></s:textfield> <s:textfield name="age" label="年龄"></s:textfield> <s:textfield name="birthday" label="生日"></s:textfield> <s:submit value="注册"></s:submit> </s:form> |
注意:不使用Strut2的标签的时候得用<s:fielderror/>来显示错误提示信息,用了Strut2的标签的时候,错误信息会在页面字段上面自动提示。
如果把年龄输入成非数字的,不可以解析的字符串的时候会怎么样?不然下面把年龄输入成”qq”。
使用全局的解决方案,xwork-core-2.2.1中有一个WEBWORK全局配置文件。
xwork-core-2.2.1\com\opensymphony\xwork2\ xwork-messages.properties
xwork.default.invalid.fieldvalue=Invalid field value for field "{0}". |
配置全局配置文件。
<constant name="struts.custom.i18n.resources" value="i18n"></constant> |
i18n.properties
xwork.default.invalid.fieldvalue={0} error |
校验框架的与手动校验不同的是,把校验逻辑放到配置文件中,实现校验逻辑代码与业务逻辑代码的分离。
位于xwork-core-2.2.1.jar压缩包中(com.opensymphony.xwork2.validator.validators)有个文件default.xml ,该文件中定义了Struts2框架内建的校验器。default.xml文件定义了常用的校验器类型。
<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"/> |
采用 Struts2的校验框架的时候,只需要为该Action指定一个校验文件即可。校验文件是一个XML的配置文件。这个XML指定Action属性必须满足的校验规则。这个校验文件的名称要求如下:
Action name-validation.xml |
也就是说-validation.xml总是固定的,这个跟自定义转换有点类似。
框架校验按照风格可以分为:
1. 字段校验器风格。
2. 非字段校验器风格。
该校验器表示表示字段必须有值,如果是非字段风格校验器风格的时候fieldName表示,要校验的字段,如果是字段校验器风格则不需。
<!-- 生日必须输入校验 --> <field-validator type="required"> <message key="birthday.required" /> </field-validator> |
必填字符串校验器requiredstring,表示字段为非空字符串,且字符串长度必须大于0,参数如下:
Fieldname:使用非字段校验风格校验的时候用来指 定要校验的字段。
trim:它介绍的是一个布尔值,表示是否去掉属性前后的空格,类似于String.trim()方法。
<field-validator type="requiredstring"> <message key="sex.required" /> </field-validator> |
整数校验器int,要求字段必须在指定的整数范围内,参数如下:
fieldName:使用非字段校验风格校验的时候用来指 定要校验的字段
min:表示该属性可以允许的最小值,该参数可选, 不写的话,不检查最小值。
max:表示该属性可以允许的最大值,该参数可选, 不写的话,不检查最大值。
<field-validator type="int"> <param name="min">0</param> <param name="max">150</param> <message key="age.range.invalid" /> </field-validator> |
日期校验器date,该校验器要求日期必须在指定范围内,该校验器接收如下参数:
fieldName:使用非字段校验风格校验的时候用来指 定要校验的字段
min: 指定属性可以接收的最小日期,该参数可选,如果不写不检测最小值。
max:指定属性可以接收的最大日期,该参数可选,如果不写不检测最大值。
<field-validator type="date"> <param name="min">1900-01-01</param> <param name="max">2013-05-31</param> <message key="birthday.range.invalid" /> </field-validator> |
字符串长度校验器,它要求被校验的字段的长度必须在指定的范围内,它有以下几个参数:
fieldName:使用非字段校验风格校验的时候用来指 定要校验的字段
minLength: 指定字符串的最小长度,该参数可选,如果不写不检测最小值。
maxLength:指定字符串的最大长度,该参数可选,如果不写不检测最大值。
<field-validator type="stringlength"> <param name="minLength">6</param> <param name="maxLength">12</param> <message>用户名的长度应该在${minLength}到${maxLength}之间!</message> </field-validator> |
类型转换器conversion,检查某个属性是否发生转换异常,参数如下:
fieldName:使用非字段校验风格校验的时候用来指定要校验的字段。
<field-validator type="conversion"> <message key="age.invalid" /> </field-validator> |
email和url校验器,用来校验属性是否是email和url。
<field name="email"> <field-validator type="requiredstring"> <message key="email.required" /> </field-validator> <field-validator type="email"> <message key="email.invalid" /> </field-validator> </field> <field name="website"> <field-validator type="requiredstring"> <message key="website.required" /> </field-validator> <field-validator type="url"> <message key="website.invalid" /> </field-validator> </field> |
字段表达式校验器fieldexpression,它要求字段满足一个逻辑表达式,参数如下:
fieldName:使用非字段校验风格校验的时候用来指 定要校验的字段。
expression:该参数指定一个逻辑表达式,基于OGNL的求值,如果返回true表示校验成功,否则是失败。
<!-- 校验两次密码输入是否一致 --> <field-validator type="fieldexpression"> <param name="expression">password.equals(repassword)</param> <message key="password.not.same" /> </field-validator> |
正则表达式校验器regex,要要求被校验的字段是否匹配一个正则表达式,介绍参数如下:
fieldName:使用非字段校验风格校验的时候用来指 定要校验的字段。
expression:该参数指定一个正则表达式式。
caseSensitive: 表示正则表达式是否区分大小写,值为布尔值。
<!-- 正则表达式校验用户名,规则:以大小写字母开头,然后接大小写字母或者数字 --> <field-validator type="regex"> <param name="expression"><![CDATA[^[A-Za-z][A-Za-z0-9]{5,11}$]]></param> <message key="username.invalid" /> </field-validator> |
<field name="user.ueraname"> <!-- 用户名必填 --> <field-validator type="requiredstring"> <param name="trim">true</param> <message>用户名必须!</message> </field-validator> <!-- 用户名长度控制 --> <field-validator type="stringlength"> <param name="minLength">6</param> <param name="maxLength">12</param> <message>用户名长度必须在 ${minLength}, ${maxLength}之间!</message> </field-validator> </field> |
密码,与确认密码校验,以及两次输入的密码是否一致。
<!-- 校验密码 --> <field name="user.password"> <!-- 用户名必填 --> <field-validator type="requiredstring"> <param name="trim">true</param> <message>密码必须!</message> </field-validator> <!-- 用户名长度控制 --> <field-validator type="stringlength"> <param name="minLength">6</param> <param name="maxLength">12</param> <message>密码长度必须在 ${minLength}, ${maxLength}之间!</message> </field-validator> </field> |
<!-- 校验确认密码 --> <field name="user.repassword"> <!-- 确认密码必填 --> <field-validator type="requiredstring"> <param name="trim">true</param> <message>确认密码必须!</message> </field-validator> <!-- 确认密码长度控制 --> <field-validator type="stringlength"> <param name="minLength">6</param> <param name="maxLength">12</param> <message>确认密码长度必须在 ${minLength}, ${maxLength}之间!</message> </field-validator> <!-- 校验密码与确认密码是否一致 --> <field-validator type="fieldexpression"> <param name="expression">user.password.equals(user.repassword)</param> <message>输入的密码不一致</message> </field-validator> </field> |
<!-- 校验年龄 --> <field name="user.age"> <field-validator type="required"> <message>年龄必须!</message> </field-validator>
<field-validator type="conversion"> <param name="repopulateField">true</param> <message>输入的不是年龄!</message> </field-validator>
<field-validator type="int"> <param name="min">0</param> <param name="max">150</param> <message>年龄 必须在 ${ min }, ${ max }之间!</message> </field-validator> </field> |
<field name="user.email"> <field-validator type="email"> <message>输入的不是邮箱地址</message> </field-validator> </field> |
<field name="user.telephone"> <field-validator type="regex"> <param name="expression"><![CDATA[^1\\d{10}$]]></param> <message>输入的电话有问题</message> </field-validator> </field> |
输入配置了conversion校验器后,但是运行的时候会发现这样的错误提示“Invalid field value for field "age".”
假如在struts2.xml中有如下的配置:
<constant name="struts.custom.i18n.resources" value="i18n" /> |
建立如下配置文件,在路径跟国际化配置文件路径一样,然后配置信息如下面,去掉默认的“转换类型异常提示信息!”。
xwork.default.invalid.fieldvalue这个的配置信息在xwork-core-2.2.1.jar中的\xwork-core-2.2.1\com\opensymphony\xwork2\ xwork-messages.properties
i18n.properites
xwork.default.invalid.fieldvalue= |
# Copyright (c) 2002-2006 by OpenSymphony # All rights reserved. xwork.error.action.execution=Error during Action invocation xwork.exception.missing-action=There is no Action mapped for action name {0}. xwork.exception.missing-package-action=There is no Action mapped for namespace {0} and action name {1}. xwork.default.invalid.fieldvalue=Invalid field value for field "{0}". |
1. 拦截器的概念
2. 配置拦截器
3. 使用拦截器
拦截器(Interceptor)是动态拦截Action调用的对象,类似于Servlet中的Filter。在执行Action的业务逻辑处理方法execute()之前,Struts2会首先执行在struts.xml中引用的拦截器。
拦截器是struts2的一个重要特性。Struts2框架可以理解成一个空的容器,的大多数核心功能都是通过拦截器来实现的,比如:避免表单重复提交、类型转换、对象组装、验证、文件上传等,都是在拦截器的帮助下实现的。拦截器之所以称为“拦截器”,是因为它可以在Action执行之前和执行之后拦截调用。
Struts2将它的核心功能放到拦截器中实现,而不是分散到Action中实现,有利于系统的解耦,使得功能的实现类似于个人电脑的组装,变成了可插拔的,需要某个功能就“插入”一个拦截器,不需要某个功能就“拔出”一个拦截器。你可以任意组合拦截器来为Action提供附加的功能,而不需要修改Action的代码。
拦截器围绕着Action和Result的执行而执行,其工作方式如图:
Struts2内建了大量的拦截器,这些拦截器以name-class对的形式配置在struts-default.xml文件中,其中name是拦截器的名字,就是以后使用该拦截器的唯一标识;class则指定了该拦截器的实现类,如果我们定义的package继承了Struts2的struts-default包,则可以自由使用下面定义的拦截器,否则必须自己定义这些拦截器。
大部分时候,开发者无需手动控制这些拦截器,因为struts-default.xml文件中已经配置了这些拦截器,只要我们定义的包继承了系统的struts-default包,就可以直接使用这些拦截器。
测试一个最简单的拦截器实例,
public class MyInterceptor implements Interceptor { public void destroy() { System.out.println("destory()......"); } public void init() { System.out.println("init()......"); } public String intercept(ActionInvocation ai) throws Exception { System.out.println("MyInterceptor.intercept() begin......"); String result = ai.invoke(); System.out.println("MyInterceptor.intercept() end......"); return result; } } |
Strutx.xml配置文件。
<package name="default" namespace="/" extends="struts-default"> <interceptors> <interceptor name="myInterceptor" class="com.struts2.interceptor.MyInterceptor" /> </interceptors> <action name="login" class="com.struts2.action.LoginAction"> <interceptor-ref name="myInterceptor" /> <result name="success">/result.jsp</result> <result name="input">/index.jsp</result> <interceptor-ref name="defaultStack" /> </action> </package> |
<interceptor-refname="defaultStack" />一定要写,不然会有问题,也就是在action中引用了自定义拦截器后,就不会在引用”defaultStack”了。这样的话会使得STRUT2里面很多功能都无法实现。
可以通过下面两种方式来设置拦截器的参数,如果两种都写的话,第二种会覆盖掉第一种的配置。
<interceptor name="myInterceptor" class="com.struts2.interceptor.MyInterceptor"> <param name="test">value1</param> </interceptor> |
<interceptor-ref name="myInterceptor"> <param name="test">value2</param> </interceptor-ref> |
在Struts2中拦截器与拦截器栈其实是没区别的,可以把拦截器叫拦截器栈中,拦截器栈也可以引用拦截器栈。所以我们可以实现下面的方式来配置:
定义一个myStack的拦截器栈,然后在这个栈中引用myInterceptor,与defaultStack栈。
<interceptors> <interceptor name="myInterceptor" class="com.struts2.interceptor.MyInterceptor"> <param name="test">value1</param> </interceptor> <interceptor-stack name="myStack"> <interceptor-ref name="myInterceptor" /> <interceptor-ref name="defaultStack" /> </interceptor-stack> </interceptors> |
<action name="login" class="com.struts2.action.LoginAction"> <result name="success">/result.jsp</result> <result name="input">/index.jsp</result> <interceptor-ref name="myStack" /> </action> |
如果上面的拦截器栈你也觉得嫌麻烦的话,可以配置一个包的默认拦截器栈<default-interceptor-refname="myStack" />在该包中所以没有引用拦截器中的action都会起作用,如果action里面有拦截器引用的话,这时候这个包的默认拦截器栈就不起作用,需要在action中引用defaultStack。
如果实现拦截器不想重写init(),destroy()方法的只希望重写intercept()的时候,这个时候可以考虑继承com.opensymphony.xwork2.interceptor AbstractInterceptor。
从代码可以看出来它重写了init(),destroy()方法,intercept()是一个抽象方法,由子类实现。
public abstract class AbstractInterceptor implements Interceptor { public void init() { } public void destroy() { } public abstract String intercept(ActionInvocation invocation) throws Exception; } |
public class MyInterceptor1 extends AbstractInterceptor { @Override public String intercept(ActionInvocation ai) throws Exception { Object action = ai.getAction(); System.out.println("action=" + action.getClass().getName()); System.out.println("MyInterceptor1.intercept() begin......"); String result = ai.invoke(); System.out.println("result=" + result); System.out.println("MyInterceptor1.intercept() end......"); return result; } } |
注意这里定义了两个拦截器,这样运行的时候我们的action会被这两个过滤器过滤,运行的顺序会按照配置的顺序来执行,当我们把myInterceptor1,放到myInterceptor之前,在测试。
<interceptors> <interceptor name="myInterceptor" class="com.struts2.interceptor.MyInterceptor"> <param name="test">value1</param> </interceptor> <interceptor name="myInterceptor1" class="com.struts2.interceptor.MyInterceptor1" /> <interceptor-stack name="myStack"> <interceptor-ref name="myInterceptor" /> <interceptor-ref name="myInterceptor1" /> <interceptor-ref name="defaultStack" /> </interceptor-stack> </interceptors> |
MethodFilterInterceptor用来拦截action里面的方法。
public class MethodInterceptor extends MethodFilterInterceptor { private static final long serialVersionUID = 7051308334327505628L; @Override protected String doIntercept(ActionInvocation ai) throws Exception { System.out.println("MethodInterceptor.doIntercept() begin......"); String result = ai.invoke(); System.out.println("result=" + result); System.out.println("MethodInterceptor.doIntercept() end......"); return result; } } |
includeMethods, excludeMethods来确定哪些拦截,哪些不拦截。
|
<interceptor name="myInterceptor2" class="com.struts2.interceptor.MyInterceptor2"></interceptor> |
<interceptor-stack name="mis"> <interceptor-ref name="myInterceptor"> <param name="test">value</param> </interceptor-ref> <!-- 把系统默认的拦截器栈加进来 --> <interceptor-ref name="defaultStack"></interceptor-ref> <!-- 把第二个拦截器加进来 --> <interceptor-ref name="myInterceptor1"></interceptor-ref> <!-- 把拦截方法的拦截器加进来 --> <interceptor-ref name="myInterceptor2"> <param name="includeMethods">execute</param> <!-- 这里指定要拦截额方法 --> <param name="excludeMethods">login</param> <!-- 指定不拦截的方法 --> </interceptor-ref> </interceptor-stack> |
<default-interceptor-ref name="mis"></default-interceptor-ref> |
这里要注意的是不要在拦截器定义的时候指定哪些方法拦截,哪些不拦截,我们在拦截器栈里面引用它了,在拦截器栈里面定义要拦截的,以及不要拦截的。
该标签用于防止表单重复提交,在STRUT2的form表单中加入一个<s:token></s:token>
<s:form action="login" method="post"> <!-- s:token用来防止表单的重复提交。 --> <s:token></s:token> <s:textfield name="username" value="test" label="用户名"></s:textfield> <s:password name="password" value="123456" label="密码"></s:password> <s:submit value="提交"></s:submit> </s:form> |
使用防止表单重复提交的时候要提供invalid.token的试图,当表单重复提交的时候,框架会自动的跳往这个invalid.token对应的页面。
<action name="login" class="com.csuinfosoft.struts2.action.LoginAction"> <!-- 防止表单重复提交的拦截器 --> <interceptor-ref name="token"></interceptor-ref> <interceptor-ref name="defaultStack"></interceptor-ref> <result>/results.jsp</result> <result name="invalid.token">/token.jsp</result> </action> |
使用拦截器做权限,只有在登陆以后才能做相应的“增删改查”操作。
public class AuthorityInterceptor extends AbstractInterceptor { @Override public String intercept(ActionInvocation ai) throws Exception { // 去session里面获取用户名 ActionContext ac = ai.getInvocationContext(); Map<String, Object> session = ac.getSession(); String name = (String) session.get("name"); String result = null; if (name != null) { logger.trace("可以正常访问!"); result = ai.invoke(); } else { // 没有权限访问 logger.error("没有权限访问!"); result = "input"; } return result; } } |
<interceptors> <interceptor name="authorityInterceptor" class="com.csuinfosoft.struts2.interceptor.AuthorityInterceptor" /> <interceptor-stack name="myStack"> <interceptor-ref name="authorityInterceptor" /> <interceptor-ref name="defaultStack" /> </interceptor-stack> </interceptors> |
<global-results> <result name="input">/login.jsp</result> </global-results> |
<action name="addStudent" class="com.csuinfosoft.struts2.action.StudentAction" method="add"> <result name="success" type="redirectAction"> queryAllStudent </result> <interceptor-ref name="myStack"></interceptor-ref> </action> |
注意:全局结果结果写在拦截器配置的下面。
要使用form表单来实现文件上传form表单有两个要求:
1. method的值是post。
2. enctype的值是multipat/form-data,表示上传的是二进制文件,这个参数的默认值是application/x-www-form-urlencoded。
上传之前配置这两个配置项
<!-- 配置上传的临时目录 --> <constant name="struts.multipart.saveDir" value="D:\temp" /> |
<!-- 配置上传时候form表单可以接受的最大字节数 --> <constant name="struts.multipart.maxSize" value="5097152" /> |
<s:form action="upload.action" method="post" enctype="multipart/form-data"> <s:textfield name="title" label="标题" /> <s:file name="file" label="选择图片" /> <s:submit value="上传" /> </s:form> |
public class UploadAction extends ActionSupport { // 普通字段 private String title; // 上传文件字段 private File file; // 文件的类型 private String fileContentType; // 文件的名称 private String fileFileName; // 上传文件的保存路径 private String savePath;
public String getSavePath() { return ServletActionContext.getServletContext().getRealPath("/" + savePath); } public void setSavePath(String savePath) { this.savePath = savePath; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public File getFile() { return file; } public void setFile(File file) { this.file = file; } public String getFileContentType() { return fileContentType; } public void setFileContentType(String fileContentType) { this.fileContentType = fileContentType; } public String getFileFileName() { return fileFileName; } public void setFileFileName(String fileFileName) { this.fileFileName = fileFileName; } @Override public String execute() throws Exception { if (!new File(this.getSavePath()).exists()) { System.out.println(this.getSavePath() + "不存在!先创建。"); new File(this.getSavePath()).mkdirs(); } String outputPath = this.getSavePath() + File.separator + this.getFileFileName(); // System.out.println("outputPath=" + outputPath); // System.out.println("fileContentType=" + fileContentType); // System.out.println("fileFileName=" + fileFileName); // OutputStream os = new FileOutputStream(outputPath); // InputStream is = new FileInputStream(file); // byte buffer[] = new byte[1024]; // int len = 0; // while ((len = is.read(buffer)) > 0) { // os.write(buffer, 0, len); // } // os.close(); // is.close(); FileUtils.copyFile(file, new File(outputPath)); return SUCCESS; } } |
<body> 上传成功!<br /> <img src="<s:property value="‘upload/‘ + fileFileName"/>" /> <img src="upload/<s:property value="fileFileName"/>" /> </body> |
上面这种上传有问题:
是上传图片,但是可以上传别的文件。
怎么解决?
提示:做校验!
使用校验来实现对上传文件类型的过滤。
添加一个参数来配置可以上传文件的类型。
<action name="upload" class="com.struts2.action.UploadAction"> <!-- 上传后的路径 --> <param name="savePath">/upload</param> <!-- 配置允许上传的文件类型 --> <param name="allowTypes"> image/png,image/gif,image/jpeg </param> <result name="success">/result.jsp</result> <result name="input">/index.jsp</result> </action> |
只例举添加代码
// 配置允许上传的文件类型 private String allowTypes; /** * 过滤类型 * @param types * @return */ public String filterTypes(String types[]) { // 获取上传的文件类型 String fileType = this.getFileContentType(); for (String type : types) { if (type.equals(fileType)) { return null; } } return ERROR; } @Override public void validate() { String filterResult = filterTypes(getAllowTypes().split(",")); if (filterResult != null) { this.addFieldError("file", "上传文件类型不支持!"); } } |
Strut2中提供了一个文件上传的拦截器,通过配置拦截器可以轻松实现文件的过滤,这个拦截器叫”fileUpload”,我们只需要在action里面引用它就可以了,这个拦截器有两个参数:
allowedTypes: 运行上传文件的类型,如果有多个用逗号隔开。
maximumSize: 指定允许上传的单个文件的大小,单位是字节。
首先测试单个文件的上传
public class UploadAction extends ActionSupport { private String title; // 上传的文件字段 private File file; private String fileContentType; private String fileFileName; private String savePath; …… public String getSavePath() { return ServletActionContext.getServletContext().getRealPath("/upload"); } public void setSavePath(String savePath) { this.savePath = savePath; }
@Override public String execute() throws Exception { logger.info("savePath={}", this.getSavePath()); logger.info("fileFileName={}", fileFileName); logger.info("fileContentType={}", fileContentType); String outputPath = this.getSavePath() + File.separator + fileFileName; logger.info("outputPath={}", outputPath); // 判断上传目录是否存在 if (!new File(this.getSavePath()).exists()) { logger.info("outputPath {}不存在,创建", outputPath); new File(this.getSavePath()).mkdirs(); }
InputStream is = new FileInputStream(file); OutputStream os = new FileOutputStream(outputPath); byte buffer[] = new byte[1024]; int len = 0; while ((len = is.read(buffer)) > 0) { os.write(buffer, 0, len); } os.close(); is.close();
return SUCCESS; } } |
// 使用commons-io-1.3.2.jar里面提供的方法实现。 File outputFile = new File(outputPath); FileUtils.copyFile(file, outputFile); |
使用STRUTS2提供的上传拦截器"fileUpload",这个拦截器可以设置上传文件的大小,以及类型的限制。
maximumSize |
单个上传文件的最大字节数,单位是字节 |
allowedTypes |
配置允许上传的文件类型,多个的话用逗号隔开。 |
配置如下:
<action name="upload1" class="com.struts2.action.UploadAction1"> <param name="savePath">/upload</param> <interceptor-ref name="fileUpload"> <!-- 单个上传文件的最大字节数 --> <param name="maximumSize">1048576</param> <!-- 配置允许上传的文件类型 --> <param name="allowedTypes"> image/png,image/gif,image/jpeg,image/bmp </param> </interceptor-ref> <interceptor-ref name="defaultStack"></interceptor-ref> <result name="success">/result.jsp</result> <result name="input">/index.jsp</result> </action> |
<s:form action="upload1.action" method="post" enctype="multipart/form-data"> <s:textfield name="title" label="标题" /> <s:file name="file" label="头像" /> <s:submit value="提交" /> </s:form> |
多个文件的上传与单个的差异不大,只是在action里面的属性变成数组或者list就行了。
public class UploadAction2 extends ActionSupport { private static Logger logger = LoggerFactory.getLogger(UploadAction2.class); private String title; private List<File> file; private List<String> fileContentType; private List<String> fileFileName; private String savePath; …… public String getSavePath() { return ServletActionContext.getServletContext().getRealPath("/" + savePath); } public void setSavePath(String savePath) { this.savePath = savePath; } @Override public String execute() throws Exception { for (int i = 0; i < file.size(); i++) { logger.info("savePath={}", this.getSavePath()); logger.info("fileFileName={}", fileFileName.get(i)); logger.info("fileContentType={}", fileContentType.get(i)); String outputPath = this.getSavePath() + File.separator + fileFileName.get(i); logger.info("outputPath={}", outputPath); // 判断上传目录是否存在 if (!new File(this.getSavePath()).exists()) { logger.info("outputPath {}不存在,创建", outputPath); new File(this.getSavePath()).mkdirs(); } // 构造目标文件 File outputFile = new File(outputPath); FileUtils.copyFile(file.get(i), outputFile); } return SUCCESS; } } |
这里和10.2.1.2几乎一致
<action name="upload2" class="com.struts2.action.UploadAction2"> <param name="savePath">/upload</param> <interceptor-ref name="fileUpload"> <!-- 单个上传文件的最大字节数 --> <param name="maximumSize">1048576</param> <!-- 配置允许上传的文件类型 --> <param name="allowedTypes"> image/png,image/gif,image/jpeg,image/bmp </param> </interceptor-ref> <interceptor-ref name="defaultStack"></interceptor-ref> <result name="success">/results.jsp</result> <result name="input">/index.jsp</result> </action> |
这里的页面开发结合了以前javascript时候的dom操作。
file_upload.jsp
<%@ page language="java" pageEncoding="UTF-8"%> <%@ taglib prefix="s" uri="/struts-tags"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title></title> <script type="text/javascript"> function addMore(){ var more = document.getElementById("more"); var br = document.createElement("br"); var file = document.createElement("input"); var button = document.createElement("input"); file.type = "file"; file.name = "file"; button.type = "button"; button.value = "删除..."; button.onclick = function(){ //alert("remove"); more.removeChild(br); more.removeChild(file); more.removeChild(button); } more.appendChild(br); more.appendChild(file); more.appendChild(button); } </script> </head> <body> <form action="upload2.action" method="post" enctype="multipart/form-data"> <table width="50%" border="0"> <tr> <td>title:</td> <td><input type="text" name="title" /></td> </tr> <tr> <td>file:</td> <td id="more"> <input type="file" name="file" /> <input type="button" value="更多..." onclick="addMore()" /> </td> </tr> <tr> <td><input type="submit" value="提交" /></td> <td><input type="reset" value="重置" /></td> </tr> </table> </form> </body> </html> |
results.jsp
<%@ page language="java" 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>上传结果页面</title> </head> <body> <s:iterator value="fileFileName"> <img src="upload/<s:property/>" /> <hr /> </s:iterator> <br /> </body> </html> |
如果文件上传失败,比如文件类型不支持,超过文件的大小的配置。这时候需要提供用户自定义的错误提示,且这种错误提示可以国际化。
上传出错的错误信息的默认值在:
struts2-core-2.2.1.jar\org\apache\struts2\struts-messages.properties里面
文件太大 struts.messages.error.file.too.large= |
文件类型不支持 struts.messages.error.content.type.not.allowed = |
大家都知道,只要我们在页面上设置一个超链接,就可以下载文件了,为什么还要讲解STRUTS2的文件下载呢,由于通过超链接下载文件,暴露了下载文件的真实地址,不利于对资源进行安全保护;而且利用超链接下载文件,服务器端的文件只能存放在Web应用程序所在的目录下。
利用程序编码实现下载,可以增加安全访问控制,对经过授权认证的用户提供下载,还可以任意提供下载的数据,我们可以将文件放到Web应用程序以外的目录中,也可以将文件保存到数据库中。
Struts2通过org.apache.struts2.dispatcher.StreamResult结果类型来支持文件下载,使得原本编写就简单的下载程序变得更加简单。
StreamResult结果类型利用HttpServletResponse对象返回的ServletOutputStream对象向客户端输出下载文件的二进制数据,它有下列参数:
contentType |
发送给Web浏览器的数据流的MIME类型(默认是text/plain),即下载文件内容类型 |
contentDisposition |
用于控制文件下载的一些信息,可选择的设置包括:inline;filename=“下载文件名”和attachment;filename=“下载文件名”,filename指定下载的文件名。Inline表示下载文件在本页面内部打开,attachement表示弹出”文件下载”对话框。不过,这也不是绝对的,对于浏览器能够显示的下载文件是这样的,对于浏览器不支持的下载类型,即使使用inline选项,仍然会弹出”文件下载”对话框。 contentDisposition的默认值是inline. |
inputName |
Action中用来下载文件的属性的名字,该属性的类型是InputStream.默认值是inputStream. |
bufferSize |
文件数据从输入复制到输出的缓冲区的大小,默认为1024字节。 |
第一个实现实现一个最简单的下载
这个action里面提供一个返回InputStream的方法,这个方法名称后配置文件中inputName属性值来生产,生成规则是: get + inputName。
public class DownloadAction extends ActionSupport { public InputStream getDownloadFile() { return ServletActionContext.getServletContext().getResourceAsStream("/upload/404.jpg"); }
@Override public String execute() throws Exception { logger.info("DownloadAction......"); return SUCCESS; } } |
这里的配置对应于104.1中所说明的配置,这里下载文件的类型,以及下载文件的名称,都在配置文件中写死了。
<action name="download" class="com.struts2.action.DownloadAction"> <result name="success" type="stream"> <param name="contentType">image/jpeg</param> <param name="contentDisposition"> attachment;fileName=test.jpg </param> <param name="inputName">downloadFile</param> <param name="bufferSize">4096</param> </result> </action> |
<a href="download.action">下载</a> |
如果contentDisposition配置的是attachment的话,会打开下载对话框,如果改成inline的话就直接下载了。
这个实例通过hibernate从数据库查询到数据,然后生成TXT文件,然后在提供下载。
public List<BookVo> queryBooks() { logger.info("queryBooks()........"); List<BookVo> books = null; Session session = null; Transaction t = null; try { session = HibernateSessionFactory.getSession(); t = session.beginTransaction(); String hql = "from BookVo as b"; books = session.createQuery(hql).setFirstResult(0).setMaxResults(100).list(); t.commit(); } catch (HibernateException e) { throw e; } finally { if (session != null && session.isOpen()) { HibernateSessionFactory.closeSession(); } } return books; } |
public class DownloadAction1 extends ActionSupport {
// 下载文件的名称 private String downloadFileName;
public String getDownloadFileName() { return downloadFileName; }
public void setDownloadFileName(String downloadFileName) { this.downloadFileName = downloadFileName; }
public InputStream getDownloadFile() { logger.info("getDownloadFile()......"); return ServletActionContext.getServletContext().getResourceAsStream( "/download" + File.separator + downloadFileName); }
@Override public String execute() throws Exception { logger.info("execute()......"); // 设置下载文件文件名:文件名规则是 时间戳.txt String fileName = new Date().getTime() + ".txt"; this.setDownloadFileName(fileName); BookService service = new BookService(); service.getBooks(fileName); return SUCCESS; } } |
public void getBooks(String downlaodFileName) {
logger.info("getBooks()......"); // 从DAO获取数据 BookDao dao = new BookDao(); List<BookVo> books = dao.queryBooks(); // 确定下载目录是否存在 String downloadDir = ServletActionContext.getServletContext().getRealPath("/download"); logger.info("downloadDir={}", downloadDir); if (!new File(downloadDir).exists()) { logger.error("downloadDir {} is not exist", downloadDir); new File(downloadDir).mkdirs(); } String filePath = downloadDir + File.separator + downlaodFileName; logger.info("下载文件路径={}", filePath);
File file = new File(filePath); try { // 写成TXT文件 FileUtils.writeLines(file, "UTF-8", books); } catch (IOException e) { e.printStackTrace(); } } |
<action name="download1" class="com.struts2.action.DownloadAction1"> <result name="success" type="stream"> <param name="contentType">image/jpeg</param> <param name="contentDisposition"> attachment;fileName=${ downloadFileName } </param> <param name="inputName">downloadFile</param> <param name="bufferSize">4096</param> </result> </action> |
JSON的全称是(javascriptobject notation)即javascript对象符号,它是一种轻量级的数据交换格式,JSON早期的时候是一种javascript的数据交换格式,后来,发展成一门与语言无关的数据交换格式,类似于XML。
说它是轻量级的数据交换格式是和XML比较的,下面可以看出使用JSON明显比XML简便。
<users> <user> <id>1</id> <name>zhansan</name> <sex>male</sex> </user> <user> <id>2</id> <name>lisi</name> <sex>femle</sex> </user> </users> |
[{id:1,name:zhansan, sex:male}, {id:2,name:lisi,sex:femle}] |
学习STRUTS2的JSON插件的使用,我们可以参考STRUTS2中提供的文档struts-2.2.1\docs\WW\json-plugin.html
使用JSON的插件的话得先把JSON的包拷贝到相应的目录下,下面是所需的包,这里要注意的是,不同的STRUTS2版本这两个包的版本会有不同。
json-lib-2.1-jdk15.jar |
struts2-json-plugin-2.2.1.jar |
Struts2里面使用json的支持,它是通过提供一种返回类型来实现的。我们打开struts2-json-plugin-2.2.1.jar/struts-plugin.xml可以看到,这个包继承了"struts-default",它提供了一个返回类型叫json,然后又定义一个拦截器。所以我们要使用json的支持的时候只要继承"json-default"就可以了。
<package name="json-default" extends="struts-default"> <result-types> <result-type name="json" class="org.apache.struts2.json.JSONResult"/> </result-types> <interceptors> <interceptor name="json" class="org.apache.struts2.json.JSONInterceptor"/> </interceptors> </package> |
页面有一个下拉框,当下拉框数据发生变化后,会把选择发生变化后,通过AJAX发送到后台。
json.jsp
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <base href="<%=basePath%>"> <title>解析STRUTS2对JSON的支持</title> <meta http-equiv="description" content="This is my page" /> <script type="text/javascript" src="<%=path%>/js/jquery-1.7.2.min.js"></script> <script type="text/javascript"> $(function(){ $("#people").change(function(){ var people = this.value.trim(); $.ajax({ type : "POST", url : "json.action", data : "people=" + people, dataType : "json", success : function(data) { alert(data); }, success : function(data) { alert(data) } }); }); }); </script> </head> <body> <s:select id="people" name="people" list="#{‘zs‘ : ‘张三‘, ‘ls‘ : ‘李四‘}" value="‘zs‘"></s:select> </body> </html> |
JsonAction.java
public class JsonAction extends ActionSupport { private String people; private String name; private String sex; private int age; setter,getter…… @Override public String execute() throws Exception { return SUCCESS; } } |
struts.xml
<package name="default" namespace="/" extends="json-default"> <action name="json" class="com.struts2.action.JsonAction"> <result type="json"></result> </action> </package> |
注意如果要使用JSON插件,包一定要继承"json-default"。
这里的JsonAction没有返回任何的页面原因在于,AJAX是异步调用,这里不不需要任何的页面。
我们通过火狐的firebug可以发现响应的结果如下
{"age":0,"name":null,"people":"zs","sex":null} |
从返回的JSON数组来看,action的属性都被序列化成了JSON数组了,这也就是STRUTS2 的JSON插件所做的事。上面的值中只有people有值,其它都为空年龄的话为零,这是因为只有people的值来至于页面。
我们把action的execute()做如下修改:
public String execute() throws Exception { logger.info("execute()......"); if ("zs".equals(this.getPeople())) { this.setName("张三"); this.setAge(22); this.setSex("男"); } else if ("ls".equals(this.getPeople())) { this.setName("李四"); this.setAge(21); this.setSex("女"); } return SUCCESS; } |
当选择李四后,返回的JSON数组如下:
|
序列化是指:从action到JSON数组的转换过程,而反序列化是指页面属性到action里面属性的转换,反序列化使用的很少。
序列化和反序列化使用JSON注解来实现,这个注解有如下几个属性:
这四个属性也可以在STRUTS2的JSON包中找到:
@JSON的使用针对这个注解的四个属性来使用,注意,这个注解要写在属性对应的getter()方法上面。
这个属性是用来定制JSON数组的名称,它的意思是指在默认情况下action的属性名称与JSON数组里面的KEY值是一致的,如果想改变的话可以通过这个属性来设置。
这里就把name属性变成realNam。
@JSON(name="realName") public String getName() { return name; } |
我们通过火狐的firebug可以发现响应的结果如下:
|
默认情况下,serialize的值为true,所有的action属性都会被序列化成JSON数组,如果某个属性不要序列号的话可以使用这个属性来设置。
比如,我people是页面传来的,我不把它序列号可以如下设置:
@JSON(serialize = false) public String getPeople() { return people; } |
我们通过火狐的firebug可以发现响应的结果如下:
{"age":22,"realName":"张三","sex":"男"} |
这里就没有了people属性了,这就是serialize属性的使用。
这个用于格式化一个日期字段,我们在action里面做如下修改
private Date date; |
然后在execute()中做初始化。
我们通过火狐的firebug可以发现响应的结果如下:
{"age":21,"date":"2013-11-02T11:19:37","realName":"李四","sex":"女"} |
我们使用format给日期型格式化。
@JSON(format="yyyy年MM月dd日") public Date getDate() { return date; } |
我们通过火狐的firebug可以发现响应的结果如下:
|
是否序列号可以通过注解来实现,也可以通过配置文件来实现,这里包括排除哪些属性,或者包括哪些属性。
这些配置可以通过一个逗号隔开表示包含,或者排除某些属性,也可以使用正则表达式。
这里我们设置date,people不用序列化
<action name="json" class="com.struts2.action.JsonAction"> <result type="json"> <param name="excludeProperties">date,people</param> </result> </action> |
我们通过火狐的firebug可以发现响应的结果如下:
|
这里发现date没有了,people也没有了。
这里指定哪些属性包含。我们这里指定date,sex,name,age包含,与此同时设置date不包含。这里要注意name属性已经改成了realName
,这里要改成realName而不是name,因为其按转换后的名称来。
|
我们通过火狐的firebug可以发现响应的结果如下:
|
这里就一个规则,如果既有包含又有不包含的话,不包含优先级高。
默认情况下序列化是从action开始的,也就是使用递归从action第一个属性开始。如果想手动的改变这个起始点话,可以配置根对象。
我们在原理的action加一个People属性
|
|
我们通过火狐的firebug可以发现响应的结果如下:
|
这里的话所以的属性都被序列化了。
指定根对象为p。
|
我们通过火狐的firebug可以发现响应的结果如下:
|
发现从属性才开始,上面的属性都没有开始,这就是root的作用。
默认情况下“基类”的属性是不会被序列化的,也就是说“基类”是可以序列化的。我们可以通过如下的配置是“基类”序列化。
|
我们通过火狐的firebug可以发现响应的结果如下:
|
这里面的actionErrors,actionMessages,errorMessages,errors,fieldErrors,locale就是基类中的属性。
有时候我们不需要将null值的属性进行序列化,这时候可以通过如下设置来达到。
|
我们通过火狐的firebug可以发现响应的结果如下:
|
发现里面的null都没有了。
标签:struts web应用开发 web框架 编程语言 开源
原文地址:http://blog.csdn.net/z929118967/article/details/37561437