Struts2官网:http://struts.apache.org/

目前最新版本:Struts 2.3.24

Struts1已经完全被淘汰了,而Struts2是借鉴了webwork的设计理念而设计的基于MVC的框架。感兴趣的可以了解一下webwork概念,这里不做涉及。我们常说的Struts2 Spring Hibernate三大面试中常被问到的框架,其实Struts2和SpringMVC是两个独立的MVC框架。而且SpringMVC是目前最好的MVC框架(个人感觉),但是由于旧的一些项目运维需要Struts2框架的知识,故而做整理。

下载了Struts2框架之后,我们要学习什么?1.学习框架运作的整个流程;2.学习如何搭建框架;3.自己动手写一个。先来看看框架的流程,如下:

Struts2(由于Struts1表现层单一,无法跟Freemarker等技术整合),它采用拦截器的机制来处理用户的请求。

先来讲讲Struts2的原理图,如上图所示:

  1.当用户发起请求时(一个URL),服务器端的Web容器收到了请求。

  2.这时,Struts2的核心控制器FilterDispatcher接受用户发起的请求,然后判断这个请求是交给action处理?还是交给web组件来处理?如果请求的action或web组件不存在,则报404错误。在整个处理过程中,需要一个辅助对象:Action映射器(ActionMapper),ActionMapper会确定调用哪个Action(这个过程的实现是依靠ActionMapper返回一个收集Action详细信息的ActionMaping对象)

  3.然后,来交给Action来处理,它会根据struts.xml的配置信息(首先执行拦截此action的所有拦截器,然后再执行请求的action对象<在这个处理过程中需要辅助对象:Action代理(ActionProxy);配置管理器(ConfigurationManager);ActionInvocation>,),

  4.Action执行完毕之后,返回一个结果(此结果用字符串来表示),这个结果经过拦截Action的所有拦截器之后,返回给主控制器。主控制器根据此结果从配置文件中找到真正的路径,然后将请求转发给相应的视图。

  5.由视图客户端作出响应。

那么接下来我们讲讲搭建框架,最好自己亲自搭建一遍或者跟着旧的项目把流程走一遍:

首先,将下载好的Struts2中的jar包拷贝到你构建的web project中,根据上图的设计流程,我们知道Struts2是通过过滤器,将所有的请求过滤,然后分发到各个action(action其实就是一个类,就是一个POJO类),然后根据返回的String字符串,查找Struts2的配置文件,找到应该返回到哪个页面,即可。具体如下:

拷贝jar包到项目中,

再来配置web.xml:

  <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>

再来配置struts.xml配置文件(struts.xml要在src目录下)

<struts>
<package name="default" extends="struts-default" namespace="/">
</package>
</struts>

再来创建Action(它就是一个POJO类)

public class HelloAction{
public String execute(){
System.out.println("Hello Struts2!");
     return "success";
}
}

再在struts.xml中配置action和返回结果集:

<struts>
<package name="default" extends="struts-default" namespace="/">
     <action name="hello" class="com.hp.it.HelloAction">
       <result name="success">/hello.jsp</result>
     </action>
</package>
</struts>

注意,这里的<action>标签中的name属性要与url路径中的过滤到的string相对应;class属性要与你写的action类相对应(注意要有包名).<result>标签(result是结果集)中的那么属性要与action中返回的string字符串相对应,返回的路径是WEB-INF/下的hello.jsp,即http://localhost:8080/projectname/hello.action

再写前台的hello.jsp:

<html>
<head></head>
<body><h1>Hello</h1></body>
</html>

整个过程就是这样的,那么问题来了:当有多个请求的时候,要写很多execute()方法么?excute方法是默认的哦!

当然不是的,我们注意到,在struts.xml的配置文件中,标签<action>中除了name class 属性,还有一个method属性,很显然,这个标签可以指定我们映射到的方法。这个当然就是几个简单的扩张了。相应的struts.xml的配置文件:

    <package name="default" extends="struts-default" namespace="/">
     <action name="addUser" class="com.hp.it.UserAction" method="addUser">
       <result name="success">/WEB-INF/user/addUser.jsp</result>
     </action>
     <action name="updateUser" class="com.hp.it.UserAction" method="updateUser">
      <result name="success">/WEB-INF/user/updateUser.jsp</result>
    </action>
  </package>

相应的UserAction中如下:

public class UserAction{
public String addUser(){
System.out.println("addUser");
     return "success";
}
public String updateUser(){
System.out.println("updateUser");
     return "success";
}
}

