Archive for the 'dispatch action' Category

You are currently browsing the archives of Enabling Technology .

覆盖DispatchAction中的分发方法

在使用struts时我们经常会用到DispatchAction.有了这个类,我们不需要针对每一个Action都要写一个特定的类,而是可以把一些相关的方法放到一个类中.
  
  DispatchActon中使用了reflection来根据你传入的method参数的值来获取相应的参数来处理你的请求.正如他的方法 — 他根据你传入的请求参数,用不同的方法来处理你的请求.
  
  但是,通常我们还会遇到另一种情况,如果我想在之行实际的action处理方法之前或者之后再去做一些别的事情而又不想修改我们实际存在的action类呢?这里我将介绍一种方法.
  
  只要看看struts中DispatchAction(以下简写做DA)的源文件你就会发现,它有一个dispatchMethod方法,接受5个参数.其中4个就是我们通常的struts action里的(mapping,request,response,form),还有一个参数就是指定方法的参数的名字.
  
  protected ActionForward dispatchMethod(ActionMapping mapping,
  ActionForm form, HttpServletRequest request,
  HttpServletResponse response, String name) throws Exception
  
  其实现方法如下:
  
  // Make sure we have a valid method name to call.
  // This may be null if the user hacks the query string.
  if (name == null) {
  return this.unspecified(mapping, form, request, response);
  }
  
  // Identify the method object to be dispatched to
  Method method = null;
  try {
  method = getMethod(name);
  
  } catch (NoSuchMethodException e) {
  String message = messages.getMessage(”dispatch.method”, mapping
  .getPath(), name);
  log.error(message, e);
  throw e;
  }
  
  ActionForward forward = null;
  try {
  Object args[] = { mapping, form, request, response };
  
  //特殊的url不进行处理(add)
  String actionUrl = request.getServletPath(); // ”/rolesign.do”
  boolean exception = isException(actionUrl);
  if(exception) {
  logger.debug(”requestUrl :”+actionUrl+”,no pre and post process”);
  }
  // 预处理(add)
  if (!disabled && !exception) {
  logger.debug(”preProcess begin”);
  preProcess(request);
  logger.debug(”preProcess end”);
  }
  
  forward = (ActionForward) method.invoke(this, args);
  
  // 后处理(add)
  if (!disabled && !exception ){
  logger.debug(”postProcess begin”);
  postProcess(request);
  logger.debug(”postProcess end”);
  }
  } catch (ClassCastException e) {
  String message = messages.getMessage(”dispatch.return”, mapping
  .getPath(), name);
  log.error(message, e);
  throw e;
  
  } catch (IllegalAccessException e) {
  String message = messages.getMessage(”dispatch.error”, mapping
  .getPath(), name);
  log.error(message, e);
  throw e;
  
  } catch (InvocationTargetException e) {
  // Rethrow the target exception if possible so that the
  // exception handling machinery can deal with it
  Throwable t = e.getTargetException();
  if (t instanceof Exception) {
  throw ((Exception) t);
  } else {
  String message = messages.getMessage(”dispatch.error”, mapping
  .getPath(), name);
  log.error(message, e);
  throw new ServletException(t);
  }
  }
  
  // Return the returned ActionForward instance
  return (forward);
  
  大部分代码还是从DA的实现方法中copy过来,但是我在这里加入了3个地方.分别是:
  
  1.对请求url的识别,确定是否使用预/后处理
  
  2.预处理方法调用
  
  3.后处理方法调用
  
  当然你要自己写预处理方法和后处理方法.这里你可以传任意你想要的参数给request,然后通过reflection来动态调用任意的java方法,比如你可以在页面设置一个preFunction参数,它的值是classname.methodname.然后你可以在preProcess的实现中通过reflection来查找这个方法并执行.
  
  如果你想让项目中所有的action都有这种特性,则只要让根Action继承DA并覆盖它的dispatchMethod方法即可.
  
  这是在实际项目中用到的一个特性,为了尽可能少的修改原有的代码使用了这种折中的做法.使用reflection时建议不要执行太过复杂的方法,可能会使你的action响应慢的.

Posted by micas on Jun 27th 2007 | Filed in dispatch action, struts | Comments (0)

Struts应用的国际化

万维网(World Wide Web)的迅猛发展推动了跨国业务的发展,它成为一种在全世界范围内发布产品信息、吸引客户的有效手段。为了使企业Web应用能支持全球客户,软件开发者应该开发出支持多国语言、国际化的Web应用。

 

1 本地化与国际化的概念

国际化(简称为I18N)指的是软件设计阶段,就应该使软件具有支持多种语言和地区的功能。这样,当需要在应用中添加对一种新的语言和国家的支持时,不需要对已有的软件返工,无需修改应用的程序代码。

本地化意味着针对不同语言的客户,开发出不同的软件版本;国际化意味着同一个软件可以面向使用各种不同语言的客户。

如果一个应用支持国际化,它应该具备以下特征:

· 当应用需要支持一种新的语言时,无需修改应用程序代码。

· 文本、消息和图片从源程序代码中抽取出来,存储在外部。

·应该根据用户的语言和地理位置,对与特定文化相关的数据,如日期、时间和货币,进行正确的格式化。

·支持非标准的字符集。

·可以方便快捷地对应用作出调整,使它适应新的语言和地区。

 

在对一个Web应用进行国际化时,除了应该对网站上的文本、图片和按钮进行国际化外,还应该对数字和货币等根据不同国家的标准进行相关的格式化,这样才能保证各个国家的用户都能顺利地读懂这些数据。

Locale(本地)指的是一个具有相同风俗、文化和语言的区域。如果一个应用没有事先把I18N作为那前的功能,那么当这个应用需要支持新的Locale时,开发人员必需对嵌入在源代码中的文本、图片和消息进行修改,然后重新编译源代码。每当这个应用需要支持新的Locale时,就必需重复这些繁琐的步骤,这种做法显然大大降低了软件开发效率。

 

2 Web应用的中文本地化

无论时对Web应用的本地化还是国际化,都会涉及字符编码转换问题。当数据流的源与目的地使用不同的字符编码时,就需要对字符编码进行正确的转换。

 

