Struts 2 Learning

目录

1. J2EE简介
2. JAVA EE应用的分层模型
3. 搭建Struts2 Demo应用
4. struts2流程
5. struts2的常规配置
6. 实现Action
7. 配置Action
8. 配置处理结果
9. 配置struts2的异常处理
10. convention插件与"约定"支持
11. 使用struts2的国际化
12. 使用struts2的标签库

 

1. J2EE简介

0x1: JavaBeans

JavaBeans是Java语言中可以重复使用的软件组件,它们是一种特殊的Java类,将很多的对象封装到了一个对象(bean)中。特点是

1. 可序列化
2. 提供无参构造器
3. 提供getter方法和setter方法访问对象的属性
4. Bean可以控制它的属性、事件和方法是否暴露给其他程序
5. Bean可以接收来自其他对象的事件,也可以产生事件给其他对象
6. 有软件可用来配置Bean
7. Bean的属性可以被序列化,以供日后重用

javabeans从本质上来讲是一个规范性的概念,要成为JavaBean类,则必须遵循关于命名、构造器、方法的特定规范。有了这些规范,才能有可以使用、复用、替代和连接JavaBeans的工具。反过来说,如果一个java类满足了这些规范,就可以称之为javabeans

package player;
 
public class PersonBean implements java.io.Serializable {
 
    /**
     * name 属性(注意大小写)
     */
    private String name = null;
 
    private boolean deceased = false;
 
    /** 无参构造器(没有参数) */
    public PersonBean() {
    }
 
    /**
     * name 属性的Getter方法
     */
    public String getName() {
        return name;
    }
 
    /**
     * name 属性的Setter方法
     * @param value
     */
    public void setName(final String value) {
        name = value;
    }
 
    /**
     * deceased 属性的Getter方法
     * 布尔型属性的Getter方法的不同形式(这里使用了is而非get)
     */
    public boolean isDeceased() {
        return deceased;
    }
 
    /**
     * deceased 属性的Setter方法
     * @param value
     */
    public void setDeceased(final boolean value) {
        deceased = value;
    }
}

TestPersonBean.java:

import player.PersonBean;
 
/**
 * <code>TestPersonBean</code>类
 */
public class TestPersonBean {
    /**
     * PersonBean 类测试方法的main函数
     * @param ARGS
     */
    public static void main(String[] args) {
        PersonBean person = new PersonBean();
 
        person.setName("张三");
        person.setDeceased(false);
 
        // 输出: "张三[活着]"
        System.out.print(person.getName());
        System.out.println(person.isDeceased() ? " [已故]" : " [活着]");
    }
}

testPersonBean.jsp

<% // 在JSP中使用PersonBean类 %>
<jsp:useBean id="person" class="player.PersonBean" scope="page"/>
<jsp:setProperty name="person" property="*"/>
 
<html>
    <body>
        姓名:<jsp:getProperty name="person" property="name"/><br/>
        已故与否?<jsp:getProperty name="person" property="deceased"/><br/>
        <br/>
        <form name="beanTest" method="POST" action="testPersonBean.jsp">
            输入姓名:<input type="text" name="name" size="50"><br/>
            选择选项:
            <select name="deceased">
                <option value="false">活着</option>
                <option value="true">已故</option>
            </select>
            <input type="submit" value="测试这个JavaBean">
        </form>
    </body>
</html>

虽然JavaBean和Java之间已经有了明确的界限,但是在某些方面JavaBean和Java之间仍然存在很容易混淆的地方,比如说重用,Java语言也可以为用户创建可重用的对象,但它没有管理这些对象相互作用的规则或标准,用户可以使用在Java中预先建立好的对象,但这必须具有对象在代码层次上的接口的丰富知识。而对于JavaBean,用户可以在应用程序构造器工具中使用各种JavaBean组件,而不需要编写任何代码。这种同时使用多个组件而不考虑其初始化情况的功能是对当前Java模型的重要扩展,所以也可以说JavaBean是在组件技术上对Java语言的扩展
如果真的要明确的定义,那么JavaBean的定义是:JavaBean是可复用的平台独立的软件组件,开发者可以在软件构造器工具中对其直接进行可视化操作

Relevant Link:

http://zh.wikipedia.org/wiki/JavaBeans
http://eduunix.ccut.edu.cn/index2/edu/%C7%E5%BB%AA%B4%F3%D1%A7%BC%C6%CB%E3%BB%FA%BF%CE%B3%CC/JAVA%B1%E0%B3%CC%D3%EF%D1%D4/text/ch09/se01/9_1_2.htm

0x2: POJO(pure old java object)

关于POJO,我们首先明确一句话

A JavaBean is a POJO that is serializable, has a no-argument constructor, and allows access to properties using getter and setter methods that follow a simple naming convention.

POJO(pure old java object)是普通java类,有一些private的参数作为对象的属性,然后针对每一个参数定义get和set方法访问的接口。仅此而已,不能有别的更多的动作了

POJO其实是比javabean更纯净的简单类或接口。POJO严格地遵守简单对象的概念,而一些JavaBean中往往会封装一些简单逻辑。
JavaBean是一种JAVA语言写成的可重用组件。它的方法命名,构造及行为必须符合特定的约定:

1.这个类必须有一个公共的缺省构造函数
2.这个类的属性使用getter和setter来访问,其他方法遵从标准命名规范
3.这个类应是可序列化的

简而言之,当一个POJO可序列化,有一个无参的构造函数,使用getter和setter方法来访问属性时,它就是一个JavaBean

Relevant Link:

http://en.wikipedia.org/wiki/Plain_Old_Java_Object
http://my.oschina.net/pacoyang/blog/151695

0x3: SSH与EJB区别

EJB是一种javabean的组合规范,SSH是3个框架jar包的组合。
EJB本身是JavaEE的规范由容器厂商负责实现,也就是使用EJB,需要使用JavaEE服务器。而用SSH,直接用Web服务器, SSH中要解决的目标和EJB是一致的。EJB是大型的,SSH是轻量级的。

Relevant Link:

http://blog.csdn.net/jojo52013145/article/details/5783677

 

2. JAVA EE应用的分层模型

0x1: Struts 2简介

Struts 2由传统的Struts 1、webwork两个经典MVC框架发展起来,与传统的Struts 1相比

1. Struts 2允许使用普通的、传统的java对象作为action
2. action的execute()方法不再与servlet api耦合,因而更易测试
3. 支持更多的视图技术
4. 基于AOP(Aspect-Oriented Programming)思想的拦截器机制,提供了更好的可扩展性
5. 更强大、更易用的输入校验功能
6. 整合的ajax支持
...

0x2: MVC简介

MVC思想将一个应用分成三个基本部分,这三个部分以最小的耦合协同工作,从而提高应用的可扩展性及可维护性

1. Model(模型)
2. View(视图)
3. Controller(控制器)

在经典的MVC模式中,事件由控制器处理,控制器根据事件的类型改变模型或视图,每个模型对应一系列的视图列表,这种对应关系通常采用注册来完成,即:把多个视图注册到同一个模型,当模型发生改变时,模型向所有注册过的视图发送通知,接下来,视图从对应的模型中获得信息,然后完成视图显示的更新
概括起来,MVC有如下特点

1. 多个视图可以对应同一个模型,可以减少代码的复制及代码的维护量,一旦模型发生改变,也易于维护
2. 模型返回的数据与显示逻辑分离。模型数据可以应用任何的显示技术
    1) JSP页面
    2) Velocity模版
    3) Excel文档
3. 应用被分隔为三层,降低了各层之间的耦合,提供了应用的可扩展性
4. 控制层的概念也很有效,由于它把不同的模型和不同的视图组合在一起,完成不同的请求。因此,控制层可以说是包含了用户请求权限的概念
5. MVC更符合软件工程化管理的思想。不同的层各司其职,每一层的组件具有相同的特征,有利于通过工程化和工具化产生管理程序代码

MVC思想将应用中各组件按功能进行分类,不同的组件使用不同技术充当,甚至推荐了严格分层,不同组件被严格限制在其所在层内,各层之间以松耦合的方式组织在一起,从而提供了良好的封装

0x3: Domain Object(领域对象)

此层由系列的POJO(Plain Old Java Object,普通的、传统的Java对象)组成,这些对象是该系统的Domain Object,往往包含了各自所需要实现的业务逻辑方法

0x4: DAO(Data Acess Object 数据访问对象)

此层由系列的DAO组件组成,这些DAO实现了对数据库的创建、查询、更新和删除(CRUD)等原子操作。
在经典Java EE应用中,DAO层也被改称为EAO层,EAO层组件的作用与DAO层组件的作用基本相似。只是EAO层主要完成对实体(Entity)的CRUD操作,因此简称为EAO层

0x5: PO(persistant object 持久对象)

持久对象,可以看成是与数据库中的表相映射的java对象。最简单的PO就是对应数据库中某个表中的一条记录,多个记录可以用PO的集合。PO中应该不包含任何对数据库的操作.

0x6: VO(value object 值对象)

通常用于业务层之间的数据传递,和PO一样也是仅仅包含数据而已。但应是抽象出的业务对象,可以和表对应,也可以不,这根据业务的需要。

0x7: DTO(data access object 数据访问对象)

此对象用于访问数据库。通常和PO结合使用,DAO中包含了各种数据库的操作方法。通过它的方法,结合PO对数据库进行相关的操作

我们通过DAO将POJO持久化为PO,用PO组装出来VO、DTO

0x8: BO(business object 业务对象)

封装业务逻辑的java对象,通过调用DAO方法,结合PO、VO进行业务操作

Relevant Link:

http://www.oecp.cn/hi/yongtree/blog/122
http://blog.sina.com.cn/s/blog_9af0d32b0101030l.html
http://my.oschina.net/pacoyang/blog/151695
http://www.cnblogs.com/xiaoluojava/archive/2010/05/07/1729992.html

 

3. 搭建Struts2 Demo应用

0x1: 创建web应用

为了让web应用具有struts 2支持功能,必须将struts 2框架的核心类库(JAR包)增加到web应用中
/HelloWorldStruts2/WebContent/WEB-INF/lib

commons-fileupload-1.3.1.jar
commons-io-2.2.jar
commons-lang-2.4.jar
commons-lang3-3.2.jar
commons-logging-1.1.3.jar
commons-logging-api-1.1.jar
freemarker-2.3.22.jar
javassist-3.11.0.GA.jar
ognl-3.0.6.jar
struts2-core-2.3.24.jar
xwork-core-2.3.24.jar

