Struts2 简介
Struts2 是一种流行的 Java模型 - 视图 - 控制器(MVC)框架。成功地结合了 WebWork和Struts1.x 两种 web 框架。Struts2是以WebWork为核心,采用拦截器机制对用户的请求进行处理。
从一个高水平角度看,Struts2 是一个MVC拉动的(或MVC2)框架,Struts2 的模型-视图-控制器模式是通过以下五个核心部分进行实现的:
- 操作(Actions)
- 拦截器(Interceptors)
- 值栈(Value Stack)/OGNL
- 结果(Result)/结果类型
- 视图技术
而Struts2 与传统的MVC框架略有不同,因为它由Action扮演模型的角色,而不是控制器,虽然这样会有一些重叠。
Struts2 配置文件
web.xml文件
这个文件为每个web应用程序提供接入点。在部署描述符(web.xml)中,Struts2 应用程序的接入点将会定义为一个过滤器。因此我们将在web.xml里定义一个FilterDispatcher类的接入点。
<filter> <filter-name>struts2</filter-name> <filter-class> org.apache.struts2.dispatcher.FilterDispatcher </filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
struts.xml文件
struts.xml中主要配置Struts项目的一些全局的属性,用户请求和响应Action之间的对应关系,以及配置Action中可能用到的参数,以及处理结果的返回页面。还包括各种拦截器的配置等。
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN" "http://struts.apache.org/dtds/struts-2.3.dtd"> <struts> <!-- 配置struts可以受理的请求的扩展名 --> <constant name="struts.action.extension" value="action,do,"></constant> <!-- package:包,struts2使用package来组织模块 name属性:必须,用于其他的包应用当前包 extends:当前包继承哪个包,继承的,即可以继承其中的所有的配置, 通常情况下继承struts-default namespace:可选,如果它没有给出,则以“/”为默认值,若namespace有一个非默认值, 则要想调用这个包里的Action,就必须 把这个属性所定义的命名空间添加到有关的URI字符串里 --> <package name="helloworld" extends="struts-default"> <!-- 配置一个action:一个struts2的请求就是一个action name:对应一个struts2的请求的名字(或对一个servletPath,但去除/和扩展名), 不包含扩展名 class:默认值为com.opensymphony.xwork2.ActionSupport method:默认值为execute result:结果,表示action方法执行后可能返回的一个结果, 代表action方法执行后,可能去的一个目的地, 所以一个action节点可能会有多个result子节点。 多个result子节点使用name来区分。 name:标识一个result,和action方法的返回值对应,默认值为success type:表示结果的类型。默认值为dispatcher(转发到结果)。redirect(重定向), 但是重定向不能跳转到WEB-INF目录下。 --> <!-- ActionSupport是默认的Action类:若某个action节点没有配置class属性,则ActionSupport即为 待执行的Action类,而execute方法即为默认执行的action方法(该方法返回success字符串) --> <action name="product-input"> <result>/WEB-INF/pages/input.jsp</result> </action> <!-- product-save请求调用Product类的save方法,方法返回值对应result的name--> <action name="product-save" class="com.struts2.helloworld.Product" method="test"> <result name="details">/WEB-INF/pages/details.jsp</result> </action> </package> </struts>
通配符匹配
<body> <a href="product-input.action">Product Input</a> <!-- 通配符匹配 --> <a href="UserAction-save?name=save">User Save</a> <a href="UserAction-update?name=update">User Update</a> <a href="UserAction-delete?name=delete">User Delete</a> <a href="UserAction-query?name=query">User Query</a> </body>
<!-- 利用通配符匹配 --> <action name="UserAciton-*" class="com.struts2.action.UserAciton" method="{1}"> <result name="{1}-success">/success.jsp</result> </action>
struts提供了一种模块化struts.xml文件的方法,你可以将文件拆分为多个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> <include file="my-struts1.xml"/> <include file="my-struts2.xml"/> </struts>
struts-config.xml文件
struts-config.xml配置文件是Web Client中View和Model组件之间的链接,但在你99.99%的项目里你不必使用这些设置。 struts-config.xml配置文件包含以下主要元素:
<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.0//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_0.dtd"> <struts-config> <!-- 这是配置文件的根节点 --> <!-- 这是你将ActionForm子类映射到name的位置,你可以在struts-config.xml文件的其余部分,甚至 在JSP页面上,将这个name用作ActionForm的别名 --> <form-beans> <form-bean name="login" type="test.struts.LoginForm" /> </form-beans> <!-- 此部分将你在webapp上的页面映射到name,你可以使用这个name来引用实际页面。这避免了对你网 页上的URL进行硬编码 --> <global-forwards> </global-forwards> <!-- 这是你声明表单处理程序的地方,也被称为操作映射(action mappings) --> <action-mappings> <action path="/login" type="test.struts.LoginAction" > <forward name="valid" path="/jsp/MainMenu.jsp" /> <forward name="invalid" path="/jsp/LoginView.jsp" /> </action> </action-mappings> <!-- 这部分是配置Struts的内部,在实际情况中很少使用 --> <controller contentType="text/html;charset=UTF-8" debug="3" maxFileSize="1.618M" locale="true" nocache="true"/> </struts-config>
struts.properties文件
这个配置文件提供了一种机制来改变框架的默认行为。实际上,struts.properties配置文件中包含的所有属性也可以在web.xml中配置使用init-param,以及在struts.xml配置文件中使用constant标签。
struts.properties文件中配置的值将覆盖default.properties中配置的默认值,这些值包含在struts2-core-x.y.z.jar分布中。有一些属性,你可以考虑改为使用struts.properties。
### When set to true, Struts will act much more friendly for developers struts.devMode = true ### Enables reloading of internationalization files struts.i18n.reload = true ### Enables reloading of XML configuration files struts.configuration.xml.reload = true ### Sets the port that the server is run on struts.url.http.port = 8080
Struts2 Actions动作
Actions是Struts2框架的核心,因为它们适用于任何MVC(Model View Controller)框架。 每个URL映射到特定的action,其提供处理来自用户的请求所需的处理逻辑。
但action还有另外两个重要的功能。 首先,action在将数据从请求传递到视图(无论是JSP还是其他类型的结果)方面起着重要作用。 第二,action必须协助框架确定哪个结果应该呈现在响应请求的视图中。
创建Action
Struts2中actions的唯一要求是必须有一个无参数方法返回String或Result对象,并且必须是POJO。如果没有指定no-argument方法,则默认是使用execute()方法。
ActionSupport类是默认的Action类,若某个action节点没有配置class属性,则ActionSupport类即为待执行的Action类,而类中的execute方法即为要默认执行的action方法。
在这个类中还默认封装了一些静态变量(同Action接口一样)
* SUCCESS -- 成功.
* INPUT -- 用于数据表单校验.如果校验失败,跳转INPUT视图.
* LOGIN -- 登录.
* ERROR -- 错误.
* NONE -- 页面不转向.
这个类中的默认执行方法
public String input() throws Exception { return "input"; } public String doDefault() throws Exception { return "success"; } public String execute() throws Exception { return "success"; }
让我们来看看在Hello World示例中的action方法:
public class UserAciton { public String test() { System.out.println("details..."); return "details"; } public String execute() throws Exception { return "success"; } public String save() { System.out.println("save..."); return "save-success"; } public String update() { System.out.println("update..."); return "update-success"; } public String delete() { System.out.println("delete..."); return "delete-success"; } public String query() { System.out.println("query..."); return "query-success"; } }
为了说明action方法控制视图的要点,让我们对execute方法进行以下更改,并扩展ActionSupport类如下:
public class HelloWorldAction extends ActionSupport{ private String name; public String execute() throws Exception { if ("SECRET".equals(name)) { return SUCCESS; }else{ return ERROR; } } public String getName() { return name; } public void setName(String name) { this.name = name; } }
在这个例子中,我们在execute方法中使用一些逻辑来查看name属性。如果属性等于字符串“SECRET”,我们返回SUCCESS作为结果,否则我们返回ERROR作为结果。因为我们已经扩展了ActionSupport,所以我们可以使用String常量、SUCCESS和ERROR。 现在,让我们修改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" /> <package name="helloworld" extends="struts-default"> <action name="hello" class="cn.w3cschool.struts2.HelloWorldAction" method="execute"> <result name="success">/HelloWorld.jsp</result> <result name="error">/AccessDenied.jsp</result> </action> </package> </struts>
如果返回结果是SUCCESS将调用HelloWorld.jsp文件,如果action的结果是ERROR,即字符串常量为“error”,AccessDenied.jsp将被框架调用。
Struts2 拦截器
拦截器在概念上与servlet过滤器或JDK代理类相同。拦截器允许横切功能,把action以及框架分开实现。你可以使用拦截器实现以下操作:
-
在调用action之前提供预处理逻辑。
-
在调用action后提供后处理逻辑。
-
捕获异常,以便可以执行备用处理。
Struts2框架中提供的许多功能都是使用拦截器实现的,包括异常处理,文件上传,生命周期回调和验证等。事实上,由于Struts2将其大部分功能基于拦截器,因此不太可能为每个action分配7个或8个拦截器。
Struts 2框架提供了一个良好的开箱即用的拦截器列表,这些拦截器预先配置好并可以使用。 下面列出了几个重要的拦截器:
序号 | 拦截器和说明 |
---|---|
1 | alias
允许参数在请求之间使用不同的别名。 |
2 | checkbox
通过为未检查的复选框添加参数值false,以辅助管理复选框。 |
3 | conversionError
将字符串转换为参数类型的错误信息放置到action的错误字段中。 |
4 | createSession
自动创建HTTP会话(如果尚不存在)。 |
5 | debugging
为开发人员提供一些不同的调试屏幕。 |
6 | execAndWait
当action在后台执行时,将用户发送到中间的等待页面。 |
7 | exception
映射从action到结果抛出的异常,允许通过重定向自动处理异常。 |
8 | fileUpload
便于文件上传。 |
9 |
i18n 在用户会话期间跟踪选定的区域。 |
10 | logger
通过输出正在执行的action的名称提供简单的日志记录。 |
11 | params
设置action上的请求参数。 |
12 | prepare
这通常用于执行预处理工作,例如设置数据库连接。 |
13 | profile
允许记录action的简单分析信息。 |
14 | scope
在会话或应用程序范围内存储和检索action的状态。 |
15 | ServletConfig
提供可访问各种基于servlet信息的action。 |
16 | timer
以action执行时间的形式提供简单的分析信息。 |
17 | token
检查action的有效性,以防止重复提交表单。 |
18 | validation
提供action的验证支持。 |
如下图,我们首先使用timer拦截器,目的是测量执行action方法所需的时间。同时我们使用params拦截器,目的是将请求参数发送给action。
<struts> <constant name="struts.devMode" value="true" /> <package name="helloworld" extends="struts-default"> <action name="hello" class="cn.w3cschool.struts2.HelloWorldAction" method="execute"> <interceptor-ref name="params"/> <interceptor-ref name="timer" /> <result name="success">/HelloWorld.jsp</result> </action> </package> </struts>
Struts2 结果类型
如前面所述,<results>标签在Struts2 MVC框架中扮演视图的角色。Action负责执行业务逻辑,下一步就是使用<results>标签显示视图。
通常有一些导航规则附加的结果。例如,如果action是进行验证用户,则有三种可能的结果:(a)成功登录(b)登录失败:用户名或密码不正确(c)帐户锁定。
在这种情况下,action将配置三个可能的结果字符串和三个不同的视图来渲染结果,这在我们前面的例子中已经看到过了。
但是,Struts2不绑定使用JSP作为视图技术。毕竟,MVC范例的目的是保持图层分离和高度可配置。例如,对于Web2.0客户端,你可能希望返回XML或JSON作为输出。在这种情况下,你可以为XML或JSON创建一个新的结果类型并实现这一点。
Struts提供了许多预定义的结果类型,我们已经看到的是默认的结果类型dispatcher,它用于分发到JSP页面。Struts允许你使用其他标记语言为视图技术呈现结果,较常选用的包括Velocity,Freemaker,XSLT和Tiles。
dispatcher结果类型
dispatcher结果类型是默认的类型,如果未指定其他结果类型,则使用此类型。它用于转发到服务器上的servlet,JSP,HTML等页面。它使用RequestDispatcher.forward()方法。
如下面这个“简写”的版本,里面我们用一个JSP路径作为结果标签的主体。
①转发到一个JSP:
<result name="success"> ${basePath}/HelloWorld.jsp //struts.xml中可以使用EL表达式 </result>
我们还可以使用<result>元素中的<param name="location">标签来指定JSP文件
<result name="success" type="dispatcher"> <param name="location"> /HelloWorld.jsp </param > </result>
②转发到一个Action:
<action name="testResult" class="com.struts2.action.TestResultAction"> <!--参数之间必须使用&。&是&在xml中转义字符。需要在对应的Action类中定义参数的变量及其get和set方法。--> <result name="success" type="chain"><!-- 转发到一个Action并携带请求参数 --> <param name="actionName">testAction</param> <param name="namespace">/atguigu</param> <param name="errorcode">${errorcode}&error=1</param> </result> </action>
redirect结果类型
redirect结果类型调用标准的response.sendRedirect()方法,使得浏览器向给定的位置创建一个新请求。
我们可以在<result...>元素的主体中或作为<param name="location">的元素中给定位置。
①重定向到一个JSP
<action name="hello" class="com.tutorialspoint.struts2.HelloWorldAction" method="execute"> <result name="success" type="redirect"> <param name="location"> /NewWorld.jsp </param > </result> </action>
②重定向到一个Action
<action name="testResult" class="com.struts2.action.TestResultAction"> <result name="index" type="redirectAction"> <!-- 下面两个参数和底下新建的package相关参数对应 --> <param name="actionName">testAction</param> <!-- 对应要跳转到的Action --> <param name="namespace">/atguigu</param> <!-- 指定目标Action所在的命名空间 --> </result> </package> <package name="testPackage" namespace="/atguigu" extends="struts-default"> <action name="testAction" class="com.struts2.action.TestAction"> <result>/success.jsp</result> </action> </package>
我们还可以通过redirect的响应类型便捷的实现redirectAction功能,但是转发不能通过这种写法实现
<result name="index" type="redirect"> ${basePath}/atguigu/testAction.do </result>
Struts2 值栈/OGNL
Struts2使用OGNL将请求Action的参数封装为对象存储到值栈中,并通过OGNL表达式读取值栈中的对象属性值。
每次访问action时候,都会创建action对象,在每个action对象里面都会有一个值栈对象。
在ValueStack对象的内部有两个逻辑部分:
①ObjectStack(对象栈):struts把Action和相关对象压入ObjectStack中。实际上是CompoundRoot类型,是一个使用ArrayList定义的栈。里边保存各种和当前Action实例相关的对象,是一个数据结构意义上的栈。
②ContextMap(Map栈):struts把各种各样的映射关系(一些Map类型的对象)压入ContextMap中,实际上是OgnlContext类型,是个Map,也是对ActionContext的一个引用。里边保存着各种Map:request、session、application、parameters,attr。
你可以在Action中获取值栈对象,如下所示:
ActionContext.getContext().getValueStack()
一旦你有一个值栈对象,你可以使用以下方法来操纵该对象:
序号 | 值栈方法和说明 |
---|---|
1 | Object findValue(String expr)
通过在默认搜索顺序中对值栈评估所给定的表达式来查找值。 |
2 | CompoundRoot getRoot()
获取将对象推入值栈的CompoundRoot。 |
3 | Object peek()
获取值栈顶部的对象而不改变值栈。 |
4 | Object pop()
获取值栈顶部的对象,并将其从值栈中删除。 |
5 | void push(Object o)
将对象放在值栈的顶部。 |
6 | void set(String key,Object o)
使用给定的key在值栈上设置一个对象,使其可通过findValue(key,...)检索。 |
7 | void setDefaultType(Class defaultType)
设置在获取值时要转换的默认类型。 |
8 | void setValue(String expr,Object value)
尝试使用由默认搜索顺序给定的表达式在值栈的bean上设置属性。 |
9 | int size()
获取值栈中的对象数。 |
OGNL(Object-Graph Navigation Language,对象图导航语言)是一种强大的表达式语言,用于引用和操作值栈上的数据,还可用于数据传输和类型转换。
OGNL非常类似于JSP表达式语言。OGNL基于上下文中存有根对象或默认对象的理念,使用标记符号(即#号)来引用默认或根对象的属性。
如前面所述,OGNL是基于上下文的,而Struts构建了一个ActionContext映射以供OGNL使用。 ActionContext映射包含以下内容:
-
应用程序 - 应用程序作用域变量
-
会话 - 会话作用域变量
-
根/值栈 - 所有的action变量都存储在这里
-
请求 - 请求作用域变量
-
参数 - 请求参数
-
属性 - 存储在页面,请求,会话和应用程序作用域中的属性
访问对象栈中的属性
若想访问ObjectStack里的某个对象的属性,可以使用以下几种形式之一(OGNL写在value中):
<s:property value="object.propertyName"> <s:property value="object.['propertyName']"> <s:property value="object.["propertyName"]">
ObjectStack里的对象可以通过一个从零开始的下标来引用。
ObjectStack里的栈顶对象可以用[0]来引用,它下面的那个对象可以用[1]引用。若希望返回栈顶对象的message属性值:[0].message或[0]['message']或[0]["message"]
若在指定的对象里没有找到指定的属性,则到指定对象的下一个对象里继续搜索,即[n]的含义是从第n个开始搜索,而不是只搜索第n个对象。若从栈顶对象开始搜索,则可以省略下标部分。
示例
创建用于访问值栈的action类,然后在details.JSP视图页面设置使用OGNL进行访问的几个key。
public class HelloWorldAction extends ActionSupport{ private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public String execute() throws Exception { //获取值栈对象 ValueStack stack = ActionContext.getContext().getValueStack(); Map<String, Object> context = new HashMap<String, Object>(); //把对象压入到值栈的栈顶 context.put("key1", new String("This is key1")); context.put("key2", new String("This is key2")); stack.push(context); System.out.println("Size of the valueStack: " + stack.size()); return "success"; } }
实际上,Struts 2在执行时会将action添加到值栈的顶部。所以,通常放置东西在值栈的方法是添加getters/setters的值到Action类,然后使用<s:property>标签访问值(property标签用来输出值栈中的一个属性值)。
创建index.jsp文件:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib prefix="s" uri="/struts-tags"%> <html> <head> <title>Hello World</title> </head> <body> <h1>Hello World From Struts2</h1> <form action="hello"> <label for="name">请输入用户名</label><br/> <input type="text" name="username"/> <input type="submit" value="Say Hello"/> </form> </body> </html>
创建视图details.jsp文件:
<%@ page contentType="text/html; charset=UTF-8" %> <%@ taglib prefix="s" uri="/struts-tags" %> <!-- 切记不要忘记引入这个 --> <html> <head> <title>Hello World</title> </head> <body> Entered value : <s:property value="username"/>或者${username}<br/> Value of key 1 : <s:property value="key1" /><br/> Value of key 2 : <s:property value="key2" /> <br/> </body> </html>
现在,在index.jsp的文本框中输入任意单词,然后单击“Say Hello”按钮执行定义的action。可以在控制台看到以下输出:
Size of the valueStack: 3
这意味着它将显示你输入的任何值和我们放在值栈上的key1和key2的值。
页面最终跳转到details.jsp,显示效果如下:
this is key1
this is key2
fyj
注意:处于栈顶的分别是 key1 和 key2,其次才是我们提交的 username 值。
访问Map栈中的属性
若想访问ContextMap里的某个对象的属性,可以使用以下几种形式之一:
<s:property value="#object.propertyName"> <s:property value="#object.['propertyName']"> <s:property value="#object.["propertyName"]">
示例
创建Action类:
public class Product { private Integer productId; private String productName; private String productDesc; public Integer getProductId() {return productId;} public void setProductId(Integer productId) {this.productId = productId;} public String getProductName() {return productName;} public void setProductName(String productName) {this.productName = productName;} public String getProductDesc() {return productDesc;} public void setProductDesc(String productDesc) {this.productDesc = productDesc;} public String save(){ System.out.println("save:"+this); //获取值栈对象 ValueStack stack = ActionContext.getContext().getValueStack(); Test test = new Test(); test.setProductDesc("AABBCC"); test.setProductName("ABCD"); stack.push(test); sessionMap.put("product",this); requestMap.put("test",test); return "success"; } private Map<String,Object> sessionMap; private Map<String,Object> requestMap; }
在视图details.jsp访问Map栈中的属性值
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="s" uri="/struts-tags" %><html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> ProductName:<s:property value="#session.product.productName"/> ProductName:<s:property value="#request.test.productName"/> <!-- 我们还可以调用对象栈的方法为一个属性赋值 --> <s:property value="setProductName('fyj')"/> </body> </html>