细谈Struts2 详解

Struts2 专栏收录该内容
5 篇文章 0 订阅


(一)自己实现struts2框架



 Struts最早是作为Apache Jakarta项目的组成部分,项目的创立者希望通过对该项目的研究,改进和提高JavaServer Pages Servlet标签库以及面向对象的技术水准。最初的struts1.x很快在企业开发中流行了起来,与此同时,当时还有一个非常的优秀的web开发框架诞生,那就是webwork,但webwork没有像struts1那么幸运,没有得到流行,但webwork简洁、灵活功能强大等优点绝不输于当时流行的strut1.x。当然struts1开发人员不是也没有意识到这一点。于是struts WebWork得到了结合,webwork算是利用struts的名气来发展自己吧,于是struts2诞生了。

Struts2概述

    Struts 2Struts的下一代产品,是在 struts WebWork的技术基础上进行了合并的全新的Struts 2框架。其全新的Struts 2的体系结构与Struts 1的体系结构的差别巨大。Struts 2WebWork为核心,采用拦截器的机制来处理用户的请求,这样的设计也使得业务逻辑控制器能够与Servlet API完全脱离开,所以Struts 2可以理解为WebWork的更新产品。虽然从Struts 1Struts 2有着太大的变化,但是相对于WebWorkStruts 2只有很小的变化。由于struts1现在开发中很少在用到,所以我们直接进入struts2的学习,但以前的项目中还是大多数保留着struts1的应用。由于struts是基于mvc模式的框架,所以我们学习struts的第一步就是开发自己的基于MVC的框架

首先看一下一个MVC的流程图的例子:

       就像图中例子,在视图层addJsp中写一个提交两个数据的表单,表单提交给控制器,在控制器中通过它所提交的uri获得表单所要提交的action,然后把请求交给action,然后在action中调用业务逻辑的方法进行逻辑运算,获得结果,把结果保存起来,然后,把所有返回的界面作为返回结果返回给控制器,然后控制器根据返回的界面的字符串选择转发到该界面

下面我们就用程序,把这个流程实现出来:

1.首先要把表单界面写出来:add.jsp

[html]  view plain  copy
  1. <form action="add.action" method="post"><div align="center"><font color="#8000ff">  
  2.   
  3.      </font><font size="5" color="#8000ff"><strong>加法器实现</strong></font><br/>  
  4.   
  5.      </div><table align="center">  
  6.   
  7.      <tr>  
  8.   
  9.      <td>第一个数:</td>  
  10.   
  11.      <td><input type="text" name="firstNmb"/></td>  
  12.   
  13.      </tr>  
  14.   
  15.      <tr>  
  16.   
  17.      <td>第二个数:</td>  
  18.   
  19.      <td><input type="text" name="secondNmb"/></td>  
  20.   
  21.      </tr>  
  22.   
  23.      <tr align="center">  
  24.   
  25.      <td colspan="2"><input type="submit" value=" 求和"/>    <input type="reset"value="重置"></td>  
  26.   
  27.      </tr>  
  28.   
  29.      </table>  
  30.   
  31.     </form>  


2.创建控制器,其实这里的控制器就是一个servlet,这里我们给规定凡是请求后缀是.action的都提交到这个控制器里,controller.java:

[html]  view plain  copy
  1. public void doPost(HttpServletRequest request, HttpServletResponse response)  
  2.   
  3. throws ServletException, IOException {  
  4.   
  5. String path=request.getRequestURI();  
  6.   
  7. String realPath=path.substring(path.lastIndexOf("/")+1, path.lastIndexOf("."));  
  8.   
  9. Action action=null;  
  10.   
  11. String path2=null;  
  12.   
  13. if("add".equals(realPath)){  
  14.   
  15. action=new AddAction();  
  16.   
  17. path2=action.execute(request, response);  
  18.   
  19. }  
  20.   
  21.           .........  
  22.   
  23.          If(....){  
  24.   
  25.                  .......  
  26.   
  27.               }  
  28.   
  29. request.getRequestDispatcher(path2).forward(request, response);  
  30.   
  31. }  


因为控制器是一个servlet,所以在web.xml中要对他进行配置:

[html]  view plain  copy
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2.   
  3. <web-app version="2.5"   
  4.   
  5. xmlns="http://java.sun.com/xml/ns/javaee"   
  6.   
  7. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   
  8.   
  9. xsi:schemaLocation="http://java.sun.com/xml/ns/javaee   
  10.   
  11. http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">  
  12.   
  13.   <servlet>  
  14.   
  15.     <servlet-name>Controller</servlet-name>  
  16.   
  17.     <servlet-class>zxj.struts2.servlet.Controller</servlet-class>  
  18.   
  19.   </servlet>  
  20.   
  21.   <servlet-mapping>  
  22.   
  23.     <servlet-name>Controller</servlet-name>  
  24.   
  25.     <url-pattern>*.action</url-pattern>  
  26.   
  27.   </servlet-mapping>  
  28.   
  29. </web-app>  


      下面来看一下action里面应该写的内容,由于一直以来都提倡面向接口编程,并且面向接口编程也能很好的体现java的可扩展性,所以我们对所有的action提供一个共同的接口:action.java

[html]  view plain  copy
  1. public interface Action {  
  2.   
  3. public String result(HttpServletRequest request,HttpServletResponse response);  
  4.   
  5. }  


      

下面是具体的action实现:addaction.java:其中具体的业务逻辑调用的add方法就是两个数相加,这里就不贴代码了:

 

[java]  view plain  copy
  1. public String execute(HttpServletRequest request,  
  2.   
  3. HttpServletResponse response) {  
  4.   
  5. double i=Double.parseDouble(request.getAttribute("firstNmb").toString());  
  6.   
  7. double n=Double.parseDouble(request.getAttribute("secondNmb").toString());  
  8.   
  9. Calculator c=new Calculator();  
  10.   
  11. double result=c.add(i, n);  
  12.   
  13. request.setAttribute("result", result);  
  14.   
  15. return "add_result.jsp";  
  16.   
  17. }  
  18.   
  19. }  

 

        这些就是我们自己写的mvc的基本框架,当然这里面有很多不足的地方,这里只是为了演示基于mvc框架的基本架构,具体细节都可以细化和扩展性的实现,比如控制器里面的选择哪个action这个可以用配置文件来实现的,基本思路:在控制器中获得所请求action的前缀名,然后去解析所配置的文件,在然后拿着这个前缀名去找配置文件中相符的action所对应的类,然后在利用反射执行对应类的方法,根据然后在执行完action后,获得结果,然后从配置中获得获该结果对应的界面,这样就可以很好的体现了这个程序的可扩展性了。

      到这里我相信大家应该对基于mvc的框架的执行流程有一定的了解了,相信大家一定对学习struts2框架迫不接待了,那大家就等待着下一篇博客:细谈struts2初识struts2框架



(二)开发第一个struts2的实例



 上面已经带大家对基于mvc业务流程熟悉了一下,现在我们就用对mvc实现最好的框架struts2来开发一个应用实例。虽然现在MyEclipse8.5以上版本已经开始支持Struts2但为了我们能更好的熟悉开发struts2的业务流程,现在我们还是手动去搭配环境。首先我们需要到struts.apache.org去下载struts-2.2.3-all包。现在最高版本应该达到2.3了。要想正常使用Struts2,至少需要如下五个包(可能会因为Struts2的版本不同,包名略有差异,但包名的前半部是一样的)。

    struts2-core-2.0.11.1.jar

    xwork-2.0.4.jar

    commons-logging-1.0.4.jar

    freemarker-2.3.8.jar

ognl-2.6.11.jar

注:貌似好像一些高版本的还需要加入一些其他jar包,如下图所示:

  

 好了,jar包加入之后,我们下一步开始搭配配置环境了。很多同学可能会有这样的疑问,为什么我提交的请求能到struts.xml去找对应的action呢??至少我刚开始学习的时候有这么个疑问。现在答案即可以为大家揭晓了,因为struts2的核心是拦截器,一切请求都要经过拦截器才转发给所对应的action的。Struts2中第一个拦截请求的就是org.apache.struts2.dispatcher.FilterDispatcher这个拦截器(下一篇博客我们即将对这个拦截器的源码进行分析),拦截器对请求进行一些处理之后然后去struts.xml寻找对应的action。我们一起来看一下web.xml的配置:


[html]  view plain  copy
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <web-app version="2.4"   
  3.     xmlns="http://java.sun.com/xml/ns/j2ee"   
  4.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   
  5.     xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee   
  6.     http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">  
  7.     <filter>  
  8.     <filter-name>struts2</filter-name>  
  9.     <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>  
  10.     </filter>  
  11.     <filter-mapping>  
  12.     <filter-name>struts2</filter-name>  
  13.     <url-pattern>*.action</url-pattern>  
  14.     </filter-mapping>  
  15.   <welcome-file-list>  
  16.     <welcome-file>index.jsp</welcome-file>  
  17.   </welcome-file-list>  
  18. </web-app>  

      在struts2官方提供的文档中要求,在服务器class文件夹下建立struts.xml文件。由于在web项目部署到服务器上,开启服务器对web项目进行编译时会自动把src文件夹下的文件加载到服务器class文件夹下,所以我们直接在src下面建立struts.xml文件,具体struts.xml配置如下:

[html]  view plain  copy
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <!DOCTYPE struts PUBLIC  
  3.     "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"  
  4.     "http://struts.apache.org/dtds/struts-2.0.dtd">  
  5. <struts>  
  6. <constant name="struts.i18n.encoding" value="utf-8" />  
  7.     <package name="struts2" extends="struts-default">  
  8.         <action name="" class="">  
  9.             <result name=""></result>  
  10.             <result name=""></result>  
  11.         </action>  
  12.     </package>  
  13. </struts>     

注:上述代码具体意义:

1.<constant>标签主要是用来修改struts.properties配置文件信息,namevalue分别相当于struts.properties文件中的namevalue

2.<package>主要是作为分包作用,比如一个项目分好几个模块,这里可以每一个模块分配一个包,一个struts.xml文件中可以出现多个<package>标签,这里一定要有extends="struts-default"因为struts的核心拦截器都配置在struts-default包中,如果没有这个,所有请求都不会请求到

3.一个<action>标签对应一个action类,主要是通过action标签中的去寻找class,然后执行对应的classAction标签里有一个一个method属性,他可以指定执行action中的哪个方法

下面我们就开始以登录为例来写一下struts2开发的视图层:

Login.jsp

[html]  view plain  copy
  1. <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>  
  2. <%  
  3. String path = request.getContextPath();  
  4. String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";  
  5. %>  
  6. <html>  
  7.   <head>  
  8.   </head>  
  9.     <body>  
  10.   <form action="LoginAction.action">  
  11.    <input type="text" name="username"/><br/>  
  12.    <input type="password" name="password"/><br/>  
  13.    <input type="submit" value="提交"/>  
  14.    </form>  
  15.   </body>  
  16. </html>  

好了,视图层写完了,下面就要开始写核心action了,由于业务逻辑层就是判断用户名和密码是否与固定的admin123456相等,所以本程序只是为了测试,就不再单独抽出来了,直接写在action里面了LoginAction.java

[java]  view plain  copy
  1. package com.bzu.action;  
  2. public class LoginAction {  
  3.       
  4.     private String username;  
  5.     private String password;  
  6.     public String getUsername() {  
  7.         return username;  
  8.     }  
  9.     public void setUsername(String username) {  
  10.         this.username = username;  
  11.     }  
  12.     public String getPassword() {  
  13.         return password;  
  14.     }  
  15.     public void setPassword(String password) {  
  16.         this.password = password;  
  17.     }  
  18.     public String execute(){  
  19.           
  20.         if(username.equals("admin")&&password.equals("123456"))  
  21.             return "success";  
  22.         return "fail";  
  23.     }  
  24. }  


从上面的程序可以看出,我们在action中要把form表单中数据都以私有变量的形式定义出来,然后在提供对应的setget方法。很多同学可能在这又有疑问了。为什么给他提供setget方法,form表单中的数据就可以设置到对应的属性上呢?为什么他会默认的去执行execute方法呢?为什么把配置文件中action标签对应的method属性修改后就可以执行新设置的方法呢?呵呵,在这在卖个关子,在接下来的博客中,会为大家一一把这些疑问解决。

Action写完之后,我们就可以把struts.xml对应的写上了,本程序完整的struts.xml

[html]  view plain  copy
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <!DOCTYPE struts PUBLIC  
  3.     "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"  
  4.     "http://struts.apache.org/dtds/struts-2.0.dtd">  
  5. <struts>  
  6. <constant name="struts.i18n.encoding" value="utf-8" />  
  7.     <package name="struts2" extends="struts-default">  
  8.         <action name="LoginAction" class="com.bzu.action.LoginAction">  
  9.             <result name="success">success.jsp</result>  
  10.             <result name="fail">fail.jsp</result>  
  11.         </action>  
  12.     </package>  
  13. </struts>     
  14.    

 

对应的success.jspfai.jsp没什么内容,就是显示成功和失败几个字。

好了,到此,我们的第一个struts2的应用程序就写完了,下面我们一起来看一下运行结果:

————》




(三)struts2拦截器源码分析




 通过前面我们知道了struts2实现请求转发和配置文件加载都是拦截器进行的操作,这也就是为什么我们要在web.xml配置struts2的拦截器的原因了。我们知道,在开发struts2应用开发的时候我们要在web.xml进行配置拦截器org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter在一些老版的一般配置org.apache.struts2.dispatcher.FilterDispatcher,不知道大家刚开始学的时候有没有这个疑问,为什么通过这个拦截器我们就可以拦截到我们提交的请求,并且一些配置文件就可以得到加载呢?不管你有没有,反正我是有。我想这个问题的答案,我们是非常有必要去看一下这个拦截器的源码去找。

打开StrutsPrepareAndExecuteFilter拦截器源码我们可以看出以下类的信息

属性摘要:

Protected        List<Pattern>              excludedPatterns 

protected      ExecuteOperations      execute 
protected      
PrepareOperations       prepare

         我们可以看出StrutsPrepareAndExecuteFilter与普通的Filter并无区别,方法除继承自Filter外,仅有一个回调方法,第三部分我们将按照Filter方法调用顺序,init>doFilter>destroy顺序地分析源码。

提供的方法:

destroy()    

  继承自Filter,用于资源释放

doFilter(ServletRequest req, ServletResponse res, FilterChain chain)

   继承自Filter,执行方法

init(FilterConfig filterConfig)  

继承自Filter,初始化参数

 postInit(Dispatcher dispatcher, FilterConfig filterConfig) 

  Callback for post initialization(一个空的方法,用于方法回调初始化)

下面我们一一对这些方法看一下:

1.init方法:我们先整体看一下这个方法:

 

[java]  view plain  copy
  1. public void init(FilterConfig filterConfig) throws ServletException {  
  2.   
  3. InitOperations init = new InitOperations();  
  4.   
  5. try {  
  6.   
  7. //封装filterConfig,其中有个主要方法getInitParameterNames将参数名字以String格式存储在List中  
  8.   
  9. FilterHostConfig config = new FilterHostConfig(filterConfig);  
  10.   
  11. // 初始化struts内部日志  
  12.   
  13. init.initLogging(config);  
  14.   
  15. //创建dispatcher ,并初始化,这部分下面我们重点分析,初始化时加载那些资源  
  16.   
  17. Dispatcher dispatcher = init.initDispatcher(config);  
  18.   
  19. init.initStaticContentLoader(config, dispatcher);  
  20.   
  21. //初始化类属性:prepare 、execute  
  22.   
  23. prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher);  
  24.   
  25. execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher);  
  26.   
  27. this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);  
  28.   
  29. //回调空的postInit方法  
  30.   
  31. postInit(dispatcher, filterConfig);  
  32.   
  33. finally {  
  34.   
  35. init.cleanup();  
  36.   
  37. }  
  38.   
  39. }  


      首先开一下FilterHostConfig 这个封装configfilter的类:  这个类总共不超过二三十行代码getInitParameterNames是这个类的核心,将Filter初始化参数名称有枚举类型转为Iterator。此类的主要作为是对filterConfig 封装。具体代码如下:

 

[java]  view plain  copy
  1. public Iterator<String> getInitParameterNames() {  
  2.   
  3.        return   MakeIterator.convert(config.getInitParameterNames());  
  4.   
  5.    }  

下面咱接着一块看Dispatcher dispatcher = init.initDispatcher(config);这是重点,创建并初始化Dispatcher  ,看一下具体代码:

   

[html]  view plain  copy
  1. public Dispatcher initDispatcher( HostConfig filterConfig ) {  
  2.   
  3.        Dispatcher dispatcher = createDispatcher(filterConfig);  
  4.   
  5.        dispatcher.init();  
  6.   
  7.        return dispatcher;  
  8.   
  9.    }     
  10.   
  11.   
  12. span style="font-size:18px;"><span style="color:#000000;background:rgb(255,255,255);">     </span><span style="color:#000000;background:rgb(255,255,255);"><span style="color:#cc0000;"><strong>创建<span style="font-family:Verdana;">Dispatcher</span><span style="font-family:'宋体';">,会读取 </span><span style="font-family:Verdana;">filterConfig </span><span style="font-family:'宋体';">中的配置信息,将配置信息解析出来,封装成为一个</span><span style="font-family:Verdana;">Map</span></strong></span><span style="font-family:'宋体';">,然后</span></span><span style="color:#000000;background:rgb(255,255,255);">根据</span><span style="color:#000000;background:rgb(255,255,255);">servlet<span style="font-family:'宋体';">上下文和参数</span><span style="font-family:Verdana;">Map</span><span style="font-family:'宋体';">构造</span><span style="font-family:Verdana;">Dispatcher </span><span style="font-family:'宋体';"></span></span></span>  
