详解Java的Struts2框架的结构及其数据转移方式

Struts2的结构

1.为什么要使用框架?

(1)框架自动完成了很多琐屑的任务

对于Struts2来说,它帮助我们方便地完成了数据类型转换、数据验证、国际化等等
Web开发中常见的任务。还有Spring中大量使用的Template模式,都是在让我们的开发
过程更加自动化、智能化。使用框架就是避免重新发明轮子,重新复制这些模板代码。
框架让我们将精力更多地放在更高级别的问题上,而不是常见工作流和基础任务上。

(2)使用框架就是优雅地继承了框架背后的架构

框架背后的架构通常定义了一系列的工作流程,我们要做的就是将特定应用的代码
依附到这套流程上,这样就可以享受到框架带来的种种好处了。有些时候我们也可以
反抗框架的架构规则,但框架通常以一种很难被拒绝的方式提供它的架构。如此简单
就可以优雅地继承一个优秀的架构,而且是免费的,何乐而不为呢?

(3)使用框架更容易找到训练有素的人

我之前所在公司整个项目几乎都没有用过什么框架,从Service服务的查找(类似JNDI)
到日志打印(类似Log4j),再到数据库连接池(类似DBCP),全都是内部人员自己
实现的。一来是因为项目比较老,当时可能还没有什么开源框架可供使用,二来也是因为
公司保守的策略,担心使用不稳定的开源框架可能会给项目带来风险。这在当时的环境下
也许是没错的,公司高层自然会从更大的视角来考虑整个项目。

但是当项目逐渐庞大起来,同时世界上优秀的开源框架越来越多时,如果不能及时重构
并引入一些成熟的开源框架,最后的结果可能就是新招来的开发人员必须从头开始学习
这个复杂的系统(都是内部系统,网上也没有文档帮助),还要小心内部框架的种种Bug,
成本真是太高了。

(4)内部框架跟不上行业的发展

前面说到了内部框架的Bug。对于开源框架,可能会有框架创始者团队、大批的开源爱好者、
开源社区来支持。人民的力量是无穷的,Bug的修复速度可想而知,这点从最近开源后的
TextMate的Bug修复进程就可以看出了。很多搁置了很久的Bug在开源后被爱好者们迅速
解决,而内部框架呢?在当初开发他的人员离开公司后,在没有重大Bug时甚至都不会有人
去读他的源代码吧,差距可见一斑!

(5)当然使用框架也不是一本万利的事情

前面也提到过,使用不成熟的框架是有风险的,对于一个不是那么激进的项目还是保守为好。
(除非这是一群自由没拘束的技术狂热分子,可以自行决定使用什么框架,那真是幸福的事)
就像我以前用过的Java的HA高可用性服务Sequioa一样,这个框架最终不再被开发公司提供支持
了,这时风险就更大了。

此外,使用一些不常见的框架时还要注意框架源码的License协议,不要在项目中随意引用、
修改框架的源码以免引起不必要的法律纠纷。

2.Struts2背后的架构

既然前面已经分析了框架的这么多好处,那我们自然会开始学习使用Struts2了。但使用Struts2
会继承什么样的优雅架构呢?其实从较高的抽象层次上看,它依然是我们熟悉的MVC模式。

对应之前HelloWorld的例子来看,控制器C(FilterDispatcher)也就是我们在web.xml中声明的
Struts2核心类。而模型M就是我们的NewsAction动作类。而视图V自然就是news.jsp了。模型
的概念似乎有些模糊,什么是模型呢?其实这个听起来很名词的概念在Struts2中既包含了静态
从Web前端传来的业务数据,也包含了业务逻辑的实现。

有人可能会说这种架构没什么新意嘛,MVC框架有很多,这跟其他框架有什么区别呢?让我们
站在低一级别的抽象层次上解剖Struts2,看看它有什么与众不同。