0x2: web.xml
通常,所有的MVC框架都需要Web应用加载一个核心控制器,对于Struts2框架而言,需要加载FilterDispatcher,只要Web应用负责加载FilterDispatcher,FilterDispatcher将会加载Struts2框架,因为Struts2将核心控制器设计成Filter,而不是一个普通Servlet。故为了让Web应用加载FilterDispatcher,只需要在web.xml文件中配置FilterDispatcher即可
/HelloWorldStruts2/WebContent/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns="http://java.sun.com/xml/ns/javaee" 
   xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
   http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
   id="WebApp_ID" version="3.0">
   
   <display-name>Struts 2</display-name>
   <welcome-file-list>
      <welcome-file>index.jsp</welcome-file>
   </welcome-file-list>
   <filter>
      <filter-name>struts2</filter-name>
      <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilterorg.apache.struts2.dispatcher.FilterDispatcher</filter-class>
   </filter>

   <filter-mapping>
      <filter-name>struts2</filter-name>
      <url-pattern>/*</url-pattern>
   </filter-mapping>
</web-app>

从本质上讲,struts2并不是一个新的东西,struts2是基于J2EE框架上的一层封装,使开发者更容易地进行J2EE的开发,struts2是一个MVC开发框架,从逻辑上位于spring、hibernate的上层
我们知道,以上配置仅仅修改了web.xml文件仅仅完成了为web应用增加struts2支持,而struts2是基于过滤器实现的,要使用strucs2功能至少还需要一个struts.xml文件

0x3: struts.xml

/HelloWorldStruts2/WebContent/WEB-INF/classes/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="com.tutorialspoint.struts2.HelloWorldAction" method="execute">
            <result name="success">/HelloWorld.jsp</result>
      </action>
   </package>
</struts>

0x4: index.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
   <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Hello World</title>
</head>
<body>
   <h1>Hello World From Struts2</h1>
   <form action="hello">
      <label for="name">Please enter your name</label><br/>
      <input type="text" name="name"/>
      <input type="submit" value="Say Hello"/>
   </form>
</body>
</html>

J2EE struts2采用了充分的面向对向、封装、解耦的设计,将后端的处理逻辑全部放到了javabeans实现,而将前台的展示由JSP实现,JSP和javabeans之间的纽带通过action实现,所有的用于处理后端业务逻辑的javabeans都继承了ActionSupport类
/HelloWorldStruts2/src/com.tutorialspoint.struts2

package com.tutorialspoint.struts2;

public class HelloWorldAction
{
       private String name;

       public String execute() throws Exception 
       {
          return "success";
       }
       
       public String getName() 
       {
          return name;
       }

       public void setName(String name) 
       {
          this.name = name;
       }
}

0x5: HelloWorld.jsp

如果execute方法返回"success", 然后我们把用户引到HelloWorld.jsp
/HelloWorldStruts2/WebContent/HelloWorld.jsp

<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
<title>Hello World</title>
</head>
<body>
   Hello World, <s:property value="name"/>
</body>
</html>

Relevant Link:

http://www.yiibai.com/struts2/struts2_examples.html
http://blog.csdn.net/xiazdong/article/details/7214967
http://www.360doc.com/content/11/0519/08/987036_117818681.shtml

 

4. struts2流程

0x1: struts2应用开发步骤

梳理一下struts2应用的开发步骤

1. 在web.xml文件中定义核心filter来拦截用户请求
由于web应用是基于请求/响应架构的应用,所以不管哪个MVC web框架,都需要在web.xml中配置该框架的核心filter或servlet,这样才可以让框架介入web应用中
/*
<filter>
    <filter-name>struts2</filter-name>
    <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilterorg.apache.struts2.dispatcher.FilterDispatcher</filter-class>
</filter>

<filter-mapping>
    <filter-name>struts2</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
*/

2. 如果需要以POST方式提交请求,则定义包含表单数据的JSP页面,如果仅仅只是以GET方式发送请求,则无须经过这一步
3. 定义处理用户请求的Action类
这一步是所有MVC框架中必不可少的,因为这个Actino就是MVC中的C,即控制层,该控制器负责调用Model里的方法来处理请求
/*
MVC框架的底层机制是,核心servlet或filter接收到用户请求后,通常会对用户请求进行简单预处理,例如解析、封装参数等,然后通过反射来创建Action实例,并调用Action的指定方法来处理用户请求,这就产生了一个问题: 当servlet或filter拦截用户请求后,它应该如何知道创建哪个Action的实例
    1) 利用配置文件
    2) 利用约定
*/

4. 我们知道,在MVC框架中,控制器实际上由2个部分共同组成
    1) 即拦截所有用户请求,处理请求的通用代码都由核心控制器完成
    2) 实际的业务控制(诸如调用model,返回处理结果等),则由Action处理

5. 配置Action
对于java领域的大部分MVC框架而言,都经常使用XML文件来配置管理,配置Action就是指定哪个请求对应哪个Action进行处理,从而让核心控制器根据该配置来创建合适的Action实例,并调用该Action的业务控制方法,例如
/*
<action name="hello" class="com.tutorialspoint.struts2.HelloWorldAction" method="execute">
    <result name="success">/HelloWorld.jsp</result>
</action>
指定了如果用户请求URL为login,则使用com.tutorialspoint.struts2.HelloWorldAction来处理,值得注意的是,struts2的convention插件借鉴了Rails框架的优点,开始支持"约定优于配置"的思想,也就是采用约定方式来规定用户请求地址和Action之间的对应关系
*/

6. 配置处理结果和物理视图资源之间的对应关系
当Action处理用户请求结束后,通常会返回一个处理结果(简单字符串),我们可以认为该名称是逻辑视图名,这个逻辑视图名需要和指定物理视图进行关联
/*
<action name="hello" class="com.tutorialspoint.struts2.HelloWorldAction" method="execute">
    <result name="success">/welcome.jsp</result>
    <result name="input">/login.jsp</result>
    <result name="error">/error.jsp</result>
</action>
*/

7. 编写视图资源,如果Action需要把一些数据传给视图资源,则可以借助于OGNL表达式

0x2: struts2的流程

StrutsPrepareAndExecuteFilterorg和XXXAction共同构成了struts2的控制器,常常把StrutsPrepareAndExecuteFilterorg称为核心控制器,把XXXAction称为业务控制器
XXXAction业务控制器通常不和物理视图关联,这种做法提供了很好的解耦,业务控制器只负责返回处理结果,而该处理结果与什么视图关联,依然由StrutsPrepareAndExecuteFilterorg来决定
这样做的好处是: 如果需要将某个视图名映射到不同视图资源,就无须修改XXXAction的代码,而只需修改配置即可,StrutsPrepareAndExecuteFilterorg会自动去加载不同的物理视图文件
在struts2框架的控制下,用户请求不再向JSP页面发送,而是由核心控制器StrutsPrepareAndExecuteFilterorg来"调用"JSP页面来生成响应,此处的调用并不是直接调用,而是将请求forward到指定JSP页面

 

5. struts2的常规配置

虽然struts2提供了Convention插件来管理Action、结果映射,但对于大部分J2EE应用来说,通常会考虑使用XML文件来管理struts2的配置信息。struts2的默认配置文件名为struts.xml,该文件应该放在web应用的类加载路径下,通常就是在WEB-INF/classes路径下
struts.xml配置文件最大的作用就是配置Action和请求之间的对应关系,并配置逻辑视图和物理视图资源之间的对应关系,除此之外,struts.xml文件还有一些额外的功能,例如Bean配置、配置常量、导入其他配置文件等

0x1: 常量配置

struts2除了可以使用struts.xml文件来管理配置之外,还可以使用struts.properties文件来管理常量,该文件定义了struts2框架的大量常量,开发者可以通过改变这些常量来满足应用的需求。struts.properties文件是一个标准的Properties文件,该文件包含了一系列的key-value对,每个key就是一个struts2常量,该key对应的value就是一个struts2常量值
struts2的常量相当于对于struts2应用整体起作用的属性,因此struts2常量也常常被称为struts2属性

只要将struts.properties文件放在web应用的类加载路径下,struts2框架就可以加载该文件
1. 基础Constants
    1) struts.devMode: 可选值true,false(默认false),在开发模式下,struts2的动态重新加载配置和资源文件的功能会默认生效。同时开发模式下也会提供更完善的日志支持 
    2) struts.i18n.reload: 可选值true,false(默认值依赖于struts.devMode),是否自动重新加载本地的资源文件。
    3) struts.i18n.encoding: 主要用于设置请求编码(默认值(UTF-8),Head和Include标签的解析编码、资源和配置文件的解析编码 
    4) struts.configuration.xml.reload: 可选值true,false(默认值依赖于struts.devMode)是否自动重新加载XML配置文件
    5) struts.action.extension: 设置struts的Action请求的后缀,支持多个时以逗号隔开 
    6) struts.action.excludePattern: 设置struts所排除的url(通过正则表达式匹配)(支持多个,以逗号隔开)
    7) struts.tag.altSyntax: 可选值true,false(默认true),是否支持ognl表达式
    8) struts.url.http.port: 设置生成URL所对应的http端口
    9) struts.url.https.port: 设置生成URL所对应的https端口
    10) struts.url.includeParams: 可选值 none、get、all(默认get),设置URL是否包含参数,以及是否只包含GET方式的参数 
    11) struts.locale: 设置struts2默认的locale,决定使用哪个资源文件 
    12) struts.ui.templateDir: 该属性指定视图主题所需要模板文件的位置,该属性的默认值是template,即默认加载template路径下的模板文件
    13) struts.ui.theme: 该属性指定视图标签默认的视图主题,该属性的默认值是xhtml 
    14) struts.ui.templateSuffix: 该属性指定模板文件的后缀,该属性的默认属性值是ftl。该属性还允许使用ftl、vm或jsp,分别对应FreeMarker、Velocity和JSP模板
    15) struts.multipart.saveDir: 设置上传临时文件的默认目录
    16) struts.multipart.maxSize: 设置上传的临时文件的最大限制
    17) struts.objectFactory.spring.autoWire: 可选值(name、type、auto、constructor、name)(默认name),设置spring的自动装配方式,只有引入spring插件后才有效 
    18) struts.objectFactory.spring.autoWire.alwaysRespect: (默认false)设置是否总是以自动装配策略创建对象 
    19) struts.objectFactory.spring.useClassCache: (默认false)对象工厂是否使用类缓存,开发模式无效 
    20) struts.xslt.nocache: (默认为false)设置XsltResult是否不是用缓存 
    21) struts.custom.properties: 设置用户的自定义属性文件名列表(用""隔开)
    22) struts.custom.i18n.resources: 设置用户自定义的资源文件路径列表(用""隔开)
    23) struts.serve.static: (默认false)设置是否支持静态资源请求(要求url在struts或static下)
    24) struts.serve.static.browserCache: (默认false)是否在静态资源响应中设置缓存。只有在支持静态资源时有效 
    25) struts.el.throwExceptionOnFailure: (默认false)是否在解析el表达式或无法找到属性时抛出RuntimeException
    26) struts.ognl.logMissingProperties: (默认false)是否日志无发找到的属性
    27) struts.ognl.enableExpressionCache: 是否缓存ognl解析的表达式
    28) struts.enable.DynamicMethodInvocation: (默认false)是否支持动态的方法调用,在URL上通过!method指定方法 
    29) struts.enable.SlashesInActionNames: 在URL中的Action段中是否支持斜线
    30) struts.mapper.alwaysSelectFullNamespace: (默认false)是否总是用最后一个斜线前的URL段作为namespace