2.1 处理HTTP请求数据编码

    默认情况下,IE浏览器发送请求时采用“ISO-8859-1字符编码,如果Web应用程序要正确地读取用户发送的中文数据,则需要进行编码转换。

    一种方法是在处理请求前,先设置HttpServletRequest对象的字符编码:

              request.setCharacterEncoding(“gb2312”);

    还有一种办法是对用户输入的请求数据进行编码转换:

              String clientData =request.getParameter(“clientData”);

              if(clientData != null)

                     clientData = newString(clientData.getBytes(“ISO-8859-1”),“GB2312”);

 

2.2 处理数据库数据编码

    如果数据库系统的字符编码为“GB2312,那么可以直接读取数据库中的中文数据,而无需进行编码转换。如果数据库字符编码为“ISO-8859-1,那么必需先对来自数据库的数据进行编码转换,然后才能使用。

 

2.3 处理XML配置文件编码

    如果在XML文件中包含中文,可以将XML文件的字符编码为“GB2312。这样,当Java程序加载和解析XML文件时无需再进行编码转换。

              <?xml version=’1.0’ encoding=”GB2312”?>

 

2.4 处理响应结果的编码

    可以通过以下方式来设置响应结果的编码:

       ·Servlet

              response.setContentType(“text/html;charset=GB2312”);

       ·JSP

              <%@ page contentType=”text/html;charset=GB2312” %>

       ·HTML

              <head>

       <META HTTP-EQUIV=”Content-Type”CONTENT=”text/html; charset=GB2312”>

              </head>

 

3 JavaI18N的支持

Java在其核心库中提供了支持I18N的类和接口。Struts框架依赖于这些Java I18N组件来实现对I18N的支持,因此,掌握Java I18N组件的使用方法有助于理解Struts应用的国际化机制。

3.1 Locale

    java.util.Locale类时最重要的Java I18N类,在Java语言中,几乎所有对国际化和本地化的支持都依赖于这个类。

Locale类的实例代表一种特定的语言和地区。如果Java类库中的某个类在运行时需要根据Locale对象来调整其功能,那么就称这个类是本地敏感的(Locale-Sensitive)。例如,java.text.DateFormat类就是本地敏感的,因为它需要依照特定的Locale对象来对日期进行相关的格式化。

Locale对象本身病不执行和I18N相关的格式化或解析工作。Locale对象仅仅负责向本地敏感的类提供本地化信息。例如,DateFormat类依据Locale对象来确定日期的格式,然后对日期进行语法分析和格式化。

创建Locale对象时,需要明确地指定其语言和国家代码。如:

       LocaleusLocale = new Locale(“en”, “

US”);

       LocalechLocale = new Locale(“ch”, “CH”);

构造方法的第一个参数是语言代码。语言代码由两个小写字母组成,遵从ISO-639规范。可以从http://www.unicode.org/unicode/onlinedat/languages.html中获得完整的语言代码列表。

构造方法的第二个参数是国家代码,它由两个大写字母组成,遵从ISO-3166规范。可以从http://www.unicode.org/unicode/onlinedat/countries.html中获得完整的国家代码列表。

Locale类提供了几个静态常量,他们代表一些常用的Locale实例。例如,如果要获得JapaneseLocale实例,可以使用如下两种方法之一:

       Localelocale1 = Locale.JAPAN;

       Localelocale2 = new Locale(“ja”, “JP”);

 

3.1.1 Web容器中Locale对象的来源

   Java虚拟机在启动时会查询操作系统,为运行环境设置默认的LocaleJava程序可以调用java.util.Locale类的静态方法getLocale()来获得默认的Locale

              LocaledefaultLocale = Locale.getDefault();

   Web容器在其本地环境中通常会使用以上默认的Locale;而对于特定的中断用户,Web容器会从HTTP请求中获取Locale信息。

 

3.1.2 Web应用中访问Locale对象

   在创建Locale对象时应该把语言和国家代码两个参数传递给构造方法。对于Web应用程序,通常不必创建自己的Locale实例,因为Web容器会负责创建所需的Locale实例。在应用程序中,可以调用HttpServletRequest对象的以下两个方法,来取得包含Web客户的Locale信息的Locale实例:

              publicjava.util.Locale getLocale();

              publicjava.util.Enumeration getLocales();

   着两个方法都会访问HTTP请求中的Accept-Language头信息。getLocale()方法返回客户优先使用的Locale,而getLocales()方法返回一个Enumeration集合对象,它包含了按优先级降序排列的所有Locale对象。如果客户没有配置任何LocalegetLocale()方法将会返回默认的Locale

 

3.1.3 Struts应用中访问Locale对象

   有序Web服务器并不和客户浏览器保持长期的连接,因此每个发送到Web容器的HTTP请求中都包含了Locale信息。Struts配置文件的<controller>元素的locale属性指定是否把Locale对象保存在session范围中,默认值为true,表示会把Locale对象保存在session范围中。在处理每一个用户请求时,RequestProcessor类都会调用它的processLocale()方法。

   尽管每次发送的HTTP请求都包含Locale信息,processLocale()方法把Locale对象存储在session范围中必需满足以下条件:

   · Struts配置文件的<controller>元素的locale属性为true

   · session范围内Locale对象还不存在。

   processLocale()方法把Locale对象存储在session范围中时,属性keyGlobals.LOCALE_KEY,这个常量的字符串值为“org.apache.struts.action.LOCALE”。

   如果应用程序允许用户在同一个会话中改变Locale,那么应该对每一个新的HttpServletRequest调用其getLocale()方法,来判断用户是否改变了Locale,如果Locale发生改变,就把新的Locale对象保存在session范围内。

   Struts应用程序中可以很方便地获取Locale信息。例如,如果在Action类中访问Locale信息,可以调用在Struts Action基类中定义的getLocale()方法。

   Action类的getLocale()方法调用RequestUtils.getUserLocale()方法。

   getUserLocale()方法先通过HttpServletRequest参数获得HttpSession对象,然后再通过HttpSession对象来读取Locale对象。如果存在HttpSession对象并且HttpSession中存储了Locale对象,就返回该Locale对象,否则就直接调用HttpServletRequestgetLocale()方法取得Locale对象,并降它返回。

   Web应用程序的其他地方也可以直接调用RequestUtils类的getUserLocale()方法来获取Locale对象。

 