乍看十分复杂,如果只从用户角度来看,在开发时我们只需要实现黄色的部分,也就是我们
HelloWorld实例中的struts.xml,NewsAction和news.jsp。这就是我们要做的全部,就如前面
说的,只需要做很少的事情,我们就成为了这个优秀架构的一部分。

现在来看其他部分。FilterDispatcher就是我们配置在web.xml中的Servlet过滤器,这是Struts2
的入口,所有Struts2的Web应用都要这样配置。接下来蓝色和绿色的部分就是Struts2的核心
了,可以说这些类都是Struts2的开发人员精心设计架构的。

(1)客户端发送请求,J2EE容器解析HTTP包,将其封装成HttpServletRequest。

(2)FilterDispatcher拦截到这个请求,并根据请求路径到ActionMapper中查询决定调用哪个Action。

(3)根据ActionMapper的返回结果,FilterDispatcher委托ActionProxy去struts.xml中找到这个Action。

(4)ActionProxy创建一个ActionInvocation,开始对Interceptor和Action进行递归调用。

(5)各个Interceptor完成各自任务

(6)真正对Action的调用,返回结果路径

(7)Result对象将返回数据输出到流中

(8)返回HttpServletResponse给J2EE容器,容器发送HTTP包到客户端。

这就是Struts2的执行流程,核心对象是ActionInvocation和Interceptor,以及还未介绍的ActionContext。
ActionInvocation是整个流程的总调度,它跟Spring AOP中的Invocation对象很像。而Interceptor有很多
都是Struts2自带的,最重要的是保存请求参数,并将前台的数据传递到Action的成员变量上。

而ActionContext就是保存这些数据的全局上下文对象,最重要的是用来保存Action实例的ValueStack。
所谓全局是指ActionContext可以在Action以及Result中访问,其实它是ThreadLocal类型。每个请求线程
都会有自己的Action和ActionContext实例。

可以说学习Struts2主要就是学习:

(1)让Interceptor和Action配合完成任务。

(2)将前台数据保存到Action中。

(3)Result通过ValueStack从Action中得到返回数据。

3.Struts2与Struts1的不同点

从上面的执行流程已经可以看出Struts1和2的巨大区别。

(1)ActionForm哪去了?Action还是那个Action吗?

最明显的就是我们在整个流程中都看不到ActionForm对象了,而且Action虽然还是叫这个名字,但是
看起来已经跟Struts1中的Action完全不同了。

首先ActionForm被抛弃了,从前台传来的数据已经可以保存到任意POJO了。先存到ActionForm再复制
到Dto对象的日子已经是过去了。第二,这个POJO其实是Action对象中的一个成员变量。这在Struts1
中所有请求共享一个Action实例时是不可能的,现在Struts2会为每个请求都创建一个Action实例,所以
这样做是行得通的。第三,虽然这样可行,可是看起来好像Action作为MVC中的模型M既保存数据,又
包含了业务逻辑,这是不是不良的设计啊?其实仔细想想,这样的设计很方便,我们已经得到了数据,
直接就可以去操作Service层了。Action的职责看似多了,其实并不多。

(2)前端Servlet怎么变成了Filter?

我们知道Struts1和Spring MVC都是通过前端Servlet来作为入口的,为什么Struts2要用Servlet的过滤器呢?
因为Struts2是基于Webwork核心的,与Struts1已经完全不同了。Webwork可以说降低了应用程序与J2EE
API的耦合,比如将ActionServlet改为Servlet的Filter,再比如对HttpServletRequest/Response的直接访问,
又如任何POJO都能担任ActionForm的角色,任何类不用实现Action接口就可以作为Action使用等等,
因此Struts2也继承了这种优秀的非侵入式设计。

这点与Spring的设计思想有些相像。比如那些Ware接口,不关心的Bean完全不需要实现,尽量降低应用
程序代码与框架的耦合。侵入性的确是框架设计时要考虑的一个重要因素。

(3)Filter、Action、Result间的粘合剂OGNL

下图可以清晰明了地展示出OGNL是如何融入Struts2框架的。