2. 核心对象Constants
    1) struts.actionProxyFactory: 设置ActionProxy的实体工厂,该工厂同时也生成默认的ActionInvoctation
    2) struts.xworkConverter: 设置XWorkConverter对象,该对象用于获取各种类型的转换器 
    3) struts.unknownHandlerManager: 设置UnknownHandlerManager的实现类,用于处理无法找到方法等异常 
    4) struts.multipart.handler: 设置mutipartRequest的handler(默认是jakarta)对应类,org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest
    5) struts.mapper.class: 可选值(struts、composite、restful、restful2)设置URL解析且映射到ACTION的实现(默认struts)
    6) struts.mapper.prefixMapping: 通过URL前缀映射到对应的Mapper,格式为urlPrefix1:mapperName2、urlPrefix2:mapperName2。必须添加mapperClass为org.apache.struts2.dispatcher.mapper.PrefixBasedActionMapper,并指定struts.mapper.class为该mapper 
    7) struts.mapper.composite: 设置是否支持复合(多个)actionMapper,mapperName用逗号隔开。必须配置struts.mapper.class 为composite 才会生效
    8) struts.mapper.idParameterName: 用于Restful2ActionMapper作为URL中id所对应的parameterName
    9) struts.ognl.allowStaticMethodAccess: (默认false)设置ognl表达式是否支持静态方法 
    10) struts.configuration: 设置struts2的Settings类 
    11) struts.urlRenderer: 设置struts2的URL render(用于生成的URL)(默认struts),类名org.apache.struts2.components.ServletUrlRenderer
    12) struts.objectFactory: 设置struts2的对象工厂,默认(struts),类名org.apache.struts2.impl.StrutsObjectFactory,当引入struts2-spring插件之后,则被修改为org.apache.struts2.spring.StrutsSpringObjectFactory
    13) struts.xworkTextProvider: 设置struts2的资源文件内容提供类的实现。默认为com.opensymphony.xwork2.TextProviderSupport
    14) struts.actionValidatorManager: 设置ActionValidatorManager 的实现类 
    15) struts.valueStackFactory: 设置struts2的ValueStack工厂的实现 
    16) struts.reflectionProvider: 设置ReflectionProvider的实现类
    17) struts.reflectionContextFactory: 设置ReflectionContextFactory的实现类
    18) struts.patternMatcher: 设置PatternMatcher的实现类
    19) struts.staticContentLoader: 设置StaticContentLoader的实现类

struts2默认会加载类加载路径下的struts.xml、struts-default.xml、struts-plugin.xml三类文件

1. struts.xml: 开发者定义的默认配置文件
2. struts-default.xml: struts2框架自带的配置文件
3. struts-plugin.xml: struts2插件的默认配置文件

struts2配置常量总共有以下几种方法,按如下搜索顺序加载

1. struts-default.xml
2. struts-plugin.xml
3. struts.xml
4. struts.properties
5. web.xml
//如果在多个文件中配置了同一个struts2常量,则后一个文件中配置的常量值会覆盖前面文件中配置的常量值

0x2: 包含其他配置文件

为了避免struts.xml文件过于庞大、提高struts.xml文件的可读性,我们可以将一个struts.xml配置文件分解成多个配置文件,然后在struts.xml文件中包含其他配置文件
/*
<include file="struts-part1.xml" />
*/

通过这种方式,struts2能以一种模块化的方式来管理struts.xml配置文件

 

6. 实现Action

对于struts2应用的开发者而言,Action才是应用的核心,开发者需要提供大量的Action类,并在struts.xml文件中配置Actino,Action类里包含了对用户请求的处理逻辑,Actino类也被称为业务控制器
相对于struts1而言,struts2采用了低侵入式设计,struts2不要求Action类继承任何的struts2基类,或者实现任何struts2接口,在这种设计方式下,struts2的Action类是一个普通的POJO(通常应该包含一个无参数的execute方法),从而有很好的代码复用性
struts2通常直接使用Action来封装HTTP请求参数,因此,Action类里还应该包含与请求参数对应的属性(并非强制),并且为这些属性提供对应的setter和getter方法(强制),例如用户请求包含user、pass两个请求参数,那么Action类应该提供user、pass两个属性来封装用户的请求参数,并且为user、pass提供对应的setter、getter方法

Action类里的属性,不仅可用于封装请求参数,还可用于封装处理结果,例如我们可以在Action类中增加一个tip属性,并为该属性提供对应的setter、getter方法
/*
private String tip;
public String getTip()
{
    return tip;
}
public void setTip(String tip)
{
    this.tip = tip;
}
*/

一旦在Action中设置了tip属性的值,就可以在下一个页面中使用struts2标签来输出该属性的值

<s:property value="tip"/>

0x1: Action接口和ActionSupport基类

为了让用户开发的Action类更规范,struts2提供了一个Action接口,这个接口定义了struts2的Action处理类应该实现的规范

public interface Action 
{
    //定义Action接口里包含的一些结果字符串,用于规范化返回值,如果开发者依然希望使用特定的字符串作为逻辑视图名,依然是可以使用的
    public static final String SUCCESS = "success";
    public static final String NONE = "none";
    public static final String ERROR = "error";
    public static final String INPUT = "input";
    public static final String LOGIN = "login";

    //定义处理用户请求的execute方法
    public String execute() throws Exception;
}

另外,struts2为Action接口提供了一个实现类: ActionSuport

/**
 * Provides a default implementation for the most common actions.
 * See the documentation for all the interfaces this class implements for more detailed information.
 */
public class ActionSupport implements Action, Validateable, ValidationAware, TextProvider, LocaleProvider, Serializable 
{ 
    protected static Logger LOG = LoggerFactory.getLogger(ActionSupport.class); 

    private final transient TextProvider textProvider = new TextProviderFactory().createInstance(getClass(), this);
    private final ValidationAwareSupport validationAware = new ValidationAwareSupport();

    //收集校验错误的方法
    public void setActionErrors(Collection errorMessages) 
    {
        validationAware.setActionErrors(errorMessages);
    }

    //返回校验错误的方法
    public Collection getActionErrors() 
    {
        return validationAware.getActionErrors();
    } 
    public void setActionMessages(Collection messages) 
    {
        validationAware.setActionMessages(messages);
    } 
    public Collection getActionMessages() 
    {
        return validationAware.getActionMessages();
    } 
    /**
    * @deprecated Use {@link #getActionErrors()}.
    */
    public Collection getErrorMessages() {
    return getActionErrors();
    } 
    /**
    * @deprecated Use {@link #getFieldErrors()}.
    */
    public Map getErrors() {
    return getFieldErrors();
    }

    //设置表单域校验错误信息
    public void setFieldErrors(Map errorMap) 
    {
        validationAware.setFieldErrors(errorMap);
    } 
    
    //返回表单域校验错误信息
    public Map getFieldErrors() 
    {
        return validationAware.getFieldErrors();
    }

    //控制Locale的相关信息
    public Locale getLocale() 
    {
        ActionContext ctx = ActionContext.getContext();
        if (ctx != null) 
        {
            return ctx.getLocale();
        } 
        else 
        {
            LOG.debug("Action context not initialized");
            return null;
        }
    }

    //返回国际化信息的方法
    public String getText(String aTextName) {
    return textProvider.getText(aTextName);
    } 
    public String getText(String aTextName, String defaultValue) {
    return textProvider.getText(aTextName, defaultValue);
    } 
    public String getText(String aTextName, String defaultValue, String obj) {
    return textProvider.getText(aTextName, defaultValue, obj);
    } 
    public String getText(String aTextName, List args) {
    return textProvider.getText(aTextName, args);
    } 
    public String getText(String key, String[] args) {
    return textProvider.getText(key, args);
    } 
    public String getText(String aTextName, String defaultValue, List args) {
    return textProvider.getText(aTextName, defaultValue, args);
    } 
    public String getText(String key, String defaultValue, String[] args) {
    return textProvider.getText(key, defaultValue, args);
    } 
    public String getText(String key, String defaultValue, List args, ValueStack stack) {
    return textProvider.getText(key, defaultValue, args, stack);
    } 
    public String getText(String key, String defaultValue, String[] args, ValueStack stack) {
    return textProvider.getText(key, defaultValue, args, stack);
    }

    //用于访问国际化资源包的方法
    public ResourceBundle getTexts() {
    return textProvider.getTexts();
    } 
    public ResourceBundle getTexts(String aBundleName) {
    return textProvider.getTexts(aBundleName);
    }

    //添加错误信息
    public void addActionError(String anErrorMessage) {
    validationAware.addActionError(anErrorMessage);
    }

    //添加字段校验失败的错误信息
    public void addActionMessage(String aMessage) {
    validationAware.addActionMessage(aMessage);
    } 
    public void addFieldError(String fieldName, String errorMessage) {
    validationAware.addFieldError(fieldName, errorMessage);
    }

    //默认的input方法,直接返回INPUT字符串
    public String input() throws Exception {
    return INPUT;
    }

    public String doDefault() throws Exception {
    return SUCCESS;
    }


    /**
    * A default implementation that does nothing an returns "success".
    * <p/>
    * Subclasses should override this method to provide their business logic.
    * <p/>
    * See also {@link com.opensymphony.xwork2.Action#execute()}.
    *
    * @return returns {@link #SUCCESS}
    * @throws Exception  can be thrown by subclasses.
    默认的处理用户请求的方法,直接返回SUCCESS字符串
    */
    public String execute() throws Exception {
    return SUCCESS;
    } 

    public boolean hasActionErrors() {
    return validationAware.hasActionErrors();
    } 

    public boolean hasActionMessages() {
    return validationAware.hasActionMessages();
    }


    public boolean hasErrors() {
    return validationAware.hasErrors();
    } 

    public boolean hasFieldErrors() {
    return validationAware.hasFieldErrors();
    } 

    /**
    * Clears field errors. Useful for Continuations and other situations
    * where you might want to clear parts of the state on the same action.
    */
    public void clearFieldErrors() {
    validationAware.clearFieldErrors();
    }


    /**
    * Clears action errors. Useful for Continuations and other situations
    * where you might want to clear parts of the state on the same action.
    */
    public void clearActionErrors() {
    validationAware.clearActionErrors();
    }


    /**
    * Clears messages. Useful for Continuations and other situations
    * where you might want to clear parts of the state on the same action.
    */
    public void clearMessages() {
    validationAware.clearMessages();
    }


    /**
    * Clears all errors. Useful for Continuations and other situations
    * where you might want to clear parts of the state on the same action.
    */
    public void clearErrors() {
    validationAware.clearErrors();
    }


    /**
    * Clears all errors and messages. Useful for Continuations and other situations
    * where you might want to clear parts of the state on the same action.
    */
    public void clearErrorsAndMessages() {
    validationAware.clearErrorsAndMessages();
    }


    /**
    * A default implementation that validates nothing.
    * Subclasses should override this method to provide validations.
    */
    public void validate() {
    }


    public Object clone() throws CloneNotSupportedException {
    return super.clone();
    }


    /**
    * <!-- START SNIPPET: pause-method -->
    * Stops the action invocation immediately (by throwing a PauseException) and causes the action invocation to return
    * the specified result, such as {@link #SUCCESS}, {@link #INPUT}, etc.
    * <p/>
    *
    * The next time this action is invoked (and using the same continuation ID), the method will resume immediately
    * after where this method was called, with the entire call stack in the execute method restored.
    * <p/>
    *
    * Note: this method can <b>only</b> be called within the {@link #execute()} method.
    * <!-- END SNIPPET: pause-method -->
    *
    * @param result the result to return - the same type of return value in the {@link #execute()} method.
    */
    public void pause(String result) {
    } 
}

ActionSupprt类是struts2默认的Action处理类,如果开发者的Action类继承该ActionSupport类,则会大大简化Action的开发,实际上,如果我们配置Action没有指定class属性(即没有用户提供的Action类),系统自动使用ActionSupport类作为Action处理类

0x2: Action访问Servlet API

struts2的Action没有与任何Servlet API耦合,这是struts2的一个改进点,但是对于web应用的控制器而言,不防问servlet api几乎是不可能的,例如跟踪HTTP session,web应用中通常需要访问的api如下