3.2ResourceBundle

    java.util.ResourceBundle类提供存放和管理与Locale相关的资源的功能。这些资源包括文本域或按钮的Label、状态信息、图片名、错误信息和网页标题等。

    Struts框架并没有直接使用Java语言提供的ResourceBundle类。在Struts框架中提供了两个类:

    · org.apache.struts.util.MessageResources

    · org.apache.struts.util.PropertyMesasgeResources

    这两个类具有和ResourceBundle相似的功能,其中PropertyMessageResourcesMessageResources类的子类。

 

3.3 MessageFormat类和符合消息。

    JavaResourceBundleStrutsMessageResources类都允许使用静态和动态的文本。静态文本指定的是实现就已经具有明确内容的文本。动态文本指的是只有在运行时才能确定内容的文本。

    通常把包含可变数据的消息成为符合消息。符合消息允许在程序运行时把动态数据加入到消息文本中。着能够减少Resource Bundle中的静态消息数量,从而减少把静态消息文本翻译成其他Locale版本所花费的时间。

    当然,在Resource Bundle中使用符合信息会使文本的翻译变得更加困难。因为文本包含了直到运行时才知道的替代值,而把包含替代值的消息文本翻译成不同的语言时,往往要对语言做适当调整。

    Struts框架封装了MessageFormat类的功能,支持复合消息文本,该功能的实现对于Struts的其他组件是透明的。

 

4 Struts框架对国际化的支持

Struts框架对国际化的支持体现在能够输出何用户Locale相符合的文本何图片上。当Struts配置文件的<controller>元素的locale属性为true时,Struts框架把用户的Locale实例保存在session范围内,这样,Struts框架能自动根据这一Lcoale实例来从Resource Bundle中选择合适的资源文件。当用户的Locale为英文时,Struts框架就会向用户返回来自于application_en.properties文件的文本内容:当用户的Locale为中文时,Struts框架就会向用户返回来自于appcation_ch.properties文件的文本内容。

4.1 创建StrutsResource Bundle

    对于多应用模块的Struts应用,可以为每个子应用配置一个或多个Resource Bundle,应用模块中的ActionActionFormBeanJSP页和客户化标签都可以访问这些BundleStruts配置文件中的每个<message-resources>元素定义了一个Resource Bundle。当应用中包含多个Resource Bundle时,它们通过<message-resources>元素的key属性来区别。

Resource Bundle的持久化消息文本存储在资源文件中,其扩展名为“.properties”,这一文件中消息的格式为:key=value

在创建Resource Bundle的资源文件时,可以先提供一个默认的资源文件,默认资源文件应该取名为application.properties。如果应用程序需要支持中文用户,可以再创建一个包含中文消息的资源文件,文件名为:application_ch_CH.propertiesapplication_ch.properties

Struts框架处理Locale为中文的用户请求时,Struts框架首先在WEB-INF/classes/目录下寻找application_ch_CH.properties文件,如果存在该文件,就从该文件中获取文本消息,否则再一次寻找application_ch.propertiesapplication.properties文件。

应该总是为Resource Bundle提供默认的资源文件,这样,当不存在和某个Locale对应的资源文件时,就可以使用默认的资源文件。

应该把Resource Bundle的资源文件放在能被定位并加载的位置。对于Web应用程序,资源文件的存放目录为WEB-INF/classes目录。如果在配置Resource Bundle时还给定了包名,那么包名应该和资源文件所在的子目录对应。

4.2 访问 Resource Bundle

    Struts应用的每个Resource Bundleorg.apache.struts.util.MessageResources类(实际上是其子类PropertyMessageResources)的一个实例对应。MessageResources对象中存放了来自资源文件的文本。当应用程序初始化时,这些MessageResources实例被存储在ServletContext中(即application范围内),因此任何一个Web组件都可以访问它们。一个MessageResources对象可以包含多种本地化版本的资源文件的数据。

    Struts应用、子应用模块、Resource Bundle和资源文件之间存在以下关系:

    ·一个Struts应用可以有多个子应用模块,必须有且只有一个默认子应用模块。

    ·一个子应用模块可以有多个Resource Bundle,必须有且只有一个默认的Resource Bundle

    ·一个Resource Bundle可以有多个资源文件,必须有且只有一个默认资源文件。

 

    下面介绍在Struts应用中访问ResourceBundle的途径。

<!–[if !supportLists]–>1. <!–[endif]–>通过编程来访问Resource Bundle

Action积累中定义了getResources(request)方法,它可以返回默认的Message Resources对象,代表当前应用模块使用的默认Resource Bundle。如果要活得特定的MessageResources对象,可以调用Action基类的getResources(request, key)方法,其中参数keyStruts配置文件中的<message-resources>元素的key属性对应。得到了MessageResources对象后,就可以通过它的方法来访问消息文本。

Org.apache.struts.util.MessageResourcesgetMessage()方法有好几种重载形式,下面列出常用的几种:

·根据参数置顶的Locale检索对应的资源文件,然后返回和参数key对应的消息文本:

getMessage(java.util.Localelocale, java.lang.String key)

·根据参数指定的Locale检索对应的资源文件,然后返回和参数key对应的消息文本,args参数用于替换复合消息文本中的参数:

getMessage(java.util.Localelocale, java.lang.String key, java.lang.Object[] args)

·根据默认的Locale检索对应的资源文件,然后返回和参数key对应的消息文本:

getMessaeg(java.lang.Stringkey)

<!–[if !supportLists]–>2. <!–[endif]–>使用和Resource Bundle绑定的Struts组件

Struts框架中的许多内在组件和Resource Bundle是绑定在一起的,如:

· ActionMessage类和<html:errors>标签。

每个ActionMessage实例代表Resource Bundle中的一条消息。在调用ActionMessage的构造方法时,需要传递消息key

对于复合消息,在创建ActionMessage对象时,可以调用带两个参数的构造方法:ActionMessage(java.lang.String key, java.lang.Object[] values)values参数用户替换复合消息中的参数。

JSP页面中,使用<html:errors>标签,就能读取并显示ActionErrors集合中所有ActionMessage对象包含的消息文本。

· Struts Bean标签库的<bean:message>标签。

        Struts框架包含了一些可以访问应用的消息资源的客户化标签,其中使用最频繁的一个标签为<bean:message>标签。<bean:message>标签从应用的Resource Bundle中获取消息字符串。

        <bean:message>有一个bundle属性,用于指定被访问的Resource Bundle,它和<message-resources>元素的key属性匹配。如果没有设置bundle属性,将访问默认的ResourceBundle

