Struts2框架

1.MVC思想

Java Web应用结构经历了Model1和Model2两个时代:
Model1——JSP + JavaBean模式
整个Web应用几乎全部由JSP页面组成,用少量的JavaBean来处理数据库连接、数据库访问等操作。
Model2——MVC模式
基于MVC架构,Servlet做为前端控制器,负责接收客户端发送的请求,Servlet中只包含控制逻辑和简单的前端处理,然后调用后端JavaBean来完成实际的逻辑处理,最后转发到相应的JSP页面。

MVC是一种设计思想,该思想将应用分成三个基本部分:Model(模型)、View(视图)、Controller(控制器),这三个部分以最少的耦合(低耦合)协同工作,提高应用的可扩展性及可维护性。

提示:传统Servlet/Jsp实现的MVC架构,其运行机制是:JSP页面(View)发送消息到Servlet(控制器Controller),Servlet获取请求数据,处理业务逻辑,分发转向。这样Servlet就变得臃肿,什么活都是Servlet在干,而且业务逻辑全是硬编码,代码都写列不利于维护。

2.Struts2介绍

官网: https://struts.apache.org/

Struts2是Apache组织下一个基于MVC设计模式的Web应用框架。
Struts2是Struts的下一代产品,是Struts1 + WebWork合并的全新框架。
Struts2采用拦截器为核心机制,就一个开源的轻量级的,应用于表示层(Web层、View)的框架。
Filter .action
Servlet .do

3.Struts2 入门案例

3.1.Maven创建项目

注意:这里点击“Add…”,在弹出窗中输入:archetypeCatalog = internal
用于提高Maven创建项目时的速度。 (构筑目录类型) (内部的)

注意:在这里解决两个问题。
问题1:JSP文件报错
解决办法:在pom.xml中引入servlet-api依赖即可。

javax.servlet javax.servlet-api 4.0.0

问题2:在项目中显示src/main/java文件夹

问题3:调整项目的JavaSE版本及动态网站支持版本
鼠标右击项目,选择最下面的Properties选项:

问题4:发布Maven中的包到Tomcat的WEB-INF中,否则报ClassNotFound异常

3.2.引入jar依赖包

在项目的pom.xml中引入Struts 2的核心依赖:struts2-core。

org.apache.struts struts2-core 2.5.16

注意:struts2的版本不同,配置也有部分差异。

3.3.配置核心拦截器

在项目webapp/MEB-INF/web.xml文件中配置Struts2核心拦截器:StrutsPrePareAndExecuteFilter。
当用户请求匹配的URL时,执行核心栏截器。

扩展知识:由于Web应用是基于请求/响应架构的应用,所以不管哪个MVC Web框架,都需要在web.xml中配置该框架的核心Servlet或Filter,这样才可以让该框架介入Web应用中。

struts2 org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter struts2 /* 注意事项: Struts 2.1版本之前用的核心过滤器是FilterDispacther。

Struts 2.1~2.4版本用的是StrutsPrepareAndExecuteFilter
org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter

Struts 2.5版本用的也是StrutsPrepareAndExecuteFilter,但路径有所改变:
org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter

3.4.定义Action

Action是业务控制器,是Struts2的重要组成部分之一。
Action是MVC中的C,也就是控制器。
该类负责调用Model里的方法来处理请求。

创建Action动作类:UserAction
/**

  • 用户业务控制类
    */
    public class UserAction{
    //execute方法为默认执行方法
    public String execute() throws Exception{
    ……
    return “success”; //返回字符串(即结果码)
    }
    }
    问题1:Action并未接收到用户请求,它怎么能处理用户请求呢?
    MVC框架的底层机制是:核心Servlet或Filter接收到用户请求后,通常会对用户请求进行简单预处理,例如解析、封装参数等,然后通过反射来创建Action实例,并调用Action指定的方法(Struts1通常是execute,Struts2可以是任意方法)来处理用户请求。

问题2:当Servlet和Filter拦截用户请求后,它如何知道创建哪个Action的实例呢?
有两种解决方案:
利用配置文件
例如配置login.action对应使用LoginAction类,这就可以让MVC框架知道创建哪个Action的实例了。
利用约定
例如约定xxx.action总是对应XxxAction类。如果核心控制器收到regist.action请求后,将会调用RegistAction类来处理用户请求。须要提供Convention(约定)插件。这见鉴了Rails框架的优点,即“约定优于配置”。

3.5.配置Action

在src或src/main/resources下创建Struts2核心配置文件:struts.xml,并配置Action类的请求与响应。

<-- 包的配置:name为自定义包名;extends当前包继承struts-default,必须要写;namespace非必填 --> <-- Action的配置:name为URL请求地址;class为实例对象全路径名;method没写,默认执行execute方法 --> …… 问题一:如何创建struts.xml?内部代码记不住怎么办? 创建普通XML文件即可,关于struts.xml中引用的dtd约束等代码,可以找到struts核心包中的struts-default.xml文件,复制即可。

3.6.配置处理结果与视图

loginForm.jsp error.jsp /WEB-INF/webs/welcome.jsp …… 3.7.编写视图资源 测试连接 测试连接

自学《附一:常用的constant常量设置》,挑选出常用的进行配置。

4.Struts2框架原理

4.1.Struts2框架的MVC

M:JavaBean + ModelDriven
V:JSP + OGNL
C:Action
Struts2框架的控制器将“获取请求”和“分发转向”代码抽取出来写在配置文件中,这样一样,控制器(action类)就能专注于业务逻辑的处理了。

4.2.Struts2的两个重要组成部分

Struts2的两个重要组成部分是:核心过滤器 + 业务控制器。
4.2.1.核心过滤器:StrutsPrepareAndExecuteFilter
作用:负责拦截所有用户的请求,该Filter在过滤用户请求后,将请求都交给Struts2框架处理。
拦截器会默认拦截扩展名为.action的请求,什么后缀都不写也可以。
例如:hello.action或者hello都会被拦截;hello.jsp就不会进行拦截,直接放行。

每次实例化对象,线程安全;Servlet是单实例的,所以线程不安全。
4.2.2.业务控制器:Action
业务控制器就是用户实现的Action类,Action类中通常包含一个execute方法,该方法返回一个字符串(即结果码),字符串与struts.xml中的result的name相对应,跳转到不同页面。

4.3.Struts2的运行流程图

Struts2的执行流程大致有以下几步:
第一步:用户发起请求(默认.action结尾的请求表示请求一个Action)。
第二步:Struts2的核心过滤器(StrutsPrepareAndExecuteFilter)接收用户发起的请求,然后判断这个请求交给Action处理还是交给Web组件处理。如果请求的Action或者Web组件不存在,那么出现404错误。在整个处理过程中需要一个辅助对象:Action映射器(ActionMapper)。
第三步:将第二步判断的结果如果是交给Action处理,并且存在对应的Action,那么根据struts.xml中对此Action的配置信息,首先执行拦截此 Action的所有拦截器,然后再执行请求的Action对象。在这个处理过程中需要辅助对象:Action代理(ActionProxy)、配置管理器(ConfigurationManager)、Action调度(ActionInvocation)。
第四步:Action执行完毕后返回一个结果(此结果用字符串表示),这个结果经过拦截Action的所有拦截器之后,返回给核心过滤器(StrutsPrepareAndExecuteFilter),核心过滤器根据此结果从配置文件中找到真正的路径,然后将请求转发给对应的视图,由视图向客户端作出响应。

Struts2框架内部运行原理图

(1) 客户端(Client)向Action发用一个请求(Request)
(2) Container通过web.xml映射请求,并获得控制器(Controller)的名字
(3) 容器(Container)调用控制器(StrutsPrepareAndExecuteFilter或FilterDispatcher)。在Struts2.1以前调用FilterDispatcher,Struts2.1以后调用StrutsPrepareAndExecuteFilter
(4) 控制器(Controller)通过ActionMapper获得Action的信息
(5) 控制器(Controller)调用ActionProxy
(6) ActionProxy读取struts.xml文件获取action和interceptor stack的信息。
(7) ActionProxy把request请求传递给ActionInvocation
(8) ActionInvocation依次调用action和interceptor
(9) 根据action的配置信息,产生result
(10)Result信息返回给ActionInvocation
(11)产生一个HttpServletResponse响应
(12)产生的响应行为发送给客服端。

5.Action中获取Web对象

Action获取Web对象有三种方式:ActionContext类、ServletActionContext类、ServletXxxAware接口。
其中:前者为解耦方式,推荐;后两者为耦合方式,不推荐。
解耦方式——即struts2测试时不需要启动服务器,提高开发效率。