1. HttpServletRequest: 对应于JSP内置对象中的requst
2. HttpSession: 对应于JSP内置对象中的session
3. ServletContext: 对应于JSP内置对象中的application

struts2提供了一个ActionContext类,struts2的Action可以通过该类来访问servlet api

1. Object get(String key): Returns a value that is stored in the current ActionContext by doing a lookup using the value's key.
2. ActionInvocation: getActionInvocation(): Gets the action invocation (the execution state).
3. Map<String,Object> getApplication(): Returns a Map of the ServletContext when in a servlet environment or a generic application level Map otherwise.返回一个map对象,该对象模拟了该应用的ServletContext实例
4. Container getContainer(): Sets the container for this request
5. static ActionContext    getContext(): Returns the ActionContext specific to the current thread.静态方法,获取系统的ActionContext实例
6. Map<String,Object> getContextMap(): Gets the context map.获取所有请求参数,类似于调用HttpServletRequest对象的getParameterMap()方法
7. Map<String,Object> getConversionErrors(): Gets the map of conversion errors which occurred when executing the action.
8. <T> T getInstance(Class<T> type) Locale getLocale(): Gets the Locale of the current action.
9. String getName():Gets the name of the current Action.
10. Map<String,Object> getParameters(): Returns a Map of the HttpServletRequest parameters when in a servlet environment or a generic Map of parameters otherwise.
11. Map<String,Object> getSession(): Gets the Map of HttpSession values when in a servlet environment or a generic session map otherwise.返回一个Map对象,该Map对象模拟了HttpSession实例
12. ValueStack getValueStack(): Gets the OGNL value stack.
13. void put(String key, Object value): Stores a value in the current ActionContext.
14. void setActionInvocation(ActionInvocation actionInvocation): Sets the action invocation (the execution state).直接传入一个Map实例,将该Map实例里的key-value对转换为application的属性名、属性值
15. void setApplication(Map<String,Object> application): Sets the action's application context.
16. void setContainer(Container cont): Gets the container for this request
17. static void    setContext(ActionContext context): Sets the action context for the current thread.
18. void setContextMap(Map<String,Object> contextMap): Sets the action's context map.
19. void setConversionErrors(Map<String,Object> conversionErrors): Sets conversion errors which occurred when executing the action.
20. void setLocale(Locale locale): Sets the Locale for the current action.
21. void setName(String name): Sets the name of the current Action in the ActionContext.
22. void setParameters(Map<String,Object> parameters): Sets the action parameters.
23. void setSession(Map<String,Object> session): Sets a map of action session values.直接传入一个Map实例,将该Map实例里的key-value对转换成session的属性名、属性值
24. void setValueStack(ValueStack stack): Sets the OGNL value stack.

code example

package com.tutorialspoint.struts2;

import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionContext;

public class HelloWorldAction implements Action
{
       private String name;

       public String execute() throws Exception 
       {
           ActionContext ctx = ActionContext.getContext();
           //通过ActionContext访问application范围的属性值
           Integer counter = (Integer)ctx.getApplication().get("counter");
           if(counter == null)
           {
               counter = 1;
           }
           else
           {
               counter = counter + 1;
           }
           //通过ActionContext设置application范围的属性
           ctx.getApplication().put("counter", counter);
           //通过ActionContext设置session范围的属性
           ctx.getSession().put("user", getName());
           if(getName().equals("LittleHann"))
           {
               //通过ActionContext设置request范围的属性
               ctx.put("tip", "login success");
               return SUCCESS;
           }
           else
           {
               ctx.put("tip", "login faild");
               return ERROR;
           }   
       }
       
       public String getName() 
       {
          return name;
       }

       public void setName(String name) 
       {
          this.name = name;
       }
}

通过struts.xml配置这个Action

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
   "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
   "http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<constant name="struts.devMode" value="true" />
   <package name="helloworld" extends="struts-default">
      <action name="hello" class="com.tutorialspoint.struts2.HelloWorldAction" method="execute">
            <result name="success">/HelloWorld.jsp</result>
      </action>
   </package>