·Validator验证框架中访问Resource Bundle

·在声明型异常处理中访问Resource Bundle

 

5 异常处理的国际化

在处理异常时,也应该考虑对I18N的支持。除非已经对应用抛出的异常消息做本地化,否则不应该直接向终端用户展示原始的异常消息。不懂Java语言的终端用户很难理解从Java虚拟机堆栈中抛出的Java异常消息,如果这些异常消息使用的不是用户的本地语言,那就更加会让用户困惑不解。

应该先把异常捕获,对异常消息进行本地化后,再把它展示给用户。StrutsResourceBundleActionMessage类可以完成这一功能。直接向终端用户显示Java异常绝对是不可取的。即使发生了无法恢复的系统错误,也应该向用户显示本地化的系统错误页。

 

6 小结

本文档介绍了软件的本地化与国际化的概念,然后消息介绍了对Struts应用实现国际化的原理和方法。与国际化密切相关的两个组件是LocaleResourceBundle

· Locale:包含了用户的本地化信息,如语言和国家。

· Resource Bundle:包含了多个消息资源文件,每个消息资源文件存放和一种Locale相对应的本地化消息文本。

Struts框架的初始化时,把ResourceBundle(即MessageResources对象)存储在application范围内;在响应用户请求时,把包装用户Locale信息的Locale实例存储在session范围内。Struts框架能自动根据这一Locale实例,从Resource Bundle中检索相应的资源文件,再从资源文件中读取本地化的消息文本。

Struts应用实现国际化应该遵循以下原则:

·尽量不在Servlet中使用含非英文字符的常量字符串。

·对于JSP文件,应该对page指令中的charset属性进行相应的设置。

·不要在JSP文件中直接包含本地化的消息资源,儿应该把消息资源存放在Resource Bundle的资源文件中。

·不比在每个JSPServlet中设置HTTP请求的字符编码,可以在Servlet过滤器中设置编码:

  HttpServletRequest.setCharaterEncoding(String encoding);

·尽量使用“UTF-8”作为HTTP请求和响应的字符编码,而不是“GBK”或“GB2312”。

·充分考虑底层数据库所使用的编码,它可能会给应用程序的移植带来麻烦。

Posted by micas on Jun 27th 2007 | Filed in dispatch action, struts | Comments (0)

Struts配置文件详解

