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>