</struts>

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns="http://java.sun.com/xml/ns/javaee" 
   xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
   http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
   id="WebApp_ID" version="3.0">
   
   <display-name>Struts 2</display-name>
   <welcome-file-list>
      <welcome-file>index.jsp</welcome-file>
   </welcome-file-list>
   <filter>
      <filter-name>struts2</filter-name>
      <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
   </filter>

   <filter-mapping>
      <filter-name>struts2</filter-name>
      <url-pattern>/*</url-pattern>
   </filter-mapping>
</web-app>

HelloWorld.jsp

<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
<title>Hello World</title>
</head>
<body>
   Hello World, <s:property value="name"/>
   login times ${applicationScope.counter} <br/>
   ${sessionScope.user}, has login! <br/>
   ${requestScope.tip}
</body>
</html>

从结果来看,struts2的Action设计非常优秀,它既可以彻底与servlet API分离,从而可以允许该Action脱离WEB容器运行,也就可以脱离WEB容器来测试Actino,又允许用简单方式来操作request、session、application范围的属性

0x3: Action直接访问servlet API

虽然struts2提供了ActionContext来访问Servlet API,但这种访问并不是直接获得Servlet API的实例,为了在Action中直接访问Servlet API,struts2还提供了如下几个接口

1. ServletContextAware: 实现该接口的Action可以直接访问WEB应用的ServletContext实例
2. ServletRequestAware: 实现该接口的Action可以直接访问用户请求的HttpServletRequest实例
3. ServletResponseAware: 实现该接口的Action可以直接访问服务器响应的HttpServletResponse实例

我们继续用之前的login case进行讨论: HelloWorldAction.java

package com.tutorialspoint.struts2;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts2.interceptor.ServletResponseAware;

import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionContext;

public class HelloWorldAction implements Action, ServletResponseAware
{
       private String name;
       private HttpServletResponse response;
       
       //重新实现ServletResponseAware接口必须实现的方法
       @Override 
       public void setServletResponse(HttpServletResponse response) 
       { 
           this.response = response;
       }
       
       
       public String execute() throws Exception 
       {
           ActionContext ctx = ActionContext.getContext();
           //通过ActionContext访问application范围的属性值
           Integer counter = (Integer)ctx.getApplication().get("counter");
           if(counter == null)
           {
               counter = 1;
           }
           else
           {
               counter = counter + 1;
           }
           //通过ActionContext设置application范围的属性
           ctx.getApplication().put("counter", counter);
           //通过ActionContext设置session范围的属性
           ctx.getSession().put("user", getName());
           if(getName().equals("LittleHann"))
           {
               //通过response添加cookie
               Cookie c = new Cookie("user", getName());
               c.setMaxAge(60 * 60);
               response.addCookie(c);
               
               //通过ActionContext设置request范围的属性
               ctx.put("tip", "login success");
               return SUCCESS;
           }
           else
           {
               ctx.put("tip", "login faild");
               return ERROR;
           }   
       }
       
       public String getName() 
       {
          return name;
       }

       public void setName(String name) 
       {
          this.name = name;
       }

    
}

HelloWorld.jsp

<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
<title>Hello World</title>
</head>
<body>
   Hello World, <s:property value="name"/>
   login times ${applicationScope.counter} <br/>
   ${sessionScope.user}, has login! <br/>
   ${requestScope.tip}
   cookie: ${cookie.user.value}
</body>
</html>

需要注意的,即使在struts2的Action类中获得了HttpServletResponse对象,也不能直接生成对客户端的响应,这在struts2中没有任何意义

0x4: 使用servletactioncontext访问servlet API

除此之外,为了能直接访问Servlet API,struts2还提供了一个ServletActionContext工具类,这个类包含如下几个静态方法

1. static ActionContext    getActionContext(javax.servlet.http.HttpServletRequest req): Gets the current action context.获取web应用的PageContext对象
2. static ActionMapping    getActionMapping():Gets the action mapping for this context
3. static javax.servlet.jsp.PageContext    getPageContext(): Returns the HTTP page context.
4. static javax.servlet.http.HttpServletRequest    getRequest():Gets the HTTP servlet request object.获取web应用的HttpServletRequest对象
5. static javax.servlet.http.HttpServletResponse getResponse(): Gets the HTTP servlet response object.获取web应用的HttpServletResponse对象
6. static javax.servlet.ServletContext getServletContext(): Gets the servlet context.获取web应用的ServletContext对象
7. static ValueStack getValueStack(javax.servlet.http.HttpServletRequest req): Gets the current value stack for this request
8. static void setRequest(javax.servlet.http.HttpServletRequest request): Sets the HTTP servlet request object.
9. static void setResponse(javax.servlet.http.HttpServletResponse response): Sets the HTTP servlet response object.
10. static void    setServletContext(javax.servlet.ServletContext servletContext): Sets the current servlet context object

借助于ServletActionContext类的帮助,开发者也可以在Action中访问Servlet API,并可避免需要实现ServletXXXAware接口

package com.tutorialspoint.struts2;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts2.ServletActionContext;
import org.apache.struts2.interceptor.ServletResponseAware;

import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionContext;

public class HelloWorldAction implements Action, ServletResponseAware
{
       private String name;
       private HttpServletResponse response;
       
       //重新实现ServletResponseAware接口必须实现的方法
       @Override 
       public void setServletResponse(HttpServletResponse response) 
       { 
           this.response = response;
       } 
       
       public String execute() throws Exception 
       {
           ActionContext ctx = ActionContext.getContext();
           //通过ActionContext访问application范围的属性值
           Integer counter = (Integer)ctx.getApplication().get("counter");
           if(counter == null)
           {
               counter = 1;
           }
           else
           {
               counter = counter + 1;
           }
           //通过ActionContext设置application范围的属性
           ctx.getApplication().put("counter", counter);
           //通过ActionContext设置session范围的属性
           ctx.getSession().put("user", getName());
           if(getName().equals("LittleHann"))
           {
               //通过response添加cookie
               Cookie c = new Cookie("user", getName());
               c.setMaxAge(60 * 60);
               //通过ServletActionContext工具类直接访问Servlet API
               ServletActionContext.getResponse().addCookie(c);
               
               //通过ActionContext设置request范围的属性
               ctx.put("tip", "login success");
               return SUCCESS;
           }
           else
           {
               ctx.put("tip", "login faild");
               return ERROR;
           }   
       }
       
       public String getName() 
       {
          return name;
       }

       public void setName(String name) 
       {
          this.name = name;
       }  
}

对上文进行一下总结梳理,在Action中访问Servlet API有如下几种方法

1. 通过ActionContext类中指定方法访问
2. 让Action类实现ServletXXXAware接口后,在类中可直接访问
3. 通过ServletActionContext工具类的静态方法访问

 

7. 配置Action

实现了Action处理类之后,就可以在struts.xml文件中配置该Action了,配置Action就是让struts2知道哪个Action处理哪个请求(配置Action和请求的映射关系),我们可以认为,Action是struts2的基本"程序单位" 

0x1: 包和命名空间

struts2使用包来组织Action,因此,将Action定义放在包定义下完成,定义Action通过使用<package../>下的<action../>子元素来完成,而每个package元素配置一个包
struts2框架中核心组件就是Action、拦截器等,struts2框架使用包来管理Action和拦截器等,每个包就是多个Action、多个拦截器、多个拦截器引用的集合

1. name: 配置<package../>元素时必须指定name属性,这个属性是引用该包的唯一标识
2. extends: 还可以指定一个可选的extends属性,extends属性值必须是另一个包的name属性,表示让该包继承另一个包,子包还可以从一个或多个父包中继承到拦截器、拦截器栈、action等配置,因为struts2的配置是从上到下处理的,所以父包应该在子包前面定义
3. namespace: 该属性是一个可选属性,该属性定义该包的命名空间
4. abstract: struts2还提供了一种抽象包,抽象包意味着改包不能包含Action定义,为了显式指定一个包是抽象包,可以为该<package../>元素增加abstract="true"属性
//struts.xml文件的<package../>元素用于定义包配置,每个<package../>元素定义一个包配置

定义每一个package元素时,都可以指定一个namespace属性,用于指定改包对应的命名空间。struts2之所以提供命名空间的功能,主要是为了处理同一个web应用中包含同名Action的情形,struts2以命名空间的方法来管理Action,同一个命名空间里不能有同名的Action
struts2不支持为单独的Action设置命名空间,而是通过为包指定namespace属性来为包下面的所有Action指定共同的命名空间,如果配置<package../>时没有指定namespace属性,则该包下的所有Action处于默认的包空间下

当某个包指定了命名空间后,该包下所有的Action处理的URL应该是命名空间+Action名
http://xxxx/namespace.action
//struts2命名空间的作用类似于struts2里模块的作用,因为它从逻辑上对Action进行了分类,它允许以模块化的方式来组织Action

除此之外,struts2还可以显示指定根命名空间,通过设置某个包的namespace="/"来指定根命名空间

如果请求为/barspace/bar.action

1. 系统首先查找/barspace命名空间里名为bar的Action,如果在该命名空间里找到对应的Action,则使用该Action处理用户请求
2. 否则,系统将到默认命名空间中查找名为bar的Action,如果找到对应的Action,则使用该Action处理用户请求
3. 如果两个命名空间里都找不到名为bar的Action,则系统出现错误

如果请求为/login.action

1. 系统会在根命名空间("/")中查找名为login的Action,如果找到了,则由该Action处理用户请求
2. 否则,系统将转入默认空间中查找名为login的Action,如果默认的命名空间中有名为login的Action,则由该Action处理用户请求
3. 如果两个命名空间里都找不到login的Action,则系统出现错误

命名空间只有一个级别,如果请求的URL是/bookservice/search/get.action,系统将先在/bookservice/search的命名空间下查找名为get的Action,如果在该命名空间内找到名为get的Action,则由该Action处理用户请求;如果在该命名空间内没有找到名为get的Action,系统将直接进入默认的命名空间中查找名为get的Action,而不会进行逐级递归

0x2: Action的基本配置

1. name: 定义Action时,至少需要指定该Action的name属性,该name属性既是该Action的名字,也指定了该Action所处理的请求的URL
2. class: class属性指定了该Action的实现类,如果我们没有指定该属性,系统则默认使用系统的ActionSupport类
3. method: 处理逻辑的方法

Action只是一个逻辑控制器,它并不直接对浏览者生成任何响应,因此,Action处理完用户请求后需要将指定的视图资源呈现给用户,因此需要配置逻辑视图和物理视图资源之间的对应关系
这通过<result../>元素来定义

0x3: 使用Action的动态方法调用

struts2允许同一个Action内包含抖个控制处理逻辑,例如对于同一个表单,当用户通过不同的提交按钮来提交同一个表单时,系统需要使用Action的不同方法来处理用户请求,这就需要让同一个Action里包含多个控制处理逻辑,此时,可以采用DMI(dynamic method invocation 动态方法调用)来处理这种请求,动态方法调用是指表单元素的action并不是直接等于某个Action的名字的名字,而是以如下形式存在

<!-- 
action属性为actionNmae!methodName的形式 
其中actionName指定提交到哪个Action,methodName指定提交到指定方法
-->
action="ActionName!methodName"

通过这种方法,我们可以在一个Action中包含多个处理逻辑,并通过为表单元素指定不同action属性来提交给Action的不同方法,默认情况下是直接调用execute方法
使用动态方法必须对struts2进行显式设置: struts.enable.DynamocMethodInvocation = true

0x4: 指定method属性及使用通配符

对于一个表单里包含多个处理流程,struts2还提供了一种处理方法,即将一个Action处理类定义成多个逻辑Action,即在配置<action../>元素时,为它指定method属性,则可以让Action调用指定方法(而不是默认的execute方法)

在配置<action../>元素时,允许在指定name属性时使用模式字符串(即用"*"代表一个或多个任意字符串),接下来就可以在class、method属性及<result../>子元素中使用{N}的形式来代表前面第N个星号(*)所匹配的子串

<?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="*Action" class="com.tutorialspoint.struts2.HelloWorldAction" method="{1}">
            <result name="success">/HelloWorld.jsp</result>
      </action>
   </package>
</struts>

index.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
   <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Hello World</title>
</head>
<body>
   <h1>Hello World From Struts2</h1>
   <form action="loginAction">
      <label for="name">Please enter your name</label><br/>
      <input type="text" name="name"/>
      <input type="submit" value="Say Hello"/>
   </form>
</body>
</html>

实例中定义的<action name="*Action"../>定义了一系列的逻辑Action,例如,如果用户请求的URL为loginAction.action,则调用com.tutorialspoint.struts2.HelloWorldAction类的login方法

对上文进行一下总结梳理,让同一个Action同时具备多个处理逻辑的方法有

1. 在前端调用端使用: actionName!methodName的形式
2. struts.xml中配置action的method属性,显式指定处理逻辑函数
3. 使用*通配符

0x5: 配置默认Action

为了让struts2的Action可以接管用户请求,我们可以配置name="*"的Action,除此之外,struts2还支持配置默认Action,当用户请求找不到对应的Action时,系统默认的Action即将处理用户请求,配置默认的Action通过<default-action-ref../>元素完成

<package name="helloworld" extends="struts-default" namespace="/">
    <!-- 配置一个默认Action,默认Action为simpleViewResultAction -->
    <default-action-ref name="simpleViewResultAction"/>

    <!-- 通过action元素配置默认的Action --> 
    <action name="simpleViewResultAction" class="com.tutorialspoint.struts2.HelloWorldAction" method="execute">
        <result name="success">/HelloWorld.jsp</result>
    </action>
</package>

这样,就可以明确指定一个Action作为默认Action,将默认Action配置在默认命名空间里就可以让该Action处理所有用户请求,因为默认命名空间的Action可以处理任何命名空间的请求

0x6: 配置Action的默认处理类

配置<action../>元素时可以不指定class属性,如果没有指定class属性,则系统默认使用ActionSupport作为Action处理类
实际上,struts2允许开发者自己配置Action默认处理类,配置Action的默认处理类使用<default-class-ref../>元素,配置该元素时只需要指定一个class属性,该class属性指定的类就是Action的默认处理类

 

8. 配置处理结果

Action只是struts2控制器的一部分,所以它不能直接生成对浏览者的响应,Action只负责处理请求,负责生成响应的视图组件(通常就是JSP页面),而Action会为JSP页面提供显示的数据,当Action处理用户请求结束后,控制器应该使用哪个视图资源来生成响应,由<result../>元素指定映射关系

0x1: 理解处理结果

Action处理完用户请求后,将返回一个普通字符串,整个普通字符串就是一个逻辑视图名,struts2通过配置逻辑视图名和物理视图名之间的映射关系,一旦系统收到Action返回的某个逻辑视图名,系统就会把对应的物理视图呈现给浏览者

相对于struts1框架而言,struts2的逻辑视图不再是ActionForward对象,而是一个普通字符串,这样的设计更有利于将Action类与struts2框架分离,提供了更好的代码复用性,除此之外,struts2还支持多种结果映射

1. JSP视图
2. FreeMarker试图
3. 下一个Action,形成Action的链式处理

0x2: 配置结果

struts2的Action处理用户请求结束之后,返回一个普通字符串(逻辑视图名),必须在struts.xml文件中完成逻辑试图和物理视图资源的映射,才可让系统转到实际的视图资源

<action name="*Action" class="com.tutorialspoint.struts2.HelloWorldAction" method="{1}">
            <result name="success" type="dispatcher">
                <param name="location">/HelloWorld.jsp</param>
            </result>
      </action>

<result../>完整结构

1. nane属性: 逻辑视图名
2. type属性: 结果类型

<param..>子元素结构

1. name属性
    1) location: 指定了该逻辑视图对应的实际视图资源
    2) parse: 指定是否允许在实际视图名字中使用OGNL表达式,默认为true
2. 对应的物理资源

0x3: struts2支持的结果类型

struts2支持使用多种视图技术,例如JSP、Velocity、FreeMarker等,当一个Action处理用户请求结束时,仅仅返回一个字符串(逻辑视图名)
struts2的结果类型要求实现com.opensymphony.xwork2.Result,这个结果是所有结果类型的通用接口,如果我们需要自己的结果类型,我们需要提供一个实现该接口的类,并且在struts.xml文件中配置该结果类型
struts2默认提供了一系列的结果类型: struts2-core-2.3.24.jar/struts-default.xml

    <package name="struts-default" abstract="true">
        <!-- 配置系统支持的结果类型 -->
        <result-types>
            <!-- 链式处理的结果类型 -->
            <result-type name="chain" class="com.opensymphony.xwork2.ActionChainResult"/>
            <!-- 用于与JSP整合的结果类型 -->
            <result-type name="dispatcher" class="org.apache.struts2.dispatcher.ServletDispatcherResult" default="true"/>
            <!-- 用于与FreeMarker整合的结果类型 -->
            <result-type name="freemarker" class="org.apache.struts2.views.freemarker.FreemarkerResult"/>
            <!-- 用于控制特殊的HTTP行为的结果类型 -->
            <result-type name="httpheader" class="org.apache.struts2.dispatcher.HttpHeaderResult"/>
            <!-- 用于跳转到其他URL的结果类型 -->
            <result-type name="redirect" class="org.apache.struts2.dispatcher.ServletRedirectResult"/>
            <!-- 用于跳转到其他Action的结果类型 -->
            <result-type name="redirectAction" class="org.apache.struts2.dispatcher.ServletActionRedirectResult"/>
            <!-- 向浏览器返回一个inputStream的结果类型 -->
            <result-type name="stream" class="org.apache.struts2.dispatcher.StreamResult"/>
            <!-- 用于整合Velocity的结果类型 -->
            <result-type name="velocity" class="org.apache.struts2.dispatcher.VelocityResult"/>
            <!-- 用于整合XML/XSLT的结果类型 -->
            <result-type name="xslt" class="org.apache.struts2.views.xslt.XSLTResult"/>
            <!-- 用于显示某个页面原始代码的结果类型 -->
            <result-type name="plainText" class="org.apache.struts2.dispatcher.PlainTextResult" />
            <result-type name="postback" class="org.apache.struts2.dispatcher.PostbackResult" />
        </result-types>

实际上,struts2提供了极好的可扩展性,它允许自定义结果类型,我们完全可以自定义结果类型

0x4: plaintext结果类型

这个结果类型并不常用,它主要用于显示实际视图资源的源代码

<?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" namespace="/">
      <action name="*Action" class="com.tutorialspoint.struts2.HelloWorldAction" method="{1}">
            <result name="success" type="plainText">
                <param name="location">/HelloWorld.jsp</param>
            </result>
      </action>
   </package>
</struts>

0x5: redirect结果类型

这种结果类型与dispatcher结果类型相对

1. dispatcher: 将请求forward(转发)到指定的JSP资源,
2. redirect: 将请求redirect(重定向)到指定的视图资源,重定向会对视所有的请求参数、请求属性、Action的处理结果

使用redirect结果类型,系统将调用HttpServletResponse的sendRedirect(String)方法来重定向指定视图资源,效果是让浏览器重新产生一个请求,因此所有的请求参数、请求属性、Action实例和Action中疯转的属性全部丢失

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
   "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
   "http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<constant name="struts.devMode" value="true" />
   <package name="helloworld" extends="struts-default" namespace="/">
      <action name="hello" class="com.tutorialspoint.struts2.HelloWorldAction" method="execute">
            <result name="success" type="redirect">
                <param name="location">/HelloWorld.jsp</param>
            </result>
      </action>
   </package>
</struts>

配置一个redirect类型的结果,可以指定如下两个参数

1. location: 指定Action处理完用户请求后跳转的地址
2. parse: 指定是否允许在location参数值中使用表达式,默认是true

0x6: redirectaction结果类型

这种结果类型与redirect类型非常相似,同样是重新生成一个全新的请求,但与redirect结果类型区别在于

redirectAction使用ActionMapperFactory提供的ActionMapper来重定向请求,当需要让一个Action处理结束后,直接将请求重定向(注意不是转发)到另一个Action时,我们需要使用这种结果类型

配置redirectAction结果类型时,可以指定如下两个参数

1. actionName: 指定重定向的Action名
2. namespace: 指定需要重定向的Action所在的命名空间

使用redirectAction结果类型时,系统将重新生成一个新请求,只是该请求的URL不是一个具体视图资源,而是另一个Action(redirect是重定向到另一个视图资源),因此前一个Action的处理结果、请求参数、请求属性都会丢失

0x7: 动态结果

动态结果的含义是指在指定实际视图资源时使用了表达式语法,通过这种语法可以允许Action处理完用户请求后,动态转入实际的视图资源

0x8: Action属性值决定物理视图资源

配置<result../>元素时,不仅可以使用${0}表达式形式来指定视图资源,还可以使用${属性名}的方式来指定视图资源,在这种情况下,${属性名}里的属性名就是对应Action实例里的属性,并且还可以使用完全的OGNL表达式,例如${属性名.属性名.属性名}

<?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" namespace="/">
      <action name="loginAction" class="com.tutorialspoint.struts2.HelloWorldAction" method="execute">
            <result name="success">
                <param name="location">/${target}.jsp</param>
            </result>
      </action>
   </package>
</struts>

使用了/${target}.jsp表达式来指定视图资源,这要求在对应的Action类里应该包含target属性,该属性值将决定实际的视图资源: HelloWorldAction.java

..
//封装请求参数的target属性
private String target;
..
public String getTarget() 
{
    return target;
}

public void setTarget(String target) 
{
    this.target = target;
}
..

index.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
   <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Hello World</title>
</head>
<body>
   <h1>Hello World From Struts2</h1>
   <form action="loginAction">
      <label for="target">Please enter the target</label><br/>
      <input type="text" name="target"/>
      <input type="submit" value="Go"/>
   </form>
</body>
</html>

struts2会自动将HTTP GET/POST传入的参数字段作为属性值赋值给Action中的对应属性变量,同时struts2的视图资源又可以根据变量进行动态跳转,当赋值一个不存在的视图资源时,struts2会报错

从本质上来说,通过Action属性值和视图资源中动态变量标记的配合,实现了"模版机制"

0x9: 全局结果

struts2的<result../>元素配置,也可以放在<global-result../>元素中配置,当在<global-result../>元素中配置<result../>元素时,该<result../>元素配置了一个全局结果,全局结果将对所有的Action都有效
如果一个Action里包含了与全局结果里同名的结果,则Action里的局部Result会覆盖全局Result,即"就近原则"

0x10: 使用preresultlistener

PreResultListener是一个监听器接口,它可以在Action完成控制处理之后,系统转入实际的物理视图之前被回调
struts2应用可由Action、拦截器添加PreResultListener监听器,添加PreResultListener监听器通过ActionInvocation的addPreResultListener()方法完成

1. 一旦为Action添加了PreResultListener监听器,该监听器就可以在应用转入实际物理视图之前回调该监听器的beforeResult()方法
2. 一旦为拦截器添加了PreResultListener监听器,该监听器会对该拦截器所拦截的所有Action都起作用

通过使用PreResultListener监听指定Action转入不同Result的细节,因此也可以作为日志的实现方式

 

9. 配置struts2的异常处理

任何成熟的MVC框架都应该提供成熟的异常处理机制,虽然可以在execute方法中手动捕捉异常,当捕捉到特定异常时,返回特定逻辑视图名,但这种处理方式非常繁琐,需要在execute方法中写大量的catch块,而且最大的缺点还在于异常处理与代码耦合,一旦需要改变异常处理方式,必须修改代码。解决这种问题最好的方式是可以通过声明式的方式管理异常处理

0x1: struts2的异常处理机制

对struts2 MVC框架而言,我们希望有如图所示的处理流程

当Action处理用户请求时,如果出现了异常1,则系统转入视图资源1,如果出现异常2,则系统转入视图资源2
类似于struts1提供的声明时异常管理,struts2允许通过struts.xml文件来配置异常的处理

//处理用户请求的execute方法,该方法抛出所有异常
public String execute() throws Exception

上面的execute方法可以抛出全部异常,这意味着我们重写该方法时,完全无须进行人呢和异常处理,而是把异常直接抛给struts2框架处理,struts2框架接收到Action抛出的异常之后,将根据struts.xml文件配置的异常映射,转入指定的视图资源
为了使用struts2的异常处理机制,我们必须打开struts2的异常映射功能,开启异常映射功能需要一个拦截器,在struts-default.xml中配置如下(所有struts.xml都继承自它)

<interceptors>
    ..
    <!-- 执行异常处理的拦截器 -->
    <interceptor name="exception" class="com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor"/>
    ..
    <!-- struts2默认的拦截器栈 -->
    <interceptor-stack name="defaultStack">
        ..
        <!-- 引用异常映射拦截器 -->
                <interceptor-ref name="exception"/>
                ..
    </interceptor-stack>
</interceptors>

通过上面配置的拦截器,实现了struts2的异常处理机制

0x2: 声明式异常捕捉

struts2的异常处理机制通过在struts.xml文件中配置<exception-mapping../>完成的,配置该元素时,需要指定如下几个属性

1. exception: 指定该异常所设置的异常类型
2. result: 指定Action出现该异常时,系统返回result属性值对应的逻辑视图名

根据<exception-mapping../>出现的位置的不同,异常映射又可分为如下两种

1. 局部异常映射: 将<exception-mapping../>元素作为<action../>元素的子元素配置
//局部异常映射仅对该异常映射所在的Action内有效

2. 全局异常映射: 将<exception-mapping../>元素作为<global-exception-mapping>元素的子元素配置
//全局异常映射对所有的Action都有效

HelloWorldAction.java

package com.tutorialspoint.struts2;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;

public class HelloWorldAction extends ActionSupport
{ 
       /**
     * 
     */
    private static final long serialVersionUID = 9053634696133432054L;
    private String name; 
       
       public String execute() throws Exception 
       {   
           if(getName().equalsIgnoreCase("LittleHann"))
           {  
                throw new MyException("自定义异常");  
            }  
            else if(getName().equalsIgnoreCase("sql")) 
            {  
                throw new java.sql.SQLException("用户名不能为SQL");  
            }  
            return SUCCESS;
       } 
       
       public String getName() 
       {
          return name;
       }

       public void setName(String name) 
       {
          this.name = name;
       } 
}