那么问题又来了,一个方法写一个action,那么这样会导致配置文件中的action量很大。一种解决办法是可以在<package>标签的平行目录下,增加标签如下所示:通过增加<include>标签来导入其他的xml文件。

<struts>
<package name="default" extends="struts-default" namespace="/">
     <action name="hello" class="com.hp.it.HelloAction">
       <result name="success">/hello.jsp</result>
     </action>
</package>
<include file="otherStruts.xml">
</struts>

引入其他的xml文件,固然可以,但是依然无法解决action配置文件过多的问题,在整理Struts2提供了两种解决方案,如下:

第一种URL:

http://localhost:8080/projectname/User!add
http://localhost:8080/projectname/User!update
http://localhost:8080/projectname/User!list

如上三个URL所示,User是Action类名,对应UserAction类:后面的!+方法名。

第二种URL:

http://localhost:8080/projectname/User?method:add
http://localhost:8080/projectname/User?method:update
http://localhost:8080/projectname/User?method:list

如上三个URL所示,User是Action类名,对应UserAction类:后面的?+method:方法名。

再接着,我们看看我们的struts.xml该如何来写?

<package name="default" extends="struts-default" namespace="/">
     <action name="user" class="com.hp.it.UserAction" >
       <result name="add">/WEB-INF/user/addUser.jsp</result>
     </action>
     <action name="user" class="com.hp.it.UserAction" >
      <result name="update">/WEB-INF/user/updateUser.jsp</result>
     </action>
<action name="user" class="com.hp.it.UserAction" >
      <result name="list">/WEB-INF/user/listUser.jsp</result>
     </action>
  </package>

注意了啊,这里的action属性name是url中那个user,即是对应着UserAction.方法是由调用的时候来决定的看具体使用谁。

同样的,我们来看看在UserAction中应该这样来写:

public class UserAction{
public String addUser(){
System.out.println("addUser");
     return "add";
}
public String updateUser(){
System.out.println("updateUser");
     return "update";
}
public String listUser(){
System.out.println("listUser");
     return "list";
}
}

这个方法虽然减少了action的配置,但是增加了大量的结果集的配置。所有问题来了,有没有解决这个问题的方法呢?

我们可以通过通配符来解决这个问题,这儿有一个核心思想:(约定优于配置),如下:

<action name="*_*" class="com.hp.it.action.{1}Action" method="{2}">
<result>/WEB-INF/{1}/{2}.jsp</result>
</action>

这里要强调一下,标签<result>默认的属性是 name="success"。约定优于配置,那么我们的约定是对于URl来说,它的格式应该如下面这样来向服务器端发出请求:

http://localhost:8080/projectname/User_add
http://localhost:8080/projectname/User_update
http://localhost:8080/projectname/User_list

上面这种对于URL的约定,直接可以使用通配符*_*来对它过滤。大大简化了配置文件的大小。(这种情况下,注意大小写字母)

前面这些都是在说,服务器端的跳转,那么客户端的跳转怎么来实现呢?比如说,我们的User类在add完成之后,往往要跳转到它的list页面,这时候应该这样来配置:

<action name="*_*" class="com.hp.it.action.{1}Action" method="{2}">
<result>/WEB-INF/{1}/{2}.jsp</result>
<result type="redirect" name="r_list">/{1}_list.action</result>
</action>

大概看这个的含义就是说,当name=r_list的时候,进行重定向,并且重定向到{1}_list.action。相应的UserAction应该这么写

public class UserAction{
public String addUser(){
System.out.println("addUser");
     return "r_list";
}
public String updateUser(){
System.out.println("updateUser");
     return "update";
}
public String listUser(){
System.out.println("listUser");
     return "list";
}
}

如果按上述方法来做,是不是效果会更好呢。但是我们通常看到的URL,往往很少再其屁股后面加".action"这个后缀,其实这个是可以

上面这个配置语句配置了对于.action的请求都进行过滤,同样也可以我们自己设定,如下:

<constant name="struts.action.extension" value="action,do,zxg" />

如上这种,当以.action;.do;.zxg的URL路径访问的时候,都会进入filter来过滤的。

*************************************************************************************************************************

接下来,我们再看看Struts2中是如何对参数传值做处理的(了解地址和类的对应关系;了解数据的通信(参数)的)。这部分是很关键的,而且一定要掌握清楚,不要跟SprigMVC相混淆。这段逻辑如果错误的话,调试代码的时候,介于前端和后端之间,断点加了也进不去,非常不好调控,所以在掌握原理的时候必须要掌握的清清楚楚的。那么接下来讲讲struts2传递参数的三种方案,分别如下:首先给你一个URL