Struts应用采用两个基于XML的配置文件来配置,分别是web.xml和struts-cofig.xml文件.web.xml文件是配置所有web应用的而struts-config.xml文件是struts专用的配置文件,事实上也是可以根据需要给这个配置文件起其他名称的.

        Web应用的发布描述文件:web应用发布描述文件可以在应用开着者,发布者和组装者之间传递配置信息,Web容器在启动的时候从该文件中读取配置信息,根据它来装载和配置web应用.文档类型定义DTD对XML文档的格式做了定义,DTD吧XML文档划分为元素,属性,实体每一种XML文档都有独自的DTD文件.可以从网上下载.<web-app>元素是web.xml的根元素,其他元素必须嵌入在<web-app>元素之内.要注意的是子元素也是有顺序的比如必须是首先<servlet>,然后<servlet-mapping>最后<taglib>.

        为Struts应用配置Web.xml文件:首先最重要的一步是配置ActionServlet,这个用<servlet>标签的servlet-name属性起一个名字叫action,然后用servlet-class属性指定ActionServlet的类.然后用<servlet-mapping>标签的servlet-name属性指定action,在用url-pattern指定接收范围是*.do的请求.不管应用中包含了多少子应用,都只需要配置一个ActionServlet,类来出来应用中的不同的功能,其实者就是不必要的,因为Servlet本身就是多线程的,而且目前Struts只允许配置一个ActionServlet.声明ActionServlet的初始化参数:<servlet>的<init-param>子元素用来配置Servlet的初始化参数.param-name设置config参数名.param-value设置struts-config.xml的路径参数值.

        配置欢迎使用清单:如果客户访问Web的时候值是访问了WEB应用的根目录URL.没有具体的指定文件,Web会自动调用Web的欢迎文件.<welcome-file-list>元素来配置的.通过其中的<welcome-file>欢迎页面</welcome-file>来配置.

        配置错误处理:尽管Struts框架功能强大的错误处理机制,但是不能保证处理所有的错误或者异常.当错误发生时,如果框架不能处理这种错误,把错误抛弃给Web容器,在默认的情况下web容器会想客户端返回错误信息.如果想避免让客户看到原始的错误信息,可以在Web应用发布描述文件中配置<error-page>元素.通过<error-code>404来定义错误的类型.然后通过<location>要处理错误的JSP页面来对错误进行处理.还可以用<exception-type>来设置异常,然后通过<location>来处理异常的JSP页面来处理异常.

        配置Struts标签库:这个就和以前学到的JSP自定义标签类似,配置元素为<taglib>来配置.<taglib-uri>这个指定标签库的uri,类似起一个名称.<taglib-location>这个是标签库的位置也就是实际所在的路径.通过这样的方法引入一个标签库,然后在前台JSP页面就可以通过自己定义的URI来调用标签.

        Struts配置文件:struts-config.xml文件.首先研讨一下org.apache.struts.config包,在struts应用启动的时候会把Struts配置文件信息读取到内存中,并把它们存放在config包中相关的JavaBean类的实例中.包中的每一个类都和struts配置文件中特定的配置元素对应,ModuleConfig在Struts框架中扮演了十分重要的角色,它是整个config包的核心,在Struts运行时来存放整个应用的配置信息.如果有多个子应用都会有一个ModuleConfig对象,它和Struts文件根元素的<struts-config>对应.根元素中包含<form-bean><action><forward>等元素.

        <struts-config>元素:时Struts配置文件的根元素,和它对应的配置类ModuleConfig类,<struts-config>元素有8个子元素.他们的DTD定义是data-sources?form-bean? global-exception?global-forwards?action-mapping?controller?message-resources?plug-in*在Struts配置文件中,必须按照DTD指定的先手顺序来配置<struts-config>元素的各个子元素,如果颠倒了这些子元素的顺序,会产生错误.

        <data-sources>元素:用来配置应用所需要的数据源,数据源负责创建和特定的数据库的连接.许多数据源采用连接池的机制实现.以便提高数据库访问的性能.JAVA语言提供了javax.sql.DataSource接口,所有的数据源都必须实现这个接口.许多应用服务器和Web服务器都提供了数据源组件.很多数据库厂商也提供了数据源的实现.<data-sources>元素包含多个<data-source>子元素永远配置特定的数据源.他们可以包含多个<set-property>子元素用于设置数据源的各种属性.配置了数据源以后,就可以在Action类中访问数据源,在Action中定义了getDataSource(HttpRequest)方法,用于获取数据源对象的引用.然后可以利用DataSource对象调用getConnection获取一个连接对象对数据库进行操作.在配置文件中声明多个数据源的时候需要为每一个数据源分配唯一的Key值,通过这个来表示特定的数据源.获取特定的数据源的时候可以用dataSource = getDataSource(reqeust,”A”);

        <form-beans>元素:用来配置多个ActionForm,包含一个或者N个<form-bean>子元素.每个<form-bean>元素都包含多个属性.className指定和<form-bean>匹配的类.name指定该ActionForm的唯一标识符,这个属性是必须的以后作为引用使用.type指定ActionForm类的完整类名,这个属性也是必须的.注意包名也要加上.<form-property>是指定动态的Form的元素,以后会深入了解.

        <global-exception>元素:用于配置异常处理,元素可以包含一个或者多个<exception>元素,用来设置JAVA异常和异常处理类ExceptionHandler之间的映射.className指定和元素对应的配置类,默认的不用动.handler指定异常处理类默认是ExceptionHandler.key指定在本地资源文件中异常的消息Key,path指定当前异常发生的时候转发的路径.scope指定ActionMessages实例存放的范围.type指定需要处理异常类的名字,必须的.bundle指定Resource Bundle.

        <global-forwards>元素:用来声明全局转发,元素可以有一个或者N个<forward>元素组成,用于把一个逻辑名映射到特定的URL,通过这种方法Action类或者JSP页面无需指定URL,只要指定逻辑名称就可以实现请求转发或者重定向.这样可以减少控制组件和视图的聚合.易于维护.className对应的配置类.contextRelative如果为true表示当path属性以/开头的时候,给出的是对应的上下文URL默认是false.name转发路径的逻辑名,必须写.path转发或者重定向的URL,必须写必须是以/开头.redirect设置为true的时候表示执行重定向操作,此项为false的时候,表示执行请求转发操作.重定向与请求转发的区别以后就是重定向是把请求生成应答给客户端然后在重新发送给定向的URL,浏览器地址栏会有显示.而转发就是直接把请求转发给本应用的另一个文件,不生成应答所以客户端IE没显示.

        <action-mapping>元素:包含一个或者N个<action>元素,描述了从特定的请求路径到响应的Action的映射.在<action>元素中可以包含多个<exception>和<forward>子元素,他们分别配置局部异常处理和局部转发.attribute设置Action关联的ActionForm在request或者session范围内的key.就是在request或者session共享内的名称.className对应配置元素的类.默认的是ActionMapping.forward指定转发URL路径include指定包含URL路径.input指定包含表单的URL,当表单验证失败的时候发送的URL.name,指定和该Action关联的Form名字.该名字必须是在form-bean中定义过的,可写可不写.path必须/开头的方位Action的路径.parameter指定Action配置参数.在Action的execute()方法中可以调用ActionMapping的getParameter()方法来读取匹配的参数.roles指定允许调用该Action的安全角色,多个角色之间逗号格开.scope指定Form的存在范围.默认是session.tyep指定Action的完整类名.unknown如果是true表示可以处理用户发出的所有的无效的ActionURL默认是false.validate指定是否调用ActionForm的validate方法.

        <controller>元素:用于配置ActionServlet.buffreSize指定上载文件的输入缓冲大小.该属性为可选默认4096.className指定元素对应的配置类,ControllerConfig.然后是contentType指定响应结果内容类型和字符编码,该属性为可选,默认是text/html如果在Action或者JSP网页也设置了类型内容,会覆盖这个.locale指定是否把Locale对象保存到当前用户的session中默认false.tempDir指定处理文件上载的临时工作目录.nochache如果是true在响应结果中加入特定的头参数.

        <message-resources>元素:用来配置Resource Bundle.用于存放本地文本消息文件.className元素对应的配置类.MessageResourcesConfig.factory指定消息的工厂类.key指定文件存放的Servlet对象中采用的属性Key.null指定如何处理未知消息.parameter指定消息的文件名.

<plug-in>元素:用于配置Struts插件.

配置多应用模块:所有的子应用都可以共享同一个ActionServlet实例,但是每个子应用都有单独的配置文件.把应用划分为多个子应用模块.首先为每个应用创建单独的Struts配置文件,在web.xml的ActionServlet配置代码中添加几个子应用信息.采用<forward>元素来实现应用之间的切换.

        Digester组件:是一个Apache的另一个开源代码项目.当Struts被初始化的时候,首先会读取并解析配置文件,框架采用Digester组件来且西配置文件.然后创建config包中的对象.者对象用于存放配置信息.

        其实配置文件不难,只要都理其中的原理就OK了.真正实际的项目开发中,采用的工具例如Eclipse系列,提供了相应的插件,在创建一个Struts工程的时候配置文件的标签都是自动生成的,而我们只需要往里面填写属性就OK了.

Posted by micas on Jun 27th 2007 | Filed in dispatch action, struts | Comments (0)

Use smart forwarding to dispatch actions

A  key component to the Struts framework is the ActionForward. A deceptively simple object, all the ActionForward does is associate a system path with a logical name. But this object gives the framework much of its power and flexibility.

The workhorse of the framework are the Action objects. When an Action completes, it returns an ActionForward to the controller. The controller then passes control to whatever path is indicated by the ActionForward. Most times, the Action doesn’t know anything about the path. It just knows to forward to “success” or “failure”. Of course some Actions are more flexible and might return any number of different forwards depending on the circumstances. An Action could start by retrieving a record and then forward to “copy” or “delete”, depending on what the user requested.