通过struts.xml文件来胚子好struts2的异常处理机制

<?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">
      <!-- 定义全局结果映射 -->
      <global-results>
         <!-- 定义当sql、root两个逻辑异常都对应HelloWorld.jsp页 -->
         <result name="sql">/HelloWorld.jsp</result>
         <result name="root">/HelloWorld.jsp</result>
      </global-results>
      <!-- 定义全局异常映射 -->
      <global-exception-mappings>
         <!-- 当Action中遇到SQLException异常时 -->
         <exception-mapping exception="java.sql.SQLException" result="sql"/>
         <!-- 当Action中遇到MyException异常时 -->
         <exception-mapping exception="java.lang.Exception" result="root"/>
      </global-exception-mappings>
      <action name="loginAction" class="com.tutorialspoint.struts2.HelloWorldAction" method="execute">
            <result name="success">
                <param name="location">/HelloWorld.jsp</param>
            </result>
            <result name="error">
                <param name="location">/HelloWorld.jsp</param>
            </result>
      </action>
   </package>
</struts>

可以看到,除了使用SUCCESS、ERROR等预定义的逻辑视图映射之外,struts2原生还提供了异常处理的框架支持

0x3: 输出异常信息

当struts2框架控制系统进入异常处理页面后,我们必须在对应页面中输出指定异常信息,为了在异常处理页面中显示异常信息,我们可以使用struts2的如下标签来输出异常信息

<s:property value="exception"/>: 输出异常对象本身
<s:property value="exception.message"/>: 输出异常信息
<s:property value="exceptionStack"/>: 输出异常堆栈信息

HelloWorld.jsp

<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
<title>Hello World</title>
</head>
<body>
   Hello World, <s:property value="name"/> <br/> 
   Exception Info: <s:property value="exception.message"/><br/> 
   Exception Stack Info:<s:property value="exceptionStack"/>
</body>
</html>

输出异常对象完整的跟踪栈信息,会更有利于项目调试

 

10. convention插件与"约定"支持

从struts2.1开始,struts2引入了Convention插件来支持"零配置",插件完全可以抛弃配置信息,不仅不需要使用struts.xml文件进行配置,甚至不需要使用Annotation进行配置,而是由struts2根据约定来自动配置,"约定优于配置"

0x1: action的搜索和映射约定

为了使用Convention插件,必须在struts2应用中安装Convention插件,即将struts2项目下的struts2-convention-plugin-2.3.24.jar文件复制到struts2应用的WEB-INF\lib路径下
对于Convention插件而言,它会自动搜索位于action、actions、struts、struts2包下的所有java类,Convention插件会把如下两种java类当成Action处理

1. 所有实现了com.opensymphony.xwork2.Action的java类
2. 所有类名以Action结尾的java类

struts2的Convention插件还允许设置如下3个常量

1. struts.convention.exclude.package: 指定不扫描哪些包下的java类,位于这些包结构下的java类将不会被自动映射到Action
2. struts.convention.package.locators: Convention插件使用该常量指定的包作为搜寻Action的根包,对于actions.lee.LoginAction类,按约定原本应该映射到/lee/login,如果将该常量设置为lee,则该Action将会映射/login
//Action的映射直接对应到URL的访问模式
3. struts.convention.action.packages: Convention插件以该常量指定包作为根包来搜索Action类,Convention插件除了扫描action、actions、struts、struts2四个包的类之外,还会扫描该常量指定的一个或多个包,Convention会视图从中发现Action类

找到合适的Action类之后,Convention插件会按约定部署这些Action,部署Action时,action、actions、struts、struts2包会映射成根命名空间,而这些包下的子包则被映射成对应的命名空间

1. org.crazy.actions.LoginActino: 映射到/
//下面类实现了com.opensymphony.xwork2.Action接口
2. org.crazyit.actions.books.GetBooks: 映射到/books
3. org.crazyit.struts.auction.bid.BidAction: 映射到/auction/bid