http://16.158.70.172:8080/wstax-admin/report/assetsTransactionsByRegionTime?startTime=2015-06-01&endTime=2015-06-10&_=1434004102399

如上图,分析这个URL如下,前面的16.158.70.172是IP地址,相当于localhost,相当于127.0.0.1.然后是项目名称wstax-admin,然后是路径名称,然后我们看这个action name="assetsTransactionsByRegionTime"其后传过来三个参数,startTime和endTime分别是起始和截止时间,然后是后面的_1434004102399这个字符串,这是由于get请求的时候,加一个由时间随机生成的字符串,这样保证了每个url不同,这样每次就不会再去取缓存中的东西,而是去服务器上的东西,保证每次取的资源都是更新过后最新的资源。现在我们拦截了这个请求,传参的方法是在Action中,定义一个跟参数完全相同名字的变量,写getter和setter方法。如下:

@Controller("dailyMonitoringAction")
@Scope("prototype")
public class DailyMonitoringAction extends BaseAction {
private static final long serialVersionUID = -2065341145635610669L;
@Autowired
private IDailyMonitoringService dailyMonitoringService;
private String startTime = null;
private String endTime = null;
public String getStartTime() {
return startTime;
}
public void setStartTime(String startTime) {
this.startTime = startTime;
}
public String getEndTime() {
return endTime;
}
public void setEndTime(String endTime) {
this.endTime = endTime;
}
}

然后你看我们的Action中,

/*SpringMVC传递参数和Struts传递参数 不同; Struts会调用setter方法来将值返回*/
public String loadAssetsTransactionsByRegionTime() {
lineVM = new LineChartVM();
lineVM.setTitle("Assets Transactions By Region");
lineVM.setyAxisName("Transactions");
Map<String, Map<String, Double>> assetRegionMap = dailyMonitoringService
.loadAssetRegionTransactionTime(startTime, endTime);
lineVM.setCategories(new ArrayList<String>(assetRegionMap.keySet()));
Map<String, List<Double>> seriesMap = pivotingMap(assetRegionMap, 0D);
List<ChartSerieVM> seriesList = new ArrayList<ChartSerieVM>();
for (String key : seriesMap.keySet()) {
ChartSerieVM chartSerieVM = new ChartSerieVM();
chartSerieVM.setName(key);
chartSerieVM.setData(seriesMap.get(key));
seriesList.add(chartSerieVM);
}
lineVM.setSeries(seriesList);
return SUCCESS;
}

你看我们的startTime和endTime是直接使用的,没有在函数的参数中写,而且定义的时候我们定义的是private String endTime = null;但是使用的时候,值就这么直接传递了进来,就是这么神奇啊。另外两种参数传递方法是ActionContext.getContext().put("startTime","2015-06-01");ActionContext.getContext.put("endTime","2015-06-10");(其中put进去的一对对的键值对)和通过Servlet的API来传值(ServletActionContext.getRequest.setAttribute("startTime","2015-06-01");ServletActionContext.getRequest.setAttribute("endTime","2015-06-10");)

前台在展现数据时候,可以有如下几种方法:

1.${startTime} ${endTime}直接取值.

2.通过struts2的标签库<%@taglib prefix="s" uri="/struts-tags"%> 引入struts2jar包中的一个tags标签库,然后使用如下方式:

<s:property value="#startTime">
<s:property value="#endTime">