5.1.使用ActionContext类(解耦)

Web应用中通常需要访问的Servlet API就是HttpServletRequest、HttpSession和ServletContext,这三个接口分别代表JSP内置对象中的request、session和application。

Struts2提供了一个ActionContext类(com.opensymphony.xwork.ActionContext),它是Action执行时的上下文,上下文可以看作是一个容器(其实我们这里的容器就是一个Map而已),它存放的是Action在执行时需要用到的对象。可以通过下面方法访问Servlet API:
方法 说明
Object get(key) 类似于HttpServletRequest的getAttribute(String)
Map getApplication() 返回一个Map对象
Static ActionContext getContext() 返回静态的ActionContext实例,相当于HttpServletRequest对象
HttpParameters getParameters() 获取所有的请求参数
Map getSession() 返回一个Map对象
void setApplication(Map) 向application传入一个Map对象
void setSession(Map) 向session传入一个Map对象
void put(key, value) 添加对象

如何获得地址栏参数、表单参数:(struts版本不一样使用也不一样)
ActionContext ac = ActionContext.getContext();

//1.获取application、session、request对象
Map application = ac.getApplication();
Map session = ac.getSession();
Map request = (Map)ac.get(“request”);

//2.设置application、session、request作用域的值
application.put(“userName”, “admin”);
session.put(“userName”, “admin”);
request.put(“userName”, “admin”);

//3.获取application、session、request作用域的值
application.get(“userName”);
session.get(“userName”);
request.get(“userName”);

//1.获取request请求参数
//先获得request对象
HttpServletRequest request = (HttpServletRequest) ac.get(StrutsStatics.HTTP_REQUEST);
//再通过request对象获取请求参数
String type = request.getParameter(“type”);

//2.直接获取请求参数
Map paramsMap = Ac.getParameters();
//参数被封装成String[]
String[] methodNameParam = (String[])paramsMap.get(“methodName”);
for(String s : methodNameParam){
System.out.println(s);
}

//3.获得HttpServletRequest对象的attribute(解耦)
Map paramsMap =context.getParameters(); //获得上下文中所有的参数值
String type = paramsMap.get(“type”).toString(); //从获得的参数值中查找某个

案例1:使用ActionContext操作作用域
ActionContext ac = ActionContext.getContext();
Integer count = (Integer) ac.getApplication().get(“count”);
// 通过ActionContext设置application范围的属性
ac.getApplication().put(“count”, 100);
// 通过ActionContext设置session范围的属性
ac.getSession().put(“max”, 1000);
// 通过ActionContext设置request范围的属性
ac.put(“min”, 10);

前端页面:
统计总人数: a p p l i c a t i o n S c o p e . c o u n t 最 大 人 数 : {applicationScope.count} 最大人数: applicationScope.count{sessionScope.max}
最小人数:${requestScope.min}

案例2:使用ActionContext获得JSP表单中的数据
(1)创建表单,提交表单到action里面

用户名: 手机号: 性 别:男 女

(2)在action使用ActionContext获取表单数据:
ActionContext context = ActionContext.getContext();
// map的key就是表单项的各name
Map map = comtext.getParameters();
//查看Map中的数据
Set keys = map.keyset();
for(String key : keys){
Object[] obj = (Object[])map.get(key);
System.out.println(Arrays.toString(obj));
}

5.2.使用ServletActionContext类(耦合)

在Action中通过ServletActionContext类也能获得Web对象,但不建议直接访问Servlet的API,这样做不利于项目的移植。
HttpServletRequest request = ServletActionContext.getRequest();
request.setAttribute(“request”, “1”);

HttpSession session = request.getSession();
session.setAttribute(“session”, “2”);

ServletContext application = request.getSession().getServletContext();
application.setAttribute(“application”, “3”);

5.3.使用ServletXxxAware接口注入(耦合)

使用ServletContextAware、ServletRequestAware、ServletResponseAware三个接口可直接获得Servlet API。
Step1:类实现ServletResponseAware接口(或其它两个接口)。
Step2:重写相应的方法:
// 自定义类实现:ServletXXXAware接口,实现方法
public class Test implements ServletRequestAware,ServletSessionAware……{
private HttpServletRequest request;

@Override
public void setServletRequest(HttpServletRequest request){
    this.request = request;
}

public String execute() throws Exception{
    return NONE;
}

}

5.4.清空Session的值

HttpSession session = ServletActionContext.getRequest().getSession();
session.invalidate();
sessionMap.invalidate();
session.invalidate():将session设置为失效,一般在退出时使用,但要注意的是:session失效的同时,浏览器会立即创建一个新的session,你第一个session已经失效了,所以调用它的getAttribute方法时候一定会抛出NullPointerException的。

6.Action数据封装(属性驱动、模型驱动)

案例:表单数据提交
对应数据在前台与后台中的交互,Struts2框架替我们做了很大部分的数据封装工作。既可以封装单个对象,也可以封装集合。

实现Action有两大方式:属性驱动、模型驱动。
(1)属性驱动
使用属性作为贯穿MVC流程的信息携带者,依附于Action实例,Action实例封装请求参数和处理结果。
属性驱动有三种:普通POJO类、实现Action接口、继承ActionSupport(推荐)。

(2)模型驱动
就是使用单独的JavaBean实例来贯穿整个MVC流程,JavaBean实例封装请求参数和处理结果。
模型驱动有一种:ModelDriven。

6.1.普通POJO类

思路:在Action中封装表单属性。
实现一个登录Action:
/**

  • User的业务控制类
    */
    public class UserAction {
    //私有属性
    private String userName;
    private String password;
    //公开的set和get方法
    public void setuserName(String userName){
    this.userName = userName;
    }
    public String getuserName(){
    return this.userName;
    }
    public void setpassword(String password){
    this.password = password;
    }
    public String getpassword(){
    return this.password;
    }
    //struts2的拦截器机制,getter/setter方法负责解析用户请求参数,并且将请求参数值赋给action对应的属性(也就是说将form表单的数据赋值给Action类中的属性,属性名与表单项控件名一致)
    //此处省略set和get方法

    public String execute() throws Exception {
    if(“admin”.equals(getuserName()) && “123456”.equals(getpassword())){
    return “success”;
    }else{
    return “error”;
    }
    }
    }

6.2.实现Action接口

为了让用户开发的Action类更加规范,Struts2提供Action接口,定义了Action处理类应该实现的规范。

思路:
(1)Action类实现Action接口,重写execute()方法,返回时使用Action接口中的常量;
(2)在Action中声明成员变量,成员变量名与表单项name属性一致;
(3)封装。

案例:
//1. 实现Action接口
public class UserAction implements Action {
//2. 私有属性
private String userName;
private String password;
//3. 此处省略set和get方法

//返回使用Action接口中的常量
public String execute() throws Exception {
if(“admin”.equals(getuserName()) && “123456”.equals(getpassword())){
return SUCCESS;
}else{
return ERROR;
}
}
}

打开Action接口,查看源代码:
public interface Action{
// 定义Action接口里包含的一些结果字符串
public static final String ERROR=”error”;
public static final String INPUT=”input”;
public static final String LOGIN=”login”;
public static final String NONE=”none”;
public static final String SUCCESS=”success”;
// 定义处理用户请求的execute方法
public String execute() throws Exception;
}

6.3.继承ActionSupport类(推荐)

ActionSupport类是Action接口的实现类。该类提供了许多默认的方法,这些默认方法包括获得国际化信息的方法、数据校验的方法、默认的处理用户请求的方法等。
如果配置Action没的指定的Action类,系统自动使用ActionSupport类作为Action处理类。

实现步骤:
(1)继承ActionSupport,重写execute()方法。不继承也可以,直接写execute()方法;
(2)在Action中声明成员变量,成员变量名与表单项name属性一致;
(3)封装。

//1. 继承ActionSupport类
public class UserAction extends ActionSupport {
//2. 声明成员变量(与表单项name一致,这里最好是私有化一个实体类对象,参见后面的)
private String userName;
private String password;
//3. 封装,省略getter/setter方法
//struts2的拦截器机制,getter/setter方法负责解析用户请求参数,并且将请求参数值赋给action对应的属性

public String execute() throws Exception {
    if("admin".equals(getuserName()) && "123456".equals(getpassword())){
        return SUCCESS;
    }else{
        return ERROR;
    }
}

}
提示:ActionSupport还可以封装对象

6.4.模型驱动封装:ModelDriven(推荐)