除此之外,我们知道struts2的Action都是以package的形式来组织的,而package还有父package,对于采用Convention插件开发的struts2应用而言,每个Action所处的package与其Action类所在的包相似(除去action、action、struts、受托人2这些包及父包部分),Convention插件里所有Action所在package的父package默认是conventionDefault
而Action的name属性(也就是该Action所要处理的URL)则根据该Action的类名映射,映射Action的name时,遵循如下原则

1. 如果该Action类名包含Action后缀,将该Action类名的Action后缀去掉,否则不做任何处理
2. 将Action类名的驼峰写法转成中划线写法

对于如下Action将被映射成的完整URL如下

org.crazyit.actions.LoginAction: 映射成/loing.action
org.crazyit.actions.books.GetBooks: 映射成/books/get-books.action

采用Convention插件之后,Action类的代码不需要任何额外的变化

package com.tutorialspoint.action.struts2;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
import com.tutorialspoint.struts2.MyException;

public class HelloWorldAction extends ActionSupport
{ 
       /**
     * 
     */
    private static final long serialVersionUID = 9053634696133432054L;
    private String name; 
   ...

根据映射规则,该Action将被映射到如下URL: /struts2/loginAction

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
   <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Hello World</title>
</head>
<body>
   <h1>Hello World From Struts2</h1>
   <form action="struts2/loginAction">
      <label for="target">Please enter the name</label><br/>
      <input type="text" name="name"/>
      <input type="submit" value="Go"/>
   </form>
</body>
</html>

0x2: 按约定映射result

默认情况下,Convention总会到web应用的WEB-INF/content路径下定位物理资源,定位物理资源的约定是:

actionName + resultcode + suffix
//当某个逻辑视图找不到对应的视图资源时,Convention会自动试图使用actionName + resultcode + suffix作为物理视图资源

为了看到struts2应用里Action等各种资源的映射情况,struts2提供了ConfigBrowser插件,使用该插件可以清楚地看出struts2应用下部署了哪些Action,以及每个Action详细的映射信息

1. 复制struts2-config-browser-plugin-2.3.24.jar到struts2应用的lib目录下
2. 重启该应用
3. http://localhost:8080/HelloWorldStruts2/config-browser/actionNames.action

0x3: action链的约定

如果希望一个Action处理结束后不是进入视图页面,而是进入另一个Action,从而形成Action链,则通过Convention插件只需遵守如下几个约定即可

1. 第一个Action返回的逻辑视图字符串没有对应的视图资源
2. 第二个Action与第一个Actino处于同一个包下
3. 第二个Action映射的URL为: firstactionName + resultcode

FirstAction.java

package org.crazyit.app.action;

public class FirstAction 
{
    //封装提示信息的tip属性
    private String tip;
    
    public void setTip(String tip)
    {
        this.tip = tip;
    }
    public String getTip()
    {
        return this.tip;
    }
    
    public String execute()
    {
        System.out.println("first Action");
        setTip("first tip");
        return "second";
    }
}

为了让该Action结束处理后进入第二个Action而不是直接进入视图页面,因此该应用的WEB-INF/content不能提供first-second.jsp、first.jsp(Convention的命名约定)
对于FirstAction返回"second"字符串,第二个Action的映射的URL应该是first-second
,因此第二个Action的类名应该为FirstSecond

package actions.lee;

public class FirstSecondAction  
{
    //处理用户请求
    public String execute()
    {
        System.out.println("second Action");
        return SUCCESS;
    }
}

0x4: 自动重加载映射

Convention插件支持自动重加载映射,只要我们为struts2应用配置如下两个常量即可(struts.xml、web.xml中配置都可)

<!-- 配置struts2应用处于开发模式 -->
<constant name="struts.devMode" value="true"/>
<!-- 配置Convention插件自动重加载映射 -->
<constant name="struts.convention.classes.reload" value="true" />

0x5: convention插件的相关常量

虽然Convention插件是所谓的"零配置",但实际上并不能真正做到零配置,至少我们需要在web.xml文件中配置struts2的核心 Filter、以及struts2应用的各宗全局配置(例如Bean配置、拦截配置等),依然需要借助于struts2的配置文件
Convention插件主要致力于解决Action管理、Result管理等最常见、最琐碎的配置,将开发者从struts.xml繁琐配置中解放出来,而不是完全舍弃struts.xml文件

0x6: convention插件相关annotation

Convention还允许使用Annotation管理Action和Result的配置,从而覆盖Convention的约定

 

11. 使用struts2的国际化

struts2的国际化是建立在java国际化的基础之上的,一样也是通过提供不同国家/语言环境的消息资源,然后通过ResourceBundle加载指定Locale对应的资源文件,再取得该资源文件中指定key对应的消息,整个过程与java程序的国际化完全相同,只是struts2框架对java程序国际化进行了进一步封装,从而简化了应用程序的国际化 

0x1: struts2中加载全局资源文件

0x2: 访问国际化信息

0x3: 输出带占位符的国际化消息

0x4: 加载资源文件的方式

0x5: 加载资源文件的顺序

 

12. 使用struts2的标签库

struts2提供了大量标签来帮助开发表现层页面

0x1: struts2标签库概述

struts2标签库不依赖于任何表现层技术,即struts2提供的大部分标签,可以在各种表现层技术中使用,包括最常用的JSP页面,也可以在Velocity、FreeMarker等模板技术中使用
struts2把所有的标签都定义在URI为"/struts-tags"的空间下,大致可以分为如下三类

1. UI(Uesr Interface 用户界面)标签: 主要用于生成HTML元素的标签
    1) 表单标签: 主要用于生成HTML页面的form元素,以及普通表单元素的标签
    2) 非表单标签: 主要用于生成页面上的树、Tab页等标签
2. 非UI标签: 主要用于数据访问、逻辑控制等的标签
    1) 逻辑控制标签: 主要包含用于实现流程控制、分支、循环等流程控制的标签,也可以完成对集合的合并、排序等操作
        1.1) if: 用于控制选择输出的标签
        1.2) elself/elseif: 与if标签结合使用,用于控制选择输出的标签
        1.3) else: 与if标签结合使用,用于控制选择输出的标签
        1.4) append: 用于将多个集合拼接成一个新的集合
        1.5) generator: 一个字符串解析器,用于将一个字符串解析成一个集合
        1.6) iterator: 一个迭代器,用于将集合迭代输出
        1.7) merge: 用于将多个集合拼接成一个新的集合,但与append的拼接方法不同
        1.8) sort: 用于对集合进行排序
        1.9) subset: 用于截取集合的部分元素,形成新的子集合
    2) 数据访问标签: 主要包含用于输出ValueStack中的值、完成国际化等功能的标签
        2.1) action: 用于在JSP页面中直接调用一个Action,通过指定executeResult参数,还可将该Action的处理结果包含到本页面中
        2.2) bean: 用于创建一个JavaBean实例,如果指定了var属性,则可以将创建的JavaBean实例放入Stack Context中
        2.3) date: 用于格式化输出一个日期
        2.4) debug: 用于在页面上生成一个调试链接,当单击该链接时,可以看到当前ValueStack和Stack Context中的内容
        2.5) i18n: 用于指定国际化资源文件的baseName
        2.6) include: 用于在JSP页面中包含其他的JSP或Servlet资源
        2.7) param: 用于设置一个参数,通常是用作bean标签、url标签的子标签
        2.8) push: 用于将某个值放入ValueStack的栈顶
        2.9) set: 用于设置一个新变量,并可以将新变量放入指定的范围内
        2.10) text: 用于输出国际化消息
        2.11) url: 用于生成一个URL地址
        2.12) property: 用于输出某个值,包括输出ValueStack、Stack Context、Action Context中的值
3. Ajax标签: 用于Ajax(asynchronous javascript and xml)支持的标签

0x2: 使用struts2标签

标签库开发包括两个步骤

1. 开发标签处理类
2. 定义标签库定义文件

struts2框架已经完成了这两个步骤,即struts2既提供了标签的处理类,也提供了struts2的标签库定义文件
struts-tags.tld

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd">
  <description><![CDATA["To make it easier to access dynamic data;
                        the Apache Struts framework includes a library of custom tags.
                        The tags interact with the framework's validation and internationalization features;
                        to ensure that input is correct and output is localized.
                        The Struts Tags can be used with JSP FreeMarker or Velocity."]]></description>
  <display-name>Struts Tags</display-name>
  <tlib-version>2.3</tlib-version>
  <!-- 指定该标签库默认的短名 -->
  <short-name>s</short-name>
  <!-- 指定该标签库默认的URI -->
  <uri>/struts-tags</uri>
  ...
 </taglib>

为了使JSP页面具有更好的兼容性,因此建议定义struts2标签库的URI时,使自定义的struts2标签库URI和默认的URI相同
使用struts2标签必须先导入标签库,在JSP页面中使用如下代码导入struts2标签库

<!-- 导入struts2标签库 -->
<%@taglib prefix="s" uri="/struts-tags"%>

0x3: struts2的OGNL表达式语言

struts2利用内建的OGNL(Object Graph Navigation Language)表达式语言支持,大大加强了struts2的数据访问功能,XWork在原有的OGNL基础上,增加了对ValueStack的支持

OGNL表达式语言和JSP2EL的作用完全相似,在struts2应用中,视图页面可通过标签直接访问Action属性值,struts2自己维护了一个大的容器,Action将数据放入其中,而JSP页面可从其中取出数据,以此达到JSP直接访问Action的效果。当Action属性不是简单值(基本类型值或String类型值)时,而是某个对象,甚至是数组、集合时,就需要使用表达式语言来访问这些对象、数据、集合的内部数据了,struts2利用OGNL表达式语言来实现这个功能。实际上,OGNL并不是真正的编程语言,只是一种数据访问语言

在传统的OGNL表达式求值中,系统会假设只有一个"根"对象,下面是标准OGNL表达式求值,如果系统的Stack Context中包含两个对象

1. foo对象: 它在Context中的名字为foo
2. bar对象: 它在Context中的名字为bar,并将foo对象设置成Context的根对象

如下代码

//返回foo.getBlah()方法的返回值
#foo.blash
//返回bar.getBlah()方法的返回值
#bar.blah
//因为foo是根对象,所以默认是取得foo对象的blah属性
blah

OGNL表达式的语法非常简洁

#bar.foo.blah
//返回bar.GetFoo().getBlah()方法的返回值,如果需要访问的属性属于根对象,则可以直接访问该属性,否则必须显式指定一个对象名作为前缀修饰该属性

struts2可以直接从对象中获取属性,struts2提供了一个特殊的OGNL Property Accessor(属性访问器),它可以自动搜寻Stack Context的所有实体(沿着栈顶从上到下),直到找到与求值表达式匹配的属性,例如

Stack Context中包括两个"根实例"
1. animal(栈顶元素)
    1) name属性
    2) species属性
2. person(紧跟在栈顶元素之后)
    1) name属性
    2) salary属性

//返回animal.getSpecies()方法的返回值
species
//返回person.getSalary()方法的返回值
salary
//因为struts2先找到animal实例,返回animal.getName()方法的返回值
name
//直接取得person实例的name属性,需要显式声明
#person.name
//还可以通过索引来访问Stack Context中的对象
//返回animal.getName()方法的返回值,struts2的"属性访问器"会自动从上到下搜索Stack Context
[0].name
//返回person.getName()方法的返回值
[1].name
//值得注意的是,索引的方式并不是直接取得指定元素,而是从指定索引开始向下搜索,直到搜索到为止