就可以将数据展现出来。(注意这里的value中的变量名前面要加‘#’号。)

备注:对于ServletActionContext.getRequest.setAttribute("endTime","2015-06-10");这种取值方式,在前台展示的时候需要这样来用,如下:

<s:property value="#request.endTime">

************************************************************************************************************************************

接下来我们看看Struts中最核心的知识点:

鸣谢:

参考博客(http://www.cnblogs.com/suxiaolei/archive/2011/10/28/2228063.html)

Struts 2.3.24源码解析+Struts2拦截参数,处理请求,返回到前台过程详析的更多相关文章

  1. Okhttp3源码解析(5)-拦截器RetryAndFollowUpInterceptor

    ### 前言 回顾: [Okhttp的基本用法](https://www.jianshu.com/p/8e404d9c160f) [Okhttp3源码解析(1)-OkHttpClient分析](htt ...

  2. Okhttp3源码解析(4)-拦截器与设计模式

    ### 前言 回顾: [Okhttp的基本用法](https://www.jianshu.com/p/8e404d9c160f) [Okhttp3源码解析(1)-OkHttpClient分析](htt ...

  3. 源码解析Grpc拦截器(C#版本)

    前言 其实Grpc拦截器是我以前研究过,但是我看网上相关C#版本的源码解析相对少一点,所以笔者借这篇文章给大家分享下Grpc拦截器的实现,废话不多说,直接开讲(Grpc的源码看着很方便,包自动都能还原 ...

  4. jquery源码解析:jQuery延迟对象Deferred(工具方法)详解2

    请接着上一课继续看. $.Deferred()方法中,有两个对象,一个是deferred对象,一个是promise对象. promise对象有以下几个方法:state,always,then,prom ...

  5. jquery源码解析:jQuery延迟对象Deferred(工具方法)详解1

    请先看上一课的回调对象.Deferred是通过extend添加到jQuery中的工具方法.如下所示: jQuery.extend({ Deferred: function( func ) { }, w ...

  6. jQuery 源码解析(二十八) 样式操作模块 scrollLeft和scrollTop详解

    scrollLeft和scrollTop用于获取/设置滚动条的,如下: scrollLeft(val) ;读取或设置整个页面的水平滚动条距离 scrollTop(val) ;读取或设置整个页面的垂直滚 ...

  7. Deeplab v3+中的骨干模型resnet(加入atrous)的源码解析,以及普通resnet整个结构的构建过程

    加入带洞卷积的resnet结构的构建,以及普通resnet如何通过模块的组合来堆砌深层卷积网络. 第一段代码为deeplab v3+(pytorch版本)中的基本模型改进版resnet的构建过程, 第 ...

  8. axios 源码解析(下) 拦截器的详解

    axios的除了初始化配置外,其它有用的应该就是拦截器了,拦截器分为请求拦截器和响应拦截器两种: 请求拦截器    ;在请求发送前进行一些操作,例如在每个请求体里加上token,统一做了处理如果以后要 ...

  9. 谷歌BERT预训练源码解析(二):模型构建

    目录前言源码解析模型配置参数BertModelword embeddingembedding_postprocessorTransformerself_attention模型应用前言BERT的模型主要 ...

随机推荐

  1. 【SVN多用户开发】代码冲突&解决办法

    SVN是一款集中式的代码存储工具,可以帮助多个用户协同开发同一应用程序. 但是SVN不能完全代替人工操作,有时也需要程序员自己进行沟通确认有效的代码. 下面就简单的看一下,常见的代码冲突以及解决方法. ...

  2. lua的table表处理 及注意事项

    lua,一款很轻量级很nice很强大的脚本语言,做为lua中使用最为频繁的table表,在使用之时还是有颇多的好处与坑的: 下面是大牛 云风的一片关于lua table的blog,可使得对lua ta ...

  3. Atitit 项目的主体设计与结构文档 v3

    Atitit 项目的主体设计与结构文档 v3 1. 实现的目标2 1.1. cross device跨设备(pc 手机 平板)作为规划2 1.2. 企业级Java体系与开发语言2 1.3. 高扩展性, ...

  4. WPF入门教程系列九——布局之DockPanel与ViewBox(四)

    七. DockPanel DockPanel定义一个区域,在此区域中,您可以使子元素通过描点的形式排列,这些对象位于 Children 属性中.停靠面板其实就是在WinForm类似于Dock属性的元 ...

  5. 使用python原生的方法实现发送email

    使用python原生的方法实现发送email import smtplib from email.mime.text import MIMEText from email.mime.multipart ...

  6. Android基于mAppWidget实现手绘地图(十)–在放大前后执行一些操作

    为了在放大前后执行一些操作,你需要添加MapEventsListener 的实例到MapWidget,使用MapWidget.removeMapEventsListener移除该监听器. mapWid ...

  7. FindBugs使用

    FindBugs简介: FindBugs是一个开源的eclipse 代码检查工具,是一种白盒静态自动化测试工具: 它可以简单高效全面地帮助我们发现程序代码中存在的bug,bad smell,以及潜在隐 ...

  8. [Azure附录]1.在Windows Server 2012中安装Active Directory域服务

    <Windows Azure Platform 系列文章目录> 1.登陆Windows Server 2012,打开服务器管理器,选择"添加角色和功能" 2.在&quo ...

  9. Elasticsearch入门介绍

    ES是一个高扩展的.开源的.全文检索的搜索引擎,它提供了近实时的索引.搜索.分析功能. ES文档翻译与总结参考:ES知识汇总 应用场景 1 它提供了强大的搜索功能,可以实现类似百度.谷歌等搜索. 2 ...

  10. js和jquery常用方法

    1.0 编码 Encode,Decode function htmlEncode(value) { return $('<div>').text(value).html(); } func ...