There’s a similar situation when an application processes a form. Most forms contain a number of fields that you can enter or edit, with an OK or CANCEL button. But other times, we might want to do several other things, like “DELETE” or “SAVE AS NEW”. We could use a DispatchAction (Tip #2), but that requires making changes to how an Action is coded.

The Struts framework already provides ActionForwards to describe how control flows through the application. An action mapping can have any number of local forwards. So it follows that we should be able to use local forwards to dispatch control. Each of the local forwards can represent one of the form operations. So, for example, the “copy” forward can send control to the copy action, and the “delete”forward can send control to the delete action. Something like this

<action name=”articleForm” path=”/do/article/Submit” … >
    <forward name=”create” path=”/do/article/Create”/>
    <forward name=”save” path=”/do/article/Store”/>
    <forward name=”delete” path=”/do/article/Recycle”/>
    <forward name=”cancel” path=”/do/Menu”/>
</action>

A HTML form can only submit to one location, so we have to start with a single “clearinghouse” action that can be the target of the form. This action needs to be able to determine the operation, and then send control to the appropriate local forward. This is very similar to what the DispatchAction does. It looks up a parameter in the request and uses the value of that parameter to select a method.

Doing the same thing in an action takes a single line in the perform() [or execute()] method:

public final class RelayAction extends Action {
    public ActionForward perform(
        ActionMapping mapping,
        ActionForm form,
        HttpServletRequest request,
        HttpServletResponse response) throws IOException, ServletException {

        return mapping.findForward(request.getParameter(Tokens.DISPATCH));

    }
} // end RelayAction

So, if we have a parameter like

    dispatch=delete

Our RelayAction will look up the “delete” forward and return it to the controller. Working from the example, this would cause the controller to forward to /do/article/Recycle. The Recycle action would then fire as if it had been the target of the action in the first place.

In the form, we can use the same JavaScript function as we used for the DispatchAction in Tip #2:

<html:hidden property=”dispatch” value=”error”/>
 function set(target){document.forms[0].dispatch.value=target;}
<html:submit onclick=”set(’save’);”>SAVE</html:submit>
<html:submit onclick=”set(’create’);”>SAVE AS NEW</html:submitl>
<html:submit onclick=”set(’delete);”>DELETE</html:submit>

This sets the dispatch property to whatever button the user presses, which in turn selects the corresponding ActionForward for the mapping.

Since the RelayAction is configured through its action-mapping, it can be used as many times as needed by your application without subclassing.

In the next tip, we will look at ways to use RelayAction and some like-minded friends to build config-based menu systems.

HTH, Ted.

Posted by micas on Jun 27th 2007 | Filed in dispatch action, struts | Comments (0)

Use LookupDispatchAction for a JavaScript-free dispatch

Struts Tip #3 - Use LookupDispatchAction for a JavaScript-free dispatch

Many forms can be put to multiple uses. The fields we need to create a record are usually the same fields we use to update a record. Likewise, the same page can usually used to identify a record to delete, copy, or update a record. 

A DispatchAction (see Tip #2) is a convenient way to collect related operations like this into a single Struts Action class. A field in the request identifies the operation, and so all the developer has to do is get the right value into the field. The simplest way to do this is to label the buttons for the operation, and give each button the same name. But that ties the presentation label to the business operation and confounds localization. 

As an alternative, most developers use a JavaScript attached to the button to update the dispatch field, which in practice works quite well. If JavaScript is not enabled, the submit fails gracefully, and in practice most applications do rely JavaScript for essential operations. 

If JavaScript is not an option, another good alternative is the LookupDispatchAction [org.apache.struts.actions.LookupDispatchAction]. This object is a little more work to setup that the original DispatchAction, but lets you use this technique without using JavaScript as a crutch. 

As with the DispatchAction, the first step is to indicate the name of the dispatch parameter in the action-mapping element. In this case the parameter will the name given to each of the buttons, rather than a hidden field. Let’s just call our buttons “submit”, which is the default name the html:submit tag will use. 

<action path=”/test”
type=”org.example.MyAction”
name=”MyForm”
scope=”request”
input=”/test.jsp”
parameter=”submit”/>

In our JSP, we can refer to the buttons in the usual way 

<html:form action=”/test”>
<html:submit>
<bean:message key=”button.add”/>
</html:submit>
<html:submit>
<bean:message key=”button.delete”/>
</html:submit>
</html:form>

Later, when the user selects a button, the form will pass the submit parameter, along with whatever message is Struts finds for “button.add” or “button.delete” in the resource bundle. In a localized application, this message could vary by the user’s selected locale. 

Using the conventional DispatchAction, this approach would problematic, since there would have to a Java method named for each message, and not all the messages might valid Java identifiers. This is where the magic of the LookupDispatchAction comes into play. 

When you create your LookupDispatchAction subclass, along with the methods for the dispatch operations (see Tip #20) you must also implement a getKeyMethodMap method. This is a “hotspot” that the LookupDispatchAction will call. 

Here’s an example of the methods you might declare in your subclass:

protected Map getKeyMethodMap(ActionMapping mapping,
    ActionForm form,
    HttpServletRequest request) {
  Map map = new HashMap();
  map.put(”button.add”, “add”);
  map.put(”button.delete”, “delete”);
  return map;
}

public ActionForward add(ActionMapping mapping,
    ActionForm form,
    HttpServletRequest request,
    HttpServletResponse response)
  throws IOException, ServletException {
  // do add
  return mapping.findForward(”success”);
}

public ActionForward delete(ActionMapping mapping,
    ActionForm form,
    HttpServletRequest request,
    HttpServletResponse response)
    throws IOException, ServletException {
  // do delete
  return mapping.findForward(”success”);
}

Internally, the base action will lookup the messages for button.add and button.delete, and match those against the submit parameter. When it finds a match, it will then use either “add” or “delete” to call the corresponding methods. 

So while the LookupDispatchAction means adding an extra method to your Action, it lets you skip putting a JavaScript in your form.

Both the DispatchAction and LookupDispatchAction are an excellent way to streamline your Struts action classes, and group several related operations into a single umbrella action. 

So, how many dispatch actions do you need? Can you use a dispatch action to collect everything into a single action?

Most often not. In practice, you can easily use one dispatch action for any forms that share a common validation. Sharing a dispatch action between different form beans, or form beans that are validated differently, can start to make things harder rather than simpler. But the use of a dispatch action can easily half or quarter the number of action classes in most Struts application. 

HTH, Ted.

Credits. The LookupDispatchAction was originated by Erik Hatcher, one of Struts many volunteer contributors. The examples used in this tip relied heavily on Erik’s documentation. (Yeah reuse! Yeah JavaDocs!)

Posted by micas on Jun 27th 2007 | Filed in dispatch action | Comments (0)

新手用Dispatch Action

本以为通过简单的在Action中加入if else判断语句,就能够在Struts中实现不同的参数对应不同的操作,谁知道原来在Servlet能够实现的方法,却在Struts中行不通。听老师说,要通过分发Action,也就是Dispatch Action来实现。之前我开会还跟我们组的人说要用ifelse对参数进行判断呢,汗,这下算是误导他们了。下次开会的时候跟他们再细说吧。 
 
  DispatchAction其实是个非常有用的东西,可以大幅缩减Action的数量。比如说我在Struts-config.xml中建立了下面这样一个action配置: 
Copy code
<action path=”/ManageUser” name=”userProfileForm” type=”com.buptsse.actions.ManageUserAction” scope=”request” validate=”true” input=”/admin/userlist.jsp” parameter=”method”> 
  <forward name=”userprofile” path=”/admin/userprofile.jsp”/> 
  <forward name=”userdel” path=”/admin/info.jsp”/> 
  <forward name=”useradd” path=”/admin/useradd.jsp”/> 
  <forward name=”userselect” path=”/admin/userselect.jsp”/> 
</action>[/action] 
  注意其中的parameter参数,他的值是method。然后下面的是多个要转发的页面。
 
  然后我们在ManageUserAction中: 
[code]public class ManageUserAction extends DispatchAction { 
  public ActionForward edit(ActionMapping mapping, ActionForm form, 
      HttpServletRequest request, HttpServletResponse response) 
      throws BusinessException { 
    DynaActionForm frm = (DynaActionForm) form; 
    try { 
        return mapping.findForward(”userprofile”); 
      }else { 
        return null; 
      } 
    } catch (Exception e) { 
    } 
  } 
  public ActionForward add(ActionMapping mapping, ActionForm form, 
      HttpServletRequest request, HttpServletResponse response) 
      throws BusinessException { 
    try { 
      return mapping.findForward(”useradd”); 
    } catch (Exception e) { 
    } 
    return null; 
  } 
  public ActionForward del(ActionMapping mapping, ActionForm form, 
      HttpServletRequest request, HttpServletResponse response) 
      throws BusinessException { 
    try { 
      return mapping.findForward(”userdel”); 
    } catch (Exception e) { 
    } 
    return null; 
  } 
  public ActionForward select(ActionMapping mapping, ActionForm form, 
      HttpServletRequest request, HttpServletResponse response) 
      throws BusinessException { 
    try { 
      return mapping.findForward(”userselect”); 
    } catch (Exception e) { 
    } 
    return null; 
  } 
}
 
  注意本action一定要继承自DispatchAction。接下来我们定义了几个不同的方法,例如profile、add等,那么,我们只要在页面中按照“ /ManageUserAction.do?method=profile ”这样的格式,就能够访问这一个action中的不同的方法,从而达到用这一个action代替多个action的目的,也使得代码的维护变得非常的方便。

Posted by micas on Jun 27th 2007 | Filed in dispatch action, struts | Comments (0)

关于Dispatch Action

Struts 的 Dispatch Action 已經行之有年了,有 Dispatch, Lookup, MappingDispatch… 好幾種可以用。但設定方法都很囉嗦 (網頁,程式,struts-config三者都要 hard-code String 在上面),而且適用的了 A ,就不能用在 B。例如雖然 DispatchAction 可以寫在 URL 上: /foo.do?method=saveOrder ,但是遇到一個 form 需要利用多個 submit button dispatch 時,就不行了。你得轉用 LookupDispatch,可是 LookupDispatch 又需要在 Action 定義一個很醜的 getKeyMethodMap() (裡面全部是 hard-code string)。接下來要分享一個通用 URL/submit button/struts-config.xml 三者設定的 SimpleDispatchAction:

/*
* created on 2005/4/23
*
* $Author: ingram $
* $Revision: 1.5 $
* $Date: 2005/05/13 02:09:21 $
*/
package org.bioinfo.util.struts;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

/**
* 簡化的 DispatchAction,並支援Spring的 ActionSupport, 本 class 可以 *獨自* 使用,不需搭配
* SimpleDispatchActionForm。
*
* 使用方法:
*
* 繼承此 class,並定義各個 action method,比方說有兩個 method 分別做儲存和刪除:
*
* <pre>
* public ActionForward save(ActionMapping mapping, ActionForm form,
* HttpServletRequest request, HttpServletResponse response)
* throws Exception {
* // orderService.save(….)
* }
*
* public ActionForward delete(ActionMapping mapping, ActionForm form,
* HttpServletRequest request, HttpServletResponse response)
* throws Exception {
* // orderService.delete(….)
* }
* </pre>
*
* 有三種方式可以設定 dispatch 到哪個 method 上。
*
* (1) submit button 法,直接寫在 submit 的 property 上:
*
* <code>
* <html:form action=”/some/work”>
* …. some thing ….
*
* <html:submit property=”dispatch=save” value=”儲存”/>
* <html:submit property=”dispatch=delete” value=”刪除”/>
* </html:form>
*
* 或者如果需要 i18n 的話,可以用 bean:message:
*
* <html:form action=”/some/work”>
* <html:submit property=”dispatch=save” >
* <bean:message key=”button.save” />
* </html:submit>
* <html:submit property=”dispatch=delete” >
* <bean:message key=”button.delete” />
* </html:submit>
* </html:form>
* </code>
*
* 注意 property 裡面的值是 ‘dispatch=xxxx’ 記得要寫等號與 method 名稱,而且大小寫要對,不能空白。當網頁按下 “儲存”
* 時,則會執行 /some/work.do 的 save(…) 的 method。 如果按下 “刪除” 則執行 delete(…),
*
* 建議 — 這種寫法通常是用在一個 Action 有多個 dispatch method,而每個 method 都共用同個 ActionForm
*
* (2) URL 法,接在 URL 後面:
*
* <code>
* <html:form action=”/some/work?dispatch=save”>
* 或是用 link 也可以
* <html:link action=”/some/work?dispatch=save” />
* </code>
*
* 建議 — 通常用在不需要 ActionForm 的 Action,或者是要將 submit button 法寫成 url 時使用。
*
* (3) struts-config 法,直接寫死在 parameter=’dispatch=foo’ 上
*
* <code>
* <action
* path=”/saveOrder”
* name=”SaveOrderForm”
* type=”antar.order.web.OrderDispatchAction”
* parameter=”dispatch=save” >
* </action>
* <action
* path=”/deleteOrder”
* name=”DeleteOrderForm”
* type=”antar.order.web.OrderDispatchAction”
* parameter=”dispatch=delete” >
* </action>
* </code>
*
* 建議 — 這種寫法通常是為了讓 Action 中每個 dispatch method 使用不同的 ActionForm。一旦寫死在
* struts-config 裡,該 mapping 的 path 就不能與 URL 法 或是 submit button 法同時使用。
*
* 最後請注意同一個 request 上,URL 法不能與 submit button 法同時使用 (但可以連續 forward):
*
* <code>
* ………..錯誤範例………..
* <html:form action=”/some/work?dispatch=save”>
* …. some thing ….
* <html:submit property=”dispatch=delete” value=”刪除”/>
* </html:form>
* </code>
*
* @author ingram
*
*/
public abstract class SimpleDispatchAction extends Action{

private static final String SIMPLE_DISPATCH_ACTION_KEY = “__SIMPLE_DISPATCH_ACTION_KEY”;

private DispatchMethodHelper dispatchMethodHelper = new DispatchMethodHelper(
SIMPLE_DISPATCH_ACTION_KEY);

private Class clazz = this.getClass();

private Class[] argTypes = new Class[] { ActionMapping.class,
ActionForm.class, HttpServletRequest.class,
HttpServletResponse.class };

private Map dispatchMethods = new HashMap();

public final ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {

String methodName = dispatchMethodHelper
.getMethodName(request, mapping);

Method method = null;
try {
Object[] args = { mapping, form, request, response };
method = obtainDispatchMethod(methodName);
return (ActionForward) method.invoke(this, args);
} catch (NoSuchMethodException e) {
throw dealWithMethodProblem(methodName, e);
} catch (IllegalAccessException e) {
throw dealWithMethodProblem(methodName, e);
} catch (InvocationTargetException e) {
if (e.getCause() instanceof Exception) {
throw (Exception) e.getCause();
} else {
throw new RuntimeException(
“invoke ” + methodName + “() failed”,
(e.getCause() != null ? e.getCause() : e));
}
}
}

private RuntimeException dealWithMethodProblem(String methodName,
Exception e) {
return new RuntimeException(
“can not access dispatching method:[”
+ methodName
+ “]. possible cause is :\n”
+ “(1) wrong parameter value, the correct format is ‘dispatch=methodName’ \n”
+ ” or\n”
+ “(2) wrong method in your DispatchAction, it should like \n”
+ ” public ActionForward methodName(ActionMapping,ActionForm,HttpServletRequest,HttpServletResponse)\n\n”,
e);
}

private Method obtainDispatchMethod(String methodName)
throws NoSuchMethodException {
Method method = (Method) dispatchMethods.get(methodName);
if (method == null) {
method = clazz.getMethod(methodName, argTypes);
dispatchMethods.put(methodName, method);
}
return method;
}

}

Posted by micas on Jun 27th 2007 | Filed in dispatch action | Comments (0)

DispatchAction的用法

Any software application is defined by the things it can do for you. In a Struts Web application, the things an application does is usually defined by its action-mapping elements. An action-mapping is designed to be the target of an HTML form, and is often used with hyperlinks as well.

Each action-mapping can specify a Struts Action class as its handler. In larger applications, developers can find themselves managing dozens or even hundreds of Action classes.

In practice, many of these Action classes handle related operations, often evidenced by their name. A package might include separate RegCreate, RegSave, and RegDelete Actions, which just perform different operations on the same RegBean object. Since all of these operations are usually handled by the same JSP page, it would be handy to also have them handled by the same Struts Action.

A very simple way to do this is to have the submit button modify a field in the form which indicates which operation to perform.

<html:hidden property=”dispatch” value=”error”/>
<SCRIPT>function set(target) {document.forms[0].dispatch.value=target;}</SCRIPT>
<html:submit onclick=”set(’save’);”>SAVE</html:submit>
<html:submit onclick=”set(’create’);”>SAVE AS NEW</html:submitl>
<html:submit onclick=”set(’delete);”>DELETE</html:submit>

Then, in the Action you can setup different methods to handle the different operations, and branch to one or the other depending on which value is passed in the dispatch field.

String dispatch = myForm.getDispatch();
if (”create”.equals(dispatch)) { …
if (”save”.equals(dispatch)) { …

The Struts Dispatch Action [org.apache.struts.actions] is designed to do exactly the same thing, but without messy branching logic. The base perform method will check a dispatch field for you, and invoke the indicated method. The only catch is that the dispatch methods must use the same signature as perform. This is a very modest requirement, since in practice you usually end up doing that anyway.

To convert an Action that was switching on a dispatch field to a DispatchAction, you simply need to create methods like this

public ActionForward create(
    ActionMapping mapping,
    ActionForm form,
    HttpServletRequest request,
    HttpServletResponse response)
  throws IOException, ServletException { …

public ActionForward save(
    ActionMapping mapping,
    ActionForm form,
    HttpServletRequest request,
    HttpServletResponse response)
  throws IOException, ServletException { …

Cool. But do you have to use a property named dispatch? No, you don’t. The only other step is to specify the name of of the dispatch property as the “parameter” property of the action-mapping. So a mapping for our example might look like this:

<action
  path=”/reg/dispatch”
  type=”app.reg.RegDispatch”
  name=”regForm”
  scope=”request”
  validate=”true”
  parameter=”dispatch”/>

If you wanted to use the property “o” instead, as in o=create, you would change the mapping to

<action
  path=”/reg/dispatch”
  type=”app.reg.RegDispatch”
  name=”regForm”
  scope=”request”
  validate=”true”
  parameter=”o”/>

Again, very cool. But why use a JavaScript button in the first place? Why not use several buttons named “dispatch” and use a different value for each?

Posted by micas on Jun 27th 2007 | Filed in dispatch action | Comments (0)