struts2使用标准的Context来进行OGNL表达式语言求值,OGNL的顶级对象是一个Context,这个Context对象就是一个Map类型,其根对象就是ValueStack,如果需要访问ValueStack里的属性,直接通过如下方式即可

//取得ValueStack中的bar属性
${bar}

除此之外,struts2还提供了一些命名对象,但这些命名对象都不是Stack Context的"根对象",它们只是存在于Stack Context中,所以访问这些对象时需要使用#前缀来指明

1. parameters对象: 用于访问HTTP请求参数,例如#parameters["foo"]、#parameters.foo,用于返回调用HttpServletRequest的getParameter("foo")方法的返回值
2. request对象: 用于访问HttpServletRequest的属性,例如#request["foo"]、#request.foo,用于返回调用HttpServletRequest的getAttribute("foo")方法的返回值
3. session对象: 用于访问HttpSession的属性,例如#session["foo"]、#session.foo,用于返回调用HttpSession的getAttribute("foo")方法的返回值
4. application对象: 用于访问ServletContext的属性,例如#application["foo"]、#application.foo,用于返回调用ServletContext的getAttribute("foo")方法的返回值
5. attr对象: 该对象将依次搜索如下对象中的属性
    1) PageContext
    2) HttpServletRequest
    3) HttpSession
    4) ServletContext

梳理下OGNL中StackContext和ValueStack两个概念

1. Stack Context: 是整个OGNL计算、求值的Context
2. ValueStack只是StackContext内的"根对象",OGNL的Stack Context里除了包括ValueStack这个根之外,还包括parameters、request、session、application、attr等命名对象,但这些命名对象都 不是根
Stack Context中的"根对象"和普通命名对象的区别在于
    1) 访问Stack Context里的普通命名对象需要在对象名之前添加#前缀
    2) 访问OGNL的Stack Context里"根对象"的属性时,可以省略对象名

3. 访问方式的区别
    1) 访问OGNL顶级对象ValueStack
    //取得ValueStack中的bar属性
    ${bar}
    当系统创建了Action实例后,该Action实例已经被保存到ValueStack中,故无须书写#即可访问Action属性

    2) 访问OGNL普通命名对象
    //返回调用HttpServletRequest的getParameter("foo")方法的返回值
    例如#parameters["foo"]、#parameters.foo

0x4: OGNL中的集合操作

使用OGNL表达式可以直接创建集合对象

//创建一个List类型集合,该集合包含3个元素
{e1, e2, e3}

直接生成Map类型集合的语法为

//创建一个Map类型的集合,该Map对象中每个key-value对象之间以冒号分割
#{key:value1, key2:value2, ...}

对于集合,OGNL提供了两个运算符

1. in: 判断某个元素是否在指定集合中
2. not in: 判断某个元素是否不再指定集合中

如下代码

<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
<s:if test"'foo' in {'foo','bar'}">
include
</s:if>
<s:else>
not include
</s:else> 
<s:if test"'foo' not in {'foo', 'bar'}">
not include
</s:if>
<s:else>
include
</s:else>
</body>
</html>

除此之外,OGNL还允许通过某个规则取得集合的子集,取得子集有如下三个操作符

1. ?: 取出所有符合选择逻辑的元素
2. ^: 取出符合选择逻辑的第一个元素
3. $: 取出符合选择逻辑的最后一个元素
//person.relatives.{? #this.gender == 'male'}
在上面的代码中,直接在集合后紧跟.{}运算符表明用于取出该集合的子集,在{}内使用?表明取出所有符合选择逻辑的元素,而#this代表集合里元素,因此,上面代码的含义是: 取出person集合中所有gender属性为male的relatives集合

可以看到,OGNL表达式语言和JSP2表达式语言作用相似,但OGNL表达式语言的功能更加强大,使用OGNL可以通过URL访问的方式直接和struts2的Action直接进行交互,甚至进行实例创建、属性修改等编程操作

0x5: 访问静态成员

struts2 OGNL表达式提供了一种访问静态成员(调用静态方法、访问静态Field)的方式,但struts2默认关闭了访问静态方法,只允许通过OGNL表达式访问静态Field,为了让OGNL表达式可以访问静态成员,应该在struts2应用中将struts.ognl.allowStaticMethodAccess设置为true

<?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="crazyit" extends="struts-default"> 
   </package>

   <constant name="struts.ognl.allowStaticMethodAccess" value="true"/>
</struts>

一旦进行了如上设置,则OGNL表达式可以通过如下语法来访问静态成员

@className@staticField
@className@staticMethod(val..)

JSP页面使用如下方式来访问静态Field和静态方法

<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
access system environment var: <s:property value="@java.lang.System@getenv('JAVA_HOME')"/> <br/>
PI: <s:property value="@java.lang.Math@PI"/>
</body>
</html>

0x6: lambda()表达式

OGNL支持基本的Lambda()表达式语法,通过这种Lambda表达式语法,可以让我们在OGNL表达式语言中使用一些简单的函数

if n==0 return 0;
elseif n==1 return 1;
else return fib(n-2)+fib(n-1);
//我们可以使用OGNL表达式将以上的斐波那契数列进行实现
<s:property value="#fib=:[#this==0 ? 0 : #this==1 ? 1 : #fib(#this-2) + #fib(#this-1)], #fib(11)"/>

0x7: 控制标签

0x8: 数据标签

1. action标签

使用action标签可以允许在JSP页面中直接调用Action,使用action标签有如下几个属性

1. var: 可选属性,一旦定义了该属性,该Action将被放入ValueStack中,该属性可用id代替,但推荐使用var
2. name: 必填属性,通过该属性指定该标签调用哪个Action
3. namespace: 可选属性,该属性指定该标签调用的Action所在的namespace
4. executeResult: 可选属性,该属性指定是否要将Action的处理结果页面包含到本页面,默认值是false
5. ignoreContextParams: 可选参数,它指定该页面中的请求参数是否需要传入调用的Action,默认是false,即不忽略,需要传入

TagAction.java

package crazyit;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;

public class TagAction extends ActionSupport
{
    private String author;
    public String getAuthor()
    {
        return this.author;
    }
    public void setAuthor(String author)
    {
        this.author = author;
    }
    
    public String execute() throws Exception
    {
        return "done";
    }
    
    public String login() throws Exception
    {
        ActionContext.getContext().put("author", getAuthor());
        return "done";
    }
}

上面的Action类包含了两个处理逻辑,可以在struts.xml文件中通过指定method属性来将该Action类映射成两个逻辑Action

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
   "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
   "http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<constant name="struts.devMode" value="true" />
   <package name="crazyit" extends="struts-default"> 
           <action name="tag1" class="crazyit.TagAction">
            <result name="done">succ.jsp</result>
           </action>
           <action name="tag2" class="crazyit.TagAction" method="login">
            <result name="done">Loginsucc.jsp</result>
           </action>
   </package>

   <constant name="struts.ognl.allowStaticMethodAccess" value="true"/>
</struts>

上面的配置文件将一个Action类定义成两个逻辑Action,可以在JSP页面中通过<s:actino../>标签来调用这两个逻辑Action

<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
调用第一个Action,并将结果包含到本页面中
<s:action name="tag1" executeResult="true"/> <br/>

调用第二个Action,并将结果包含到本页面中,但阻止本页面请求参数传入Action
<s:action name="tag2" executeResult="true" ignoreContextParams="true"/><br/>

调用第三个Action,且并不将结果包含到本页面中
<s:action name="tag2" executeResult="false"/><br/>
</body>
</html>

2. bean标签

标签用于创建创建一个JaveBean实例,创建JavaBean实例时,可以在该标签体内使用<param..>标签为该JavaBean实例传入属性,使用bean标签时可以指定如下两个属性

1. name: 必填属性,该属性指定要实例化的JavaBean的实现类
2. var: 可选属性,如果指定了该属性,则该JavaBean实例会被放入Stack Context中,并放入requestScope中,该var属性可用id属性代替,但推荐使用var属性

在bean标签的标签体内,bean标签创建的JavaBean实例位于ValueStack的顶端,但一旦该bean标签结束了,则bean标签创建的JavaBean实例被移出ValueStack(一个map结构),将无法再次访问该JavaBean实例,除非指定了var属性,则还可通过Stack Context来访问该实例

package crazyit;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;

public class Person extends ActionSupport
{
    private String name;
    private String age;
    
    //name
    public String getName()
    {
        return this.name;
    }
    public void setName(String name)
    {
        this.name = name;
    }
    
    //age
    public String getAge()
    {
        return this.age;
    }
    public void setAge(String age)
    {
        this.age = age;
    }
    
    public String execute() throws Exception
    {
        return SUCCESS;
    }
     
}

提供了上面的Person类后,就可以在JSP页面中通过<s:bean../>标签来创建该JavaBean的实例

<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
    <!-- 使用bean标签创建一个Person类的实例 -->
    <s:bean name="crazyit.Person">
        <!-- 使用param标签为Person类的实例传入参数 -->
        <s:param name="name" value="'LittleHann'"/>
        <s:param name="age" value="24"/>
        <!-- 因为在bean标签内,Person实例位于ValueStack的栈顶,故可以直接访问Person实例 -->
        Person.name: <s:property value="name"/><br/>
        Person.age: <s:property value="age"/>
    </s:bean>
</body>
</html>

除此之外,我们还可以在使用<s:bean/>标签时指定var属性,如果指定了var属性后,就可以将该JaveBean实例放在Stack Context中(并放入requestScope中)了,这样即使不在<s:bean..>标签内,也可以通过该var属性来访问该JavaBean实例

<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
    <!-- 使用bean标签创建一个Person类的实例,为其指定了var属性 -->
    <s:bean name="crazyit.Person" var="p">
        <!-- 使用param标签为Person类的实例传入参数 -->
        <s:param name="name" value="'LittleHann'"/>
        <s:param name="age" value="24"/>
    </s:bean>
    
    <!-- 根据JavaBean实例指定的var属性来访问JavaBean实例 -->
    Person.name: <s:property value="#p.name"/><br/>
    Person.age: <s:property value="#p.age"/>

</body>
</html>

0x9: 主题和模版

因为struts2所有的UI标签都是基于主题和模版的,主题和模版是struts2所有UI标签的核心

1. 模版是一个UI标签的外在表现形式
2. 如果为所有的UI标签都提供了对应的模版,那么这系列的模版就会形成一个主题

0x10: 自定义主题

0x11: 表单标签

0x12: 非表单标签

Relevant Link:

https://www.google.com.hk/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&ved=0CB4QFjAA&url=%68%74%74%70%3a%2f%2f%77%77%77%2e%33%36%30%64%6f%63%73%2e%6e%65%74%2f%77%6f%72%64%2d%64%6f%77%6e%6c%6f%61%64%2f%32%32%37%33%62%31%64%35%62%39%66%33%66%39%30%66%37%36%63%36%31%62%37%36%2d%31%2e%64%6f%63&ei=KV-TVbfpA9iC8gXwzKGgAQ&usg=AFQjCNFu7Ch3-jAOZ9V-OsOlobH2B7yk4g&bvm=bv.96952980,d.dGc 

 

Copyright (c) 2015 LittleHann All rights reserved

 

posted @ 2015-04-08 14:51  郑瀚Andrew  阅读(526)  评论(0编辑  收藏  举报