<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>绿色记忆 &#187; Struts</title>
	<atom:link href="https://blog.gmem.cc/tag/struts/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Mon, 13 Apr 2026 08:03:10 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>Struts2学习笔记</title>
		<link>https://blog.gmem.cc/struts2-study-note</link>
		<comments>https://blog.gmem.cc/struts2-study-note#comments</comments>
		<pubDate>Tue, 21 Feb 2012 10:19:12 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[Struts]]></category>
		<category><![CDATA[学习笔记]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=13016</guid>
		<description><![CDATA[<p>简介 Struts2是一个流行的基于Java的MVC框架，它基于WebWork，因而最初被称为WebWork2。它具有以下特性： 基于POJO的表单和Action。Struts1的ActionForm已经被废弃，Action现在也没有任何接口的限定。任何一个Java类都可以作为Action使用 改进的表单标签、新标签 集成的Ajax支持 易于和其它框架整合，例如Spring 支持模板技术，利用模板生成视图。这些模板技术包括Velocity、FreeMarker等 插件支持 需要更少的配置 Struts2架构 类图和处理流程参考：MVC模式 Struts2由以下核心组件：Action、ActionContext、拦截器、值栈（ValueStack）、OGNL、结果/结果类型、视图构成： 关于Struts2的架构，需要注意： 与经典的MVC框架不同，Struts2的Action充当了Model角色，而不是控制器角色。Action有两大职责： 在一个方法中封装请求处理逻辑 作为数据传递的容器 结果路由，Action的返回值负责选择一个结果 控制器角色，则由一个Servlet过滤器：FilterDispatcher但当（此类已废弃，目前由StrutsPrepareAndExecuteFilter代替） 结果映射到一个视图 Action执行时的上下文，存放在基于ThreadLocal的ActionContext中。此上下文包含了ValueStack、请求、会话等对象 ValueStack是保存所有请求相关数据的存储区域，得益于ThreadLocal技术，这些数据在请求处理的全过程中都可以被访问 OGNL是一种表达式语言，可以用于操控值栈中的数据 <a class="read-more" href="https://blog.gmem.cc/struts2-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/struts2-study-note">Struts2学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<p>Struts2是一个流行的基于Java的MVC框架，它基于WebWork，因而最初被称为WebWork2。它具有以下特性：</p>
<ol>
<li>基于POJO的表单和Action。Struts1的ActionForm已经被废弃，Action现在也没有任何接口的限定。任何一个Java类都可以作为Action使用</li>
<li>改进的表单标签、新标签</li>
<li>集成的Ajax支持</li>
<li>易于和其它框架整合，例如Spring</li>
<li>支持模板技术，利用模板生成视图。这些模板技术包括Velocity、FreeMarker等</li>
<li>插件支持</li>
<li>需要更少的配置</li>
</ol>
<div class="blog_h2"><span class="graybg">Struts2架构</span></div>
<p>类图和处理流程参考：<a href="/mvc-pattern#struts2">MVC模式</a></p>
<p>Struts2由以下核心组件：Action、ActionContext、拦截器、值栈（ValueStack）、OGNL、结果/结果类型、视图构成：</p>
<p><img class="size-full wp-image-13020 aligncenter" src="https://blog.gmem.cc/wp-content/uploads/2016/08/struts_2_architecture.gif" alt="struts_2_architecture" width="459" height="392" /></p>
<p>关于Struts2的架构，需要注意：</p>
<ol>
<li>
<p>与经典的MVC框架不同，Struts2的Action充当了Model角色，而不是控制器角色。Action有两大职责：</p>
<ol>
<li>在一个方法中封装请求处理逻辑</li>
<li>作为数据传递的容器</li>
<li>结果路由，Action的返回值负责选择一个结果</li>
</ol>
</li>
<li>
<p>控制器角色，则由一个Servlet过滤器：FilterDispatcher但当（此类已废弃，目前由StrutsPrepareAndExecuteFilter代替）</p>
</li>
<li>结果映射到一个视图</li>
<li>Action执行时的上下文，存放在基于ThreadLocal的ActionContext中。此上下文包含了ValueStack、请求、会话等对象</li>
<li>ValueStack是保存所有请求相关数据的存储区域，得益于ThreadLocal技术，这些数据在请求处理的全过程中都可以被访问</li>
<li>OGNL是一种表达式语言，可以用于操控值栈中的数据</li>
</ol>
<div class="blog_h2"><span class="graybg">请求处理生命周期</span></div>
<ol>
<li>为了请求一些资源（即页面），用户发送请求到服务器</li>
<li>调用拦截器进行前置处理，实现验证，文件上传等功能</li>
<li>FilterDispatcher 查看请求，然后确定哪个Action处理它，然后调用Action</li>
<li>拦截器进行后置处理</li>
<li>处理视图，返回给浏览器</li>
</ol>
<div class="blog_h2"><span class="graybg">与Spring MVC的比较</span></div>
<p>Spring MVC和Struts是Java Web领域最流行的两个MVC框架，它们的<span style="background-color: #c0c0c0;">可扩展性都足够好</span>，能够满足绝大部分应用场景的需要。它们的比较如下：</p>
<ol>
<li>Spring MVC严格的分离控制器、JavaBean模型、视图等组件。而Struts2不是</li>
<li>Spirng MVC是Spring框架的一部分，可以和Spring其它组件无缝继承</li>
<li>Spring MVC更容易启用RESTful的Web服务</li>
<li>Spring MVC对JSON、XML等Ajax常用请求/响应格式的支持非常好，能根据请求头自动决定使用JSON还是XML。Struts需要通过扩展结果类型来手工实现XML/JSON的支持；要实现对JSON、XML请求体的支持，必须扩展拦截器</li>
<li>Spring MVC拦截器支持明确区分的preHandle、postHandle、afterCompletion三阶段，很容易在Handler（相当于Action）执行完毕之后，改变处理流程。然而Struts2不允许在postProcessing阶段改变处理流程，这导致定制404错误页面这样的功能难以基于拦截器实现</li>
<li>Struts的拦截机制可以针对不同包灵活的定制</li>
<li>Struts内置了Ajax支持</li>
<li>数据传递方式不同：Spring MVC采用方法参数注入；Struts采用JavaBean属性注入。前者的优势是，数据所属的业务非常明确，后者则会把多个业务的数据混在一起，大家都是属性——除非你编写很多细粒度的Action</li>
<li>Spring的@Controller是单例的，而Action实例针对每次请求创建</li>
</ol>
<div class="blog_h1"><span class="graybg">整合Spring示例</span></div>
<p>本章以一个用户管理系统为例，阐述如何搭建Struts + Spring的基本框架，以及Struts的基本用法。工程结构如下：</p>
<pre class="crayon-plain-tag">/home/alex/JavaEE/projects/idea/ssm-study
├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   │   └── cc
│   │   │       └── gmem
│   │   │           └── study
│   │   │               └── ssm
│   │   │                   ├── action
│   │   │                   │   └── UserAction.java
│   │   │                   ├── entity
│   │   │                   │   └── User.java
│   │   │                   └── service
│   │   │                       └── UserService.java
│   │   ├── resources
│   │   │   ├── applicationContext.xml
│   │   │   ├── log4j.properties
│   │   │   └── struts.xml
│   │   └── webapp
│   │       ├── css
│   │       │   └── style.css
│   │       └── WEB-INF
│   │           ├── jsp
│   │           │   ├── AccessDenied.jsp
│   │           │   └── Users.jsp
│   │           └── web.xml</pre>
<div class="blog_h2"><span class="graybg">配置文件</span></div>
<div class="blog_h3"><span class="graybg">Maven依赖</span></div>
<pre class="crayon-plain-tag">&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;log4j&lt;/groupId&gt;
        &lt;artifactId&gt;log4j&lt;/artifactId&gt;
        &lt;version&gt;1.2.15&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.slf4j&lt;/groupId&gt;
        &lt;artifactId&gt;slf4j-log4j12&lt;/artifactId&gt;
        &lt;version&gt;1.7.13&lt;/version&gt;
    &lt;/dependency&gt;

    &lt;dependency&gt;
        &lt;groupId&gt;javax.annotation&lt;/groupId&gt;
        &lt;artifactId&gt;jsr250-api&lt;/artifactId&gt;
        &lt;version&gt;1.0&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;javax.inject&lt;/groupId&gt;
        &lt;artifactId&gt;javax.inject&lt;/artifactId&gt;
        &lt;version&gt;1&lt;/version&gt;
    &lt;/dependency&gt;

    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework&lt;/groupId&gt;
        &lt;artifactId&gt;spring-web&lt;/artifactId&gt;
        &lt;version&gt;3.1.2.RELEASE&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.apache.struts&lt;/groupId&gt;
        &lt;artifactId&gt;struts2-core&lt;/artifactId&gt;
        &lt;version&gt;2.3.24&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;!-- 很多注解也定义在这个依赖中 --&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.apache.struts&lt;/groupId&gt;
        &lt;artifactId&gt;struts2-convention-plugin&lt;/artifactId&gt;
        &lt;version&gt;2.3.24&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;!-- 此插件让Action能够被依赖注入，但不需要作为Spring Bean扫描、由Spring创建实例 --&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.apache.struts&lt;/groupId&gt;
        &lt;artifactId&gt;struts2-spring-plugin&lt;/artifactId&gt;
        &lt;version&gt;2.3.24&lt;/version&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;</pre>
<div class="blog_h3"><span class="graybg">Web.xml配置</span></div>
<pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"&gt;
    &lt;context-param&gt;
        &lt;param-name&gt;contextConfigLocation&lt;/param-name&gt;
        &lt;param-value&gt;
            classpath*:applicationContext.xml
        &lt;/param-value&gt;
    &lt;/context-param&gt;
    &lt;listener&gt;
        &lt;listener-class&gt;
            org.springframework.web.context.ContextLoaderListener
        &lt;/listener-class&gt;
    &lt;/listener&gt;
    &lt;!-- Struts2.1 新版本的过滤器 --&gt;
    &lt;filter&gt;
        &lt;filter-name&gt;struts2&lt;/filter-name&gt;
        &lt;filter-class&gt;org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter&lt;/filter-class&gt;
    &lt;/filter&gt;
    &lt;filter-mapping&gt;
        &lt;filter-name&gt;struts2&lt;/filter-name&gt;
        &lt;!-- 映射/*，而不是 */.action**，这意味着所有的URL将被struts的过滤器解析 --&gt;
        &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
    &lt;/filter-mapping&gt;
&lt;/web-app&gt;</pre>
<div class="blog_h3"><span class="graybg">Spring配置</span> </div>
<pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;beans
        xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
    "&gt;
    &lt;context:component-scan base-package="cc.gmem.study.ssm"/&gt;
&lt;/beans&gt;</pre>
<div class="blog_h3"><span class="graybg">Struts配置</span></div>
<p>Struts默认配置文件为struts.xml，你可以在此文件中引入其它配置文件。很多配置信息可以用注解代替。</p>
<pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
        "http://struts.apache.org/dtds/struts-2.0.dtd"&gt;
&lt;struts&gt;
    &lt;!-- 启用开发模式，可以得到一些日志信息。更改配置文件不需要重启 --&gt;
    &lt;constant name="struts.devMode" value="true"/&gt;
    &lt;!-- 指定URL的后缀，具有此后缀的URL，由对应的Action负责处理 --&gt;
    &lt;constant name="struts.action.extension" value="action" /&gt;
    &lt;!-- 包可以把相关的动作分为一组  --&gt;
    &lt;!-- Struts的URL构成：http://host:port/Servlet上下文/包命名空间/动作名.action  --&gt;
    &lt;package name="usermgr" extends="struts-default" namespace="/usermgr"&gt;
        &lt;action name="home" class="cc.gmem.study.ssm.action.UserAction" method="home"&gt;
            &lt;!-- 结果命名了视图：当Action执行结果为success时，使用Users.jsp作为视图 --&gt;
            &lt;result name="success"&gt;/WEB-INF/jsp/Users.jsp&lt;/result&gt;
            &lt;!-- 可以定义多个结果 --&gt;
            &lt;result name="error"&gt;/WEB-INF/jsp/AccessDenied.jsp&lt;/result&gt;
        &lt;/action&gt;
    &lt;/package&gt;
&lt;/struts&gt;</pre>
<div class="blog_h2"><span class="graybg">源代码</span></div>
<div class="blog_h3"><span class="graybg">Action</span></div>
<pre class="crayon-plain-tag">package cc.gmem.study.ssm.action;

import cc.gmem.study.ssm.entity.User;
import cc.gmem.study.ssm.service.UserService;
import com.opensymphony.xwork2.ActionSupport;

import javax.inject.Inject;
import java.util.List;
// Action类可以选择扩展ActionSupport
public class UserAction extends ActionSupport {
    // 自动进行依赖注入，不需要作为Spring Bean
    @Inject
    private UserService service;

    private List&lt;User&gt; users;

    // 添加getter，把Action的property变为值栈的Key，暴露给其它Struts组件
    public List&lt;User&gt; getUsers() {
        return users;
    }

    // 对动作的唯一要求：是一个0参方法，返回字符串或者Result对象
    public String home() throws Exception {
        users = service.loadAllUsers();
        return SUCCESS; // 此常量来自ActionSupport
    }
}</pre>
<div class="blog_h3"><span class="graybg">Entity</span></div>
<pre class="crayon-plain-tag">public class User {
    private int id;
    private int age;
    private String name;
    // getter/setter/constructor略
}</pre>
<div class="blog_h3"><span class="graybg">Service</span> </div>
<pre class="crayon-plain-tag">@Service
public class UserService {
    private List&lt;User&gt; userdb;

    @PostConstruct
    public void init() {
        userdb = new ArrayList&lt;User&gt;();
        userdb.add( new User( 1, 30, "Alex Wong" ) );
        userdb.add( new User( 2, 30, "Meng Lee" ) );
        userdb.add( new User( 3, 30, "Caicai Wong" ) );
    }

    public List&lt;User&gt; loadAllUsers() {
        return userdb;
    }
}</pre>
<div class="blog_h3"><span class="graybg">View</span> </div>
<pre class="crayon-plain-tag">&lt;%@ page contentType="text/html;charset=UTF-8" language="java" %&gt;
&lt;%-- 声明使用Struts的标签库 --%&gt;
&lt;%@ taglib prefix="s" uri="/struts-tags" %&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;title&gt;Users List&lt;/title&gt;
    &lt;%-- 静态资源的解析与框架无关 --%&gt;
    &lt;link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}css/style.css"/&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;%-- 你可以使用EL表达式，或者标签库来访问值栈 --%&gt;
&lt;div&gt;Loaded ${users.size()} users &lt;span class="comment"&gt;(&lt;s:property value="users.size()"/&gt; users available)&lt;/span&gt;:&lt;/div&gt;
&lt;div&gt;
    &lt;%-- 使用标签库来进行条件分支、迭代等操作 --%&gt;
    &lt;table&gt;
        &lt;tr&gt;
            &lt;th&gt;ID&lt;/th&gt;
            &lt;th&gt;NAME&lt;/th&gt;
            &lt;th&gt;AGE&lt;/th&gt;
        &lt;/tr&gt;
        &lt;s:iterator value="users"&gt;
            &lt;tr&gt;
                &lt;td&gt;&lt;s:property value="id"/&gt;&lt;/td&gt;
                &lt;td&gt;&lt;s:property value="name"/&gt;&lt;/td&gt;
                &lt;td&gt;&lt;s:property value="age"/&gt;&lt;/td&gt;
            &lt;/tr&gt;
        &lt;/s:iterator&gt;
    &lt;/table&gt;
&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
<div class="blog_h2"><span class="graybg">让Spring管理Action</span></div>
<p>即使你启用了struts2-spring-plugin插件，默认情况下也仅仅是支持从Application Context获得注入，Action的生命周期仍然是由Struts2自己管理的。</p>
<p>要让Spring管理这些Action，你必须：</p>
<ol>
<li>在Spring中扫描Action类</li>
<li>如果使用基于XML的Struts配置，则需要把action元素的class字段改为Spring Bean的ID</li>
</ol>
<div class="blog_h3"><span class="graybg">多例Action</span></div>
<p>一般情况下，我们为每个请求创建一个Action，然而Spring Bean默认是单例的。因此在集成Spring时需要注意配置：</p>
<pre class="crayon-plain-tag">&lt;bean id="userAction" class="cc.gmem.study.ssm.action.UserAction" scope="prototype" /&gt;</pre>
<div class="blog_h1"><span class="graybg">包</span></div>
<p>不管你使用XML还是注解方式进行配置，当框架运行时，Action和其它组件都被一起存放到称为包（Package）的逻辑容器内。在配置文件中包使用package元素表示。</p>
<p>包提供了一种基于功能（比如开放功能、需授权功能）或者业务共性（必须系统管理、设备管理）来分组Action的机制。包提供了继承机制，你可以继承Struts2预定义的包，拓展功能。</p>
<div class="blog_h2"><span class="graybg">包的属性</span></div>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>name</td>
<td>包的名称，唯一标识符。其它组件可以通过此名称引用包</td>
</tr>
<tr>
<td>namespace</td>
<td>
<p>限定了其内部Action的URL前缀，此前缀可以包含多个/ </p>
<p>两个包可以具有相同的命名空间</p>
<p>如果在命名空间内，找不到匹配的Action，Struts会<span style="background-color: #c0c0c0;">到默认命名空间（""）去寻找名字等于URL末段的Action</span></p>
<p>注意两个概念的不同：</p>
<ol>
<li>根命名空间：即"/"，显式定义，与其它命名空间一样，必须严格匹配URL前缀，才会调用其内的Action</li>
<li>默认命名空间：即""，不需要定义。不被包含在package声明内部的组件，都定义在默认命名空间</li>
</ol>
</td>
</tr>
<tr>
<td>extends</td>
<td>
<p>该包继承自的包的名称，可以用逗号分隔，指定多个</p>
<p>当前包会获得所有父包定义的成员，还可以覆盖之</p>
</td>
</tr>
<tr>
<td>abstract</td>
<td>如果为true，该包仅仅定义可继承组件，不能定义Action</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">struts-default包</span></div>
<p>该包在struts-defaults.xml中定义，你可以获得很多组件——例如一组拦截器栈（interceptor-stack）、一组常用的结果类型（result-type）：</p>
<pre class="crayon-plain-tag">&lt;struts&gt;
    &lt;package name="struts-default" abstract="true"&gt;
        &lt;result-types&gt;...&lt;/result-types&gt;
        &lt;interceptors&gt;
            &lt;!-- 拦截器栈，从上向下调用 --&gt;
            &lt;interceptor-stack name="defaultStack"&gt;
                &lt;interceptor-ref name="exception"/&gt;
                &lt;interceptor-ref name="alias"/&gt;
                &lt;interceptor-ref name="servletConfig"/&gt;
                &lt;interceptor-ref name="i18n"/&gt;
                &lt;interceptor-ref name="prepare"/&gt;
                &lt;interceptor-ref name="chain"/&gt;
                &lt;interceptor-ref name="scopedModelDriven"/&gt;
                &lt;interceptor-ref name="modelDriven"/&gt;
                &lt;interceptor-ref name="fileUpload"/&gt;
                &lt;interceptor-ref name="checkbox"/&gt;
                &lt;interceptor-ref name="datetime"/&gt;
                &lt;interceptor-ref name="multiselect"/&gt;
                &lt;interceptor-ref name="staticParams"/&gt;
                &lt;interceptor-ref name="actionMappingParams"/&gt;
                &lt;interceptor-ref name="params"/&gt;
                &lt;interceptor-ref name="conversionError"/&gt;
                &lt;interceptor-ref name="validation"&gt;
                    &lt;param name="excludeMethods"&gt;input,back,cancel,browse&lt;/param&gt;
                &lt;/interceptor-ref&gt;
                &lt;interceptor-ref name="workflow"&gt;
                    &lt;param name="excludeMethods"&gt;input,back,cancel,browse&lt;/param&gt;
                &lt;/interceptor-ref&gt;
                &lt;interceptor-ref name="debugging"/&gt;
                &lt;interceptor-ref name="deprecation"/&gt;
            &lt;/interceptor-stack&gt;
        &lt;/interceptors&gt;
        &lt;!-- 该包默认使用的拦截器栈 --&gt;
        &lt;default-interceptor-ref name="defaultStack"/&gt;
        &lt;default-class-ref class="com.opensymphony.xwork2.ActionSupport"/&gt;
    &lt;/package&gt;
&lt;/struts&gt;</pre>
<p>通常我们会从此包继承，你会获得一个默认的拦截器栈。struts2框架<span style="background-color: #c0c0c0;">很多核心功能都是通过此拦截器栈</span>完成的。</p>
<div class="blog_h1"><span class="graybg">Action</span></div>
<p>该组件是Struts2框架的核心，也是日常使用最多的组件。前面我们提到到过Action的三大职责：</p>
<ol>
<li>封装逻辑：Action作为匹配URL的入口点，包含对业务逻辑的调用</li>
<li>数据容器：Action类的JavaBean属性自动作为ValueStack的直接Key，你需要的任何数据都可以作为Action属性来声明</li>
<li>结果路由：Action返回结果，指定了使用什么视图</li>
</ol>
<p>注意与Struts1不同，Struts2的Action不是单例的，<span style="background-color: #c0c0c0;">每个请求都被赋予一个新的Action实例</span>。</p>
<p>任何POJO的方法都可以作为Action，作为Action的方法必须0-arg且返回Result或字符串。可选的，你可以实现Action接口，从而获得一些常量。</p>
<div class="blog_h2"><span class="graybg">ActionSupport类</span></div>
<p>该类实现了Action和其它几个有用的接口。提供了：数据验证、错误消息本地化等功能。一般你可以选择从此类继承，编写新的Action。本节介绍此类提供的功能。</p>
<div class="blog_h3"><span class="graybg">基本验证</span></div>
<p>虽然Struts2提供了丰富、高度可配置的验证框架，但是ActionSupport可以让你快速的完成表单的基本验证。ActionSupport基于来自默认拦截器栈的workflow（DefaultWorkflowInterceptor）和两个接口（Validateable、ValidationAware）实现验证功能。</p>
<p>要实现验证，你可以覆盖Validateable的唯一方法：</p>
<pre class="crayon-plain-tag">public class UserAction extends ActionSupport {
    private User user;

    @Override
    public void validate() {
        if(user.getName() ==null){
            // 创建和存储错误消息，方法来自ValidationAware，类似的还有addActionError
            addFieldError( "name", "User name is required");
        }
    }
}</pre>
<p>该方法会被workflow自动调用，调用后如果错误消息不为空，则workflow会改变请求处理流程，自动转向名字为input的结果。该结果对应的页面往往就是用户提交表单时的那个页面。</p>
<div class="blog_h3"><span class="graybg">消息本地化</span></div>
<p>ActionSupport实现了TextProvider、LocaleProvider接口，通过这两个接口可以实现消息本地化：</p>
<ol>
<li>TextProvider提供了读取资源束的功能，你可以调用其getText()方法获得消息文本</li>
<li>LocaleProvider只提供了一个获取当前Locale的方法</li>
</ol>
<div class="blog_h1"><span class="graybg">URL映射</span></div>
<p>前面讨论包的时候我们提到过URL如何映射到Action：</p>
<ol>
<li>由名字空间前缀 + Action名字 + Action后缀，确定一个Action对应的URL</li>
<li>对于默认名字空间中的Action，可以不考虑名字空间前缀进行匹配</li>
</ol>
<p>除了这种简单的映射外，Struts2还支持通配符映射以及类似于Spring MVC的路径变量，这些方式都承担了一些<span style="background-color: #c0c0c0;">数据绑定</span>的职责。 </p>
<div class="blog_h2"><span class="graybg">通配符映射</span></div>
<p>Struts还支持所谓通配符映射，即在声明包的namespace、Action的name时，指定通配符：</p>
<ol>
<li><pre class="crayon-plain-tag">*</pre> ，通配0-N个字符，但是不能跨越斜杠/</li>
<li><pre class="crayon-plain-tag">**</pre> ，通配0-N个字符，且可以跨越斜杠/</li>
</ol>
<p>所有通配符匹配的文本（捕获），被从1开始编号。然后你可以在Action配置文件中使用<pre class="crayon-plain-tag">{N}</pre> 的方式引用它：</p>
<pre class="crayon-plain-tag">&lt;action name="/edit*" class="cc.gmem.study.ssm.Edit{1}Action"&gt;
    &lt;result&gt;{1}.jsp&lt;/result&gt;
&lt;/action&gt;</pre>
<p>如果多个通配符映射匹配了一个URL，那么，在配置文件<span style="background-color: #c0c0c0;">最后面声明的那个</span>映射被启用。 </p>
<p>你可以在很多地方使用通配符捕获：</p>
<pre class="crayon-plain-tag">&lt;action name="/edit/*" class="cc.gmem.study.ssm.EditAction"&gt;
    &lt;param name="id"&gt;{1}&lt;/param&gt;
    &lt;result&gt;
      &lt;param name="location"&gt;/mainMenu.jsp&lt;/param&gt;
      &lt;param name="id"&gt;{1}&lt;/param&gt; 
    &lt;/result&gt;
&lt;/action&gt;</pre>
<div class="blog_h3"><span class="graybg">Action名字中的斜杠</span></div>
<p>默认的情况下，Action名字中是不允许出现斜杠的，你可以解除此限制：</p>
<pre class="crayon-plain-tag">&lt;constant name="struts.enable.SlashesInActionNames" value="true"/&gt;
&lt;constant name="struts.mapper.alwaysSelectFullNamespace" value="false"/&gt;</pre>
<p>然后，就可以这样使用通配符映射了：</p>
<pre class="crayon-plain-tag">&lt;action name="/person/*" class="cc.gmem.study.ssm.EditAction"&gt;
    &lt;param name="id"&gt;{1}&lt;/param&gt;
&lt;/action&gt;</pre>
<div class="blog_h2"><span class="graybg">路径变量</span></div>
<div class="blog_h3"><span class="graybg">名字空间路径变量</span></div>
<p>启用此功能：</p>
<pre class="crayon-plain-tag">&lt;constant name="struts.patternMatcher" value="namedVariable"/&gt;</pre>
<p>把名字空间中的一段捕获为Action属性的例子：</p>
<pre class="crayon-plain-tag">&lt;package name="crud" extends="struts-default" namespace="/crud/{entity}"&gt;
    &lt;!-- Action的entity字段使用捕获来填充 --&gt;
    &lt;action name="create" class="cc.gmem.study.ssm.action.CrudAction" method="create"&gt;
        &lt;result name="success"&gt;/WEB-INF/jsp/CreateEntity.jsp&lt;/result&gt;
    &lt;/action&gt;
&lt;/package&gt;</pre>
<div class="blog_h3"><span class="graybg">Action名中的路径变量</span></div>
<p>从2.1.9+开始，可以启用此功能：</p>
<pre class="crayon-plain-tag">&lt;constant name="struts.enable.SlashesInActionNames" value="true"/&gt;
&lt;constant name="struts.mapper.alwaysSelectFullNamespace" value="false"/&gt;
&lt;!-- 基于命名变量的路径匹配 --&gt;
&lt;constant name="struts.patternMatcher" value="namedVariable" /&gt;
&lt;!-- 或者，基于正则式的路径匹配 --&gt;
&lt;constant name="struts.patternMatcher" value="regex" /&gt;</pre>
<p>把Action路径中一段捕获为Action属性的例子：</p>
<pre class="crayon-plain-tag">&lt;package name="crud" extends="struts-default" namespace="/crud"&gt;
    &lt;action name="/update/{entity}/{id}" class="cc.gmem.study.ssm.action.CrudAction" method="update"&gt;
        &lt;result name="success"&gt;/WEB-INF/jsp/update-${entity}.jsp&lt;/result&gt;
    &lt;/action&gt;
&lt;/package&gt;</pre><br />
<pre class="crayon-plain-tag">public class CrudAction extends ActionSupport {
    private String entity;
    private String id;

    public String update() throws Exception {
        return SUCCESS;
    }
}</pre>
<p>注意：此功能不能和名字空间路径变量一起使用。</p>
<div class="blog_h1"><span class="graybg">拦截器</span></div>
<p>拦截器类似于Servlet过滤器，你可以使用拦截器实现横切（Cross-cutting）的逻辑：</p>
<ol>
<li>在Action被调用之前提供Preprocessing逻辑</li>
<li>在Action被调用之后提供Postprocessing逻辑</li>
<li>捕获异常并处理</li>
</ol>
<p>Action被调用时，其关联的拦截器栈会首先被调用。拦截器栈实际上是一系列拦截器构成的链条，之所以称为栈（也必须是栈，这取决于方法调用本身的特点），是因为最先Preprocessing的那个拦截器，最后获得Postprocessing的机会。拦截器由下面的接口定义：</p>
<pre class="crayon-plain-tag">public abstract class AbstractInterceptor implements Interceptor {
    // 拦截器接口并没有为预处理、后处理分别定义方法，所谓预、后只取决于调用此方法的时机：
    public abstract String intercept(ActionInvocation invocation) throws Exception;
}</pre>
<p>如果继承struts-default包，你将获得一个默认的拦截器栈。Struts的拦截器栈非常灵活，除了可以使用默认的（满足大部分场景）的栈，你还可以<span style="background-color: #c0c0c0;">为包、甚至Action定制拦截器栈</span>。定制行为包括顺序重排、添加/删除拦截器等。</p>
<p>Struts框架提供的很多功能是基于拦截器实现的，例如异常处理，文件上传，生命周期回调和验证。Struts把这些通用的逻辑分离到拦截器中，让你的Action尽可能简洁。</p>
<div class="blog_h2"><span class="graybg">拦截器工作原理</span></div>
<div class="blog_h3"><span class="graybg">ActionInvocation</span></div>
<p>当框架接收到一个HTTP请求后，首先需要决定此URL映射到哪个Action。此Action的一个新实例会被加入到一个新的ActionInvocation中。</p>
<p>接着框架查询配置信息，确定哪些拦截器需要按何种顺序触发，并把这些拦截器的引用加入到ActionInvocation中。</p>
<p>此外当前HttpServletRequest、当前Action可用的结果对象，也被ActionInvocation引用。</p>
<p>框架通过调用ActionInvocation.invoke方法，将控制权转移给ActionInvocation，后者是后续处理过程的总指挥。</p>
<div class="blog_h3"><span class="graybg">拦截器触发</span></div>
<p>ActionInvocation维持一个指针，随着拦截器的调用推进，该指针会不断移向下一个拦截器，直到最终指向Action。</p>
<p>ActionInvocation首先调用第一个拦截器的intercept方法。拦截器首先进行进行Preprocessing——准备、过滤、改变或者操控任何请求相关的数据（包括Action本身），Preprocessing完毕后通常会调用ActionInvocation.invoke，这导致下一个拦截器或者最后的Action被调用。</p>
<p>每个拦截器都具有<span style="background-color: #c0c0c0;">改变请求处理流程</span>的能力，因为请求处理流程本身就依赖于拦截器调用ActionInvocation.invoke方法来驱动。只要拦截器<span style="background-color: #c0c0c0;">不调用invoke，自己返回一个控制字符串</span>，正常处理流程即被中止——后面的拦截器、Action都不会被调用。例如workflow拦截器如果发现验证有错误，直接返回控制字符串input，input通常映射到出错的表单。注意：流传被中止后，立即开始当前拦截器的Postprocessing。</p>
<p>如果处理流程没有被终止，那么请求最终交由Action处理。Action执行完毕后，结果页面即渲染完毕。</p>
<p>每个拦截器都可以在ActionInvocation.invoke调用的后面添加Postprocessing代码。但此时Action已经被调用，Postprocessing不会影响到页面内容。</p>
<div class="blog_h2"><span class="graybg">预定义拦截器</span></div>
<p>Struts2框架提供了一些开箱即用的拦截器，这些拦截器中，很多是默认拦截器栈的成员。括号内标注D的是默认拦截器栈成员。</p>
<div class="blog_h3"><span class="graybg">计时器（timer）</span></div>
<p>记录请求处理消耗的时间，它在栈中的位置觉得了它记录的是哪些代码消耗的时间。</p>
<div class="blog_h3"><span class="graybg">日志记录（logger）</span></div>
<p>简单的日志记录，记录预处理的进入、后处理的退出。</p>
<div class="blog_h3"><span class="graybg">请求参数绑定（params,D）</span></div>
<p>将请求参数绑定到ValueStack公开的属性上。拦截器并不知道数据最终被送到哪里，它只是将其绑定到ValueStack第一个匹配的属性上去。</p>
<p>那么ValueStack中的属性是哪里来的呢？前面提过，Action对象在请求处理开始时即被放到ValueStack上。而对于ModelDriven暴露的模型，则由modelDriven拦截器负责放到ValueStack上。</p>
<div class="blog_h3"><span class="graybg">静态参数绑定（static-params,D）</span></div>
<p>与请求参数绑定类似，不同的是参数的来源。可以绑定Action声明的静态参数：</p>
<pre class="crayon-plain-tag">&lt;action&gt;
    &lt;param name="dob"&gt;1986-09-12&lt;/param&gt;
&lt;/action&gt;</pre>
<p>注意：在默认拦截器栈中，此拦截器先于params拦截器触发，因此请求参数可以覆盖静态参数。</p>
<div class="blog_h3"><span class="graybg">自动注入拦截器（autowiring）</span></div>
<p> 让明明不是Spring Bean的Action，能够被Spring自动注入属性。</p>
<div class="blog_h3"><span class="graybg">servlet-config（D）</span></div>
<p>用于把Servlet API中的各种对象注入到Action字段中。你可以让Action实现不同的接口以获得相应的注入：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">接口</td>
<td style="text-align: center;">注入</td>
</tr>
</thead>
<tbody>
<tr>
<td>ServletContextAware</td>
<td>ServletContext</td>
</tr>
<tr>
<td>ServletRequestAware</td>
<td>HttpServletRequest</td>
</tr>
<tr>
<td>ServletResponseAware</td>
<td>HttpServletResponse</td>
</tr>
<tr>
<td>ParameterAware</td>
<td>Map类型的请求参数集</td>
</tr>
<tr>
<td>RequestAware</td>
<td>Map类型的请求属性集</td>
</tr>
<tr>
<td>ApplicationAware</td>
<td>Map类型的ServletContext属性</td>
</tr>
<tr>
<td>PrincipalAware</td>
<td>安全相关的Principal对象</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">文件上传（fileupload）</span></div>
<p>使用此拦截器时，你只需要设置好表单的enctype以及Action属性即可。上传单个文件的例子：</p>
<pre class="crayon-plain-tag">&lt;s:form action="upload" method="POST" enctype="multipart/form-data"&gt;
    &lt;s:file name="pic"/&gt;
&lt;/s:form&gt;</pre><br />
<pre class="crayon-plain-tag">public class UploadAction {
    File pic;
    String picContentType;
    String picFileName;
}</pre>
<p>如果要上传多个文件，则需要把上面三个属性都改为数组。 </p>
<div class="blog_h3"><span class="graybg">工作流（workflow,D）</span></div>
<p>该拦截器与Action配合，提供数据验证机制，并在验证失败后改变后续工作流。workflow通过判断ValidationAware.hasErrors()的返回值，确认验证是否失败。</p>
<p>与Action一样，拦截器也可以通过参数来调整行为。workflow提供以下参数：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">参数</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>inputResultName</td>
<td>验证失败时，使用的结果的名称，默认Action.INPUT</td>
</tr>
<tr>
<td>excludeMethods</td>
<td>逗号分隔的方法名，哪些Action入口方法不执行该拦截器</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">验证（validataion,D）</span></div>
<p>前面我们介绍过，ActionSupport基于Validateable接口，支持编程式的验证机制。Struts也支持声明式验证，而validataion过滤器则是声明式验证的核心。</p>
<p>注意：workflow并不关心你使用编程式、还是声明式的验证。它只负责去调用ValidationAware接口。因此，为了让<span style="background-color: #c0c0c0;">validataion生效，必须将其放在workflow的前面</span>。</p>
<div class="blog_h3"><span class="graybg">准备（prepare,D）</span></div>
<p>用于把预处理代码转移到Action中，让后者决定做什么。该拦截器检查Action是否实现了Preparable接口，如果是则：</p>
<ol>
<li>调用ActionClass.prepareActionName、ActionClass.prepareDoActionName根据firstCallPrepareDo参数决定调用顺序</li>
<li>如果alwaysInvokePrepare，调用ActionClass.prepare()方法</li>
</ol>
<div class="blog_h3"><span class="graybg">模型驱动（modelDriven,D）</span></div>
<p>该拦截器调用getModel()方法，把模型对象放到ValueStack上接收请求参数。如果没有该拦截器，参数会被params拦截器直接绑定到Action对象上。</p>
<div class="blog_h3"><span class="graybg">异常（exception,D）</span></div>
<p>该拦截器是程序中丰富异常处理的基础。该拦截器位于默认拦截器栈的第一位，它也应该在所有栈的第一位，这样才能确保它有机会拦截所有异常。</p>
<p>根据异常类型，该拦截器会转向不同的错误页面。例如下面的配置：</p>
<pre class="crayon-plain-tag">&lt;global-results&gt;
    &lt;result name="error"&gt;/error.jsp&lt;/result&gt;
&lt;/global-results&gt;
&lt;global-exception-mappings&gt;
    &lt;exception-mapping exception="java.lang.RuntimeException" result="error"&gt;&lt;/exception-mapping&gt;
&lt;/global-exception-mappings&gt;</pre>
<p>如果exception拦截器捕获了RuntimeException，它会显示error.jsp。</p>
<div class="blog_h3"><span class="graybg">令牌（token）</span></div>
<p>该拦截器和token-session拦截器可以用于防止表单重复提交。该拦截器检查请求中传入的令牌，如果同一令牌第二次出现，说明这是重复的表单提交。</p>
<div class="blog_h3"><span class="graybg">scoped-modelDriven</span></div>
<p>该拦截器拓展了modelDriven的功能，允许模型跨请求存在（例如存放在Session中），可以实现向导式的业务。</p>
<div class="blog_h2"><span class="graybg">配置拦截器栈</span></div>
<div class="blog_h3"><span class="graybg">声明拦截器/栈</span></div>
<p>拦截器/栈的声明必须位于package内部：</p>
<pre class="crayon-plain-tag">&lt;package name="pkgname" abstract="true"&gt;
    &lt;interceptors&gt;
        &lt;!-- 声明一个拦截器，names是它的逻辑名称，供包内其它组件引用 --&gt;
        &lt;interceptor name="intname" class="package.IntName"/&gt;
        &lt;!-- 声明一个拦截器栈--&gt;
        &lt;interceptor-stack name="stackname"&gt;
            &lt;!-- 引用前面声明的拦截器、或者拦截器栈 --&gt;
            &lt;interceptor-ref name="alias"&gt;
                &lt;!-- 可以为拦截器配置参数 --&gt;
                &lt;param name="name"&gt;value&lt;/param&gt;
            &lt;/interceptor-ref&gt;
        &lt;/interceptor-stack&gt;
    &lt;/interceptors&gt;
    &lt;!-- 声明包内Action默认使用哪个栈 --&gt;
    &lt;default-interceptor-ref name="stackname"/&gt;
&lt;/package&gt;</pre>
<div class="blog_h3"><span class="graybg">关联栈到Action</span></div>
<p>下面的声明，让Action使用具有两个拦截器的栈：</p>
<pre class="crayon-plain-tag">&lt;action name="home" class="cc.gmem.study.ssm.action.UserAction" method="home"&gt;
    &lt;interceptor-ref name="timer"/&gt;
    &lt;interceptor-ref name="logger"/&gt;
&lt;/action&gt;</pre>
<p>一般情况下，上面的配置是没意义的，因为Struts2大部分功能都位于默认栈中，因此我们一般可以追加引用默认栈：</p>
<pre class="crayon-plain-tag">&lt;action name="home" class="cc.gmem.study.ssm.action.UserAction" method="home"&gt;
    &lt;interceptor-ref name="timer"/&gt;
    &lt;interceptor-ref name="logger"/&gt;
    &lt;interceptor-ref name="defaultStack"/&gt; &lt;!-- 必须扩展structs-default包，才能引用此栈  --&gt;
&lt;/action&gt;</pre>
<div class="blog_h3"><span class="graybg">覆盖拦截器参数</span></div>
<p>设置拦截器参数，只需要简单声明param子元素即可。那么，如何覆盖默认拦截器栈中的某个拦截器的某个参数呢。也不复杂： </p>
<pre class="crayon-plain-tag">&lt;interceptor-ref name="defaultStack"&gt;
    &lt;!-- 使用拦截器名.参数名指定name --&gt;
    &lt;param name="workflow.excludeMethods"&gt;hello&lt;/param&gt;
&lt;/interceptor-ref&gt;</pre>
<div class="blog_h1">值栈与Action上下文</div>
<div class="blog_h2"><span class="graybg">ValueStack</span></div>
<p>值栈是Struts2中的一个数据结构，它由一系列对象构成。值栈对外表现为一个虚拟对象：一系列对象属性的聚合。虚拟对象的属性，就是栈上那些对象的属性，如果值栈中有两个对象具有name属性，那么位置更高（更加接近栈顶）的那个对象的属性，作为虚拟对象的属性。</p>
<p>值栈代表当前请求的数据模型（领域数据），是所有OGNL表达式求值时的<span style="background-color: #c0c0c0;">默认</span>上下文对象。</p>
<div class="blog_h2"><span class="graybg">ActionContext</span></div>
<p>与请求处理相关的数据，不仅仅包含存放在值栈中的领域数据，一些更基础的、业务无关的数据也必须存储起来。所有这些数据，连同ValueStack，都被存放在ActionContext中。</p>
<p>尽管OSGL表达式默认的解析上下文是ValueStack，你可以通过指定ActionContext属性的名字，让OSGL针对其它对象求值。可用的ActionContext属性包括：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>ValueStack</td>
<td>默认的求值上下文，值栈</td>
</tr>
<tr>
<td>parameters</td>
<td>当前请求中请求参数构成的Map</td>
</tr>
<tr>
<td>application</td>
<td>
<p>当前应用作用域属性的Map</p>
</td>
</tr>
<tr>
<td>session</td>
<td>当前会话作用域属性的Map</td>
</tr>
<tr>
<td>request</td>
<td>当前请求作用域属性的Map</td>
</tr>
<tr>
<td>attr</td>
<td>按照页面、请求、会话、应用顺序，返回第一个出现的属性</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">获得当前Action上下文</span></div>
<p>你可以随时使用下面的API得到当前的Action上下文并操控它：</p>
<pre class="crayon-plain-tag">request = ServletActionContext.getRequest();
response = ServletActionContext.getResponse();
ActionContext context = ActionContext.getContext();
// 获得所有请求参数
Map&lt;String, Object&gt; parameters = context.getParameters();
// 获得当前会话属性
Map&lt;String, Object&gt; session = context.getSession();
// 获得当前Servlet上下文属性
Map&lt;String, Object&gt; application = context.getApplication();
// 获得并操控值栈
ValueStack vs = context.getValueStack();
vs.push( new User( "Alex", 31, "1986-09-12" ) );
vs.push( new User( null, 0, "2017-04-20" ) );
assert vs.findValue( "name" ) == null;</pre>
<div class="blog_h1"><span class="graybg"><a id="ognl"></a>OGNL</span></div>
<p>OGNL（Object-Graph Navigation Language，对象图导航语言）是Struts2默认的表达式语言。它还帮助实现数据绑定、类型转换。在Struts中它是基于字符串的HTTP输入/输出与Java内部对象之间的粘合剂：</p>
<p><img class="alignnone size-full wp-image-13051" src="https://blog.gmem.cc/wp-content/uploads/2011/02/ognl.png" alt="ognl" width="100%" /></p>
<p>在数据进入值栈的阶段，OGNL解析请求中的点号导航，映射到值栈中的属性，根据此属性的类型，找到框架提供的类型转换器，转换值并设置到值栈上。</p>
<p>在数据进入视图的阶段，OGNL解析标签中的点号导航，根据类型转换器，把值栈属性值转换为字符串格式。</p>
<div class="blog_h2"><span class="graybg">混用EL</span></div>
<p>你可以在基于Struts2标签库的JSP中使用EL表达式。但是要注意：</p>
<ol>
<li>EL表达式是将pageScope、requestScope、sessionScope、applicationScope作为上下文来解析表达式的，此外它还可以访问param、paramValues、header、headerValues、cookie、pageContext等对象</li>
<li>在Struts2中EL表达式不是XSS安全的，你必须这样：<pre class="crayon-plain-tag">${fn:escapeXml(name)}</pre> 才能保证安全</li>
</ol>
<div class="blog_h2">转义</div>
<p>在Struts标签中使用OGNL表达式时，如果标签属性不是String类型，则不必对OGNL表达式进行任何转义。如果标签的属性是String类型，需要用<pre class="crayon-plain-tag">%{ognlExpr}</pre> 格式转义：</p>
<pre class="crayon-plain-tag">&lt;!-- 字面值  --&gt;
&lt;s:tagname strattr="obj.field"/&gt;
&lt;!-- 以值栈为上下文估算  --&gt;
&lt;s:tagname strattr="%{obj.field}"/&gt; 
&lt;!-- 以Action上下文的obj属性来估算field --&gt;
&lt;s:tagname strattr="%{#obj.field}"/&gt; </pre>
<p>在Struts2等声明性配置文件中使用表达式时，必须使用类似于EL表达式语言的<pre class="crayon-plain-tag">${ognlExpr}</pre> 格式转义。</p>
<div class="blog_h2"><span class="graybg">语法</span></div>
<p>OGNL大部分语法都是C风格的，这里用若干示例来阐述：</p>
<div class="blog_h3"><span class="graybg">属性导航</span></div>
<pre class="crayon-plain-tag">// 简单的属性导航
alex.address.tel
// 集合导航
list[0]
array[0]
list[0].address
users['alex'].addresses[3].city</pre>
<div class="blog_h3">属性设置</div>
<pre class="crayon-plain-tag">// 可以直接赋值给属性导航表达式 
alex.address.tel = '13309029039'
list[0] = 100</pre>
<div class="blog_h3"><span class="graybg">访问集合</span></div>
<pre class="crayon-plain-tag">// 使用过Set
set.iterator      // 返回Set的迭代器
// 列表、数组属性
list.size         // list.size()
array.length      // array.length
list.isEmpty      // list.isEmpty()
{1, 2, 3}         // 动态创建List，这是一个直接量

// Native构造法
new int[] { 1, 2, 3 }
new int[5]

// 使用Map
map['stringkey']
map.stringkey
map[123]
map['alex'].age
map.size
map.isEmpty
#{1:"one", 2:"two"}                      // 动态创建Map，这是一个直接量
// 指定Map的Java类型
#@java.util.LinkedHashMap@{ "foo" : "foo value", "bar" : "bar value" }

// 使用迭代器
iter.next        // 获得下一个元素
iter.hasNext     // 判断是否存在下一个元素


// 集合过滤，语法：collectionName.{? expr}
users.{? #this.age &gt; 30}                 // 获得一个子集，每个用户的age大于30

// 得到集合第一个匹配的元素
objects.{^ #this instanceof String }
// 得到集合中最后一个匹配的元素
objects.{$ #this instanceof String }

// 集合投影，语法：collectionName.{expr}
users.{firstName + '' + lastname}        // 根据两个字段建立新的集合
objects.{ #this instanceof String ? #this : #this.toString()}</pre>
<div class="blog_h3"><span class="graybg">直接量</span> </div>
<pre class="crayon-plain-tag">// 字面值
true                      // boolean
'c'                       // char
'str'                     // String
123.45                    // double
123b                      // BigDecimal
123h                      // BigInteger
{1, 2, 3}                 // List
{'alex':30}               // Map</pre>
<div class="blog_h3"><span class="graybg">方法调用</span> </div>
<pre class="crayon-plain-tag">// 0参方法调用
user.getAge()
// 1参方法调用。注意：括号(ensureLoaded(), name) 内的是逗号表达式，最后一个作为表达式的值
method( (ensureLoaded(), name) )
// 2参方法调用
method( ensureLoaded(), name )</pre>
<div class="blog_h3"><span class="graybg">静态成员访问</span></div>
<pre class="crayon-plain-tag">// 静态成员访问: @fullclassname@property_or_methodcall
@cc.gmem.study.ssm.User@DEFAULT_AGE
@cc.gmem.study.ssm.User@load(1)</pre>
<div class="blog_h3"><span class="graybg">访问当前上下文</span></div>
<pre class="crayon-plain-tag">// 当前上下文对象，在Struts中通常是值栈
#this.getClass().getName()

// 在链式子表达式中访问当前上下文，这里上下文是一个数字
listeners.size().(#this &gt; 100? 2*#this : 20+#this)</pre>
<div class="blog_h3"><span class="graybg">改变上下文</span></div>
<p>OGNL表达式解析时，默认使用的上下文对象是ValueStack，此时栈顶对象的属性会被优先读取。使用此默认上下文时，不需要任何特殊语法，例如你要访问值栈的alex.name属性，只需要用表达式<pre class="crayon-plain-tag">alex.name</pre> 。</p>
<p>要改变OSGL的解析上下文，必须使用井号前缀：</p>
<pre class="crayon-plain-tag">// actionContextProperty['property'].ognlExpr
#session['alex'].name  

// actionContextProperty.property.ognlExpr
#session.alex.name</pre>
<div class="blog_h3"><span class="graybg">链式子表达式</span></div>
<p>如果你在点号后面直接使用括号，那么括号内的表达式的上下文是点号前的那个对象：</p>
<pre class="crayon-plain-tag">// ensureLoaded()、name的上下文是parent属性
headline.parent.(ensureLoaded(), name)</pre>
<div class="blog_h3"><span class="graybg">Lambda</span></div>
<pre class="crayon-plain-tag">// 定义一个Lambda
#fact = :[#this&lt;=1? 1 : #this*#fact(#this-1)]
// 调用Lambda
#fact(30H)</pre>
<div class="blog_h3"><span class="graybg">操作符</span></div>
<p>OGNL可以使用绝大部分来自Java的操作符，但是需要注意行为的差异、操作符扩展：</p>
<pre class="crayon-plain-tag">// 逗号操作符，借用自C语言，返回最后一个子表达式的值
ensureLoaded(), name

// in、not in操作符，判断元素是否存在于集合中
name in {null,"Untitled"}
id not in {1, 2, 3} || baid


// 很多操作符都有对应的单词化版本：
! e1       //  not e1
e1 || e2   //  e1 or e2
e1 &amp;&amp; e2   //  e1 and e2
e1 | e2    //  e1 bor e2
e1 ^ e2    //  e1 xor e2
e1 &amp; e2    //  e1 band e2
e1 == e2   //  e1 eq e2
e1 != e2   //  e1 neq e2
e1 &lt; e2    //  e1 lt e2
e1 &lt;= e2   //  e1 lte e2
e1 &gt; e2    //  e1 gt e2
e1 &gt;= e2   //  e1 gte e2
e1 &lt;&lt; e2   //  e1 shl e2
e1 &gt;&gt; e2   //  e1 shr e2
e1 &gt;&gt;&gt; e2  //  e1 ushr e2 </pre>
<div class="blog_h1"><span class="graybg">数据绑定</span></div>
<p>使用Struts2时，你可以通过几种方式完成数据绑定：</p>
<ol>
<li>为Action声明多个简单属性，每个属性容纳一个表单字段</li>
<li>为Action声明一个对象属性，把整个表单绑定到此对象，表单字段的名称必须使用点号导航</li>
<li>实现泛型接口ModelDriven，把表单绑定到特化后的泛型参数类型的对象上</li>
</ol>
<p>从基于文本的请求参数到各种类型的Java对象的绑定，必然牵涉到数据类型转换。Struts2自带了大量常用的类型转换器，你也可以开发自己的类型转换器。</p>
<div class="blog_h2"><span class="graybg">绑定到Action属性</span></div>
<div class="blog_h3"><span class="graybg">单个属性绑定</span></div>
<p>表单页面如下：</p>
<pre class="crayon-plain-tag">&lt;s:textfield name="name" label="User Name" /&gt;</pre>
<p>Action如下： </p>
<pre class="crayon-plain-tag">public class UserAction extends ActionSupport {
    private String name;
    // getter/setter略
}</pre>
<p>结果页面如下：</p>
<pre class="crayon-plain-tag">User Name: &lt;s:property value="name"/&gt;</pre>
<p>结合数据收集、处理、显示这三个步骤，我们可以看到单个属性的绑定非常简单。就是根据表单字段名和Action属性名的相同性进行绑定。</p>
<div class="blog_h3"><span class="graybg">对象属性绑定</span></div>
<p>表单页面如下：</p>
<pre class="crayon-plain-tag">&lt;s:textfield name="user.name" label="User Name" /&gt;</pre>
<p>Action如下： </p>
<pre class="crayon-plain-tag">public class UserAction extends ActionSupport {
    private User user;
    // getter/setter略
    @Override
    public void validate() {
        if(user.getName() ==null){
            // 注意Field总是要和表单字段名对应
            addFieldError( "user.name", "User name is required");
        }
    }
}</pre>
<p>结果页面如下：</p>
<pre class="crayon-plain-tag">User Name: &lt;s:property value="user.name"/&gt;</pre>
<p>还是差不多，区别只是用点号导航来定位属性，而Action属性和点号导航根对象名字相同。 </p>
<div class="blog_h2"><span class="graybg">ModelDriven</span></div>
<p>这是一个泛型接口，只有一个方法getModel()，即返回当前业务使用的模型对象。你的Action必须实现此方法：</p>
<pre class="crayon-plain-tag">public class UserAction implements ModelDriven&lt;User&gt; {

    private User user;

    public User getModel() {
        // 你必须实现此方法并初始化模型
        return new User();
    }
}</pre>
<p>需要注意的是，Action执行时，此方法已经被框架调用过，并在请求处理过程中一直作为数据绑定的容器。因而你不能在Action中改变user变量指向的对象。</p>
<p>使用ModelDriven的好处是<span style="background-color: #c0c0c0;">避免不必要的点号导航</span>。以用户管理的例子来说，你的表单字段名称和简单属性绑定时一样。</p>
<div class="blog_h2">内置类型转换器</div>
<p>Struts2内建了从HTTP字符串到下列Java类型的转换支持：</p>
<ol>
<li>转换为String：直接使用原始值</li>
<li>转换为Boolean/boolean：true和false字符串分别转换为对应的布尔值</li>
<li>转换为Character/char：直接使用原始值</li>
<li>各种数字类型及其包装类：直接使用原始值</li>
<li>转换为Date：使用当前Locale的SHORT格式来解析原始值</li>
<li>转换为数组：每个原始值的元素，依次转换数组元素类型的Java对象</li>
<li>转换为List：默认创建元素为字符串的列表</li>
<li>转换为Map：默认创建键值为字符串的Map</li>
</ol>
<p>为了使用这些转换器，你必须构建OGNL表达式。这些表达式可能是表单字段的名称，或者Struts2标签的属性。</p>
<p>注意：尽管OGNL内置了类型转换接口ognl.TypeConverter，但是Struts2没有直接从它扩展出上述内置类型转换器，而是使用了适配器模式。Struts2自己的转换器接口为：com.opensymphony.xwork2.conversion.TypeConverter</p>
<div class="blog_h2"><span class="graybg">自定义类型转换器</span></div>
<p>OGNL/Struts2的类型转换器接口，可以针对任意类型之间的相互转换。但是在我们数据绑定的场景中，仅仅需要字符串与其它类型的转换，因此可以选择从</p>
<pre class="crayon-plain-tag">public abstract class StrutsTypeConverter extends DefaultTypeConverter {
    protected Object performFallbackConversion(Map context, Object o, Class toClass) {
        return super.convertValue(context, o, toClass);
    }
    public abstract String convertToString(Map context, Object o);
}</pre>
<p>扩展。使用该扩展点，还可以访问一个代表了当前Action上下文的Map对象。</p>
<div class="blog_h3"><span class="graybg">注册类型转换器</span></div>
<p>你可以通过两种方式注册类型转换器：</p>
<ol>
<li>配置到ActionClassName-conversion.properties，则转换器仅仅用于一个Action类：<br />
<pre class="crayon-plain-tag"># Action类的customField，使用的转换器：
customField=package.CustomFieldTypeConverter </pre>
</li>
<li>配置到xwork-conversion.properties，则转换器用于全局：<br />
<pre class="crayon-plain-tag"># CustomField类型，使用的转换器：
package.CustomField=package.CustomFieldTypeConverter</pre>
</li>
</ol>
<div class="blog_h3"><span class="graybg">覆盖默认日期格式</span></div>
<p>Struts默认的日期格式可能不太受欢迎，我们可以实现自己的日期类型转换器：</p>
<pre class="crayon-plain-tag">import org.apache.struts2.util.StrutsTypeConverter;

public class DateConverter extends StrutsTypeConverter {

    public static final String DATE_FORMAT = "yyyy-MM-dd";

    public static final String[] DATE_FORMATS = {
            "yyyy-MM-dd",
            "yy-M-d"
    };

    @Override
    public Object convertFromString( Map context, String[] values, Class toClass ) {
        String dateStr = values[0];
        try {
            return DateUtils.parseDate( dateStr, DATE_FORMATS );
        } catch ( ParseException e ) {
            return null;
        }
    }

    @Override
    public String convertToString( Map context, Object o ) {
        Date d = (Date) o;
        return DateFormatUtils.format( d, DATE_FORMAT );
    }
}</pre>
<p>修改全局设置：</p>
<pre class="crayon-plain-tag">java.util.Date=com.ecfund.base.struts2.DateConverter</pre>
<div class="blog_h2"><span class="graybg">表单字段绑定到属性</span></div>
<p>你可以使用OGNL表达式作为表单字段的name，这样就可以自动绑定到ValueStack属性。</p>
<p>如果属性是基本类型、字符串，映射很简单，直接根据点号导航匹配，然后调用内置转换器转换就可以了。</p>
<div class="blog_h3"><span class="graybg">数组和列表映射</span></div>
<pre class="crayon-plain-tag">&lt;!-- 方式一： --&gt;
&lt;s:textfield name="ages" /&gt;
&lt;s:textfield name="ages" /&gt;
&lt;s:textfield name="ages" /&gt;

&lt;!-- 方式二： --&gt;
&lt;s:textfield name="names[0]" /&gt;
&lt;s:textfield name="names[1]" /&gt;
&lt;s:textfield name="names[2]" /&gt;

&lt;!-- 如果数组/列表属性元素是对象，可以使用属性导航： --&gt;
&lt;s:textfield name="users.age" /&gt;
&lt;s:textfield name="users[1].age" /&gt;</pre>
<p>这两种方式都可以实现数组、列表类型属性的绑定：</p>
<ol>
<li>第一种方式提交1个请求参数，值为3个字符串构成的数组。而ages属性类型为int[]，因此框架为每个字符串执行String-int的转换</li>
<li>第二种方式提交3个请求参数</li>
</ol>
<p>转换为List时，与数组类似。如果没有使用泛型，则认为列表元素都是字符串类型的；如果使用了泛型，执行相应的类型转换。</p>
<div class="blog_h3"><span class="graybg">Map映射</span></div>
<pre class="crayon-plain-tag">&lt;!-- 方式一： --&gt;
&lt;s:textfield name="nameAddrMapping.alex" /&gt;
&lt;s:textfield name="nameAddrMapping.meng" /&gt;
&lt;s:textfield name="nameAddrMapping.cai" /&gt;

&lt;!-- 方式二： --&gt;
&lt;s:textfield name="nameAddrMapping['alex']" /&gt;
&lt;s:textfield name="nameAddrMapping['meng']" /&gt;
&lt;s:textfield name="nameAddrMapping['cai']" /&gt;


&lt;!-- 如果映射属性的Value是对象，可以使用属性导航： --&gt;
&lt;s:textfield name="nameAddrMapping.alex.tel" /&gt;
&lt;s:textfield name="nameAddrMapping['alex'].tel" /&gt;</pre>
<p>与List类似，Map映射也支持基于泛型的类型推导、类型转换。 </p>
<div class="blog_h1"><span class="graybg">结果</span></div>
<p>Action执行的最后一件事情，就是返回一个控制字符串——其中包含结果的逻辑名称，框架根据此名称，找到对应的视图进行渲染。</p>
<p>结果是一个比较抽象的Struts2概念，它是对MVC模式中，视图关注点的封装。在经典的Web应用中，这些视图关注点就是返回给客户的HTML页面；而在基于Ajax的应用中，视图关注点则是JSON、XML等纯数据。</p>
<p>如果仅仅使用JSP作为视图技术，你基本不需要了解结果的细节。struts-defaults包的默认的dispatcher结果类型支持JSP：</p>
<pre class="crayon-plain-tag">&lt;package name="struts-default" abstract="true"&gt;
    &lt;result-types&gt;
        &lt;result-type name="chain" class="com.opensymphony.xwork2.ActionChainResult"/&gt;
        &lt;!-- dispatcher是默认的结果类型 --&gt;
        &lt;result-type name="dispatcher" class="org.apache.struts2.dispatcher.ServletDispatcherResult" default="true"/&gt;
        &lt;result-type name="freemarker" class="org.apache.struts2.views.freemarker.FreemarkerResult"/&gt;
        &lt;result-type name="httpheader" class="org.apache.struts2.dispatcher.HttpHeaderResult"/&gt;
        &lt;result-type name="redirect" class="org.apache.struts2.dispatcher.ServletRedirectResult"/&gt;
        &lt;result-type name="redirectAction" class="org.apache.struts2.dispatcher.ServletActionRedirectResult"/&gt;
        &lt;result-type name="stream" class="org.apache.struts2.dispatcher.StreamResult"/&gt;
        &lt;result-type name="velocity" class="org.apache.struts2.dispatcher.VelocityResult"/&gt;
        &lt;result-type name="xslt" class="org.apache.struts2.views.xslt.XSLTResult"/&gt;
        &lt;result-type name="plainText" class="org.apache.struts2.dispatcher.PlainTextResult" /&gt;
        &lt;result-type name="postback" class="org.apache.struts2.dispatcher.PostbackResult" /&gt;
    &lt;/result-types&gt;
&lt;/package&gt; </pre>
<div class="blog_h2"><span class="graybg">结果类型</span></div>
<p>所谓结果类型，就是例如上面的XML中声明的，结果的种类（Class）。实例化后的结果类型，就是结果，两者是类与实例的关系。</p>
<p>所有结果类都必须实现接口：</p>
<pre class="crayon-plain-tag">public interface Result extends Serializable {
    // 继续处理Action调用过程，完成视图显示的服务器端逻辑：
    // 例如生成网页、生成电子邮件、发送JSM消息，等等
    public void execute(ActionInvocation invocation) throws Exception;

}</pre>
<div class="blog_h2"><span class="graybg">内置结果类型</span></div>
<div class="blog_h3"><span class="graybg">dispatcher</span></div>
<p>该结果类型用于JSP、Servlet等类型的视图，dispatcher是struts-default包默认的结果类型。它的参数包括：</p>
<ol>
<li>location，默认参数，目标视图的位置</li>
<li>parse，指定是否将location参数中的<pre class="crayon-plain-tag">${expr}</pre> 作为OGNL表达式来解析</li>
</ol>
<p>dispatcher依赖于javax.servlet.RequestDispatcher，后者能够将请求处理权从一个Servlet转向另外一个（通常此Servlet是JSP）。RequestDispatcher提供了两种方法来转移处理权：</p>
<ol>
<li>include()：临时转移处理权，当前Servlet已经在输出响应内容，而需要另外一个Servlet的输出时，使用此方式</li>
<li>forward()：永久转移处理权，使用此方式时，当前Servlet不能已经输出任何响应</li>
</ol>
<p>这两种方式都让第二个Servlet得到当前请求、响应对象，让它处理同一个用户请求。</p>
<p>RequestDispatcher与HTTP重定向不同，后者是向浏览器发送一个重定向响应，前者的全部处理都发生在服务器内部，浏览器不知情。</p>
<div class="blog_h3"><span class="graybg">redirect</span></div>
<p>该结果用于将浏览器重定向到另外一个URL，与当前请求有关的全部信息将被丢弃。它的参数包括：</p>
<ol>
<li>location，默认参数，目标视图的位置</li>
<li>parse，指定是否将location参数的<pre class="crayon-plain-tag">${expr}</pre>作为OGNL表达式来解析</li>
</ol>
<p>示例：<pre class="crayon-plain-tag">&lt;result type="redirect"&gt;http://gmem.cc/${postId}&lt;/result&gt;</pre> </p>
<div class="blog_h3"><span class="graybg">redirectAction</span></div>
<p>与redirect类似，但是此结果类型能够识别Action的名称和名字空间，并且指定重定向时使用的请求参数：</p>
<pre class="crayon-plain-tag">&lt;result type="redirectAction"&gt;
    &lt;param name="actionName"&gt;home&lt;/param&gt;
    &lt;param name="namespace"&gt;/usermgr&lt;/param&gt;
    &lt;!-- 下面指定请求参数：--&gt;
    &lt;param name="param1"&gt;staticValue&lt;/param&gt;
    &lt;param name="param2"&gt;${dynamicValue}&lt;/param&gt;
&lt;/result&gt;</pre>
<div class="blog_h3"><span class="graybg">velocity</span></div>
<p>Velocity和FreeMarker是Struts2支持的、用于代替JSP的视图技术。与它们对应的结果类型已经内置在Struts2框架中。</p>
<p>使用此结果类型的Action配置示例如下：</p>
<pre class="crayon-plain-tag">&lt;action name="home" class="cc.gmem.study.ssm.action.UserAction" method="home"&gt;
    &lt;result name="success" type="velocity"&gt;/WEB-INF/jsp/Users.vm&lt;/result&gt;
&lt;/action&gt;</pre>
<p>基于标签库的代码：<pre class="crayon-plain-tag">&lt;s:property value="users.size()"/&gt;</pre> 在Velocity中等价语法是#<pre class="crayon-plain-tag">sproperty("value=users.size()")</pre> 。</p>
<div class="blog_h3"><span class="graybg">freemarker</span></div>
<p>FreeMarker内置于Struts2框架中，不需要引入额外的JAR包。使用此结果类型的Action配置示例如下：</p>
<pre class="crayon-plain-tag">&lt;action name="home" class="cc.gmem.study.ssm.action.UserAction" method="home"&gt;
    &lt;result name="success" type="freemarker"&gt;/WEB-INF/jsp/Users.ftl&lt;/result&gt;
&lt;/action&gt;</pre>
<p>基于标签库的代码：<pre class="crayon-plain-tag">&lt;s:property value="users.size()"/&gt;</pre> 在Velocity中等价语法是#<pre class="crayon-plain-tag">&lt;@s.property value="users.size()" /&gt;</pre> 。 </p>
<div class="blog_h2"><span class="graybg">全局结果</span></div>
<p>你可以在包内配置全局结果，这种结果可以被包内所有Action使用。当一个动作返回匹配一个控制字符串后，框架首先检查Action自身配置的结果，如果找不到匹配项，就会转而使用全局结果。</p>
<p>全局结果主要用于错误处理：</p>
<pre class="crayon-plain-tag">&lt;global-results&gt;
    &lt;result name="error"&gt;/error.jsp&lt;/result&gt;
&lt;/global-results&gt;</pre>
<div class="blog_h2"><span class="graybg">扩展结果类型：JSON</span></div>
<p>基于Ajax的Web应用通常使用JSON作为响应格式，这是默认的dispatcher无法满足的。我们可以自己实现一个JSON结果类型，满足Ajax应用的需要。</p>
<div class="blog_h3"><span class="graybg">实现JSONResult</span></div>
<pre class="crayon-plain-tag">public class JSONResult implements Result {
    // 结果默认参数名，该参数的值可以直接作为result元素的文本，而不是param子元素
    private static final String DEFAULT_PARAM = "property";
    // 把值栈的什么属性取出来转换为JSON
    private String property = "jsonResult";

    public String getProperty() {
        return property;
    }

    public void setProperty( String property ) {
        this.property = property;
    }

    public void execute( ActionInvocation invocation ) throws Exception {
        HttpServletResponse resp = ServletActionContext.getResponse();
        // 设置合适的响应类型
        resp.setContentType( "text/json;charset=UTF-8" );
        PrintWriter out = resp.getWriter();
        // 从值栈得到客户端需要的数据并转换为JSON
        ValueStack vs = invocation.getStack();
        Object jsonModel = vs.findValue( getProperty() );
        // 把数据转换为JSON格式，写入到响应
        out.println( JSONUtils.toJSON( jsonModel ) );
    }
}</pre>
<div class="blog_h3"><span class="graybg">注册JSONResult</span></div>
<p>实现了结果类型后，还必须将其注册到Struts才能生效：</p>
<pre class="crayon-plain-tag">&lt;package name="usermgr" extends="struts-default" namespace="/usermgr"&gt;
    &lt;result-types&gt;
        &lt;result-type name="json" class="cc.gmem.study.struts2.JSONResult"&gt;&lt;/result-type&gt;
    &lt;/result-types&gt;
&lt;/package&gt;</pre>
<div class="blog_h3"><span class="graybg">使用JSONResult</span> </div>
<pre class="crayon-plain-tag">&lt;package name="usermgr" extends="struts-default" namespace="/usermgr"&gt;
    &lt;action name="userinfo" class="cc.gmem.study.ssm.action.UserAction" method="home"&gt;
        &lt;!-- 由于json不是此包的默认结果类型，因此需要声明 type --&gt;
        &lt;result name="success" type="json"&gt;
            &lt;param name="property"&gt;user&lt;/property
        &lt;/result&gt;
    &lt;/action&gt;
&lt;/package&gt;</pre>
<p>由于JSONResult的默认参数就是Property，因此result元素可以简写为：</p>
<pre class="crayon-plain-tag">&lt;result name="success" type="json"&gt;user&lt;/result&gt;</pre>
<p>当客户端请求/usermgr/userinfo时Action的处理结果是success时，Struts会调用我们的JSONResult，并把值栈上的user属性编码为JSON文本，写到响应体中。  </p>
<div class="blog_h1"><span class="graybg">标签库</span></div>
<div class="blog_h2"><span class="graybg">Struts2标签简介</span></div>
<p>Struts2提供了大量不同类型的标签，这些标签有针对不同视图技术——JSP、Velocity、FreeMarker的语法变体。这些标签可以分为四个类别：数据标签、流程控制标签、UI标签、其它标签。</p>
<div class="blog_h2"><span class="graybg">标签语法</span></div>
<p>Struts2标签API定义了一个独立于视图技术的抽象层， 该API指定了标签的公开参数和属性。针对三种视图技术的标签语法，差异并不大，并且有规律可循。</p>
<div class="blog_h3"><span class="graybg">JSP语法</span></div>
<p>在JSP中使用标签库，必须声明：</p>
<pre class="crayon-plain-tag">&lt;%@ taglib prefix="s" uri="/struts-tags" %&gt;</pre>
<p>标签用法示例：</p>
<pre class="crayon-plain-tag">&lt;%-- 数据标签：格式化一个属性为文本 --%&gt;
&lt;s:property value="name"/&gt;
&lt;%-- UI标签：表单 --%&gt;
&lt;s:form action="/usrmgr/updateuser"&gt;
    &lt;s:textfield name="name" label="User Name"/&gt;
    &lt;s:password name="password" label="Password"/&gt;
    &lt;s:submit value="Submit"/&gt;
&lt;/s:form&gt;</pre>
<p>在JSP中，你可以<span style="background-color: #c0c0c0;">使用EL表达式</span>来访问值栈属性：<pre class="crayon-plain-tag">${expr}</pre> 。</p>
<div class="blog_h3"><span class="graybg">Velocity语法</span></div>
<pre class="crayon-plain-tag">#sproperty("value=name")

#sform("action=/usrmgr/updateuser")
    #stextfield("label=User Name" "name=name")
    #spassword("label=Password" "name=password")
    #ssubmit("value=Submit")
#end</pre>
<div class="blog_h3"><span class="graybg">FreeMarker语法</span></div>
<p>它的语法和JSP语法非常接近：</p>
<pre class="crayon-plain-tag">&lt;@s.property value="name" /&gt;
&lt;@s.form action="/usrmgr/updateuser"&gt;
    &lt;@s.textfield name="name" label="User Name" /&gt;
    &lt;@s.password name="password" label="Password" /&gt;
    &lt;@s.submit value="Submit" /&gt;
&lt;/@s.form&gt;</pre>
<div class="blog_h2"><span class="graybg">作为标签属性的OGNL</span></div>
<p>为Struts标签设置属性时，你需要搞清楚，目标属性期望的是一个字符串字面值还是ValueStack属性的OGNL表达式。</p>
<p>如果某个<span style="background-color: #c0c0c0;">标签属性的类型是字符串</span>，那么传递给它的值被作为字面值解析；否则，作为OGNL表达式解析。</p>
<p>如果要强制作为OGNL表达式解析，可以使用转义序<pre class="crayon-plain-tag">%{expr}</pre> ：</p>
<pre class="crayon-plain-tag">&lt;!-- 默认值是字面值： --&gt;
&lt;s:property value="nonExistingProperty" default="defaultValue"/&gt;
&lt;!-- 默认值是OGNL： --&gt;
&lt;s:property value="nonExistingProperty" default="%{defaultValue}"/&gt;</pre>
<div class="blog_h2"><span class="graybg">数据标签</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">标签</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>property</td>
<td>
<p>将ValueStack或者ActionContext上其它对象的属性值输出到HTML中，属性列表：</p>
<ol>
<li>value，指定被显示的值，类型Object，默认值栈顶</li>
<li>default，值为空时的默认值</li>
<li>escape，是否转义HTML字符，默认True</li>
</ol>
</td>
</tr>
<tr>
<td>set</td>
<td>
<p>在某个作用域设置一个属性，属性列表：</p>
<ol>
<li>name，在指定作用域新增加的变量的名称</li>
<li>scope，在哪里设置属性，application/session/request/page/action，默认action</li>
<li>value，新属性的值，类型Object</li>
</ol>
<p>示例：</p>
<pre class="crayon-plain-tag">&lt;s:set name="username" value="user.name" /&gt;

&lt;s:set name="username" scope="application" value="user.name" /&gt;
&lt;s:property value="#application['username']" /&gt;</pre>
</td>
</tr>
<tr>
<td>push</td>
<td>把一个对象压到值栈的顶端，这个对象的属性将获得最高优先级——优先代表虚拟对象的属性。示例：<br />
<pre class="crayon-plain-tag">&lt;s:push value="alex"&gt;
    &lt;s:property value="age" /&gt;
&lt;/s:push&gt;</pre>
<p> 在此标签的结束标签处，此对象从值栈弹出</p>
</td>
</tr>
<tr>
<td>bean</td>
<td>
<p>可以创建一个对象，然后将其放置到值栈上，或者设置为ActionContext的顶级字段。默认情况下，新对象被压到栈顶，并在结束标签处弹出。属性列表：</p>
<ol>
<li>name，新对象的全限定类名</li>
<li>var，在结束标签之后，仍然能够通过var访问此对象，var为ActionContext的顶级字段</li>
</ol>
<p>示例：</p>
<pre class="crayon-plain-tag">&lt;s:bean name="org.apache.struts2.util.Counter" var="counter"&gt;
    &lt;s:param name="last" value="10" /&gt;
&lt;/s:bean&gt;

&lt;s:iterator value="#counter"&gt;
    &lt;s:property /&gt;
&lt;/s:iterator&gt; </pre>
</td>
</tr>
<tr>
<td>action</td>
<td>
<p>可以从当前视图层调用其它的Action。属性列表：
<ol>
<li>name，必须，目标Action名称</li>
<li>namespace，目标Action的名字空间</li>
<li>var，页面后续代码使用Action对象时，用此变量引用</li>
<li>executeResult，是否排除动作的结果，默认false——包含Action结果到当前页面</li>
<li>flush，是否在结束标签处flush缓冲</li>
<li>ignoreContextParams，在执行Action时是否不包含请求参互诉，默认false</li>
</ol>
</td>
</tr>
<tr>
<td>actionerror</td>
<td>
<p>和Action相关的错误</p>
</td>
</tr>
<tr>
<td>fielderror</td>
<td>
<p>和字段相关的错误</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">控制标签</span> </div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">标签</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>iterator</td>
<td>
<p>可以方便的遍历Collection、Map、Enumeration、Iterator或者数组。该标签支持在ActionContext中保存一个定义遍历状态的变量，你可以通过此变量获取当前循环状态的基本信息。属性列表：</p>
<ol>
<li>value，类型Object，被遍历的对象</li>
<li>status，如果指定，那么IteratorStatus以此字段指定的名字，存放在ActionContext对象中</li>
<li>var，如果指定，则当前遍历对象在栈上依据此变量存储</li>
</ol>
<p>注意：在标签内部，当前<span style="background-color: #c0c0c0;">正在迭代的元素，会被放到压入ValueStack顶部</span>。示例：</p>
<pre class="crayon-plain-tag">&lt;s:iterator value="users" var="user"&gt;    
    &lt;tr&gt;
        &lt;!-- 注意status是放在ActionContext上的，因此需要前缀# --&gt;
        &lt;td&gt;&lt;s:property value="#status.count"/&gt;&lt;/td&gt;
        &lt;td&gt;&lt;s:property value="#user.id"/&gt;&lt;/td&gt;
        &lt;!-- #user.name和 name效果通常一样，除非User没有name属性（注意：不是说name为null），才会向栈底部追溯--&gt;
        &lt;td&gt;&lt;s:property value="name"/&gt;&lt;/td&gt;
        &lt;td&gt;&lt;s:property value="age"/&gt;&lt;/td&gt;
    &lt;/tr&gt;
&lt;/s:iterator&gt;</pre>
<p>你可以访问IteratorStatus的以下属性/方法：</p>
<ol>
<li>count，当前元素的计数，从1开始</li>
<li>index，当前元素的索引，从0开始</li>
<li>even，是否第偶数个元素</li>
<li>odd，是否第奇数个元素</li>
<li>modulus(int operand)，执行取模操作</li>
</ol>
</td>
</tr>
<tr>
<td>if-else</td>
<td>
<p>此标签用于实现分支逻辑，属性列表：</p>
<ol>
<li>test，布尔型的测试条件</li>
</ol>
<p>示例：</p>
<p><pre class="crayon-plain-tag">&lt;s:if test="user.age == 30"&gt;
    ${user.name} is 30 years old
&lt;/s:if&gt;
&lt;s:elseif test="user.age &gt;30"&gt;
    ${user.name} is older then 30
&lt;/s:elseif&gt;
&lt;s:else&gt;
    ${user.name} is younger than 30
&lt;/s:else&gt;</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">其它标签</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">标签</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>include</td>
<td>
<p>类似于JSP的<pre class="crayon-plain-tag">&lt;jsp:include&gt;</pre> 但是和Struts集成的更好，可以在当前页面包含其它Web资源的输出。属性列表：</p>
<ol>
<li>value，字符串，页面、Action、Servlet以及其它可以被引用的URL</li>
</ol>
<p>你可以使用s:param子标签，向被包含的URL传递请求参数。示例：</p>
<pre class="crayon-plain-tag">&lt;s:include value="%{includeURL}"&gt;
    &lt;s:param name="username" value="alex" /&gt;
&lt;/s:include&gt;</pre>
</td>
</tr>
<tr>
<td>url</td>
<td>
<p>用于构建URL。属性列表：
<ol>
<li>value，字符串，基础URL，默认为当前页面的URL</li>
<li>action，字符串，生成的URL指向的Action名称，不需要.action后缀</li>
<li>var，如果指定此参数，则把构建的URL存放在ActionContext中备用</li>
<li>includeParams，包含哪些请求参数，可选值all、get、none</li>
<li>includeContext，默认true，是否把当前Servlet上下文路径作为URL前缀</li>
<li>encode，如果客户端不支持Cookie，是否把Session ID追加在生成的URL后面</li>
<li>scheme，指定使用的HTTP协议</li>
</ol>
<p>你可以使用s:param子标签，向生成的URL传递请求参数</p>
</td>
</tr>
<tr>
<td>text</td>
<td>
<p>在国际化应用中，该标签用于显示具体语言相关的文本。该标签从框架的资源束中查找消息。属性列表：</p>
<ol>
<li>name，到资源束中查找的关键字</li>
<li>var，如果找到，具体语言相关的文本会存放在此属性指定的ActionContext变量中</li>
</ol>
</td>
</tr>
<tr>
<td>param</td>
<td>
<p>作为特定标签的子标签，向其传递参数，属性列表：</p>
<ol>
<li>name，String，参数名</li>
<li>value，Object，参数值</li>
</ol>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">UI标签</span></div>
<p>使用UI组件，你可以：</p>
<ol>
<li>生成HTML标记。Struts2可以根据你选择的主题，生成不同风格的HTML标记。</li>
<li>绑定HTML表单字段和Java对象属性</li>
<li>使用框架提供的类型转换机制</li>
<li>使用框架提供的验证机制</li>
<li>使用框架提供的国际化功能 </li>
</ol>
<p>UI标签对应的HTML文本可能比你想象的更复杂，例如在XHTML主题下，UI标签：</p>
<pre class="crayon-plain-tag">&lt;s:textfield name="username" label="User Name" /&gt;</pre>
<p>生成的HTML标记是一行表格：</p>
<pre class="crayon-plain-tag">&lt;tr&gt;
    &lt;td class="tdLabel"&gt;&lt;label for="username" class="label"&gt;User Name:&lt;/label&gt;&lt;/td&gt;
    &lt;td&gt;&lt;input type="text" name="username" value="" id="username"/&gt;&lt;/td&gt;
&lt;/tr&gt;</pre>
<p>如果你选用其它主题，生成的标记可能不一样。 </p>
<div class="blog_h3"><span class="graybg">标签、模板、主题</span></div>
<p>标签API是一个高层API——它是独立于具体视图技术之上的，我们已经在上面讨论过一些种类的标签了。 </p>
<p>不管是哪种标签，Struts都提供了对应的FreeMarker模板用来呈现它的HTML标记。不管标签是在JSP、Velocity还是FreeMarker中使用，它到HTML标记的转换过程都是一样的（基于FreeMarker模板）。如果你想自定义标签，则需要知道如何编写FreeMarker模板。</p>
<p>不管是哪种标签，都有几套不同的FreeMarker模板，这些模板<span style="background-color: #c0c0c0;">分属于不同的主题</span>。默认情况下，所有标签都使用xhtml主题呈现。Struts2自带的主题包括</p>
<ol>
<li>simple：呈现基本的HTML元素</li>
<li>xhtml：使用表格布局来呈现UI元素</li>
<li>css_xhtml：使用纯CSS布局来呈现UI元素</li>
<li>ajax：基于xhtml主题扩展，提供丰富的Ajax组件</li>
</ol>
<p>如果想改变默认的主题，可以创建一个struts.properties文件，在其中添加：</p>
<pre class="crayon-plain-tag">struts.ui.theme=css_xhtml</pre>
<p>你可以指定单个标签的theme属性，这样可以细粒度的控制主题。 </p>
<div class="blog_h3"><span class="graybg">UI标签通用属性</span></div>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 15%; text-align: center;">属性</td>
<td style="width: 10%; text-align: center;">主题</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>name</td>
<td>simple</td>
<td>设置表单输入元素的name属性，如果没有手工设置value属性，则该<span style="background-color: #c0c0c0;">name属性自动设置到value属性</span></td>
</tr>
<tr>
<td>value</td>
<td>simple</td>
<td>Object，指向ValueStack属性的OGNL表达式，用来预填充表单元素</td>
</tr>
<tr>
<td>key</td>
<td>simple</td>
<td>从资源束取得本地化的Label，可以传播到name属性、value属性</td>
</tr>
<tr>
<td>label</td>
<td>xhtml</td>
<td>为组件创建一个Label，如果设置了key和本地化文本则不需要此属性</td>
</tr>
<tr>
<td>labelPosition</td>
<td>xhtml</td>
<td>Label的位置，可以是left、top</td>
</tr>
<tr>
<td>required</td>
<td>xhtml</td>
<td>Boolean，如果true，则Label旁边显示一个星号暗示这是个必输项</td>
</tr>
<tr>
<td>id</td>
<td>simple</td>
<td>HTML元素的id属性</td>
</tr>
<tr>
<td>cssClass</td>
<td>simple</td>
<td>HTML元素的class属性</td>
</tr>
<tr>
<td>cssStyle</td>
<td>simple</td>
<td>HTML元素的style属性</td>
</tr>
<tr>
<td>disabled</td>
<td>simple</td>
<td>HTML的disabled属性</td>
</tr>
<tr>
<td>tabindex</td>
<td>simple</td>
<td>HTML的tabidex属性</td>
</tr>
<tr>
<td>theme</td>
<td> </td>
<td>使用何种主题呈现此组件，例如xhtml、css_xhtml、ajax、simple</td>
</tr>
<tr>
<td>templateDir</td>
<td> </td>
<td>用于覆盖模板所在目录名</td>
</tr>
<tr>
<td>template</td>
<td> </td>
<td>用于覆盖模板名，所有UI标签都有了默认模板，但是可以覆盖</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">UI标签示例</span></div>
<pre class="crayon-plain-tag">&lt;!-- 格式说明 --&gt;
&lt;s:form action="URL或者Action名字" namespace="Action的名字空间" method="POST|GET" validate="是否启用客户端JS验证"&gt;
    &lt;s:textfield maxLength="最大长度" readonly="true" size="可视长度" /&gt;
    &lt;s:password maxLength="" size="" readonly="" showPassword="是否启用密码预填充" /&gt;
    &lt;s:textarea rows="行数" cols="列数" wrap="是否换行" /&gt;
    &lt;s:checkbox fieldValue="复选框提交的真实值，可以是true/false" value="和fieldValue联用指示是否被选中"/&gt;
    &lt;s:select list="可迭代对象" listKey="元素是对象时，提交其什么属性" listValue="元素是对象时，显示其什么属性"
              multiple="是否可多选" size="显示选项数量" /&gt;
    &lt;s:radio list="可迭代对象" listKey="元素是对象时，提交其什么属性" listValue="元素是对象时，显示其什么属性" /&gt;
    &lt;s:checkboxlist list="可迭代对象" listKey="元素是对象时，提交其什么属性" listValue="元素是对象时，显示其什么属性" /&gt;
    &lt;s:label name="显示什么ValueStack属性" label="标签" /&gt; &lt;!-- 类似于DisplayField --&gt;
    &lt;s:hidden name="对应么ValueStack属性" /&gt;
&lt;/s:form&gt;

&lt;!-- radio 示例--&gt;
&lt;s:radio name="entity.status" list="#@java.util.LinkedHashMap@{'0':'否','1':'是'}" cssClass="weui_radio" value="1" /&gt;</pre>
<div class="blog_h1"><span class="graybg">验证框架</span></div>
<div class="blog_h2"><span class="graybg">架构</span></div>
<p>Struts2验证框架主要包含3类组件：</p>
<p><img class="size-full wp-image-13099 aligncenter" src="https://blog.gmem.cc/wp-content/uploads/2011/02/validation-architecture.png" alt="validation-architecture" width="693" height="650" /></p>
<div class="blog_h3"><span class="graybg">域数据</span></div>
<p>即被验证的数据，通常是Action属性，或者是ModelDriven的模型数据。</p>
<div class="blog_h3"><span class="graybg">验证元数据</span></div>
<p>指定了验证规则，Struts2支持两种指定元数据的方式：</p>
<ol>
<li>ActionClassName-validator.xml，指定一个类的验证规则的XML</li>
<li>基于注解的验证配置 </li>
</ol>
<div class="blog_h3"><span class="graybg">验证器</span></div>
<p>实际负责执行验证的一系列组件。框架内建了很多验证器，你也可以自己扩展验证器。</p>
<div class="blog_h2"><span class="graybg">工作流程</span></div>
<p>前文我们已经简单的介绍过验证框架，并做了一个简单的例子。</p>
<p>validation拦截器（AnnotationValidationInterceptor）是验证框架的入口点，它的工作流程（主要是父类ValidationInterceptor完成）是：</p>
<ol>
<li>validation获得域数据关联的验证元数据</li>
<li>根据元数据，找到一系列的验证器，执行验证</li>
<li>如果验证失败，则调用ValidationAware.addFieldError等方法，添加错误消息</li>
</ol>
<p>validation拦截器必须配置在workflow前面，验证框架才能正常工作。validation处理完成后，workflow拦截器继续推进请求处理流程：</p>
<ol>
<li>workflow被触发时，首先检查动作是否实现了Validateable接口</li>
<li>如果是，则workflow调用validate()方法执行基本验证。如果使用验证框架，该方法一般是空的</li>
<li>当validate()返回时，workflow调用ValidationAware接口的hasErrors()方法</li>
<li>如果存在错误，workflow中止流程，返回一个input结果</li>
<li>input结果一般导致页面转向表单提交时的页面</li>
</ol>
<div class="blog_h2"><span class="graybg">关联Action到验证框架</span></div>
<div class="blog_h3"><span class="graybg">实现Validateable接口</span></div>
<p>这种方式之前我们已经尝试过，可以实现基本的、编程的验证。</p>
<div class="blog_h3"><span class="graybg">声明XML元数据</span></div>
<p>你可以声明一个针对Action的验证配置文件，该文件必须和Action类在同一类路径目录下，该文件必须命名为ActionClassName-validator.xml：</p>
<pre class="crayon-plain-tag">&lt;!DOCTYPE validators PUBLIC "-//Apache Struts//XWork Validator 1.0.3//EN"
        "http://struts.apache.org/dtds/xwork-validator-1.0.3.dtd"&gt;
&lt;validators&gt;
    &lt;!-- 字段验证器 --&gt;
    &lt;!-- 被验证的Action属性，属性必须具有getter/setter --&gt;
    &lt;field name="age"&gt;
        &lt;!-- 使用何种验证器 --&gt;
        &lt;field-validator type="required"&gt;
            &lt;!-- 验证失败时的消息 --&gt;
            &lt;message&gt;年龄为必填项&lt;/message&gt;
        &lt;/field-validator&gt;
        &lt;!-- 一个属性可以指定多个验证器 --&gt;
        &lt;field-validator type="int"&gt;
            &lt;param name="min"&gt;2&lt;/param&gt;
            &lt;param name="max"&gt;10&lt;/param&gt;
            &lt;!-- 验证失败消息可以从资源束中获取 --&gt;
            &lt;message key="age.outofrange"/&gt;
        &lt;/field-validator&gt;
    &lt;/field&gt;

    &lt;!-- 非字段验证器 --&gt;
    &lt;validator type="expression"&gt;
        &lt;param name="expression"&gt;age lt 30&lt;/param&gt;
        &lt;message&gt;年龄必须大于30&lt;/message&gt;
    &lt;/validator&gt;
&lt;/validators&gt;</pre>
<p> 消息从以下资源束中获取：</p>
<pre class="crayon-plain-tag">age.outofrange=年龄必须在${min}和${max}之间</pre>
<div class="blog_h3"><span class="graybg">验证上下文</span></div>
<p>有时候，一个Action类中有几个方法，这几个方法都作为Action使用，如果这些方法需要不同的验证逻辑，该怎么办呢？</p>
<p>很简单，只需要分别为每个Action编写XML元数据文件就可以了，这些文件需要命名为：ActionClassName-aliasName-validator.xml。其中aliasName就是Action的名字。</p>
<div class="blog_h2"><span class="graybg">关联模型类到验证框架</span></div>
<p>我们可以直接使用模型类上的验证元数据，这样更容易实现验证规则的重用。</p>
<div class="blog_h3"><span class="graybg">声明XML元数据</span></div>
<p>与关联Action时完全相同，只是文件前缀改为实体类名字，并且和实体类放在一个类路径下。</p>
<div class="blog_h3"><span class="graybg">关联到Action</span></div>
<p>配置在模型类上的XML元数据不会直接生效，你必须将其关联到一个Action，才能间接的关联到验证框架。</p>
<p>首先，模型类必须是Action的一个属性（可以基于ModelDriven），然后你需要在Action的XML元数据中添加：</p>
<pre class="crayon-plain-tag">&lt;field name="user"&gt;
   &lt;!-- visitor 将验证逻辑转给字段对应模型类的验证XML元数据负责 --&gt;
    &lt;field-validator type="visitor"&gt;
    &lt;/field-validator&gt;
&lt;/field&gt;</pre>
<p>注意，当使用ModelDriven时，模型属性直接暴露为值栈的顶级属性。例如访问用户名称，你只需要声明name，而不是user.name，此时Action的验证配置要做一点修改： </p>
<pre class="crayon-plain-tag">&lt;field-validator type="visitor"&gt;
    &lt;!-- 查找user字段（User类）的属性时，不需要user.前缀 --&gt;
    &lt;param name="appendPrefix"&gt;false&lt;/param&gt;
&lt;/field-validator&gt; </pre>
<div class="blog_h3"><span class="graybg">验证上下文</span></div>
<p>利用visitor验证器使用模型XML元数据时，也可以指定上下文：</p>
<pre class="crayon-plain-tag">&lt;field-validator type="visitor"&gt;
    &lt;param name="context"&gt;admin&lt;/param&gt;
&lt;/field-validator&gt;</pre>
<p>上面这个配置，会自动定位并使用XML元数据文件User-admin-validation.xml。 </p>
<div class="blog_h2"><span class="graybg">验证器分类</span></div>
<p>验证器可以分为：</p>
<ol>
<li>字段验证器：仅仅针对一个字段/属性执行验证</li>
<li>非字段验证器：针对整个Action的验证器，某些验证规则不能简单的关联到一个字段</li>
</ol>
<p>内置验证器中，只有expression是非字段验证器。</p>
<div class="blog_h2"><span class="graybg">内建验证器</span></div>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 30%; text-align: center;">验证器</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>required<br />@RequiredFieldValidator</td>
<td>验证值不为空</td>
</tr>
<tr>
<td>requiredstring<br />@RequiredStringValidator</td>
<td>
<p>验证值不为空，且不为空串。参数列表：</p>
<ol>
<li>trim，是否清楚前后空格，默认true</li>
</ol>
</td>
</tr>
<tr>
<td>stringlength<br />@StringLengthFieldValidator</td>
<td>
<p>验证字符串长度在指定的范围内。参数列表：</p>
<ol>
<li>trim，是否清楚前后空格，默认true</li>
<li>minLength，最小长度</li>
<li>maxLength，最大长度</li>
</ol>
</td>
</tr>
<tr>
<td>int<br />@IntRangeFieldValidator</td>
<td>
<p>验证整数在指定的范围内。参数列表：</p>
<ol>
<li>min，最小值</li>
<li>max，最大值</li>
</ol>
</td>
</tr>
<tr>
<td>double<br />@DoubleRangeFieldValidator</td>
<td>
<p>验证浮点数在指定的范围内</p>
</td>
</tr>
<tr>
<td>date<br />@DateRangeFieldValidator</td>
<td>
<p>验证日期在指定范围内。参数列表：</p>
<ol>
<li>min，最小日期，格式MM/DD/YYYY</li>
<li>max，最大日期，格式MM/DD/YYYY</li>
</ol>
</td>
</tr>
<tr>
<td>email<br />@EmailValidator</td>
<td>验证是否合法电子邮件地址</td>
</tr>
<tr>
<td>url<br />@UrlValidator</td>
<td>验证是否合法URL</td>
</tr>
<tr>
<td>fieldexpression<br />@FieldExpressionValidator</td>
<td>根据ValueStack解析OGNL表达式，如果结果为true则验证通过。参数expression</td>
</tr>
<tr>
<td>expression<br />@ExpressionValidator</td>
<td>与fieldexpression类似，但是用在动作级别。参数expression</td>
</tr>
<tr>
<td>regx<br />@RegexFieldValidator</td>
<td>
<p>根据正则式验证。参数列表：</p>
<ol>
<li>expression，正则式</li>
<li>caseSensitive，是否大小写敏感</li>
<li>trim，是否去除前后空格</li>
</ol>
</td>
</tr>
<tr>
<td>visitor<br />@VisitorFieldValidator</td>
<td>将对象类型的属性的验证工作交给此对象的<span style="background-color: #c0c0c0;">类自己的验证元数据</span></td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">自定义验证器</span></div>
<p>要扩展自己的验证器，可以选择继承FieldValidatorSupport或者ValidatorSupport。例如我们这里实现一个密码强度验证器：</p>
<pre class="crayon-plain-tag">public class PasswordIntegrityValidator extends FieldValidatorSupport {
    public void validate( Object object ) throws ValidationException {
        String fieldName = getFieldName();
        String fieldValue = (String) getFieldValue( fieldName, object );
        String passwd = fieldValue.trim();
        if ( !matches( passwd ) ) {
            //如果验证失败，添加错误信息
            addFieldError( fieldName, object );
        }
    }
}</pre>
<div class="blog_h3"><span class="graybg">注册验证器</span></div>
<p>新编写的验证器，必须注册到Struts2才能生效：</p>
<pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;!DOCTYPE validators PUBLIC
        "-//Apache Struts//XWork Validator Definition 1.0//EN"
        "http://struts.apache.org/dtds/xwork-validator-definition-1.0.dtd"&gt;

&lt;validators&gt;
    &lt;validator name="passwordintegrity" class="cc.gmem.study.struts2.PasswordIntegrityValidator"/&gt;
&lt;/validators&gt;</pre>
<div class="blog_h2"><span class="graybg">基于注解的验证</span></div>
<p>后面的章节有示例，需要注意一些事项：</p>
<ol>
<li>可以配置validation拦截器的validateAnnotatedMethodOnly参数，仅仅去验证注解了Validations的Acion方法</li>
<li>可以为Action方法配置@SkipValidation，禁止对此方法进行验证</li>
</ol>
<div class="blog_h1"><span class="graybg">基于注解的配置</span></div>
<p>熟悉了基于XML的配置方式后，要转到注解方式很简单，仅仅是形式不同而已。本章主要用示例来说明如何进行注解配置。</p>
<div class="blog_h2"><span class="graybg">包与Action配置</span></div>
<pre class="crayon-plain-tag">// 从哪个父包扩展
@ParentPackage( "struts-default" )
@Namespace( "/crud" )
// 全局结果
@Results( {
        @Result( name = "error", location = "/error.jsp" )
} )
// 全局异常映射
@ExceptionMappings( { @ExceptionMapping( exception = "java.lang.RuntimeException", result = "error" ) } )
public class CrudAction extends ActionSupport {
    // getter/setter略
    private String entity;
    private int id;

    @Action( value = "/update/{entity}/{id}", results = {
            @Result( name = SUCCESS, location = "/WEB-INF/jsp/update-${entity}.jsp" )
    } )
    // 匹配：/crud/update/org/10000.action  转向 /WEB-INF/jsp/update-org.jsp
    public String update() throws Exception {
        return SUCCESS;
    }

    // 多个Action映射到一个方法
    @Actions( {
            @Action( "/name2" ),
            @Action( "/name2" )
    } )
    public String fail() {
        throw new RuntimeException();
    }
}</pre>
<div class="blog_h2"><span class="graybg">验证元数据</span></div>
<pre class="crayon-plain-tag">public class UserAction extends ActionSupport {
    private String username;
    private String password;

    // 非字段验证
    @ExpressionValidator( expression = "username != password", message = "" )
    public String execute() {
        return SUCCESS;
    }
    
    // 为某个Action方法声明单独的验证规则，如果设置validateAnnotatedMethodOnly = true，则只有标注了@Validations
    // 注解的方法才会被验证
    @Validations(
        visitorFields = @VisitorFieldValidator(fieldName = "entity", context = "admin", appendPrefix = true )
    )
    // 这个注解刚好想法，如果不设置validateAnnotatedMethodOnly=true（默认不设置），则可以禁止某些方法的验证
    @SkipValidation
    // 你可以指定在验证失败时返回的结果，不一定非要input
    @InputConfig(resultName = "errinfo")
    public String execute(){
        return SUCCESS;
    }
    public String home() throws Exception {
        return SUCCESS;
    }

    // 字段验证，必须编写在Getter上
    @StringLengthFieldValidator( maxLength = "100" )
    @RequiredStringValidator( message = "" )
    public String getUsername() {
        return username;
    }

    public void setUsername( String username ) {
        this.username = username;
    }

}</pre>
<div class="blog_h1"><span class="graybg">常用API</span></div>
<div class="blog_h2"><span class="graybg">ServletActionContext</span></div>
<p>通过此工具类声明的静态方法，你可以获得当前HttpServletRequest、HttpServletResponse 、ServletContext、PageContext等对象的引用：</p>
<pre class="crayon-plain-tag">public class UserAction {
    private HttpServletRequest request;
    private HttpServletResponse response;
    public String home() {
        request = ServletActionContext.getRequest();
        response = ServletActionContext.getResponse();
        return SUCCESS;
    }
}</pre>
<div class="blog_h2"><span class="graybg">ActionContext</span></div>
<p>调用此类的静态方法<pre class="crayon-plain-tag">getContext()</pre> ，可以得到当前的Action上下文，可以进一步得到值栈。</p>
<div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h2"><span class="graybg">零散问题</span></div>
<div class="blog_h3"><span class="graybg">Action类添加@Transactional注解导致异常</span></div>
<p>整合Spring时，如果基于JDK动态代理实现基于AOP的事务控制，并且在Action类上添加事务注解，会报错：</p>
<p>java.lang.NoSuchMethodException: com.sun.proxy.$Proxy25.add()</p>
<p>解决办法：永远不要在控制器层加事务控制。</p>
<div class="blog_h3"><span class="graybg">如何访问所有验证错误</span></div>
<p>让你的Action类继承ActionSupport或者实现ValidationAware，这样接口中暴露的getter就可以通过标签库直接访问了：</p>
<pre class="crayon-plain-tag">&lt;s:property value="fieldErrors"/&gt; </pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/struts2-study-note">Struts2学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/struts2-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