[java]  view plain  copy
  1. private Dispatcher createDispatcher( HostConfig filterConfig ) {  
  2.   
  3. Map<String, String> params = new HashMap<String, String>();  
  4.   
  5. for ( Iterator e = filterConfig.getInitParameterNames(); e.hasNext(); ) {  
  6.   
  7. String name = (String) e.next();  
  8.   
  9. String value = filterConfig.getInitParameter(name);  
  10.   
  11. params.put(name, value);  
  12.   
  13. }  
  14.   
  15. return new Dispatcher(filterConfig.getServletContext(), params);  
  16.   
  17. }  


      Dispatcher构造玩以后,开始对他进行初始化,加载struts2的相关配置文件,将按照顺序逐一加载:default.propertiesstruts-default.xml,struts-plugin.xml,struts.xml……我们一起看看他是怎么一步步的加载这些文件的 dispatcherinit()方法:

   

[html]  view plain  copy
  1. public void init() {  
  2.   
  3.   
  4.     if (configurationManager == null) {  
  5.   
  6.     configurationManager = createConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME);  
  7.   
  8.     }  
  9.         try {  
  10.   
  11.             init_DefaultProperties(); // [1]  
  12.   
  13.             init_TraditionalXmlConfigurations(); // [2]  
  14.   
  15.             init_LegacyStrutsProperties(); // [3]  
  16.   
  17.             init_CustomConfigurationProviders(); // [5]  
  18.   
  19.             init_FilterInitParameters() ; // [6]  
  20.   
  21.             init_AliasStandardObjects() ; // [7]  
  22.   
  23.   
  24.             Container container = init_PreloadConfiguration();  
  25.   
  26.             container.inject(this);  
  27.   
  28.             init_CheckConfigurationReloading(container);  
  29.   
  30.             init_CheckWebLogicWorkaround(container);  
  31.   
  32.   
  33.             if (!dispatcherListeners.isEmpty()) {  
  34.   
  35.                 for (DispatcherListener l : dispatcherListeners) {  
  36.   
  37.                     l.dispatcherInitialized(this);  
  38.   
  39.                 }  
  40.   
  41.             }  
  42.   
  43.         } catch (Exception ex) {  
  44.   
  45.             if (LOG.isErrorEnabled())  
  46.   
  47.                 LOG.error("Dispatcher initialization failed", ex);  
  48.   
  49.             throw new StrutsException(ex);  
  50.   
  51.         }  
  52.   
  53.     }  


下面我们一起来看一下【1】,【2】,【3】,【5】,【6】的源码,看一下什么都一目了然了:

  • DefaultPropertiesProvider

1.这个方法中是将一个DefaultPropertiesProvider对象追加到ConfigurationManager对象内部的ConfigurationProvider队列中。 DefaultPropertiesProviderregister()方法可以载入org/apache/struts2/default.properties中定义的属性。

[html]  view plain  copy
  1. try {  
  2.   
  3.            defaultSettings = new PropertiesSettings("org/apache/struts2/default");  
  4.   
  5.        } catch (Exception e) {  
  6.   
  7.            throw new ConfigurationException("Could not find or error in org/apache/struts2/default.properties", e);  
  8.   
  9.        }  


  • init_TraditionalXmlConfigurations()方法

2. 调用init_TraditionalXmlConfigurations()方法,实现载入FilterDispatcher的配置中所定义的config属性。 如果用户没有定义config属性,struts默认会载入DEFAULT_CONFIGURATION_PATHS这个值所代表的xml文件。它的值为"struts-default.xml,struts-plugin.xml,struts.xml"。也就是说框架默认会载入这三个项目xml文件。如果文件类型是XML格式,则按照xwork-x.x.dtd模板进行读取。如果,是Struts的配置文件,则按struts-2.X.dtd模板进行读取。

 private static final String DEFAULT_CONFIGURATION_PATHS = "struts-default.xml,struts-plugin.xml,struts.xml";

  • LegacyPropertiesConfigurationProvider

3.创建一个LegacyPropertiesConfigurationProvider类,并将它追加到ConfigurationManager对象内部的ConfigurationProvider队列中。LegacyPropertiesConfigurationProvider类载入struts.properties中的配置,这个文件中的配置可以覆盖default.properties中的。其子类是DefaultPropertiesProvider

  • init_CustomConfigurationProviders()

5.init_CustomConfigurationProviders()此方法处理的是FilterDispatcher的配置中所定义的configProviders属性。负责载入用户自定义的ConfigurationProvider

[html]  view plain  copy
  1. String configProvs = initParams.get("configProviders");  
  2.   
  3.         if (configProvs != null) {  
  4.   
  5.             String[] classes = configProvs.split("\\s*[,]\\s*");  
  6.   
  7.             for (String cname : classes) {  
  8.   
  9.                 try {  
  10.   
  11.                     Class cls = ClassLoaderUtils.loadClass(cname, this.getClass());  
  12.   
  13.                     ConfigurationProvider prov = (ConfigurationProvider)cls.newInstance();  
  14.   
  15.                     configurationManager.addConfigurationProvider(prov);  
  16.   
  17.                 } catch (InstantiationException e) {  
  18.   
  19.                     throw new ConfigurationException("Unable to instantiate provider: "+cname, e);  
  20.   
  21.                 } catch (IllegalAccessException e) {  
  22.   
  23.                     throw new ConfigurationException("Unable to access provider: "+cname, e);  
  24.   
  25.                 } catch (ClassNotFoundException e) {  
  26.   
  27.                     throw new ConfigurationException("Unable to locate provider class: "+cname, e);  
  28.   
  29.                 }  
  30.   
  31.             }  
  32.   
  33.         }  

  • init_FilterInitParameters()

6.init_FilterInitParameters()此方法用来处理FilterDispatcher的配置中所定义的所有属性

  • init_AliasStandardObjects()

7. init_AliasStandardObjects()将一个BeanSelectionProvider类追加到ConfigurationManager对象内部的ConfigurationProvider队列中。BeanSelectionProvider类主要实现加载org/apache/struts2/struts-messages

[html]  view plain  copy
  1. private void init_AliasStandardObjects() {  
  2.         configurationManager.addConfigurationProvider(  
  3. new BeanSelectionProvider());  
  4. }  



相信看到这大家应该明白了,struts2的一些配置的加载顺序和加载时所做的工作,其实有些地方我也不是理解的很清楚。其他具体的就不在说了,init方法占时先介绍到这

2doFilter方法

     doFilter是过滤器的执行方法,它拦截提交的HttpServletRequest请求,HttpServletResponse响应,作为strtus2的核心拦截器,在doFilter里面到底做了哪些工作,我们将逐行解读其源码,大体源码如下:

[html]  view plain  copy
  1. public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {  
  2.   
  3. //父类向子类转:强转为http请求、响应  
  4.   
  5. HttpServletRequest request = (HttpServletRequest) req;  
  6.   
  7. HttpServletResponse response = (HttpServletResponse) res;  
  8.   
  9. try {  
  10.   
  11. //设置编码和国际化  
  12.   
  13. prepare.setEncodingAndLocale(request, response);  
  14.   
  15. //创建Action上下文(重点)  
  16.   
  17. prepare.createActionContext(request, response);  
  18.   
  19. prepare.assignDispatcherToThread();  
  20.   
  21. if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {  
  22.   
  23. chain.doFilter(request, response);  
  24.   
  25. } else {  
  26.   
  27. request = prepare.wrapRequest(request);  
  28.   
  29. ActionMapping mapping = prepare.findActionMapping(request, response, true);  
  30.   
  31. if (mapping == null) {  
  32.   
  33. boolean handled = execute.executeStaticResourceRequest(request, response);  
  34.   
  35. if (!handled) {  
  36.   
  37. chain.doFilter(request, response);  
  38.   
  39. }  
  40.   
  41. } else {  
  42.   
  43. execute.executeAction(request, response, mapping);  
  44.   
  45. }  
  46.   
  47. }  
  48.   
  49. } finally {  
  50.   
  51. prepare.cleanupRequest(request);  
  52.   
  53. }  
  54.   
  55. }  


下面我们就逐句的来的看一下:设置字符编码和国际化很简单prepare调用了setEncodingAndLocale,然后调用了dispatcher方法的prepare方法:

[html]  view plain  copy
  1. /**  
  2.   
  3. * Sets the request encoding and locale on the response  
  4.   
  5. */  
  6.   
  7. public void setEncodingAndLocale(HttpServletRequest request, HttpServletResponse response) {  
  8.   
  9. dispatcher.prepare(request, response);  
  10.   
  11. }  


看下prepare方法,这个方法很简单只是设置了encoding locale ,做的只是一些辅助的工作:

[html]  view plain  copy
  1. public void prepare(HttpServletRequest request, HttpServletResponse response) {  
  2.   
  3. String encoding = null;  
  4.   
  5. if (defaultEncoding != null) {  
  6.   
  7. encoding = defaultEncoding;  
  8.   
  9. }  
  10.   
  11. Locale locale = null;  
  12.   
  13. if (defaultLocale != null) {  
  14.   
  15. locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());  
  16.   
  17. }  
  18.   
  19. if (encoding != null) {  
  20.   
  21. try {  
  22.   
  23. request.setCharacterEncoding(encoding);  
  24.   
  25. } catch (Exception e) {  
  26.   
  27. LOG.error("Error setting character encoding to '" + encoding + "' - ignoring.", e);  
  28.   
  29. }  
  30.   
  31. }  
  32.   
  33. if (locale != null) {  
  34.   
  35. response.setLocale(locale);  
  36.   
  37. }  
  38.   
  39. if (paramsWorkaroundEnabled) {  
  40.   
  41. request.getParameter("foo"); // simply read any parameter (existing or not) to "prime" the request  
  42.   
  43. }  
  44.   
  45. }  


下面咱重点看一下创建Action上下文重点

   Action上下文创建(重点)

       ActionContext是一个容器,这个容易主要存储requestsessionapplicationparameters等相关信息.ActionContext是一个线程的本地变量,这意味着不同的action之间不会共享ActionContext,所以也不用考虑线程安全问题。其实质是一个Mapkey是标示requestsession……的字符串,值是其对应的对象:

static ThreadLocal actionContext = new ThreadLocal();

Map<String, Object> context;

下面我们看起来下创建action上下文的源码

[html]  view plain  copy
  1. /**  
  2.   
  3. *创建Action上下文,初始化thread local  
  4.   
  5. */  
  6.   
  7. public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) {  
  8.   
  9. ActionContext ctx;  
  10.   
  11. Integer counter = 1;  
  12.   
  13. Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);  
  14.   
  15. if (oldCounter != null) {  
  16.   
  17. counter = oldCounter + 1;  
  18.   
  19. }  
  20.   
  21.   
  22.   
  23. //注意此处是从ThreadLocal中获取此ActionContext变量  
  24.   
  25. ActionContext oldContext = ActionContext.getContext();  
  26.   
  27. if (oldContext != null) {  
  28.   
  29. // detected existing context, so we are probably in a forward  
  30.   
  31. ctx = new ActionContext(new HashMap<String, Object>(oldContext.getContextMap()));  
  32.   
  33. } else {  
  34.   
  35. ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();  
  36.   
  37. stack.getContext().putAll(dispatcher.createContextMap(request, response, null, servletContext));  
  38.   
  39. //stack.getContext()返回的是一个Map<String,Object>,根据此Map构造一个ActionContext  
  40.   
  41. ctx = new ActionContext(stack.getContext());  
  42.   
  43. }  
  44.   
  45. request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);  
  46.   
  47. //将ActionContext存如ThreadLocal  
  48.   
  49. ActionContext.setContext(ctx);  
  50.   
  51. return ctx;  
  52.   
  53. }  


一句句来看:

ValueStackstack= dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();  

dispatcher.getContainer().getInstance(ValueStackFactory.class)根据字面估计一下就是创建ValueStackFactory的实例。这个地方我也只是根据字面来理解的。ValueStackFactory是接口,其默认实现是OgnlValueStackFactory,调用OgnlValueStackFactorycreateValueStack()

下面看一下OgnlValueStack的构造方法  

[html]  view plain  copy
  1. protected OgnlValueStack(XWorkConverter xworkConverter, CompoundRootAccessor accessor, TextProvider prov, boolean allowStaticAccess) {     
  2.   
  3.       //new一个CompoundRoot出来    
  4.   
  5.     setRoot(xworkConverter, accessor, new CompoundRoot(), allowStaticAccess);     
  6.   
  7.     push(prov);     
  8.   
  9. }    


接下来看一下setRoot方法:

[html]  view plain  copy
  1. protected void setRoot(XWorkConverter xworkConverter, CompoundRootAccessor accessor, CompoundRoot compoundRoot,     
  2.   
  3.                        boolean allowStaticMethodAccess) {     
  4.   
  5.     //OgnlValueStack.root = compoundRoot;                    
  6.   
  7.     this.root = compoundRoot;     
  8.   
  9. 1    //方法/属性访问策略。    
  10.   
  11.     this.securityMemberAccess = new SecurityMemberAccess(allowStaticMethodAccess);     
  12.   
  13.     //创建context了,创建context使用的是ongl的默认方式。    
  14.   
  15.     //Ognl.createDefaultContext返回一个OgnlContext类型的实例    
  16.   
  17.     //这个OgnlContext里面,root是OgnlValueStack中的compoundRoot,map是OgnlContext自己创建的private Map _values = new HashMap(23);    
  18.   
  19.     this.context = Ognl.createDefaultContext(this.root, accessor, new OgnlTypeConverterWrapper(xworkConverter), securityMemberAccess);       
  20.   
  21.          
  22.   
  23.      //不是太理解,猜测如下:    
  24.   
  25.     //context是刚刚创建的OgnlContext,其中的HashMap类型_values加入如下k-v:    
  26.   
  27.    //key:com.opensymphony.xwork2.util.ValueStack.ValueStack    
  28.   
  29.     //value:this,这个应该是当前的OgnlValueStack实例。    
  30.   
  31.     //刚刚用断点跟了一下,_values里面是:    
  32.   
  33.     //com.opensymphony.xwork2.ActionContext.container=com.opensymphony.xwork2.inject.ContainerImpl@96231e    
  34.   
  35.     //com.opensymphony.xwork2.util.ValueStack.ValueStack=com.opensymphony.xwork2.ognl.OgnlValueStack@4d912    
  36.   
  37.     context.put(VALUE_STACK, this);     
  38.   
  39.   //此时:OgnlValueStack中的compoundRoot是空的;    
  40.   
  41.    //context是一个OgnlContext,其中的_root指向OgnlValueStack中的root,_values里面的东西,如刚才所述。    
  42.   
  43.     //OgnlContext中的额外设置。    
  44.   
  45.     Ognl.setClassResolver(context, accessor);     
  46.   
  47.     ((OgnlContext) context).setTraceEvaluations(false);     
  48.   
  49.     ((OgnlContext) context).setKeepLastEvaluation(false);     
  50.   
  51. }             
  52.        

  上面代码中dispatcher.createContextMap,如何封装相关参数:,我们以RequestMap为例,其他的原理都一样:主要方法实现:

[html]  view plain  copy
  1. //map的get实现  
  2.   
  3. public Object get(Object key) {  
  4.   
  5. return request.getAttribute(key.toString());  
  6.   
  7. }  
  8.   
  9. //map的put实现  
  10.   
  11. public Object put(Object key, Object value) {  
  12.   
  13. Object oldValue = get(key);  
  14.   
  15. entries = null;  
  16.   
  17. request.setAttribute(key.toString(), value);  
  18.   
  19. return oldValue;  
  20.   
  21. }  


        到此,几乎StrutsPrepareAndExecuteFilter大部分的源码都涉及到了。自己感觉都好乱,所以还请大家见谅,能力有限,希望大家可以共同学习



(四)struts2中action执行流程和源码分析




struts执行流程


 首先我们看一下struts官方给我们提供的struts执行流程

 从上面流程图我们可以看出struts执行的流程大体分一下阶段:


1. 初始的请求通过一条标准的过滤器链,到达servlet 容器比如tomcat 容器,WebSphere 容器)

2. 过滤器链包括可选的ActionContextCleanUp 过滤器,用于系统整合技术,如SiteMesh 插件。

3. 接着调用FilterDispatcher,FilterDispatcher 查找ActionMapper,以确定这个请求是否需要调用某个Action。

4. 如果ActionMapper 确定需要调用某个Action,FilterDispatcher 将控制权交给ActionProxy。

5. ActionProxy 依照框架的配置文件(struts.xml),找到需要调用的Action 类。

6. ActionProxy 创建一个ActionInvocation 的实例。ActionInvocation 先调用相关的拦截器(Action 调用之前的部分),最后调用Action。

7. 一旦Action 调用返回结果,ActionInvocation 根据struts.xml 配置文件,查找对应的转发路径。返回结果通常是(但不总是,也可能是另外的一个Action 链)JSP 技术或者FreeMarker的模版技术的网页呈现。Struts2 的标签和其他视图层组件,帮助呈现我们所需要的显示结果。在此,我想说清楚一些,最终的显示结果一定是HTML 标签。标签库技术和其他视图层技术只是为了动态生成HTML 标签。

8. 接着按照相反次序执行拦截器链执行Action 调用之后的部分)。最后,响应通过滤器链返回(过滤器技术执行流程与拦截器一样,都是先执行前面部分,后执行后面部)。如果过滤器链中存在ActionContextCleanUpFilterDispatcher 不会清理线程局部的ActionContext。如果不存在ActionContextCleanUp 过滤器,FilterDispatcher 会清除所有线程局部变量。

下面我们就来具体分析一下3-6四个步骤:

步骤三:FilterDispatcher 查找ActionMapper,以确定这个请求是否需要调用某个Action。

1)

[java]  view plain  copy
  1. ActionMapping mapping;  
  2.   
  3.             try {  
  4.   
  5.                 mapping = actionMapper.getMapping(request, dispatcher.getConfigurationManager());  
  6.   
  7.             } catch (Exception ex) {  
  8.   
  9.                 log.error("error getting ActionMapping", ex);  
  10.   
  11.                 dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);  
  12.   
  13.                 return;  
  14.             }  

  