在输入页面InputForm.html和返回页面ResultPage.jsp使用Struts2标签中访问Action中的数据是如此方便,
OGNL使访问ValueStack中保存的Action的属性就像访问ValueStack自己的属性一样方便。

对OGNL的大量使用是Struts2的一大特色。包括前台标签传值到Action,Result从Action中取值等都会大量
用到OGNL。而OGNL中大量用到了反射,我想也许这是Struts2性能不如Struts1的一个原因吧。毕竟获得了
灵活而低耦合的架构的同时是要付出一定代价的。

(4)Interceptor的强是无敌的强

Struts2中另一个强大的特性就是Interceptor拦截器了。Struts2内建了大量的拦截器,拦截器使大量代码可以
重复使用,自动化了之前我们所说的琐屑的任务,从而使Struts2达到了高水平的关注分离。这真是AOP思想
在框架中应用的典范!

Struts2三种数据转移方式
Struts2提供了JavaBean属性,JavaBean对象,ModelDriven对象三种方式来保存HTTP请求中的参数。下面通过一个最常见的
登录的例子来看下这三种数据转移方式。页面代码很简单,提交表单中包含有用户名和密码,在Action中得到这两个参数从而
验证用户是否登录成功。

一、JavaBean属性

<%@ page contentType="text/html;charset=UTF-8" %> 

<html> 

<head></head> 

<body>
  <h1>登录页</h1> 

  <form action="/cdai/login" method="post"> 

    <div>
      <label for="username">名称:</label>
      <input id="username" name="username" type="textfield"/>
    </div> 

    <div>
      <label for="password">密码:</label>
      <input id="password" name="password" type="password"/>
    </div> 

    <div>
      <label for="rememberMe">
        <input id="rememberMe" name="rememberMe" type="checkbox"/> 记住我
      </label>
      <input type="submit" value="登录"></input>
    </div> 

  </form> 

</body> 

</html>
package com.cdai.web.ssh.action; 

import com.cdai.web.ssh.request.LoginRequest;
import com.cdai.web.ssh.service.UserService;
import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ModelDriven; 

public class LoginAction implements Action { 

  private String username; 

  private String password; 

  private UserService userService; 

  @Override
  public String execute() { 

    System.out.println("Login action - " + request); 

    return SUCCESS;
  } 

  public String getUsername() {
    return request;
  } 

  public void setUsername(String username) {
    this.username = username;
  } 

  public String getPassword() {
    return request;
  } 

  public void setPassword(String Password) {
    this.Password = Password;
  } 

}

这种方式比较简明,直接将表单中的参数保存到Action中的属性中。Action在验证时可能还需要将用户名和密码再封装成Dto的
形式传给Service层进行验证。所以为什么不更进一步,直接将用户名和密码保存到Dto中。

二、JavaBean对象

<%@ page contentType="text/html;charset=UTF-8" %> 

<html> 

<head></head> 

<body>
  <h1>登录页</h1> 

  <form action="/cdai/login" method="post"> 

    <div>
      <label for="username">名称:</label>
      <input id="username" name="request.username" type="textfield"/>
    </div> 

    <div>
      <label for="password">密码:</label>
      <input id="password" name="request.password" type="password"/>
    </div> 

    <div>
      <label for="rememberMe">
        <input id="rememberMe" name="rememberMe" type="checkbox"/> 记住我
      </label>
      <input type="submit" value="登录"></input>
    </div> 

  </form> 

</body> 

</html>
package com.cdai.web.ssh.action; 

import com.cdai.web.ssh.request.LoginRequest;
import com.cdai.web.ssh.service.UserService;
import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ModelDriven; 

public class LoginAction implements Action { 

  private LoginRequest request; 

  private UserService userService; 

  @Override
  public String execute() { 

    System.out.println("Login action - " + request); 

    return SUCCESS;
  } 

  public LoginRequest getRequest() {
    return request;
  } 

  public void setRequest(LoginRequest request) {
    this.request = request;
  } 

}