模型驱动:就是使用单独的JavaBean实例来贯穿整个MVC流程,JavaBean实例封装请求参数和处理结果。

实现步骤:
(1)Action类实现ModelDriven接口;
(2)实现接口的getModel()方法,并把创建对象返回;
(3)在Action中创建实体类对象;
(4)execute()一样法中使用实体类对象名即可。

案例:
//1. 实现ModelDriven接口
public class UserAction implements ModelDriven {
//定义用于封装请求参数和处理结果的Model
private User user = new User();
//2. 实现接口的getModel()方法,并把创建对象返回
@Override
public User getModel() {
return this.user;
}

//4. 在execute()中使用对象
public String execute() throws Exception {
    if("admin".equals(user.getUserName()) && "123456".equals(user.getPassword())){
        return SUCCESS;
    }else{
        return ERROR;
    }
}

}

User类:属性名要与表单控件名字相同,否则报错。
//3. 创建实体类并封装
public class User implements Serializable{
private static final long serialVersionUID = 1L;
//私有的请求参数,与表单控件名相同,否则无法通过反射赋值
private String userName;
private String password;
//省略set和get方法
//无参构造方法
public User() { }
public User(String userName, String password) {
super();
this.userName = userName;
this.password = password;
}
@Override
public String toString() {
return “User [userName=” + userName + “, password=” + password + “]”;
}
}

7.表单数据的封装

7.1.封装数据到对象

实现步骤:
(1)在Action类中实现ActionSupport声明实体类;
(2)生成实体类对象名的set()和get()的方法;
(3)在表单输入项的name属性值里写表达式形式:对象.属性。

实体类:
public class Student implements Serializable{
private Integer stuId;
private String stuNo;
private String stuName;
private String stuAge;
// 此处省略set和get方法
public Student() { }
public Student(int stuId, String stuNo, String stuName, String stuAge) {
super();
this.stuId = stuId;
this.stuNo = stuNo;
this.stuName = stuName;
this.stuAge = stuAge;
}

View代码:

编号: 姓名: 年龄:

Action代码:
public class UserAction extends ActionSupport {
private Student stu;
public void setStu(Student stu){
this.stu = stu;
}
public Stu getStu(){
return this.stu;
}

public String addStu(){
System.out.println(stu);
return SUCCESS;
}
}

7.2.封装数据到List集合

List集合使用‘变量名[索引].属性’的形式。
前台:

学生1 编号: 姓名: 年龄:
学生2 编号: 姓名: 年龄:
学生3 编号: 姓名: 年龄:

实例类:同上

Action类:
public class UserAction extends ActionSupport {
@Element(Student.class)
private List stuList;
public void setStuList(List stuList){
this.stuList = stuList;
}
public List getStuList(){
return this.stuList;
}

public String addStuList(){
for (Student stu : stuList) {
System.out.println(stu);
}
return SUCCESS;
}
}

7.3.封装数据到Map集合

Map集合使用的是‘变量名.key名.属性’ 也可以是‘变量名[‘key’].属性’。
前台:

学生1 编号: 姓名: 年龄:
学生2 编号: 第二种写法 姓名: 年龄:
学生3 编号: 姓名: 年龄:

实体类:同上

Action类:
public class UserAction extends ActionSupport {
@Key(String.class)
@Element(Student.class)
private Map stuMap = new HashMap<>();
//此处省略公开setList的set和get方法

public String addStuMap(){
for (String key : stuMap.keySet()) {
System.out.println(key + “----” + stuMap.get(key));
}
return SUCCESS;
}
}

7.4.封装数据到Set集合

Set集合比较特殊,必须使用到OGNL中makeNew的运算符来表示。格式:变量名.makeNew[索引].属性
前台:

学生1 编号: 姓名: 年龄:
学生2 编号: 姓名: 年龄:
学生3 编号: 姓名: 年龄:

实体类:同上

Action类:
public class UserAction extends ActionSupport {
// Student中的标识字段,该字段需要get方法,该配置不可少
@KeyProperty(“stuNo”)
@Element(Student.class)
private Set stuSet = new HashSet<>();
//此处省略公开setList的set和get方法

public String addStuSet(){
System.out.println(stuSet);
for (Student stu : stuSet) {
System.out.println(stu);
}
return SUCCESS;
}
}

8.配置Action

在Struts.xml中配置Action就是让Struts2知道哪个Action处理哪个请求。

8.1.包和命名空间

Action通过使用下的子元素来完成,而每个package元素配置一个包。

……

name 必需属性。该属性指定该包的名字,该名字是该包被其他包引用的key。
extends 可选属性。该属性指定该包继承其他包。继承其他包,可以继承其他包中的Action定义、拦截器定义等。
namespace 可选属性。该属性定义该包的的命名空间。
abstract 可选属性。它指定该包是否为一个抽象包。抽象包中不能包含Action定义。

extends=”struts-default”,表示该包继承了struts-default包,那么这个struts-default包从何而来呢?
在struts2-core-2.3.x.jar中有一个struts-default.xml文件,该文件内包含如下代码:

……

该包中包含了大量结果类型定义、拦截器定义、拦截器引用定义等,这些定义是配置普通Action的基础,所在开发者定义的package通常应该继承struts-default包。

namespace命名空间:主要是为了处理同一个Web应用中包含同名Action的情形。Struts2以命名空间的方式来管理Action,同一个命名空间里不能有同名的Action,不同的命名空间里可以有同名的Action。
如不指定则该包下的所有Action处于默认的包空间下。
*注意这里要带extends=”struts-default”,否则后面result会让页面报404

8.2.Action的基本配置

…… 说明: name=”login” 配置处理login.action请求的Action。 class=”com.zking.action.LoginAction” 请求对应的Action类。 method=”loginMethod” 指定运行Action类中的方法 ## 8.3.使用Action的动态方法调用 ! struts 2.5版本后要配置动态方法调用,配置方法有两种: 第一种:修改配置信息 在Struts 2的核心jar包struts2-core中,有一个default.properties的默认配置文件(路径:struts-2.5.2-min\lib\org\apache\struts2\default.properties)里面配置了一些全局的信息,其中有条语句是配置动态方法调用的: struts.enable.DynamicMethodInvocation = true

第二种:在struts.xml中配置

login.jsp login 多个方法请求逗号隔开。 测试

对于同一个表单,当用户通过不同的提交按钮来提交同一个表单时,系统需使用Action的不同方法来处理用户请求,这就需要让同一个Action里包含多个控制处理逻辑。
此时可以采用DIM(Dynamic Method Invocation,动态方法调用)来处理这种Action。

…… 其中:actionName指定提交到哪个Action,而methodName指定提交到指定方法。

如果是通过JS动态提交:

JavaScript脚本代码:
function regist(){
var targetForm = document.forms[0];
targetForm.action = “login!regist”;
}

注意:使用动态方法调用前必须设置,Struts2是通过struts.enable.DynamicMethodInvocation常量完成,设置该常量的值为true。Struts2的动态方法调用存在一些安全方面的缺陷,应尽量少用动态方法调用。一般通过指定method属性及使用通配符。

8.4.指定method属性

中若没有配置method,则默认执行Action类中的execute()方法;配置了method则调用配置指定的方法。如下:

……

8.5.使用通配符 *

Struts 2.5版本后,struts对Action类中的方法进行了保护(安全),不能像以前配置那样直接通过通配符访问方法,需要配置的。
8.5.1.配置匹配通配符

regex:.


……


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

  …… 只要用户请求*Action.action的模式,都可以使用com.zking.action.LoginAction来处理。 比如请求: http://127.0.0.1:8080/test/LoginAction.action 则会执行LoginAction类中指定的Login()方法执行。

也可以写成:

  ……

8.5.3.多个通配符的使用

……

如果用户请求的是:
http://127.0.0.1:8080/test/Book_save
则第一个的值为Book,第二个的值为save,那么将调用BookAction中的save方法。
实际上,Struts2不仅允许在class属性、name属性中使用表达式,还可以在子元素中使用{N}表达式。
8.5.4.通配符在中的使用

/WEB-INF/content/{1}.jsp

如果用户请求的是:
http://127.0.0.1:8080/test/a.action
则进入a.jsp;如果请求b.action,则进入b.jsp页面。
8.6.配置默认Action
当用户请求找不到对应的Action时,系统默认的Action即将处理用户请求。
配置默认Action通过元素完成。


也可以直接配置指定的处理类(推荐):



9.配置Action处理结果
Struts2的Action处理用户请求结束后,返回一个普通字符串——逻辑视图名,必须在struts.xml文件中完成逻辑视图和物理视图资源的映射,才可以让系统转到实际的视图资源。
简单地说,结果配置是告诉Struts2框架:当Action处理结束时,应用程序下一步做什么,下一步应该调用哪个物理视图资源来显示处理结果。

Struts2在struts.xml文件中使用元素来配置结果,根据元素所在位置的不同,Struts2提供了两种结果:
局部结果:将作为元素的子元素配置。
全局结果:将作为元素的子元素配置。
9.1.局部结果配置


/WEB-INF/jsp/thank_you.jsp

在配置中:
name 表示匹配Action中方法返回的结果码。
这里表示如果LoginAction类中方法的返回结果码是success,就跳转到/WEB-INF/jsp/thank_you.jsp页面。
type 表示到视图资源的跳转方式。
默认为:dispatcher(转发),还可以选择其他跳转方式,如下:
参数 说明
dispatcher Action处理完后请求转发到一个视图资源(type的默认值转发到JSP),请求参数不会丢失,Action处理结果也不会丢失。
redirect Action处理完后重定向到一个视图资源(如:JSP),请求参数全部丢失,Action处理结果也全部丢失。
redirectAction Action处理完后重定向到一个Action,请求参数全部丢失,Action处理结果也全部丢失。
chain Action处理完后转发到一个Action,请求参数全部丢失,Action处理结果不会丢失。
注意:
为了防止重复提交我们选取重定向(redirect),
当Action做增删改操作时使用重定向(redirectAction)
当Action做操作查询时使用转发(chain)
问题:如果选用重定向,JSP页面如何获得Action处理的结果数据?

在用Struts2在做项目时候,从一个Action跳转到另一个Action,并且得带上值。
第一:第一个Action中的id必须要有set/get方法。
第二:配置跳转方式一
/topicAction!findTopics.do?Id=${tId}

配置跳转方式二

findTopics ${tId}

如果是多个参数的话,继续加就行了,对于方式一如果是多个参数的怎么办?
/topicAction!findTopics.do?Id=KaTeX parse error: Expected 'EOF', got '&' at position 6: {tId}&̲amp;elementId={elementId}

注意这里的&符号要用转义的代码:&

9.2.全局结果
全局结果对该包中所有的Action都有效。




/WEB-INF/content/404.jsp


……


如果一个Action里包含了与全局结果同名的结果,则Action里的局部Result会覆盖全局Result,也就是说:
当Action处理用户请求结果后,会首先在本Action里的局部结果里搜索逻辑视图对应的结果,只有在Action里的局部结果里找不到逻辑视图对应的结果时,才会到全局结果里搜索。

10.值栈 ValueStack
在Servlet中使用域对象(page、request、session、application)进行存值和取值,将其作为载体来承载页面和后台之间的数据传递。
在Struts2中,又有了一种新的机制来进行数据的传递,那就是ValueStack,即值栈。

在理解ValueStack之前我们先要了解一下Servlet和Action的区别。
10.1.Servlet和Action的区别
Servlet:单对象(单例模式)
默认在第一次访问时创建对象,但它只会创建一次对象,无论后面访问多少次这个Servlet,都只有一开始创建的那一个Servlet对象。

Action:多对象
默认在第一次访问时创建对象,但每一次访问它的时候,都会创建一个新对象。
10.2.什么是值栈
官方的说法是:
ValueStack是Struts的一个接口,字面意义为值栈,OgnlValueStack是ValueStack的实现类。客户端发出一个请求,struts2框架会为我们创建一个Action对象,同时创建一个OgnlValueStack的实例,我们可以在Action中将数据封装到OgnlValueStack中,在页面中通过Ognl表达式或者EL表达式将其取出。

通俗点的理解:
值栈是一种类似于域对象的,用来存值和取值,在页面和后台之间传递数据的功能。
在Action中将数据存入值栈,然后在页面中通过EL表达式或者OGNL表达式将值取出。
10.3.值栈存储的位置
值栈和Action的关系是:
值栈存在于Action对象中。
每创建一个Action对象,就会创建一个值栈对象。且每个Action对象中只有一个值栈对象。

10.4.在Action中获取值栈对象
获取值栈对象的方法很多,最常用的是使用ActionContext类来获取:
ActionContext context = ActionContext.getContext();
// 获得值栈对象
ValueStack stack = context.getValueStack();

值栈本质上是一种栈类型的数据结构,如图所示:

栈中数据遵循后进先出的原则,在线最上面的元素叫做栈顶元素,新的数据存储进来的时候会压在原有数据的上面,这个操作被称为压栈。
10.5.值栈的内部结构
值栈分为两个部分:root和context
root部分是List结构;context部分是Map结构。
存值和取值一般都是操作root部分的数据,而context部分存储的是一些对象的引用。

测试:
在ActionContext context = ActionContext.getContext();处设置断点测试,运行看结果:stack1,里面有root和context。

Ctrl+shift+T查询CompoundRoot类。

画图解析context存储的对象引用(不是真正的对象,只是对象的引用):

三个域对象,向三个域对象放值,名称都相同:setAttribute(“name”,value);,使用attr操作时获取域对象里的值,获取最小域对象里的值。很少用。

使用Struts2标签中的查看值栈内部结构和储存的值


可以看到值栈中root部分的结构如下:

可以明显的看出root部分的结构是一个List集合(在后面会通过在ValueStack里面存值和取值更加详细深入的理解值栈的原理)

这里需要注意的是:在action没有做任何操作的情况下,栈顶元素是 action引用。
因为action对象里面有值栈对象, 值栈对象里面有action引用。这样的设计方式有利于action对象和值栈对象的互相调用。

总结:值栈就是struts2为我们提供的一套类似于域对象的用来存值和取值的机制,自此我们在struts2框架中传递数据就有了两种方式:值栈和域对象。这两种方式都能实现功能,没有孰高孰低,根据我们的业务需求灵活使用即可。

10.6.将数据存入值栈
在Struts2中,往Action实例对象的值栈存入数据有三种方式:set方法、push方法、get方法。
10.6.1.set方法
步骤:
Step1:在Action中获取值栈对象;
Step2:调用值栈对象的set方法存值。

代码示例:
public class ValueStackDemoAction extends ActionSupport {

public String execute() throws Exception {
    //1 获取值栈对象
    ActionContext context = ActionContext.getContext();
    ValueStack stack = context.getValueStack();

    //2 调用值栈对象中的set方法
    stack.set("demo", "DemoData");
    return "success";
}

}

Set方法使用Map方式存值,第一个元素为String类型的Key,第二个元素为Object类型的Value。
用标签查看下值栈的结构:

从图上可以看到set()方法是使用HashMap的方式将值存入到值栈中,然后取值时根据Map中的Key即可取其Value出来。

10.6.2.push方法
步骤:
Step1:在Action中获取值栈对象;
Step2:调用值栈对象的push()方法存值。

代码示例:
public class ValueStackDemoAction extends ActionSupport {

public String execute() throws Exception {
    //1 获取值栈对象
    ActionContext context = ActionContext.getContext();
    ValueStack stack = context.getValueStack();

    //2 调用值栈对象中的push方法
    stack.push("abcd");   // 压栈操作,压入栈顶
    return "success";
}

}

Push方法只能接收一个Object类型的参数,可以存放任意类型的对象。
Push方法是自动将所存的数据转换为对应的对象类型,然后放入值栈中,如图:

10.6.3.get方法(推荐)
步骤:
Step1:在Action定义变量
Step2:生成变量的get()方法
Step3:在执行方法里面为变量赋值

代码示例:
public class ValueStackDemoAction extends ActionSupport {
//1. 声明变量
public String name;
//2. 生成变量的get方法
public String getName(){
return name;
}
//3. 在执行方法中为变量赋值
public String execute() throws Exception {
name = “DemoData”;
return “success”;
}
}
使用该方法存数据后值栈中的数据结构,如图:

可以看出这种方式存值不会在值栈中创建新的对象,而是直接将值进了值栈中原有的action对象中,这种方式的好处就是避免了值栈中存储空间的浪费,不用为每个值都设置存储空间。因此这种方式更加常用。

10.6.4.使用get方法向值栈中存放一个对象
User实体类:
public class User{
Private String username;
Private String password;
//省略set和get方法
}

Action类:
public class ListDemoAction extends ActionSupport {
// 1. 定义对象
private User user = new User();
// 2. 生成get方法
public User getUser() {
return user;
}
public String execute() throws Exception {
//3. 向对象中设置值
User user = new User();
user.setUsername(“小奥”);
user.setPassword(“123”);
return “success”;
}
}
10.6.5.使用get方法向值栈中存放一个List集合
User实体类同上

Action类:
public class ListDemoAction extends ActionSupport {
// 1. 定义list变量
private List list = new ArrayList();
// 2. 生成get方法
public List getList() {
return list;
}
public String execute() throws Exception {
//3. 向list中设置值
User user1 = new User();
user1.setUsername(“小奥”);
user1.setPassword(“123”);
User user2 = new User();
user2.setUsername(“小王”);
user2.setPassword(“250”);
list.add(user1);
list.add(user2);
return “success”;
}
}
值栈中的结构如下:

接下来学习如何在JSP页面中获取值栈中的数据,这里我们通过学习Struts2内置的OGNL表达式语言来实现从值栈取值。

11.OGNL表达式
11.1.OGNL简介
OGNL(ObjectGraphic Navigation Language)对象图导航语言,它是一个开源项目。
Struts2框架使用OGNL作为默认的表达式语言,大大加强了数据访问功能。

OGNL表达式与EL表达式有很多相似的地方,也有不同的地方。

相同点:
获取域对象(page,request,session,application)的数据。

不同点:
EL表达式不能存放数据,不能调用方法。
OGNL表达式可以存放数据,可以调用方法。

OGNL优势:
(1)支持对象方法调用,如xxx.sayHello();
(2)支持类静态方法调用和值访问,表达式的格式为:
@[类全名(包路径)]@[方法名|值名]
例如:@java.lang.String@format(‘foo%s’, ‘bar’) 或 @cn.itcast.Constant@APP_NAME;
(3)支持赋值操作和表达式串联
如:price=100, discount=0.8,calculatePrice()这个表达式会返回80。
(4)操作集合对象。
(5)访问OGNL上下文(OGNL context)和ActionContext。

OGNL有一个上下文(Context)概念,说白了上下文就是一个Map结构,它实现了java.utils.Map接口,在Struts2中上下文实现为ActionContext。

OGNL是针对值栈做些操作,负责从值栈中取出数据。
11.2.基本操作步骤
第一步:导入OGNL的jar包:ognl-3.0.6.jar,Struts包中已自带。
第二步:引入Struts2的标签库:
<%@ taglib prefix=“s” uri = “/struts-tags” %>
第三步:使用Struts2的标签:OGNL与Struts2中的标签配合使用。
//可以调方法,测试字符串’haha’的长度

11.3.OGNL中#、%的使用
11.3.1.# 的用法

使用 # 来获取Action值栈中Context部分中的数据,相当于ActionContext.getContext();
那么Context也分了几个区域,如:Context的根值、parameters、request、session、application、attr,
如何获得这几个区域的值?
(1)用法一:
// 获得ActionContext根下user对象的username属性值

// 获得parameters区域中的id,相当于request.getParameter(“id”)

// 获得request区域中的password,相当于request.getAttribute(“password”)

// 获得session区域中的password,相当于session.getAttribute(“password”)

// 获得application区域中的password,相当于application.getAttribute(“password”)

// 获得request、session、application区域中的password

attr 用于按request > session > application顺序访问其属性(attribute) #attr.userName相当于按顺序在以上三个范围(scope)内读取password属性,直到找到为止。
(2)用于过滤和投影(projecting)集合,如books.{?#this.price<100};
(3)构造Map,如#{‘foo1’:’bar1’, ‘foo2’:’bar2’}。

(2)%使用
如果直接在Struts2表单标签里面使用OGNL表达式Struts2不能识别,需要在OGNL表达式前加%。
示例代码:

运行后可以看它的源代码:即多了个标签,value="#request.req"这样写,它不认识#request.req这种OGNL表达式的写法,只有加上%才能识别。正确代码如下:

二、”%”的用法
  “%”符号的用途是在标志的属性为字符串类型时,计算OGNL表达式的值。例如在Ognl.jsp中加入以下代码:
  

%的用途




三、” ” 的 用 法     1 、 用 于 在 国 际 化 资 源 文 件 中 , 引 用 O G N L 表 达 式     2 、 在 S t r u t s 2 配 置 文 件 中 , 引 用 O G N L 表 达 式 例 如 : < a c t i o n n a m e = " A d d P h o t o " c l a s s = " a d d P h o t o " > < i n t e r c e p t o r − r e f n a m e = " f i l e U p l o a d S t a c k " / > < r e s u l t t y p e = " r e d i r e c t " > L i s t P h o t o s . a c t i o n ? a l b u m I d = ”的用法   1、用于在国际化资源文件中,引用OGNL表达式   2、在Struts 2配置文件中,引用OGNL表达式 例如: ListPhotos.action?albumId=   1OGNL  2Struts2OGNL<actionname="AddPhoto"class="addPhoto"><interceptorrefname="fileUploadStack"/><resulttype="redirect">ListPhotos.action?albumId={albumId}

11.4.OGNL获取值栈中的数据
11.4.1.获取使用set方法存入值栈中的值


如果在struts.xml中的type设置为:redirect重定向,则获取不到值。

11.4.2.获取使用push方法存入值栈中的值
push()方式存值是没有map结构的,那么没有key,我们如何取值?
实际上struts2中将push()方法存入值栈的值都放在一个名为top的集合中,那么我们只需要利用这个集合便可获取到其中的值。

//取栈顶元素
这里需要特别注意JSP页面中使用OGNL表达式获取List集合中元素的写法,不是top[0],而是[0].top。和我们在Java代码中取List集合元素的写法是有区别的。

11.4.3.获取使用变量的get方法存入值栈中的值
这是往值栈中存值的最常用的方法,这里我们将其分为取字符串,取对象和取List集合三种方式:

(1)获取get方式存入值栈的字符串
首先我们先使用变量方法存入一个字符串到值栈中,关键代码:
private String username;
public String getUsername(){
return username;
}

public String execute() throws Exception {
username = “demo”;
retrun “success”;
}

然后我们在jsp页面中获取这个字符串的值,关键代码:

<%@ taglib uri="/struts-tags" prefix=“s”%>

(2)获取get方式存入值栈的对象
首先将对象存入到值栈中去,关键代码:
private User user = new User();
public User getUser(){
return user;
}

public String execute() throws Exception {
user.setUsername(“demo”);
user.setPassword(123456);
user.setAddress(“beijing”);

return “success”;
}

然后在jsp页面中获取这个对象,关键代码:

<%@ taglib uri="/struts-tags" prefix=“s”%>



(3)获取get方式存入值栈的集合
首先将list集合存入值栈,关键代码:
private List list = new ArrayList();
public List getList() {
return list;
}

public String execute() throws Exception {
User user1 = new User();
user1.setUsername(“小奥”);
user1.setPassword(“123”);
user1.setAddress(“美国”);

User user2 = new User();
user2.setUsername("小王");
user2.setPassword("250");
user2.setAddress("越南");
    
list.add(user1);
list.add(user2);
    
return "success";

}

然后在jsp页面中获取list中的数据,在jsp中获取值栈中的list有三种方式,下面我们分别演示,关键代码:

<%@ taglib uri="/struts-tags" prefix=“s”%>


获取list的值第一种方式:








获取list的值第二种方式:









获取list的值第三种方式:








这里第一和第二种方法都比较好理解,第三种方法需要注意的地方在:在s:iterator 标签时如果使用了var属性,那么struts2会在值栈的context部分新开辟一个临时空间,并将遍历出来的list集合中的元素放到这个临时空间中,那么这时候再使用ognl表达式获取数据要使用#号,否则无法取出数据。

这样可以避免浪费root部分的空间,加快检索的速度,因为我们通常操作数据都是操作root部分。

12.Struts2标签库
Struts2提供了一套s标签库,该标签库分:表单标签、控制标签、数据标签等。
12.1.导入标签库
<%@ taglib prefix=”s” uri=”/struts-tags” %>
prefix属性值是使用此标签库的前缀。
12.2.控制标签
12.2.1.标签
作用:用于控制选择输出。

标签体


标签体


标签体




12.2.2.迭代标签
var属性:它的取值就是一个普通的字符串.
用了var:把每次遍历的对象作为value,把var的值作为key,存入ContextMap中
没用var:把每次遍历的对象压入栈顶,再下次遍历之前弹栈(从栈顶移走)。
begin:开始遍历的索引
end:遍历的结束索引
step:遍历的步长。
status:计数器对象
count 已经遍历的集合元素个数
index 当前遍历元素的索引值
odd 是否奇数行
even 是否偶数行
first 是否第一行
last 是否最后一行

//for循环



//迭代数组元素



//迭代对象(property放对象属性名)




//迭代集合(简单类型,如String)





//List(自定义对象属性)
public String execute(){
List list = new ArrayList();

UserInfo userInfo = new UserInfo();
userInfo.setUsername(“u1”);
userInfo.setPassword(“p1”);
userInfo.setRoleInfo(new RoleInfo(“r1”));

userInfo = new UserInfo();
userInfo.setUsername(“u2”);
userInfo.setPassword(“p2”);
userInfo.setRoleInfo(new RoleInfo(“r2”));
list.add(userInfo);
list.add(userInfo);
}






或者:





//Map(普通属性)
public String execute(){
Map map = new HashMap();
map.put(“k1”, “v1”);
map.put(“k2”, “v2”);
}






或者:




//Map(自定义对象属性)
public String execute(){
Map map = new HashMap();
UserInfo userInfo = new UserInfo();
userInfo.setUsername(“u1”);
userInfo.setPassword(“p1”);
userInfo.setRoleInfo(new RoleInfo(“r1”));

  userInfo = new UserInfo(); 
  userInfo.setUsername("u2"); 
  userInfo.setPassword("p2"); 
  userInfo.setRoleInfo(new RoleInfo("r2")); 

map.put(“k1”, userInfo);
map.put(“k2”, userInfo);
}







或者






//Map嵌套Map(自定义对象属性)
public String execute(){
Map> map = new TreeMap>();
Map innerMap = new TreeMap();
UserInfo userInfo = new UserInfo();
userInfo.setUsername(“u1”);
userInfo.setPassword(“p1”);
userInfo.setRoleInfo(new RoleInfo(“r1”));
userInfo = new UserInfo();
userInfo.setUsername(“u11”);
userInfo.setPassword(“p11”);
userInfo.setRoleInfo(new RoleInfo(“r11”));
innerMap.put(“k1”, userInfo);
innerMap.put(“k11”, userInfo);
map.put(“key1”, innerMap);
//////////////////////////
innerMap = new TreeMap();
userInfo = new UserInfo();
userInfo.setUsername(“u2”);
userInfo.setPassword(“p2”);
userInfo.setRoleInfo(new RoleInfo(“r2”));
userInfo = new UserInfo();
userInfo.setUsername(“u22”);
userInfo.setPassword(“p22”);
userInfo.setRoleInfo(new RoleInfo(“r22”));
innerMap.put(“k2”, userInfo);
innerMap.put(“k22”, userInfo);
map.put(“key2”, innerMap);
}


 
  

  
  

 

12.2.3.排序标签
准备工作:
1)创建排序规则类,如StudentSort


–StudentSort cmp = new StudentSort();






12.2.4.标签
作用:用于将多个集合拼成一个新的集合

案例1:



结果:

案例2:
Action类
public class AppendTagAction extends ActionSupport{
private List list1 = new ArrayList();
private List list2 = new ArrayList();
private List list3 = new ArrayList();
private Map map1 = new HashMap();
private Map map2 = new HashMap();
private Map map3 = new HashMap();
@Override
public String execute() {
list1.add(“List1 - 1”);
list1.add(“List1 - 2”);
list1.add(“List1 - 3”);

	list2.add("List2 - 1");
	list2.add("List2 - 2");
	list2.add("List2 - 3");
	
	list3.add("List3 - 1");
	list3.add("List3 - 2");
	list3.add("List3 - 3");
	
	map1.put("map1-key1", "map1-value1");
	map1.put("map1-key2", "map1-value2");
	map1.put("map1-key3", "map1-value3");
	
	map2.put("map2-key1", "map2-value1");
	map2.put("map2-key2", "map2-value2");
	map2.put("map2-key3", "map2-value3");
	
	map3.put("map3-key1", "map3-value1");
	map3.put("map3-key2", "map3-value2");
	map3.put("map3-key3", "map3-value3");
	
	return SUCCESS;
}
//getter methods...

}

View:


