2调用actionmapper去寻找对应的ActionMapping因为actionmapper是一个接口,所有我们去他对应的实现类(DefaultActionMapper里面去找getMapping方法,下面我们来看一下实现类里面的getMapping方法源代码:

 

[java]  view plain  copy
  1. public ActionMapping getMapping(HttpServletRequest request,  
  2.   
  3.                                     ConfigurationManager configManager) {  
  4.   
  5.         ActionMapping mapping = new ActionMapping();  
  6.   
  7.         String uri = getUri(request);  
  8.         int indexOfSemicolon = uri.indexOf(";");  
  9.   
  10.         uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri;  
  11.   
  12.         uri = dropExtension(uri, mapping);  
  13.   
  14.         if (uri == null) {  
  15.   
  16.             return null;  
  17.   
  18.         }  
  19.   
  20.         parseNameAndNamespace(uri, mapping, configManager);  
  21.   
  22.         handleSpecialParameters(request, mapping);  
  23.   
  24.         if (mapping.getName() == null) {  
  25.   
  26.             return null;  
  27.   
  28.         }  
  29.         parseActionName(mapping);  
  30.   
  31.         return mapping;  
  32. }  


      ActionMapping 代表struts.xml 文件中的一个Action 配置,被传入到serviceAction 中。注意ActionMapping 不代表Action 集合,只代表某个对应的Action。如果是一个Action 请求,( 请求路径在struts.xml 有对应的Action 配置,即actionmapping不为空),则调用dispatcher.serviceAction() 处理。找到对应的ActionMapping,下一步就去找具体的执行哪一个action,从FilterDispatcher源码中我们可以找到下一步流程:

[java]  view plain  copy
  1. dispatcher.serviceAction(request, response, servletContext, mapping);  


    从上面可以看出,FilterDispatcher类中是调用的serviceAction方法来寻找的去调用哪一个action。serviceAction()方法作用:加载Action 类,调用Action 类的方法,转向到响应结果。响应结果指<result/> 标签所代表的对象。

步骤四、五、六:如果ActionMapper 确定需要调用某个Action,FilterDispatcher 将控制权交给ActionProxy。

我们来看一下具体的serviceAction源码:

[java]  view plain  copy
  1. public void serviceAction(HttpServletRequest request, HttpServletResponse response,  
  2.   
  3. ServletContext context, ActionMapping mapping) throws ServletException {  
  4.   
  5. Map<String, Object> extraContext = createContextMap  
  6.   
  7. (request, response, mapping, context);  
  8.   
  9. //1 以下代码目的为获取ValueStack,代理在调用的时候使用的是本值栈的副本  
  10.   
  11. ValueStack stack = (ValueStack) request.getAttribute  
  12.   
  13. (ServletActionContext.STRUTS_VALUESTACK_KEY);  
  14.   
  15. boolean nullStack = stack == null;  
  16.   
  17. if (nullStack) {  
  18.   
  19. ActionContext ctx = ActionContext.getContext();  
  20.   
  21. if (ctx != null) {  
  22.   
  23. stack = ctx.getValueStack();  
  24.   
  25. }  
  26.   
  27. }  
  28.   
  29. //2 创建ValueStack 的副本  
  30.   
  31. if (stack != null) {  
  32.   
  33. extraContext.put(ActionContext.VALUE_STACK,  
  34.   
  35. valueStackFactory.createValueStack(stack));  
  36.   
  37. }  
  38.   
  39. String timerKey = "Handling request from Dispatcher";  
  40.   
  41. try {  
  42.   
  43. UtilTimerStack.push(timerKey);  
  44.   
  45. //3 这个是获取配置文件中<action/> 配置的字符串,action 对象已经在核心控制器中创建  
  46.   
  47. String namespace = mapping.getNamespace();  
  48.   
  49. String name = mapping.getName();  
  50.   
  51. String method = mapping.getMethod();  
  52.   
  53. // xwork 的配置信息  
  54.   
  55. Configuration config = configurationManager.getConfiguration();  
  56.   
  57. //4 动态创建ActionProxy  
  58.   
  59. ActionProxy proxy =  
  60.   
  61. config.getContainer().getInstance(ActionProxyFactory.class).  
  62.   
  63. createActionProxy(namespace, name, method, extraContext, truefalse);  
  64.   
  65. request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY,  
  66.   
  67. proxy.getInvocation().getStack());  
  68.   
  69. //5 调用代理  
  70.   
  71. if (mapping.getResult() != null) {  
  72.   
  73. Result result = mapping.getResult();  
  74.   
  75. result.execute(proxy.getInvocation());  
  76.   
  77. else {  
  78.   
  79. proxy.execute();  
  80.   
  81. }  
  82.   
  83. //6 处理结束后,恢复值栈的代理调用前状态  
  84.   
  85. if (!nullStack) {  
  86.   
  87. request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);  
  88.   
  89. }  
  90.   
  91. catch (ConfigurationException e) {  
  92.   
  93. //7 如果action 或者result 没有找到,调用sendError 报404 错误  
  94.   
  95. }  


关于valuestack说明一下:

1.valueStack 的建立是在doFilter 的开始部分,在Action 处理之前。即使访问静态资源ValueStack 依然会建立,保存在request 作用域。

2. ValueStack 在逻辑上包含2 个部分:object stack 和context map,object stack 包含Action 与Action 相关的对象。

context map 包含各种映射关系。request,session,application,attr,parameters 都保存在context map 里。

parameters: 请求参数

atrr: 依次搜索page, request, session, 最后application 作用域。

几点说明:

1. Valuestack 对象保存在request 里,对应的key ServletActionContext.STRUTS_VALUESTACK_KEY。调用代理之前首先创建Valuestack 副本,调用代理时使用副本,调用后使用原实例恢复。本处的值栈指object stack

2. Dispatcher 实例,创建一个Action 代理对象。并把处理委托给代理对象的execute 方法。

最后我们在一起看一下ActionInvocation实现类中invoke方法执行的流程:invoke源代码:

[java]  view plain  copy
  1.  public String invoke() throws Exception {  
  2.   
  3.         String profileKey = "invoke: ";  
  4.   
  5.         try {  
  6.   
  7.             UtilTimerStack.push(profileKey);  
  8.   
  9.             if (executed) {  
  10.   
  11.                 throw new IllegalStateException("Action has already executed");  
  12.   
  13.             }  
  14.   
  15.             if (interceptors.hasNext()) {  
  16.   
  17.                 final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next();  
  18.   
  19.                 String interceptorMsg = "interceptor: " + interceptor.getName();  
  20.   
  21.                 UtilTimerStack.push(interceptorMsg);  
  22.   
  23.                 try {  
  24.   
  25.                                 resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);  
  26.   
  27.                             }  
  28.   
  29.                 finally {  
  30.   
  31.                     UtilTimerStack.pop(interceptorMsg);  
  32.   
  33.                 }  
  34.   
  35.             } else {  
  36.   
  37.                 resultCode = invokeActionOnly();  
  38.   
  39.             }  
  40.   
  41.   
  42.                if (!executed) {  
  43.   
  44.                 if (preResultListeners != null) {  
  45.   
  46.                     for (Object preResultListener : preResultListeners) {  
  47.   
  48.                         PreResultListener listener = (PreResultListener) preResultListener;  
  49.   
  50.   
  51.                         String _profileKey = "preResultListener: ";  
  52.   
  53.                         try {  
  54.   
  55.                             UtilTimerStack.push(_profileKey);  
  56.   
  57.                             listener.beforeResult(this, resultCode);  
  58.   
  59.                         }  
  60.   
  61.                         finally {  
  62.   
  63.                             UtilTimerStack.pop(_profileKey);  
  64.   
  65.                         }  
  66.   
  67.                     }  
  68.   
  69.                 }  
  70.   
  71.   
  72.                    if (proxy.getExecuteResult()) {  
  73.   
  74.                     executeResult();  
  75.   
  76.                 }  
  77.   
  78.   
  79.                 executed = true;  
  80.   
  81.             }  
  82.             return resultCode;  
  83.   
  84.         }  
  85.   
  86.         finally {  
  87.   
  88.             UtilTimerStack.pop(profileKey);  
  89.   
  90.        }  
  91. }  


        这里算是执行action中方法的最后一步了吧,至此,action的整个流程就基本差不多了,从头到尾看下来,说实话,感触很多,很多不明白的地方,这算是近了自己最大的努力去看这些源码,感觉从里面收获了很多,里面很多的机制和知识点值得我们去学习,记住了圣思源张龙老师的那句话:源码面前,一目了然




(五)action基础知识和数据校验




一:首先看一下struts2action的实现方式:

1.建立普通的pojo类:

 这种方式能够实现简单的action功能,但struts2内自带的一些验证和其他功能不能够实现

2.继承ActionSupport类实现action

 因为ActionSupport已经实现了Action接口,还实现了Validateable接口,提供了数据校验功能。通过继承该ActionSupport类,可以简化Struts 2Action开发。

3.实现action接口

 这个接口里面定义了一些action所要实现的功能的标准,但验证等功能没有,所以一般还是继承actionsupport来实现action

Action 跟 Actionsupport 的区别:

当我们在写action的时候,可以实现Action接口,也可以继承Actionsupport这个类.到底这两个有什么区别呢

Action接口有

[java]  view plain  copy
  1. public static final java.lang.String SUCCESS = "success";   
  2.   
  3. public static final java.lang.String NONE = "none";   
  4.   
  5. public static final java.lang.String ERROR = "error";   
  6.   
  7. public static final java.lang.String INPUT = "input";   
  8.   
  9. public static final java.lang.String LOGIN = "login";   
  10.   
  11. public abstract java.lang.String execute() throws java.lang.Exception;   


Actionsupport这个工具类在实现了Action接口的基础上还定义了一个validate()方法,重写该方法,它会在execute()方法之前执行,如校验失败,会转入input处,必须在配置该Action时配置input属性。 

另外,Actionsupport还提供了一个getText(String key)方法还实现国际化,该方法从资源文件上获取国际化信息

这样在自定义标签时可以定义一个变量为new actionsupport对象实现国际化。

ActionSupport类的作用 

       struts2不要求我们自己设计的action类继承任何的struts基类或struts接口,但是我们为了方便实现我们自己的action,大多数情况下都会继承com.opensymphony.xwork2.ActionSupport类,并重写此类里的public String execute() throws Exception方法。因为此类中实现了很多的实用借口,提供了很多默认方法,这些默认方法包括国际化信息的方法、默认的处理用户请求的方法等,这样可以大大的简化Acion的开发。 

      Struts2中通常直接使用Action来封装HTTP请求参数,因此,Action类里还应该包含与请求参数对应的属性,并且为属性提供对应的gettersetter方法。

二.action数据校验

    在上面应用中,即使浏览者输入任何用户名、密码,系统也会处理用户请求。在我们整个HelloWorld应用中,这种空用户名、空密码的情况不会引起太大的问题。但如果数据需要保存到数据库,或者需要根据用户输入的用户名、密码查询数据,这些空输入可能引起异常。为了避免用户的输入引起底层异常,通常我们会在进行业务逻辑操作之前,先执行基本的数据校验。    

Action数据校验功能是struts2给我们提供的一个服务器端简单验证的功能,这个功能使我们简化了一些没必要的代码。下面看一下具体实现:

1.继承ActionSupport

ActionSupport类是一个工具类,它已经实现了Action接口。除此之外,它还实现了Validateable接口,提供了数据校验功能。通过继承该ActionSupport类,可以简化Struts 2Action开在Validatable接口中定义了一个validate()方法,重写该方法,如果校验表单输入域出现错误,则将错误添加到ActionSupport类的fieldErrors域中,然后通过OGNL表达式负责输出为了让Struts 2增加输入数据校验的功能,改写程序中的LoginAction,增加重写validate方法。下面看一下具体代码实现:

[java]  view plain  copy
  1. package com.bzu.action;  
  2.   
  3. import com.opensymphony.xwork2.ActionSupport;  
  4.   
  5. public class LoginAction extends ActionSupport {  
  6.   
  7. private String username;  
  8.   
  9. private String password;  
  10.   
  11. public String getUsername() {  
  12.   
  13. return username;  
  14.   
  15. }  
  16.   
  17. public void setUsername(String username) {  
  18.   
  19. this.username = username;  
  20.   
  21. }  
  22.   
  23. public String getPassword() {  
  24.   
  25. return password;  
  26.   
  27. }  
  28.   
  29. public void setPassword(String password) {  
  30.   
  31. this.password = password;  
  32.   
  33. }  
  34.   
  35. public void validate() {  
  36.   
  37. if("".equals(username))  
  38.   
  39. this.addActionError("soory,the username can't blank");  
  40.   
  41. if("".equals(password))  
  42.   
  43. this.addActionError("soory,the password can't blank");  
  44.   
  45. }  
  46.   
  47. public String execute(){  
  48.   
  49. if(username.equals("admin")&&password.equals("123456"))  
  50.   
  51. return "success";  
  52.   
  53. return "fail";  
  54.   
  55. }  
  56.   
  57. }  


      这里简单的实现了表单数据验证功能,上面的Action类重写了validate方法,该方法会在执行系统的execute方法之前执行,如果执行该方法之后,Action类的fieldErrors中已经包含了数据校验错误,请求将被转发到input逻辑视图处。还是要注意以下几点:

1.在实现表单验证功能的时候一定不要忘记了在struts.xml中相对应的action中配置result=input”,因为表单验证失败默认返回的字符串为input,如果没有的话会找不到这个结果而报错。

2.数据验证中,如果数据不符的时候可以报三种错误,我们上面代码中只是列举了action错误,另外两种是Field字段的错误,还有一种就是actionMessage

3.注意在显示界面接收action错误时,要在想显示错误的地方加上 <s:actionerror/>标签,如果想接收Filed域的错误时,一定要用struts标签,如果不用的话是不会显示字段错误的

2.使用Struts 2的校验框架

上面的输入校验是通过重写ActionSupport类的validate方法实现的,这种方法虽然不错,但需要大量重写的validate方法——毕竟,重复书写相同的代码不是一件吸引人的事情。
类似于Struts 1Struts 2也允许通过定义配置文件来完成数据校验。Struts 2的校验框架实际上是基于XWorkvalidator框架。
下面还是使用原来的Action类(即不重写validate方法),却增加一个校验配置文件,校验配置文件通过使用Struts 2已有的校验器,完成对表单域的校验。Struts 2提供了大量的数据校验器,包括表单域校验器和非表单域校验器两种。 本应用主要使用了requiredstring校验器,该校验器是一个必填校验器——指定某个表单域必须输入。
下面是校验规则的定义文件:

[html]  view plain  copy
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2.   
  3. <!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN" "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">  
  4.   
  5. <validators>  
  6.   
  7. <field name="username">  
  8.   
  9. <field-validator type="requiredstring">  
  10.   
  11. <param name="trim">false</param>  
  12.   
  13. <message>username can't be blank!</message>  
  14.   
  15. </field-validator>  
  16.   
  17. <field-validator type="stringlength">  
  18.   
  19. <param name="minLength">4</param>  
  20.   
  21. <param name="maxLength">6</param>  
  22.   
  23. <param name="trim">false</param>  
  24.   
  25. <message key="username.invalid"></message>  
  26.   
  27. </field-validator>  
  28.   
  29. </field>  
  30.   
  31. <field name="password">  
  32.   
  33. <field-validator type="requiredstring">  
  34.   
  35. <message>password can't be blank!</message>  
  36.   
  37. </field-validator>  
  38.   
  39. <field-validator type="stringlength">  
  40.   
  41. <param name="minLength">4</param>  
  42.   
  43. <param name="maxLength">6</param>  
  44.   
  45. <message>length of password should be between ${minLength} and ${maxLength}</message>  
  46.   
  47. </field-validator>  
  48.   
  49. </field>  
  50.   
  51. <field name="age">  
  52.   
  53. <field-validator type="required">  
  54.   
  55. <message>age can't be blank!</message>  
  56.   
  57. </field-validator>  
  58.   
  59. <field-validator type="int">  
  60.   
  61. <param name="min">10</param>  
  62.   
  63. <param name="max">40</param>  
  64.   
  65. <message>age should be between ${min} and ${max}</message>  
  66.   
  67. </field-validator>  
  68.   
  69. </field>  
  70.   
  71. </validators>  


定义完该校验规则文件后,该文件的命名应该遵守如下规则:
ActionName-validation.xml:其中ActionName就是需要校验的Action的类名。因此上面的校验规则文件应该命名为LoginAction-validation.xml,且该文件应该与Action类的class文件位于同一个路径下。因此,将上面的校验规则文件放在WEB-INF/classes/lee路径下即可。当然,在struts.xml文件的Action定义中,一样需要定义input的逻辑视图名,将input逻辑视图映射到login.jsp页面。在这种校验方式下,无需书写校验代码,只需要通过配置文件指定校验规则即可,因此提供了更好的可维护性。

三、action中的执行方法

Action中默认的执行方法是execute方法,这个方法执行请求,然后转向其他的页面,这是常规的做法,但有时候我们不想用这个方法名,为了代码的可读性,我们希望让他执行我们自己定义的方法,下面我们就来看一下执行其他方法的两种方法:

1.在struts.xml配置method属性

其实执行execute方法是对应action在配置文件method的默认方法,所以要想执行其他的方法,我们可以修改这里的默认方法,只要把默认的方法改为我们自定义的方法就可以了。部分配置代码:

[java]  view plain  copy
  1. <action name="LoginAction" class="com.bzu.action.LoginAction" method="login">  
  2.   
  3. <result name="success">success.jsp</result>  
  4.   
  5. <result name="fail">fail.jsp</result>  
  6.   
  7. <result name="input">login.jsp</result>  
  8.   
  9. </action>  
  10.   
  11. <action name="RegisteAction" class="com.bzu.action.LoginAction" method="registe">  
  12.   
  13. <result name="success">success.jsp</result>  
  14.   
  15. <result name="fail">fail.jsp</result>  
  16.   
  17. <result name="input">login.jsp</result>  
  18.   
  19. </action>  


2.DMI(动态直接调用)

 这种方法不需要进行struts.xml的配置。而是在html或者jsp页面中通过标示符号指定了要调用的方法。 关键的标示符号为"",具体看一下下面表单:

 

[java]  view plain  copy
  1. <s:form action="LoginAction!login">  
  2.   
  3.   <s:actionerror/>  
  4.   
  5.   username:<s:textfield name="username"></s:textfield>  
  6.   
  7.   password:<s:password name="password"></s:password>  
  8.   
  9.   <s:submit value="提交"></s:submit>  
  10.   
  11.    </s:form>  


3.提交按钮指定提交方法

 普通的提交按钮我们会这么写: <s:submit value="提交"></s:submit>

当我们想提交到我们指定的方法时我们可以在这个标签里添加一个method属性指定要提交的方法,如下:

 

[html]  view plain  copy
  1. <s:submit value="提交" method="login"></s:submit>  


4.使用通配符配置Action

 这种方法可以解决action配置文件混乱的问题,减少action的配置:

 

[html]  view plain  copy
  1. <action name="helloworld_*" class="com.bird.action.HelloWorld" method="{1}">     
  2.   
  3.          <result name="success">/WEB-INF/jsp/{1}_success.jsp</result>     
  4.   
  5.   </action>  
  6.   
  7. <span style="font-size:18px;"><span style="color:#000000;">   </span></span>  

   在name属性的值后面加上*,意思是只要你请求的action名字以helloworld开头,本action就是你找的action然后method是大括号加上1,这个1代表第一个星号的值,这样就可以动态执行请求的方法。

       

最后说一点,有时候用户在地址栏随便输入的时候,找不到对应的action,直接对报出一些错误,这样的界面一般都很难看,所以为了能给用户一个友好的提示界面,一般我们会再struts.xml文件配置默认的action,代码如下:




(六)获取servletAPI和封装表单数据




一:获取servletAPI的三种方法

       在传统的Web开发中,经常会用到Servlet API中的HttpServletRequest、HttpSession和ServletContext。Struts 2框架让我们可以直接访问和设置action及模型对象的数据,这降低了对HttpServletRequest对象的使用需求,同时降低了对servletAPI的依赖性,从而降低了与servletAPI的耦合度。但在某些应用中,我们可 能会需要在action中去访问HttpServletRequest等对象,所以有时候我们不得不拉近struts2和servletAPI的关系,但struts2也有尽量减少耦合度的方法,下面我们就一起具体看一下在struts2中获得ServletAPI的三种方法:

 

1.ServletAPI解藕方式(一)获取Map对象:

    为了避免与Servlet API耦合在一起,方便Action类做单元测试,Struts 2对HttpServletRequest、HttpSession和ServletContext进行了封装,构造了三个Map对象来替代这三种对象,在Action中,直接使用HttpServletRequest、HttpSession和ServletContext对应的Map对象来保存和读取 数据。可以通过com.opensymphony.xwork2.ActionContext类来得到这三个对象。ActionContextAction执行的上下文,保存了很多对象如parametersrequestsessionapplicationlocale等。通过ActionContext类获取Map对象的方法为:

[java]  view plain  copy
  1. ActionContextcontext=ActionContext.getContext(); --得到Action执行的上下文  
  2.   
  3. Maprequest=(Map)context.get("request");--得到HttpServletRequest的Map对象  
  4.   
  5. Mapsession=context.getSession();--得到HttpSession的Map对象  
  6.   
  7. Mapapplication=context.getApplication();--得到ServletContext的Map对象  


          ActionContext中保存的数据能够从请求对象中得到,其中的奥妙就在于Struts 2中的org.apache.struts2.dispatcher.StrutsRequestWrapper类,这个类是 HttpServletRequest的包装类,它重写了getAttribute()方法(在页面中获取request对象的属性就要调用这个方法), 在这个方法中,它首先在请求对象中查找属性,如果没有找到(如果你在ActionContext中保存数据,当然就找不到了),则到 ActionContext中去查找。这就是为什么在ActionContext中保存的数据能够从请求对象中得到的原因。

2.IOC(控制反转)获取servletAPI

     Action类还有另一种获得ServletAPI的解耦方式,这就是我们可以让他实现某些特定的接口,让Struts2框架在运行时向Action实例注入requestsessionapplication对象。这种方式也就是IOC(控制反转)方式,与之对应的三个接口和它们的方法如下所示:

[java]  view plain  copy
  1. public class SampleAction implementsAction,  
  2. RequestAware, SessionAware, ApplicationAware  
  3. {  
  4. private Map request;  
  5. private Map session;  
  6. private Map application;  
  7.   
  8. @Override  
  9. public void setRequest(Map request)  
  10. {this.request = request;}  
  11.   
  12. @Override  
  13. public void setSession(Map session)  
  14. {this.session = session;}  
  15.   
  16. @Override  
  17. public void setApplication(Map application)  
  18. {this.application = application;}  
  19.   
  20. }  


ServletRequestAware接口和ServletContextAware接口不属于同一个包,前者在org.apache.struts2.interceptor包中,后者在org.apache.struts2.util包中,这很让人迷惑。

 

3.与Servlet API耦合的访问方式

    直接访问Servlet API将使你的Action与Servlet环境耦合在一起,我们知道对于HttpServletRequest、 HttpServletResponse和ServletContext这些对象,它们都是由Servlet容器来构造的,与这些对象绑定在一起,测试时就需要有Servlet容器,不便于Action的单元测试。但有时候,我们又确实需要直接访问这些对象,那么当然是以完成任务需求为主。要直接获取HttpServletRequest和ServletContext对象,可以使用org.apache.struts2. ServletActionContext类,该类是ActionContext的子类,在这个类中定义下面两个静态方法:

1.得到HttpServletRequest对象:

public static HttpServletRequestgetRequest()

2.得到ServletContext对象:

public static ServletContextgetServletContext()

此外,ServletActionContext类还给出了获取HttpServletResponse对象的方法,如下:

public static HttpServletResponsegetResponse()

ServletActionContext类并没有给出直接得到HttpSession对象的方法,HttpSession对象可以通过HttpServletRequest对象来得到。

除了上述的方法调用得到HttpServletRequest和ServletContext对象外,还可以调用ActionContext对象的 get()方法,传递ServletActionContext.HTTP_REQUEST和 ServletActionContext.SERVLET_CONTEXT键值来得到HttpServletRequest和 ServletContext对象同样的,也可以向ActionContext的get()方法传递ServletActionContext.HTTP_ RESPONSE键值来得到HttpServletResponse对象

 

       总结:通过上面三种方式的讲解我们可以看出,三种获得servletAPI的方式基本都差不多,通常我们建议大家采用第一种方式来获取HttpServletRequestServletContext对象,这样简单而又清晰,并且降低了和servletAPI的耦合度,这样也方便进行单元测试

二:struts2封装请求参数三种方式

     在struts2开发应用中,我们可能经常要求获得视图层传过来的很多数据,一般都是一个实体类的n多属性,很多时候实体类的属性特别多,这时候如果还是按以前的方式在action里面一个个的定义出这些属性的私有变量,然后在提供set、get方法的话,这样就会使整个action太臃肿,严重妨碍了代码的可阅读性,并且也违背了代码的可复用性,这时我们就需要对这些请求参数进行封装,提高代码的可复用性,下面我们就一起来具体看一下三种封装请求参数的方法:

1.利用实体类封装参数

        这种方式是封装参数最简单的方法,一般也比较常用,因为在我们的struts应用程序中,我们一般会根据数据库的信息写出对应的实体类,所以这正好使我们可以利用的,下面我们看一下具体操作:

1.创建实体类user(包括用户名和密码属性),这里比较简单,我们就不贴出代码了。

2.创建action,这里我们主要是来看一下action接收数据的属性这个地方,我们就不是在一一定义这些属性的私有变量了,我们直接定义一个对应实体类的私有对象就可以了,代码如下:

[java]  view plain  copy
  1. package com.bzu.action;  
  2.   
  3. publicclass LoginAction extends ActionSupport {  
  4.   
  5.     private User user;  
  6.   
  7.     public User getUser() {  
  8.   
  9.         returnuser;  
  10.   
  11.     }  
  12.   
  13.     publicvoid setUser(User user) {  
  14.   
  15.         this.user = user;  
  16.   
  17.     }  
  18.   
  19.     public String execute(){  
  20.   
  21.     if(user.getUsername().equals("admin")&&user.getPassword().equals("123456"))  
  22.   
  23.             return"success";  
  24.   
  25.         return"fail";  
  26.   
  27.     }  
  28.   
  29. }  


3.定义表单,这里我们需要注意一下,这里表单里面的控件的name属性定义有一定的要求,定义name时我们应该定义为:对象.属性的形式,示例代码:

[html]  view plain  copy
  1. <s:form action="LoginAction">  
  2.   
  3.   <s:actionerror/>  
  4.   
  5.  <s:textfield name="user.username"></s:textfield>  
  6.   
  7. <s:password name="user.password"></s:password>  
  8.   
  9.   <s:submit value="提交" ></s:submit>  
  10.   
  11.    </s:form>  


4.配置struts.xml,这里配置和平常一样,这里就不再重复了

至此,我们简单的实体类封装请求参数就完成了,我相信大家一定也会感觉很简单吧

 

2.模型驱动封装请求参数

      模型驱动是指使用 JavaBean 来封装来回请求的参数 . 这种方式的好处就是减少了 action 的压力。既用于封装来回请求的参数 , 也保护了控制逻辑 , 使它的结构清晰 . 这就是模型驱动的优势 .

下面我们具体来看一下模型驱动的具体实现:

模型驱动的实现主要是体现在action上

1.首先建立一个实体,比较简单,这里就不再写了。

2.建立action类,继承自ActionSupport,实现ModelDriven接口,这个接口定义了一个getModel()方法,用于返回定义的Model,然后调用set方法,进行赋值。代码示例:

[java]  view plain  copy
  1. <span>publicclass LoginAction3 extends ActionSupport implementsModelDriven<User> {  
  2.  private User user=new User();//这里记住要实例化  
  3.  private LoginService loginService=new LoginServiceImpl();//这里是调用登录的业务处理逻辑  
  4. @Override  
  5.  public User getModel() {  
  6.  //TODOAuto-generated method stub  
  7.  return user;  
  8. }  
  9.  public String execute()  
  10. {  
  11. System.out.println(user.getUsername());  
  12. System.out.println(user.getPassword());  
  13.   
  14.  if(loginService.isLogin(user.getUsername(),user.getPassword()))  
  15. {  
  16.  return SUCCESS;  
  17. }  
  18.  return INPUT;  
  19. }  
  20. }</span>  


在com.opensymphony.xwork2.ModelDriven接口源代码中有一段很重要的说明,现抄录如下
ModelDriven Actions provide a model object to bepushed onto the ValueStack in additionto the Action itself,allowing a FormBeantype approach like Struts
翻译:模型驱动的Action。将模型对象以及Action对象都放到ValueStack里面,允许像Struts一样的FormBean方式
也即:一个Action要想成为模型驱动的话,就必须实现ModelDriven接
口,而我们之前所一直继承的ActionSupport类并没有实现ModelDriven接口


ModelDrivenAction类的执行流程是:首先调用getModel()方法得到User对象,接着根据JavaBean的原则将客户端传过来的属性,一个一个的set到User对象的属性中,将属性全部set完之后,再执行execute()方法。对于模型驱动,只要了解这些就足够了

扩展:模型驱动的底层实现机制
这里用到了defaultStack拦截器栈中的modelDriven拦截器
它对应com.opensymphony.xwork2.interceptor.ModelDrivenInterceptor类,其API描述如下
public class ModelDrivenInterceptor extends AbstractInterceptor
Watches for ModelDriven actions and adds the action`s model on to the valuestack.
翻译:观察模型驱动的Action,并将这个Action的模型【这里指User对象】放到值栈中
Note:The ModelDrivenInterceptor must come before the bothStaticParametersInterceptor and ParametersInterceptor if you want theparameters to be applied to the model.
翻译:若希望将表单提交过来的参数应用到模型里面,那么ModelDrivenInterceptor拦截器就必须位于StaticParametersInterceptor和ParametersInterceptor拦截器前面。

实际上struts-default.xml已完成这个工作了。可以在defaultStack拦截器栈中查看三者位置,所以对于采用模型驱动的方式的话,在struts.xml中只需要指定模型驱动的类就可以了,其它的都不需要我们手工修改

3,属性驱动接收参数

这种方式应该不算是参数封装的方式,但我们很多情况下都用属性驱动的方式接收参数,因为这种方式方便,简洁,易控制。属性驱动在Action中提供与表单字段一一对应的属性,然后一一set赋值,采用属性驱动的方式时,是由每个属性来承载表单的字段值,运转在MVC流程里面。由于这种方式比较简单,这里就不在赘述了。


到底是用属性驱动和是模型驱动呢?

1)统一整个系统中的Action使用的驱动模型,即要么都是用属性驱动,要么都是用模型驱动。

2)如果你的DB中的持久层的对象与表单中的属性都是一一对应的话,那么就使用模型驱动吧,毕竟看起来代码要整洁得多。

3)如果表单的属性不是一一对应的话,那么就应该使用属性驱动,否则,你的系统就必须提供两个Bean,一个对应表单提交的数据,另一个用与持久层。



(七)数据类型转换详解




 Web应用程序的交互都是建立在HTTP之上的,互相传递的都是字符串。也就是说服务器接收到的来自用户的数据只能是字符串或者是字符数组,而在Web应用的对象中,往往使用了多种不同的类型,如整数(int)、浮点数(float)、日期(Date)或者是自定义数据类型等。因此在服务器端必须将字符串转换成合适的数据类型。

         Struts2框架中为我们提供了一些简单类型的转换器,比如转换为intfloat等简单数据类型是不需要我们自己定义转换器去转换的,struts2内部本身就为我们提供了转换的方法,但像一些复杂的类型和我们自定义的数据类型还是需要我们自己去写转换器去转换的。在转换工程中,如果在类型转换中出现异常,类型转换器开发者无需关心异常处理逻辑,Struts2conversionError拦截器会自动处理该异常,并且提示在页面上生成提示信息。

下面我们就一步步的实现和注册一个我们自己的转换器,也就是自定义类型转换器的几个步骤:

一:自定义类型转换器

实现自定义类型转换器我们一般有两种方式:

1.实现OGNL提供的TypeConvert接口以及实现了TypeConvert接口的DefaultTypeConvert类来实现自定义的类型转换器

 我们来看一下DefaultTypeConvert类的源码:

 

[java]  view plain  copy
  1. publicclass DefaultTypeConverter implements TypeConverter{  
  2.   
  3.     public DefaultTypeConverter(){  
  4.   
  5.         super();  
  6.   
  7.     }  
  8.   
  9. /** 
  10.  
  11.  * @param context:类型转换的上下文 
  12.  
  13.  * @param value:需要转换的参数 
  14.  
  15.  * @param toType:转换后的目的类型 
  16.  
  17. */  
  18.   
  19. public Object convertValue(Map context,   
  20.   
  21. Object value,   
  22.   
  23. Class toType)  
  24.   
  25.     {  
  26.   
  27.         return OgnlOps.convertValue(value, toType);  
  28.   
  29.     }  
  30.   
  31. public Object convertValue(Map context, Object target,   
  32.   
  33. Member member, String propertyName,  
  34.   
  35.  Object value, Class toType)  
  36.   
  37.     {  
  38.   
  39.         return convertValue(context, value, toType);  
  40.   
  41.     }  
  42. }  

convertValue方法的作用:

该方法负责完成类型的双向转换,为了实现双向转换,我们通过判断toType的类型即可判断转换的方向。toType类型是需要转换的目标类型如:当toType类型是User类型时,表明需要将字符串转换成User实例;当toType类型是String类型时,表明需要把User实例转换成字符串类型。通过toType类型判断了类型转换的方向后,我们就可以分别实现两个方向的转换逻辑了。实现类型转换器的关键就是实现conertValue方法,该方法有三个参数:

第一个参数 context:类型转换的上下文

第二个参数 value:需要转换的参数

第三个参数 toType:转换后的目的类型

 

2. 基于Struts2的类型转换器

Struts 2提供了一个StrutsTypeConverter的抽象类,这个抽象类是DefaultTypeConverter的子类。开发时可以直接继承这个类来进行转换器的构建。通过继承该类来构建类型转换器,可以不用对转换的类型进行判断(和第一种方式的区别),下面我们来看一下StrutsTypeConverter类的源码:

[java]  view plain  copy
  1. publicabstractclass StrutsTypeConverter extends DefaultTypeConverter {  
  2.   
  3.    //重写DefaultTypeConverter类的convertValue方法  
  4.   
  5.  public Object convertValue(Map context, Object o, Class toClass) {  
  6.   
  7.         //如果需要把复合类型转换成字符串类型  
  8.   
  9. if (toClass.equals(String.class)) {  
  10.   
  11.             return convertToString(context, o);  
  12.   
  13.         }   
  14.   
  15.  //如果需要把字符串转换成符合类型  
  16.   
  17. elseif (o instanceof String[]) {  
  18.   
  19.             return convertFromString(context, (String[]) o, toClass);  
  20.   
  21.         }   
  22.   
  23. //如果需要把字符串转换成符合类型  
  24.   
  25. elseif (o instanceof String) {  
  26.   
  27.             return convertFromString(  
  28.   
  29. context, new String[]{(String) o}, toClass);  
  30.   
  31.         } else {  
  32.   
  33.             return performFallbackConversion(context, o, toClass);  
  34.   
  35.         }  
  36.   
  37.     }