这样就可以很方便地直接调用Service层了。但是有一个小缺点就是这样加深了页面参数名的深度,只有为参数名加上request
前缀(Action中的属性名)才能使Struts2通过OGNL将表单中的参数正确保存到request对象中。


三、ModelDriven对象

<%@ page contentType="text/html;charset=UTF-8" %> 

<html> 

<head></head> 

<body>
  <h1>登录页</h1> 

  <form action="/cdai/login" method="post"> 

    <div>
      <label for="username">名称:</label>
      <input id="username" name="username" type="textfield"/>
    </div> 

    <div>
      <label for="password">密码:</label>
      <input id="password" name="password" type="password"/>
    </div> 

    <div>
      <label for="rememberMe">
        <input id="rememberMe" name="rememberMe" type="checkbox"/> 记住我
      </label>
      <input type="submit" value="登录"></input>
    </div> 

  </form> 

</body> 

</html>
package com.cdai.web.ssh.action; 

import com.cdai.web.ssh.request.LoginRequest;
import com.cdai.web.ssh.service.UserService;
import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ModelDriven; 

public class LoginAction implements Action, ModelDriven<LoginRequest>
{ 

  private LoginRequest request = new LoginRequest(); 

  private UserService userService; 

  @Override
  public String execute() { 

    System.out.println("Login action - " + request); 

    return SUCCESS;
  } 

  @Override
  public LoginRequest getModel() {
    return request;
  } 

}

这种方式要多实现一个ModelDriven接口,将ModelDriven提供的对象也保存到ValueStack上,从而使前台页面可以直接通过
username和password属性名来定义表单的参数名了。

三种方式具体采用哪种不能一概而论,还是看项目的具体需求再自己定吧!

(0)