  • 12.3.数据标签
    12.3.1.标签
    作用:直接从JSP页面中调用Action,如果“executeResult=true”,则该标签会把Action的处理结果(即视图资源)包含到本页面中。

    Action:
    public class TestAction extends ActionSupport {
    @Override
    public String execute() throws Exception {
    // TODO Auto-generated method stub
    return “abc”;
    }
    }

    Struts.xml配置:

    /helloWorld.jsp

    把TestAction中的execute()方法的结果码对应的视图资源(helloWorld.jsp)包含到当前页面中。

    其它写法:



    12.3.2.标签
    id:可选属性,指定该元素的标识。
    default:可选属性,如果要输出的属性值为null,则显示default属性的指定值。
    escape:可选属性,指定是否忽略HTML代码。
    value:可选属性,指定需要输出的属性值,如果没有指定该属性,则默认输出ValueStack栈顶的值
    <%-- 输出值栈中的值 --%>

    12.3.3.标签
    作用:实例化一个对象。

    第一种赋值方式:

    张三
    19


    第二种赋值方式:




    在bean标签外利用id取值:

    12.4.表单标签
    12.4.1.表单
    action 指定提交时对应的action,不需要action后缀
    enctype HTML表单enctype属性
    method HTML表单method属性
    namespace 所提交action的命名空间

    ……

    12.4.2.文本框
    maxlength 文本输入控件可以输入字符的最大长度
    readonly 当该属性为true时,不能输入
    size 指定可视尺寸
    id 用来标识元素的id。在ui和表单中为HTML的id属性
    name 控件名称
    label 控件文字符

    12.4.3.密码框

    12.4.4.文本域
    cols 列数
    rows 行数
    readonly 当该属性为true时,不能输入
    wrap 指定多行文本输入控件是否应该换行
    id 用来标识元素的id。在ui和表单中为HTML的id属性

    12.4.5.下拉框
    //使用name和list属性,list属性的值是一个列表

    //使用name和list属性,list属性的值是一个Map

    //使用headerKey和headerValue属性设置header选项

    //使用emptyOption属性在header选项后添加一个空的选项

    //使用multiple属性设置多选

    //使用size属性设置下拉框可显示的选项个数

    //使用listKey和listValue属性,利用Action实例的属性(property)来设置选项的值和选项的内容

    12.4.6.级联列表框
    label=“请选择所在省市”
    name=“province”
    list="{‘四川省’,‘山东省’}"
    doubleName=“city”
    doubleList=“top == ‘四川省’ ? {‘成都市’, ‘绵阳市’} : {‘济南市’, ‘青岛市’}”
    />

    label=“请选择所在省市”
    name=“province”
    list=“provinces” //要迭代的集合
    listKey=“id” //指定集合中的id属性作为选项的value
    listValue=“name” //指定集合中的name属性作为选项的内容

        doubleList="cities"  //要迭代的集合
        doubleListKey="id"   //指定集合中的id属性作为选项的value
        doubleListValue="name"    //指定集合中的name属性作为选项的内容
        doubleName="city"    //指定第二个列表框的city映射
    
        headerKey="-1"       //选择header时选择的value
        headerValue="---------- 请选择 ----------"  
        emptyOption="true"
    

    />

    12.4.7.复选按钮与复选按钮组

    12.4.8.单选框

    12.4.9.盾牌

    View渲染:

    经典案例:防止用户重复提交
    原因:当用户填写完表单提交后,若再次操作点击提交、刷新页面、提交页面后退按钮,都会导致表单重复提交。
    解决办法:session Token机制,即在表单中添加
    当用户首次访问表单页面时,服务器会做三件事:
    1)创建一个session对象。
    2)通过产生一个随机数,并保存在session中。
    3)服务器把产生的随机数发送给客户端。
    当用户向服务器提交表单时,此时服务器会做:
    1)判断从客户端发送过来的请求参数中的随机数和保存在session对中随机数是否相等,如果相等,则认为是第一次提交。
    2)若是第一次提交,服务器会把原来保存在session的随机数改变成其他的随机数。发给客户端的随机数不变。
    3)因为在第一次提交表单后,服务器随机数和客户端的不一样了,所以当重复提交的时候,服务器看到客户端的随机数和自己的不一样了,就可以判断这是在重复提交了。

    View代码:在表单中设置盾牌




    Struts.xml:配置token的拦截器及重复提交时跳转的页面

    /success.jsp

     
     /invalid.jsp  
     
       
       
    

    12.4.10.文件选择
    Fileupload jquery上传插件

    参见后面的文件上传。

    12.4.11.标签

    12.4.12.按钮
    Type 按钮类型,有image\button
    Method 提交调用方法
    value 按钮文本
    Src 图片引用路径

    页面渲染:


    页面渲染:

    12.5.表单案例
    <%@ page language=“java” contentType=“text/html; charset=UTF-8” pageEncoding=“UTF-8”%>
    <%@ taglib uri="/struts-tags" prefix=“s” %>

    注册界面

    13.输入校验
    Java EE开发中通常是使用JavaScript脚本来实现客户端验证。但不应单独依赖于客户端验证。最佳实践表明,验证应引入各级应用程序框架。

    Struts的校验主要有:手动完成输入校验、基于注解的输入校验等。
    13.1.手动完成输入校验
    View代码:




    Action代码:
    public class Employee extends ActionSupport{
    private String name;
    private int age;
    //省略setter和getter方法

    @Override
    public String execute(){
    return SUCCESS;
    }
    //验证方法
    @Override
    public void validate(){
    if (name == null || name.trim().equals("")){
    this.addFieldError(“name”,“姓名已存在”);
    }
    if (age < 28 || age > 65){
    this.addFieldError(“age”,“年龄必须在28~65”);
    }
    }
    }
    Validate()方法作用就是对View页面中传过来的数据进行验证,验证规则由自己写。
    addFieldEerror()的作用是将错误信息保存到FieldError中,如果struts发现FieldError不为空,将会自动跳转到input逻辑视图,因此在struts.xml中要为该Action的input指定视图资源。




    /error.jsp
    /success.jsp


    Error.jsp页面代码:

    13.2.基于注解的输入校验
    public class Employee extends ActionSupport{
    private String name;
    private int age;

    @RequiredStringValidator(key=”name.requried”, message=””)
    @RegexFieldValidator(regex=”\w{4,25}”, key=”name.regex”, message=””)
    public void setName(String name){
    this.name = name;
    }

    @Override
    public String execute(){
    return SUCCESS;
    }
    }
    自学
    Jquery validate插件,form表单验证插件

    14.类型转换
    14.1.什么是类型转换?
    客户端(浏览器)请求的所有内容都是以文本编码方式传输到服务器端的,服务器端的编程语言却有着丰富的数据类型。

    输入坐标:

    在Servlet中,我们通过以下代码将接收到的参数由字符串转换成想要的类型:

    String agestr = request.getParameter(“age”);
    int age = Integer.parseInt(agestr);

    String strdate = request.getParameter(“date”);
    SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd”);
    sdf.format(str);

    Struts 2的类型转换分:自动类型转换、自定义类型转换。
    自动类型转换由struts2提供。
    自定义类型转换需要编写转换器。
    14.2.Struts2内置自动类型转换器
    表单中所有输入的值都将作为String类型提交到相应的Action,至于如何将这些String类型转换为Action中的属性的类型是需要做一些判断的,Struts2中默认有一个类型转换器,可以帮助我们完成大部分的自动转换操作。其支持的从String类型转换的目标类型如下:
    String和boolean、Boolean 完成字符串与布尔值之间的转换
    String和char、Character 往常字符串与字符之间的转换
    String和int、Integer 完成字符串与整型之间的转换
    String和long、Long 完成字符串与长整型值之间的转换
    String和double、Double 完成字符串与双精度浮点值的转换
    String和float、Float 完成字符串和单精度浮点之间的转换
    String和Date 完成字符串和日期类型之间的转换,可以接收yyyy-MM-dd格式字符串
    String和数组 可以将多个同名参数,转换到数组中
    String和Map、List 支持将数据保存到List或者Map集合
    在Action中的属性类型,如果是以上这些类型,那么从客户端提交的过来的字符串就可以默认使用该机制自动转换成对应的类型,完成自动赋值。如果不是上述的类型,那么就需要自定义类型转换器来实现显式的转换类型。

    案例1:
    View代码:

    用户名:
    密码:
    年龄:
    生日:
    兴趣:音乐 电影

    Action代码:
    public class ConverterAction extends ActionSupport {
    private String name;
    private String password;
    private int age;
    private Date birthday;
    private String[] hobby;
    //此处省略setter和getter方法

    @Override
    public String execute() throws Exception {
        //输出属性值,发现从form表单中接收到的String类型数据已自动转换
        System.out.println(“姓名:”+this.name);
        System.out.println(“密码:”+this.password);
        System.out.println(“年龄:”+this.age);
        System.out.println(“生日:”+this.birthday);
        for(String s: hobby){
            System.out.println(s);
        }
    	return SUCCESS;
    }
    

    }

    Struts.xml



    /success.jsp


    14.3.基于OGNL的类型转换
    对于非基本类型,我们使用默认的转换机制是不能解决问题的。(使用模型驱动)

    Action代码:
    //其中walker是一个符合Javabean规范的类,其具有两个属性username和age
    public class LoginAction extends ActionSupport {
    private Walker walker;
    public void setWalker(Walker w){
    this.walker =w;
    }
    public Walker getWalker(){
    return this.walker;
    }
    @Override
    public String execute() throws Exception{
    return SUCCESS;
    }
    }
    Action实例的一个属性是自定义的类型Walker,那么login表单页面原有的代码肯定是不能生效的,因为指定的username和age在Action实例中是没有的。那么怎么将一个字符串赋值给Action实例属性呢?ognl语法是可以做到的。

    View代码:





    在login页面使用ognl语法,walker.username指定了为Action实例属性walker的username属性传值,walker.age指定了为Action实例属性的walker的age属性传值。其实我们到这里可以看出来,使用ognl语法可以实现非基本类型的转换。

    View代码:输出对象属性

    操作List集合:
    //修改属性为一个list集合
    public class LoginAction extends ActionSupport {
    private List list;
    public void setList(List w){
    this.list =w;
    }
    public List getList(){
    return this.list;
    }
    @Override
    public String execute() throws Exception{
    return SUCCESS;
    }
    }

    //修改了的login表单页面






    操作Map集合:
    //修改后的LoginAction 页面
    public class LoginAction extends ActionSupport {
    private Map map;
    public void setMap(Map w){
    this.map = w;
    }
    public Map getMap(){
    return this.map;
    }

    @Override
    public String execute() throws Exception{
        return SUCCESS;
    }
    

    }

    //login页面的表单传值

    14.4.自定义类型转换器
    因为在struts2提供的类型转换在我们实际需求开发中,会存在不能满足开发需求的情况,比如将字符串2017/6/27装换成时间数据Data2017年6月27日上就会发生异常,因为struts2并没有这种类型转换器,而为了解决我们的需求问题,我们必须自己写一个自定义类型转换器。

    自定义类型转换器步骤
    (1)创建一个自定义类型转换器
    创建自定义类型转换器有三种方法:
    A.实现TypeConverter接口
    B.继承DefaultConverter类
    C.继承DefaultTypeConverter类的一个子类:StrutsTypeConverter(推荐)

    (2)重写接口中的方法,实现类型转换操作
    //建议使用这个方法,因为在这个类中,将从页面传递的数据怎么封装以及action中的数据怎样在页面上显示做了分离。
    //参数value:表单要转换的值
    //参数toClass:要转换的类型
    public abstract Object convertFromString(Map context, String[] values, Class toClass);
    public abstract String convertToString(Map context, Object o);

    (3)注册类型转换器
    怎样注册一个自定义类型转换器?
    局部–针对action
    配置文件所在位置以及名称:在Action类所在包,创建Action类名-conversion.properties
    配置文件书写格式:属性名称=类型转换器的全类名

    局部–针对model
    配置文件所在位置以及名称:在model类所在包,创建model类名-conversion.properties
    配置文件书写格式:属性名称=类型转换器的全类名

    全局
    配置文件所在位置以及名称:在src下创建一个xwork-conversion.properties
    配置文件书写格式:要转换的类型全名=类型转换器的全类名

    案例1:坐标转换
    Point坐标实体类:
    public class Point {
    private int x;
    private int y;
    //此处省略各属性的setter和getter方法
    }

    Action类:
    public class RegisterAction extends ActionSupport {
    private String name;
    private int age;
    private Date birthday;
    private Point point;
    //此处省略各属性的setter和getter方法
    @Override
    public String execute() throws IOException{
    return SUCCESS;
    }
    }

    Struts.xml配置:



    success.jsp


    表单:






    这里运行后会报错,一个是类型转换中的错误,一个是result错误未配置(404)。

    类型转换错误解决:
    在上述代码我们发现Point这个类型是用户自定义的类型,提交(108,120)数据存入Point对象会出现错误,Struts自带的类型转换器不支持用户定义的数据类型 ,因此,我们需要写一个自定义类型转换器类,用于转换用户自定义的类。

    具体操作步骤:
    1)创建一个自定义类型转换类,继承StrutsTypeConverter类。
    2)重写convertFromString()和convertToString方法,实现转换业务。
    public class PointConvert extends StrutsTypeConverter {

    //将一个或多个字符串转成对象
    @Override
    public Object convertFromString(Map context, String[] values, Class toClass) {
    	// TODO Auto-generated method stub
    	String str = values[0];
    	String xy[] = str.split(",");
    	int x = Integer.parseInt(xy[0]);
    	int y = Integer.parseInt(xy[1]);
    	//构建坐标对象
    	Point point = new Point();
    	point.setX(x);
    	point.setY(y);
    	return point; //返回坐标对象
    }
    
    //将指定对象转换成字符串
    @Override
    public String convertToString(Map context, Object o) {
    	// TODO Auto-generated method stub
    	//将坐标对象转换为字符串
    	Point point = (Point) o;
    	int x = point.getX();
    	int y = point.getY();
    	String str = "(" + x + "," + y + ")";
    	return str; //返回字符串
    }
    

    }

    3)在转换类同包中创建RegisterAction-conversion.properties文件,将Action中point属性与转换类关联。
    注意properties的命名是有要求的。
    point=com.zking.PointConverter
    point是Action类中的属性。

    result错误未配置(404)
    在struts.xml中配置:

    input.jsp

    案例:日期类型转换
    public class DateConverter extends StrutsTypeConverter {
    private final DateFormat[] dfs = {
    new SimpleDateFormat(“yyyy年MM月dd日”),
    new SimpleDateFormat(“yyyy-MM-dd”),
    new SimpleDateFormat(“yyyy/MM/dd”) };

    public Object convertFromString (Map context, String[] values, Class toType) {
    	String dateStr = values[0];// 获取日期的字符串
    	for (int i=0;i

    }

    应用于全局范围,在src目录下创建文件xwork-conversion.properties
    java.util.Date=com.zking.DateConverter

    14.5.类型转换中的错误处理
    对于struts2中的类型转换器,如果表单数据提交时,将数据想model封装,出现的问题,就会报错,例如:No result defined for action cn.itcast.action.RegistAction and result input。
    将异常信息翻译过来的意思为:在RegistAction的配置中没有配置input结果视图。
    //如果配置以下信息,那么出现类型转换问题,就会跳转到指定的视图。

    /error.jsp

    那么为什么会向input视图跳转呢?
    原因是struts2中的拦截器(intercepter)。
    //用于记录类型转换问题

    // 用于得到问题,向input视图跳转。

    因为struts2中引入了默认的拦截器栈。在这个默认的拦截器栈中,有一个拦截器是conversionError,它的作用是当类型转换出现错误的时候,会向action中存储错误信息。然而在栈尾还有一个拦截器是workflow,它的作用是如果前面的拦截器发现了问题,并将问题存储到了action中,或者抛出了异常,那么workflow拦截器就会直接跳转的到input视图。

    1. 关于错误信息展示
      通过分析拦截器作用,得知当类型转换出错时,自动跳转input视图 ,在input视图页面中 显示错误信息。
    • 在Action所在包中,创建 ActionName.properties,在局部资源文件中配置提示信息 : invalid.fieldvalue.属性名= 错误信息

    如果是自定义类型转换器,出现类型转换问题,要跳转到input视图,在类型转换器中,必须抛出异常才可以。

    15.文件上传、下载
    基于表单的文件上传。
    Commons-fileupload
    15.1.Commons-FileUpload组件
    Commons是Apache开放源代码组织的一个Java子项目,其中的FileUpload是用来处理HTTP文件上传的子项目。

    Commons-FileUpload组件特点:
    使用简单:可以方便地嵌入到JSP文件中,编写少量代码即可完成文件的上传功能
    能够全程控制上传内容
    能够对上传文件的大小、类型进行控制
    15.2.引入依赖(导包)
    http://jakarta.apache.org/commons/fileupload/
    –下载commons-fileupload-1.2-bin.zip文件
    –解压后得到commons-fileupload-1.2.jar

    http://jakarta.apache.org/commons/io/
    –下载commons-io-1.3.2-bin.zip文件
    –解压后得到commons-io-1.3.2.jar

    注意:如果使用Maven创建项目并引入struts核心依赖包的(Struts-core),已包含有FileUpload包。

    15.3.Web表单
    文件上传页面upload.jsp:
    <%@ taglib uri="/struts-tags" prefix=“s”%>

    文件:
    
    
        
        
    
    

    注意:上面是基于表单的上传,在form标签中有两个重点属性:
    method表单提交方式必须为“post”;
    必须为表单添加enctype值,并设置为:multipart/form-data。
    15.4.配置Struts.xml

    
        
    

    /success.jsp

            
            /error.jsp
            
    
    
    
    
    
     
    
    
    
    

    22.附二:常用拦截器
    在struts.xml中的配置:

    0

    常用拦截器:
    拦截器 名字 说明
    Alias Interceptor alias 在不同请求之间将请求参数在不同名字件转换,请求内容不变
    Chaining Interceptor chain 让前一个Action的属性可以被后一个Action访问,现在和chain类型的result()结合使用。
    Checkbox Interceptor checkbox 添加了checkbox自动处理代码,将没有选中的checkbox的内容设定为false,而html默认情况下不提交没有选中的checkbox。
    Cookies Interceptor cookies 使用配置的name,value来是指cookies
    Conversion Error Interceptor conversionError 将错误从ActionContext中添加到Action的属性字段中。
    Create Session Interceptor createSession 自动的创建HttpSession,用来为需要使用到HttpSession的拦截器服务。
    Debugging Interceptor debugging 提供不同的调试用的页面来展现内部的数据状况。
    Execute and Wait Interceptor execAndWait 在后台执行Action,同时将用户带到一个中间的等待页面。
    Exception Interceptor exception 将异常定位到一个画面
    File Upload Interceptor fileUpload 提供文件上传功能
    I18n Interceptor i18n 记录用户选择的locale,国际化
    Logger Interceptor logger 输出Action的名字
    Message Store Interceptor store 存储或者访问实现ValidationAware接口的Action类出现的消息,错误,字段错误等。
    Model Driven Interceptor model-driven 如果一个类实现了ModelDriven,将getModel得到的结果放在Value Stack中。
    Scoped Model Driven scoped-model-driven 如果一个Action实现了ScopedModelDriven,则这个拦截器会从相应的Scope中取出model调用Action的setModel方法将其放入Action内部。
    Parameters Interceptor params 将请求中的参数设置到Action中去。
    Prepare Interceptor prepare 如果Acton实现了Preparable,则该拦截器调用Action类的prepare方法。
    Scope Interceptor scope 将Action状态存入session和application的简单方法。
    Servlet Config Interceptor servletConfig 提供访问HttpServletRequest和HttpServletResponse的方法,以Map的方式访问。
    Static Parameters Interceptor staticParams 从struts.xml文件中将中的中的内容设置到对应的Action中。
    Roles Interceptor roles 确定用户是否具有JAAS指定的Role,否则不予执行。
    Timer Interceptor timer 输出Action执行的时间
    Token Interceptor token 通过Token来避免双击
    Token Session Interceptor tokenSession 和Token Interceptor一样,不过双击的时候把请求的数据存储在Session中
    Validation Interceptor validation 使用action-validation.xml文件中定义的内容校验提交的数据。
    Workflow Interceptor workflow 调用Action的validate方法,一旦有错误返回,重新定位到INPUT画面
    Parameter Filter Interceptor N/A 从参数列表中删除不必要的参数
    Profiling Interceptor profiling 通过参数激活profile

    conversionError 类型转换错误拦截器
    exception 异常拦截器
    fileUpload 文件上传拦截器
    i18n 国际化拦截器
    logger 日志拦截器
    params 解析请求参数拦截器
    validation 校验拦截器

    23.http请求时程序处理流程

    请求过程
    24.配置文件连接点

    25.国际化(可选)


    在struts.xml同一级目录下创建:messageResource_zh_CN.properties文件。
    示例代码:
    pass.required=\u4F60\u597D
    pass.length=\u6D4B\u8BD5
    properties文件默认的格式不是UTF-8,所以不支持中文,这里不用改,运行时会自动翻译成中文。
    鼠标移上去也会显示。

    你可能感兴趣的:(Struts2框架)