相关推荐

  • 详解在Java的Struts2框架中配置Action的方法

    在Struts2中Action部分,也就是Controller层采用了低侵入的方式.为什么这么说?这是因为在Struts2中action类并不需要继承任何的基类,或实现任何的接口,更没有与Servlet的API直接耦合.它通常更像一个普通的POJO(通常应该包含一个无参数的execute方法),而且可以在内容定义一系列的方法(无参方法),并可以通过配置的方式,把每一个方法都当作一个独立的action来使用,从而实现代码复用. 例如: package example; public class U

  • Struts2学习笔记(1)-入门教程

    什么是Structs2?它就是在Struts1的基础上演化而来的,是一种MVC模式的框架,其实它的功能很简单,就是将View和逻辑处理层分开.本人才疏学浅,以前都是用Spring做项目,由于要毕业,所以需要进行转行(暂且这么说吧),公司都是用的Structs框架,木有办法,从头开始学吧! 所需软件-Eclipse.TomCat7.0.Struts-2.3.1.2(源码自己下载),下面以具体小项目来说明. 首先是Struts2的工作原理:首先Tomcat对用户所输入的URL进行解析,将其中的项目名

  • java struts2框架简介

    一.Struts2简介  1.Struts2概述 Struts2是Apache发行的MVC开源框架.注意:它只是表现层(MVC)框架. M:model-----数据封装------->javabean V:view------视图----------->jsp C:control------控制器--------> struts2( filter),struts1(servlet)  2.Struts2的来历 Struts1:也是apache开发的一套mvc的开源框架.在2005年之前非

  • struts2框架入门

    如果你之前在MVC模式的时候一直都是通过servlet,获取和返回数据,那么现在开始学习struts2框架,Struts是一个实现MVC设计模式的优秀的框架.它的许多优点我就不说了. 我用自己做的一张图说明servlet和struts2的区别. 写一个最基本的开发步骤,完成开发.         (1)创建WEB 工程         (2)导入必要jar包         (3) 编写JSP 页面         (4)编写Action 服务器端处理逻辑         (5)进行框架配置we

  • 概述Java的struts2框架

    一.struts2框架概念 Struts2框架是一个轻量级的MVC流程框架,轻量级是指程序的代码不是很多,运行时占用的资源不是很多,MVC流程框架就是说它是支持分层开发,控制数据的流程,从哪里来,到那里去,怎么来,怎么去的这样一个框架:Struts2是一个基于MVC设计模式的Web应用框架,它本质上相当于一个servlet,在MVC设计模式中,Struts2作为控制器(Controller)来建立模型与视图的数据交互. 二.struts2框架的优缺点 1.优点 a) 实现了MVC模式,层次结构清

  • Java的Struts2框架配合Ext JS处理JSON数据的使用示例

    最近尝试用extjs来展示树状菜单.着实花了一番功夫.树状菜单的菜单项需要动态加载,而目前版本的extjs中只支持JSON格式的数据.查了一些资 料,决定使用struts2的json-plugin.首先按照例子做了一个,但是结果就是不成功,界面上只出来了一个js中生成的root节点,不能加 载从后台生成的数据.研究后发现是数据格式有问题.使用json-plugin生成的数据格式如下: {"cls":"folder","id":10,"l

  • struts2入门Demo示例

    本文讲述了struts2入门Demo示例.分享给大家供大家参考.具体如下: 1.新建Web Project, 名称:struts2Demo; 2.建立一个用户库struts2, 包含最少的struts2的最少的6个jar文件; 其实呢, 对于MyEclipse8以上来说, 是不必须的, 因为它直接支持struts2了.不需要另外导包. 3.用Build Path将struts2的库加进来; 4.在web.xml中加入以下配置: <?xml version="1.0" encodi

  • java Struts2框架下实现文件上传功能

    本文实例为大家分享了Struts2框架实现文件上传的方法,供大家参考,具体内容如下 struts2的配置过程 (1)在项目中加入jar包 (2)web.xml中filter(过滤器)的配置 <?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:x

  • Java的Struts2框架中拦截器使用的实例教程

    1.拦截器小介 拦截器的功能类似于web.xml文件中的Filter,能对用户的请求进行拦截,通过拦截用户的请求来实现对页面的控制.拦截器是在Struts-core-2.2.3.jar中进行配置的,原始的拦截器是在struts-default.xml中配置的,里面封存了拦截器的基本使用方法. Struts2拦截器功能类似于Servlet过滤器.在Action执行execute方法前,Struts2会首先执行struts.xml中引用的拦截器,如果有多个拦截器则会按照上下顺序依次执行,在执行完所有

  • 详解Java的Struts2框架的结构及其数据转移方式

    Struts2的结构 1.为什么要使用框架? (1)框架自动完成了很多琐屑的任务 对于Struts2来说,它帮助我们方便地完成了数据类型转换.数据验证.国际化等等 Web开发中常见的任务.还有Spring中大量使用的Template模式,都是在让我们的开发 过程更加自动化.智能化.使用框架就是避免重新发明轮子,重新复制这些模板代码. 框架让我们将精力更多地放在更高级别的问题上,而不是常见工作流和基础任务上. (2)使用框架就是优雅地继承了框架背后的架构 框架背后的架构通常定义了一系列的工作流程,

  • 详解Java的Spring框架下bean的自动装载方式

    Spring容器可以自动装配相互协作bean之间的关系,这有助于减少对XML配置,而无需编写一个大的基于Spring应用程序的较多的<constructor-arg>和<property>元素. 自动装配模式: 有下列自动装配模式,可用于指示Spring容器使用自动装配依赖注入.使用<bean/>元素的autowire属性为一个bean定义中指定自动装配模式. byName模式 这种模式规定由自动装配属性名称.Spring容器在外观上自动线属性设置为byName的XML

  • 详解Java虚拟机管理的内存运行时数据区域

    详解Java虚拟机管理的内存运行时数据区域 概述 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同数据区域.这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则是依赖用户线程的启动和结束而建立和销毁. 程序计数器 程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器.在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复等基

  • 详解Java枚举类在生产环境中的使用方式

    目录 前言 使用 1.确定业务场景状态 2.定义枚举类 3.自定义查询方法 4.测试效果 总结 前言   Java枚举在项目中使用非常普遍,许多人在做项目时,一定会遇到要维护某些业务场景状态的时候,往往会定义一个常量类,然后添加业务场景相关的状态常量.但实际上,生产环境的项目中业务状态的定义大部分是由枚举类来完成的,因为更加清晰明确,还能自定义不同的方法来获取对应的业务状态值,十分方便. 以下代码均为生产环境已上线项目的代码片段,仅供参考. 使用 大体分为确定业务场景状态.定义枚举类.自定义查询

  • 详解Java实现JSONArray转Map的三种实现方式

    目录 第一种 第二种 第三种 本文只是自己常用的三种,自己总结一下,不是只有这三种,杠精走开: JSONArray数据 [ { "flagType": 1, "flagIcon": "1.jpg" }, { "flagType": 2, "flagIcon": "2.jpg" }, { "flagType": 3, "flagIcon": &quo

  • 详解java爬虫jsoup解析多空格class数据

    在使用jsoup爬取其他网站数据的时候,发现class是带空格的多选择,如果直接使用doc.getElementsByClass("class的值"),这种方法获取不到想要的数据. 1.问题描述: 在使用jsoup爬取其他网站数据的时候,发现class是带空格的多选择,如果直接使用doc.getElementsByClass("class的值"),这种方法获取不到想要的数据. 爬取网站页面结构如下: 2.其中文章列表的div为:<div class="

  • 详解Java的Struts框架中上传文件和客户端验证的实现

    文件上传 Struts 2框架提供了内置支持处理文件上传使用基于HTML表单的文件上传.上传一个文件时,它通常会被存储在一个临时目录中,他们应该由Action类进行处理或移动到一个永久的目录,以确保数据不丢失. 请注意,服务器有一个安全策略可能会禁止写到目录以外的临时目录和属于web应用的目录. 在Struts中的文件上传是通过预先定义的拦截文件上传拦截器这是可通过org.apache.struts2.interceptor.FileUploadInterceptor类的defaultStack

  • 详解Java的Struts框架中栈值和OGNL的使用

    值栈: 值栈是一个集合中的几个对象保持下列对象提供的顺序: 值栈可以通过JSP,Velocity或者Freemarker的标签.有各种不同的标签在单独的章节中,我们将学习,用于获取和设置Struts 2.0 的值栈. ValueStack的对象里面可以得到动作如下: ActionContext.getContext().getValueStack() 一旦拥有了值对象,就可以用下面的方法来操纵该对象: OGNL: 对象图形导航语言(OGNL)是一个功能强大的表达式语言是用来参考值栈上的数据和操纵

  • 详解Java的Hibernate框架中的注解与缓存

    注解 Hibernate注解是一个没有使用XML文件来定义映射的最新方法.可以在除或替换的XML映射元数据使用注解. Hibernate的注解是强大的方式来提供元数据对象和关系表的映射.所有的元数据被杵到一起的代码POJO java文件这可以帮助用户在开发过程中同时要了解表的结构和POJO. 如果打算让应用程序移植到其他EJB3规范的ORM应用程序,必须使用注解来表示映射信息,但仍然如果想要更大的灵活性,那么应该使用基于XML的映射去. 环境设置Hibernate注释 首先,必须确保使用的是JD

  • 详解Java单元测试Junit框架实例

    问题: 1.目前测试存在的问题 2.Junit注意的细节 3.Junit使用规范 4.断言 5.案例 junit(单元测试框架) 1.目前存在的问题 1.目前的测试方法如果需要测试,都需要在main方法上调用 2.目前的结果都需要我们人工比对 2.Junit 注意的细节 1.如果使用junit测试一个方法的时候,在junit窗口上显示绿色那么表示测试正确,如果显示了红色,则代表该方法测试出现了异常不通过 2.如果点击方法名.类名.包名.工程名运行junit分别测试的是对于的方法,类.包中的所有类

随机推荐