(转帖)Struts-menu源码分析

(转自:http://champion.ewuxi.com/old/opensource/struts-new/strutsmenu.htm)

Struts-menu源码分析

        好的代码读起来让人如饮醍醐,读完以后神清气爽。如果你想提高你的编程水平,如果你想提高你的设计能力,如果你也想成为大师,那么就去阅读代码吧。以本人十几年来的编程经验,阅读代码能让你得到的比阅读文章(那怕是大师的文章)得到的更多。优秀而且实用的代码有很多,比如Junit,比如Jive,比如petStore,甚至是tomcat的Example、Log4j的Example。

        一段广告完毕,下面就为大家分析一下struts-menu的源码,作为送给大家的圣诞礼物吧。Struts-Menu也来自一位大师的作品, Matt Raible。有很多优秀的作品,比如使用struts和hibernate的struts-resume。官方网站是http://raibledesigns.com/wiki/Wiki.jsp?page=Main。Struts-Menu的最新版本是2.1。功能是使用struts技术,构建树形菜单。应该说是一个非常实用的技术,极大的方便了广大的开发人员。与此同时,个人认为它的作用还不止于些。比如,同时它也是一个使用Maven和velocity的一个很好的例子。

        首先,我们去看一下它的效果。http://www.raibledesigns.com/struts-menu/。可以看到,如此丰富多彩的菜单效果,都是在演示一个配置文件里的内容。这是一个非常好的数据与表示相分离的实现。我们打开它的源码来看。首先看一下它的包图

共有五个包,其中menu自然是完成数据组织功能,是核心之一,displayer是显示方式包,完成数据显示大部分功能。也是核心之一。taglib意义明显。example自然是一些example。util是读取资源文件的包。因些,我们重点研究的包只有三个menu,displayer和taglib。

首先我们来看menu包的类图

首先是MenuPlugIn这个类。这个类的功能很明显,就是一个struts的plug-in。可以看到,它只有一个参数menuConfig,就是menu的配置文件路径。果然,在struts-conf文件中有这么一段

  <!-- ========== Plug Ins Configuration ================================== -->

<plug-in className="net.sf.navigator.menu.MenuPlugIn">

<set-property property="menuConfig" value="/WEB-INF/menu-config.xml"/>

</plug-in>

 

 

说明配置文件来自于/WEB-INF/menu-config.xml,当然,我们可以找到相应路径下找到这个文件。如果你以前没有做过struts的plug-in,现在该知道怎么做了吧,就这么简单。通过阅读初始化函数,知道它的功能就是调用MenuRepository来建立菜单。因此。我们知道MenuRepository必然是一个组织管理管理菜单的组织类。

public void init(ActionServlet servlet, ModuleConfig config)

throws ServletException {

if (log.isDebugEnabled()) {

log.debug("Starting struts-menu initialization");

}

this.servlet = servlet;

repository = new MenuRepository();

repository.setLoadParam(menuConfig);

repository.setServlet(servlet);

try {

repository.load();

servlet.getServletContext().setAttribute(

MenuRepository.MENU_REPOSITORY_KEY,

repository);

if (log.isDebugEnabled()) {

log.debug("struts-menu initialization successfull");

}

} catch (LoadableResourceException lre) {

throw new ServletException(

"Failure initializing struts-menu: " + lre.getMessage());

}

}

 

打开MenuRepository类,我们可以看到这个类也很简单,不过已经有少可以学习的了。首先是FastHashMap,可以看到,这个类里有三个FastHashMap。顾名思议,是快速HashMap了,再看一下,它来自org.apache.commons.collections.FastHashMap;。看到org.apache.commons这个著名的包了?如果你以前从没使用过它,那么建议你花上一段时间去研究使用它,我保证物有所值。

protected FastHashMap menus = new FastHashMap();

protected FastHashMap displayers = new FastHashMap();

protected FastHashMap templates = new FastHashMap();

接下来我们看到log的定义。对了,log,调试的核心之一。而下面这一句则是commons log的最常用的使用方法。快快让你的程序使用上commons log吧,第一,它功能强大,第二,它使用简单,就是这么简单。

private Log log = LogFactory.getLog(getClass().getName());

下面看一个的函数

 protected Digester initDigester() {

        Digester digester = new Digester();

        digester.setClassLoader(Thread.currentThread().getContextClassLoader());

        digester.push(this);

        //digester.setDebug(getDebug());

        // 1

        digester.addObjectCreate("MenuConfig/Menus/Menu",

            "net.sf.navigator.menu.MenuComponent", "type");

        digester.addSetProperties("MenuConfig/Menus/Menu");

        digester.addSetNext("MenuConfig/Menus/Menu", "addMenu");

        // 2

        digester.addObjectCreate("MenuConfig/Menus/Menu/Item",

            "net.sf.navigator.menu.MenuComponent", "type");

        digester.addSetProperties("MenuConfig/Menus/Menu/Item");

        digester.addSetNext("MenuConfig/Menus/Menu/Item", "addMenuComponent",

            "net.sf.navigator.menu.MenuComponent");

        // 3       

        digester.addObjectCreate("MenuConfig/Menus/Menu/Item/Item",

            "net.sf.navigator.menu.MenuComponent", "type");

        digester.addSetProperties("MenuConfig/Menus/Menu/Item/Item");

        digester.addSetNext("MenuConfig/Menus/Menu/Item/Item",

            "addMenuComponent", "net.sf.navigator.menu.MenuComponent");

        // 4

        digester.addObjectCreate("MenuConfig/Menus/Menu/Item/Item/Item",

            "net.sf.navigator.menu.MenuComponent", "type");

        digester.addSetProperties("MenuConfig/Menus/Menu/Item/Item/Item");

        digester.addSetNext("MenuConfig/Menus/Menu/Item/Item/Item",

            "addMenuComponent", "net.sf.navigator.menu.MenuComponent");

        // 5

        digester.addObjectCreate("MenuConfig/Menus/Menu/Item/Item/Item/Item",

            "net.sf.navigator.menu.MenuComponent", "type");

        digester.addSetProperties("MenuConfig/Menus/Menu/Item/Item/Item/Item");

        digester.addSetNext("MenuConfig/Menus/Menu/Item/Item/Item/Item",

            "addMenuComponent", "net.sf.navigator.menu.MenuComponent");

        // 6

        digester.addObjectCreate("MenuConfig/Menus/Menu/Item/Item/Item/Item",

            "net.sf.navigator.menu.MenuComponent", "type");

        digester.addSetProperties("MenuConfig/Menus/Menu/Item/Item/Item/Item");

        digester.addSetNext("MenuConfig/Menus/Menu/Item/Item/Item/Item",

            "addMenuComponent", "net.sf.navigator.menu.MenuComponent");

        // 7

        digester.addObjectCreate("MenuConfig/Menus/Menu/Item/Item/Item/Item",

            "net.sf.navigator.menu.MenuComponent", "type");

        digester.addSetProperties("MenuConfig/Menus/Menu/Item/Item/Item/Item");

        digester.addSetNext("MenuConfig/Menus/Menu/Item/Item/Item/Item",

            "addMenuComponent", "net.sf.navigator.menu.MenuComponent");

        digester.addObjectCreate("MenuConfig/Displayers/Displayer",

            "net.sf.navigator.displayer.MenuDisplayerMapping", "mapping");

        digester.addSetProperties("MenuConfig/Displayers/Displayer");

        digester.addSetNext("MenuConfig/Displayers/Displayer",

            "addMenuDisplayerMapping",

            "net.sf.navigator.displayer.MenuDisplayerMapping");

        digester.addSetProperty("MenuConfig/Displayers/Displayer/SetProperty",

            "property", "value");

           

        return digester;

    }

 

这里又是一个经典,digester,Digester的使用,如果你需要读一个XML配置文件,并且不想与DOM直接打交道的话,Digester将是一个很好的选择。实际上我们看到load函数调用一句 digester.parse(input);就已经把menu-config.xml建立到内存里了,就这么简单。如果你想要初始化你的系统,这种方法是不是可以学习呢?"工欲善其事,必先利其器"。我们可以看到Raible是怎么样利用现有的工具来减轻开发量的。

由于MenuRepository举重若轻的初始化过程,甚至都没有让我们看到树形结构是怎么建立到内存里去的。不过不要着急,类图给我们了明示。

看到MenuBase类了吗?对了,看名字就知道是一个Menu的基类。可以看到,它是一个简单的JavaBean。而且相信它的每个属性大家根据名字也能猜出来。所以重点讲解是MenuComponent,一个简化的 "Composite"模式。

如上图所示。由于此处的Leaf没有任何方法,只有属性。因此Leaf和Composite收缩成了一个MenuComponent类。大家都知道,Composite模式是实现树形结构最好的方法。如果你以前没有机会实现或者没有从Composite模式得到好处,那么,从这里看一下用Composite模式得到的好处。首先看它的简单,MenuComponet的实际代码很少,加起来不到十行。

public void addMenuComponent(MenuComponent menuComponent) {

        menuComponents.add(menuComponent);

        menuComponent.setParent(this);

        if ((menuComponent.getName() == null) ||

                (menuComponent.getName().equals(""))) {

            menuComponent.setName(this.name + menuComponents.size());

        }

    }

    public MenuComponent[] getMenuComponents() {

        MenuComponent[] menus =

            (MenuComponent[]) menuComponents.toArray(_menuComponent);

        return menus;

    }

 

如果你用十行来实现一个树型结构(并且还是通用的),你愿不愿意?就是通过简单的这么一些代码,实现的在内存中建立树型结构的目标。

下面我们来看DispLay包,这个包的功能也是很清楚的,就是用来显示啦。这个包的类图非常漂亮,遗憾的是也非常大。只能缩小了给大家看了。

从类图中可以看到一个非常极漂亮的面象对象的设计思路。通过一个接口,利用模板方法。最后具体实现树型结构的显示。其主要方法是displayComponents和display这两方法,init方法则实现了初始化的工作,读取javascript和图片等文件。displayComponents是一个迭代函数。从而可以遍历一个MenuCompont树。并将其显示出来。

应该说,Menu包是一个M层,而Dispplya包是一个view层,而加上TagLib包,就实现了MVC的完整结构。

两个Tag类很清楚,首先我们从怎么使用它来看它们实现的功能

<menu:useMenuDisplayer name="ListMenu"

bundle="org.apache.struts.action.MESSAGE">

        <menu:displayMenu name="ToDoListMenuFile"/>

        <menu:displayMenu name="ToDoListMenuEdit"/>

        <menu:displayMenu name="CaseDetailMenuCase"/>

        <menu:displayMenu name="Standalone"/>

</menu:useMenuDisplayer>

显而易见。useMenuDisplayer这个类是实现使用哪一种显示方式。在menu-config里我们看到ListMenu的定义

<Displayer name="ListMenu"

type="net.sf.navigator.displayer.ListMenuDisplayer"/>

displayMenu则是取得一菜单,并将其显示出来,同样在menu-config也能找到。

<Menu  name="ToDoListMenuEdit"  title="EDIT">         <Item  name="TDLselect" title="SELECT_ALL"       image="images/select-all.png"                    location="index.jsp" width="100" />         <Item  name="TDLprefs"  title="USER_PREFERENCES" image="images/prefs.png"                    location="index.jsp" width="150" />         <Item  title="Action Test" action="setPermissions?displayer=${displayer}"/></Menu>

 

查看 DisplayMenu的代码,可以看到。它完成的功能只是从context里取得MenuComponent对象,然后通过 displayer.display(menu);把它交给一个MenuDisplayer的实例来负责画出来。

因此,Control层很好的完成了控制的功能。

综上所述。通过这样一个优美的设计,把各个功能都他离开来了。如果我们需要增加一种显示方式,只要继承MenuDisplayer或它的一个子类,然后写出我们的方法,而不需要修改系统的其他部分。同样的,如果我们的菜单不准备存放在ServletContext而准备存放在比如Session里了,那么我们也只需要修改control部分和生成部分(即MenuRepository)部分。而不影响Display部分。

OK,对struts-menu的介绍结束了,下一篇文章将是如果使用struts-menu和数据库技术动态生成菜单了。请大家继续关注我的网站。

 

 

紫龙,于12/22/2003 16:45:09

蓝色天空版权所有

 

使用静态常量的注意事项

  两个*.java文件,一个是静态常量,一个是使用静态常量的。含有静态常量的文件在我本地和虚拟空间的内容是不一样的,使用静态常量的文件是一样的。我在本地更新了使用静态常量的那个文件,上传到虚拟空间后发现,这个文件引用的是本地的静态常量。

  反编译使用静态常量的那个*.class文件,发现引用静态常量的变量统统都是本地的静态常量值。

  原来,java中使用的静态常量是编译时就固定了,并不是运行时间的动态调用。看来,有必要学习一下java运行的基本原理。

使用标记的怪现象

       <html:select property="parentID" name="oldAdvice">

         <html:options collection="processGoalsList" property="articleID" labelProperty="articleName"/>

       </html:select>

  上面代码的作用是把一个包含同样对象的List中的对象显示到下拉列表中,其中默认为对象“oldAdvice”,本来这个用法在资料中说得很清楚,我用它却出现了至今也想不通的怪现象两次。就是我按照正确的方法写了代码后,运行中却始终得不到正确的效果。我不知道是eclipse或tomcat的问题。

  以后为了保险起见,遇到自己不熟悉的代码编写后,运行时要:1、删除程序运行的work目录;2、重新编译源程序;3、重新启动tomcat服务器。

  我想,这样总不会再扯拐吧?

疑问:不知道在Struts标签中怎么引用jsp片断里的变量

如下面这段代码:

      <td width = "30%" align="center">

        <%/* pseudo code:

           * get the lastUpdated sub-article of anGoal,called sub-article as:subArt;

           * if (subArt is a goal)

           *   display:Goal:subArt.getArticleName();

           * else if (subArt is a diary)

           *   display:Diary:subArt.getArticleName();

           * else {

           *   if (getTypeIDByID(getParentIDByID(subArt.getArticleID()))==1) 

           *     display:advice for goal:subArt.getArticleName();

           *   else

           *     display:advice for diary:subArt.getArticleName();

           * }       

           */  

           int goalID = anGoal.getArticleID();

   TransContext trans = new TransContext();

           ArticleDB myDB = new ArticleDB(trans);

           ArticleInfo lastUpdatedArt=myDB.getLastArtByID(goalID, Consts.HTML_FLAG);

           int lastArtID=lastUpdatedArt.getArticleID();

           String lastArtName=lastUpdatedArt.getArticleName();

           String lastAuthorName=lastUpdatedArt.getUserName();

           int lastArtTypeID=lastUpdatedArt.getTypeID();

           int lastParentID=lastUpdatedArt.getParentID();

           if (lastArtTypeID==1){

         %>目标:

          <html-el:link action="disGoalContentAction.do?goalID=${anGoal.articleID}&naviStr=${requestScope['naviStr']}" target="_blank">

           <%= lastArtName %>

          </html-el:link><br>

          <%= lastAuthorName %><br>

         <%

           }else if(lastArtTypeID==2){

         %>日记:

          <a href="disDiaryContentAction.do?searchDiaryID=<%= lastArtID %>&goalID=<%= lastArtID %>&naviStr=<%= request.getAttribute("naviStr") %>" target="_blank">

           <%= lastArtName %>

          </a><br>

          <%= lastAuthorName %><br>

         <%

           }else {

               if (myDB.getArtTypeByID(myDB.getParentIDByID(lastArtID))==1){

         %>评论目标:

          <html-el:link action="disGoalContentAction.do?goalID=${anGoal.articleID}&naviStr=${requestScope['naviStr']}" target="_blank">

           <%= lastArtName %>

          </html-el:link><br>

          <%= lastAuthorName %><br>

         <%

           }  else {

         %>评论日记:

          <a href="disDiaryContentAction.do?searchDiaryID=<%= lastParentID %>&goalID=<%= lastParentID %>&naviStr=<%= request.getAttribute("naviStr") %>" target="_blank">

           <%= lastArtName %>

          </a><br>

          <%= lastAuthorName %><br>

         <%

             }

           } 

         %>

        <bean:write name="anGoal" property="lastUpdate" scope="page" filter="false"/>

      </td>

  怎样用Struts的标签完成呢?搞不懂,尤其是: <a href="disDiaryContentAction.do?searchDiaryID=<%= lastArtID %>&goalID=<%= lastArtID %>&naviStr=<%= request.getAttribute("naviStr") %>" target="_blank">

           <%= lastArtName %>

          </a><br>

怎么用<html:link/>来完成这个功能呢?

先记在这里,希望知道的朋友提示一下。

疑问:不知道中value的用法

结果用了下列方法完成功能,真被动。还是要好好的把书看一遍了。

       <c:if test="${param.typeID == 3}">

         <html-el:text value="re:${requestScope['parentName']}" property="articleName" maxlength="60" size="60"/>

       </c:if>

       <c:if test="${param.typeID == 4}">

         <input type="text" name="articleName" maxlength="60" size="60" value="<bean:message key="learndiary.message.defaultName"/>">

       </c:if>

或:

<%@ page pageEncoding="gb2312" %>

...

       <c:if test="${param.typeID == 3}">

         <html-el:text value="re:${requestScope['parentName']}" property="articleName" maxlength="60" size="60"/>

       </c:if>

       <c:if test="${param.typeID == 4}">

         <html:text value="留言" property="articleName" maxlength="60" size="60"/>

       </c:if>

来代替。如果把上面"html:text"换成"html-el:text"在tomcat5.0下正常,但在虚拟空间的resin下面就会报错,不知为什么。

         <c:if test="${param.typeID == 4}">

         <html-el:text value="留言" property="articleName" maxlength="60" size="60"/>

       </c:if>

真是惭愧,至今也没有通读一遍Struts的教程

  电子教程倒是下了好几本,都是临到用时去翻。一些基本的东西的概念也是模糊的。像<html:link/>的用法。我就记不得了。书上说:

  The html:link tag renders an HTML anchor tag (i.e., a hyperlink). This tag uses a lot of the same common

attributes as described earlier.You have multiple options for rendering the URL of a hyperlink. You can use the

href, action, forward, or page attributes to specify the URL.

 The href attribute is used to specify a full

URL without any knowledge of the web context of this web application.

 The page attribute is used to specify a

web context relative link.

 The action attribute is used to specify a link to an action mapping, as described in the

Struts config file.

 The forward attribute is used to specify a link to a global forward, as described in the Struts

config file.

Tip: TIP: If you are following a Model 2/MVC architecture, then you should use the page attribute and

the href attribute sparingly. In fact, you should almost never use the page attribute. The href

attribute should only be used to link to resources that are not in the current web application. This helps

you separate the controller from the View by not letting the View select the next View directly. Only the

controller should select the next View. Using the action and forward attributes instead forces you

to delegate selection of the next View to the controller.

Here is an example of linking to an action (/html-link.do) with the page attribute:

<html:link page="/html-link.do">

Linking with the page attribute.

</html:link>

Notice that you do not have to specify the web context of the web application. Conversely, if you used the href

attribute, you would have to specify the web context as follows (where the context is struts-exercise):

<html:link href="/struts-exercise-taglib/html-link.do">

Using Href

</html:link>

Obviously, it is better to use the page attribute when you are linking to things in the same web application (thus,

the same context). You can also use the href attribute to create links that are not on the same server as follows:

<html:link

href="http://otherserver/strutsTut/html-link.do">

Using Href

</html:link>

Another way to link to the html-link.do action is to use the action attribute as follows:

<html:link action="/html-link">

Using Action attribute

</html:link>

(转帖)web中下拉列表的几种实现

转自:http://www.matrix.org.cn/resource/article/43/43809.html

Matrix首页 Java文栏 业界新闻 部落格 资源下载 Java 论坛 web中下拉列表的几种实现

wldandan 发表于2005-09-23 作者:wldandan 评价:0/0 评论数:4 点击数:1490 [收藏]

摘要:

总结一下关于web上使用下拉框的情况

本文Matrix永久镜像:http://www.matrix.org.cn/resource/article/43/43809.html

说明:本文可能由Matrix原创,也可能由Matrix的会员整理,或者由

Matrix的Crawler在全球知名Java或者其他技术相关站点抓取并永久

保留镜像,Matrix会保留所有原来的出处URL,并在显著地方作出说明,

如果你发觉出处URL有误,请联系Matrix改正.

总结一下关于web上使用下拉框的情况

从数据库中获得数据List,将数据放到Request里面

        使用setAttribute(”AList”,AList)

A中有2个属性(String id,String value)

1.        使用JSTL的forEach方式

<select name=”xx” ……..>

<c:forEach items="${AList}" var="p" >

        <c:choose>

                <c:when test="${xxx == p.id}">

                        <option value='<c:out value="${p.id}"/>' selected="selected">

                                        <c:out value="${p.value}"/>

                        </option>

                </c:when>

        <c:otherwise>

                        <option value='<c:out value="${p.id}"/>'>

                                <c:out value="${p.value}"/>

                        </option>

                </c:otherwise>

        </c:choose>       

<c:forEach>

</select>

2.        使用struts的标签

<html:select property=”xxx”>

<html:options collection="AList" labelProperty="value" property="id" />

</html:select>

查一下struts的api文档,可以看到select 中选项有3 taglib可以使用。

第一种直接使用把所有选项写在中间。

<html:option value="0-15">0-15</html:option> <html:option value="15-20" >15-20</html:option> <html:option value="20-30" >20-30</html:option> <html:option value="20 or above">30 or above</html:option>

第二种:把选项放在一个Collection中(这里使用List).在实际项目中,更多的是可能数据来源于db,文件等。这种情况用得比较多。

<html:options collection="AList" property="value" labelProperty="label"/>把option放在list中的过程在Action中作处理//prepare the age selector list.List ageList =new ArrayList();ageList.add(new LabelValueBean("0-15","0-15"));ageList.add(new LabelValueBean("15-20","15-20"));ageList.add(new LabelValueBean("20-30","20-30"));ageList.add(new LabelValueBean("30 or above","30 or above"));request.setAttribute("AList",AList);

这里使用了LabelValueBean,可以不用的,象

<html:options collection="AList" labelProperty="value" property="id" />

只要在AList中填入的bean有value和id属性就可以

第三种,把此list 作为Form 的一个属性.

<html:optionsCollection property="AList" />

在Form 中添加AList 的setter和getter. Form中作如下处理。

//the list can be a form property.

f.setAgeList(AList);

1.        从数据库中获得数据,你应该在Action里面取得数据后,将数据放到Request里面

2.        数据取出来后放在一个List或Collection或Map里面,我习惯用List

3.        从List或其它的容器中取数据应该用<html:options> 或<html:optionsCollection>

4.        <html:options> 和<html:optionsCollection>外层必须用<html:select property="">,所以这个属性你必须在FormBean里定义

5.        由于你要用到这些标签,所以你必须定义FormBean

6.       

从Action取数据,以List为例

List list = xxxxx;//从数据库中取得下拉列表中的数据

request.setAttribute("list",list);

在页面显示

<html:form action="xxxx">...<html:select property="xxx"><html:options collection="list" labelProperty="下拉框中显示的内容,一般是name或其它相似属性" property="各选项对应的值,一般是id" /></html:select>...</html:form>

补充一点点:

因为数据你要从 数据库去取, 所以一般在 action 里调用 DAO ,作为 request 的一个属性传到页面上; 这时一般用 <html:options .../> 标签

另外,如果数据不从数据库去取,而是代码固定的,则一般把这种放到 ActionForm 里,作为属性在页面上取,这时一般用 <html:optionsCollection ... />

我来评价此文: 非常好 还行 一般 扔鸡蛋 总得分:0 投票人次:0

→用户评论列表

#7269 评论作者: littlebat 发表时间:2005-12-27 10:34

正准备用它,对于我来说,上面的信息足够了。谢谢。

#6861 评论作者: jctr 发表时间:2005-12-09 03:59

还应该有第三种,Tapestry的实现,其实也不只这三种还有很多很多吧

#5288 评论作者:xuerldx 发表时间:2005-10-25 07:37

还是不错,就是 有点肤浅

#4811 评论作者: highfan 发表时间:2005-10-05 04:46

你可以把collection 作为

actionform 的

一个属性,在 actionform 中 初始化 也可以 ,在 action 中

初始化 也可以。这样更简单。同时减少view和 control 直接的命名依赖。

(转帖)Struts资源文件:Struts Message Resources

转自:http://www.javafan.net/article/20040430102526725.html

 

页面功能  【加入收藏】 【推荐给朋友】 【字体:大 中 小】 【关闭】   

  

 

Struts Message Resources

作者:Nick Heudecker    来自:未知

总览

许多刚刚学习Struts的程序员在使用Struts的MessageResources特性的时候会遭遇很多困难。本文将试图阐述MessageResources特性的优点并给出了具体的例子说明它的用法。

作者: Nick Heudecker, System Mobile Inc.

概述

类MessageResources可以使开发者方便地支持多语言,包括支持多时间格式和数字格式。使用资源包的另一个好处是允许开发者将标签字符串集中存储在一个位置,而不是分散在不同的JSP页面里。例如,对于每个用户的名字的标签"First Name" ,我们可以将它写在资源包中,在适当的地方通过Struts标签简单的进行引用:

<bean:write key="label.first.name"/>

这样做将会让你对程序的更改变的简单容易,你不必在每一个JSP页面里更改标签的内容了。

用法

使用消息资源包需要你做下面的事情:

1. 为你想要支持的地方创建一个消息资源包。

2. 配置WEB应用,加载消息资源包。

3. 使用相应的JSP标签加载资源或者...

4. ...在一个Action类中加载资源。

创建资源包

MessageResources 类的默认的实现是一个包含"key=value" 对的文件,下面的一个消息资源包文件的例子。

label.username=Username

label.password=Password

label.first.name=First Name

label.last.name=Last Name

label.email=Email Address

label.phone.number=Phone Number

label.welcome=Welcome back {0} {1}!

error.min.length=The input must be at least {0} characters in length.

error.max.length=The input cannot be longer than {0} characters in length.

大括号包围的整数是对java.text.MessageFormat 类的支持,程序员可以向value字符串中传递参数,对每个value字符串你最多可以传递4个参数。

配置

有两种途径通知Struts你的资源包的位置:web.xml 文件或者struts-config.xml 文件。首先来看web.xml 文件的配置:

<servlet>

<servlet-name>action</servlet-name>

<servlet-class>

    org.apache.struts.action.ActionServlet

</servlet-class>

<init-param>

<param-name>

    application

</param-name>

<param-value>

    com.systemmobile.example.ApplicationResources

</param-value>

</init-param>

</servlet>

这个配置说明你的资源包的名字是ApplicationResources.properties,它位于com.systemmobile.example 包中。后缀".properties" 是隐含的,你不必显式地写出来。如果你还有另一个资源文件在相同的包中,例如ApplicationResources_fr.properties ,用来支持法语,你只需要象上面定义的那样列出文件名字即可。

定义资源文件的第二中方法(上面已经提到),是在struts-config.xml 文件中配置:

<message-resources parameter="com.systemmobile.example.ApplicationResources"/>

属性parameter 是必须的。和在web.xml文件中配置一样, 需要注意的是文件在包中的位置。

使用struts-config.xml 文件来配置消息资源文件是推荐的做法,因为它更有可扩展性,更灵活。

你可以使用message-resources 标签从不同的资源文件取不同的消息,前提是在配置的时候为不同的资源文件给出不同的key 属性的值。例如: <message-resources key="myResources" parameter="com.systemmobile.example.ApplicationResources"/>

<message-resources key="moreResources" parameter="com.systemmobile.example.MoreApplicationResources"/>

然后你必须这样使用bean:message 标签: <bean:message bundle="moreResources" key="some.message.key"/>

设置属性null 的值为"false" 后,如果某个资源字符串不存在将返回???key??? 而不是仅仅显示null。这样很容易在JSP页面中看到你没有定义的资源,使得内部测试的速度更快。(关于如何从资源文件中获得消息的详细内容参见国际化 一节) <message-resources parameter="com.systemmobile.example.ApplicationResources" null="false"/>

另外,message-resources 标签允许你使用自己实现的MessageResourcesFactory 接口,这不在本文的讨论范围。

资源文件放在哪里

关于资源文件最常见的问题是将资源文件放在WAR文件的哪里。简单的回答是该文件必须放在你的classpath下面,这意味着将资源文件放在一个JAR 文件中,或者放在/WEB-INF/classes 目录极其子目录下。下表给出了资源文件的位置,在message-resources 标签中"parameter" 属性的值以及简短的说明。

Resources Location parameter Value Description

/WEB-INF/classes/ApplicationResources.properties ApplicationResources 文件放在classes 目录下, 该目录在web应用的classpath中.

/WEB-INF/classes/resources/ApplicationResources.properties resources.ApplicationResources 该文件放在"resources" 目录下, 所以包名也就是路径名要给出。

In the app.jar file, in the com.systemmobile.example package/directory. com.systemmobile.example.ApplicationResources 文件在JAR文件中的全路径。

Tags

最常用Struts 标签是bean:message 标签。使用这个标签的"key" 可以从资源文件中读特定的消息资源。你还可以传入四个参数中的一个或全部:

<bean:message key="label.password"/>

<bean:message key="error.min.length" arg0="6"/>

<bean:message key="label.welcome" arg0="Ralph" arg1="Nader"/>

html:message 可以让你向用户显示错误信息(默认)或消息信息,而html:errors 只显示错误信息。很明显,错误信息或消息信息一定要保存在request里,否则就什么也不会显示。这里有一个显示消息信息的例子:

<logic:messagesPresent message="true">

  <html:messages id="msg" message="true">

    <div class="success">

      <bean:write name="msg"/>

    </div><br/>

  </html:messages>

</logic:messagesPresent>

还有一些标签也有限地支持消息资源,比如html:link。html:link标签通过定义"titleKey" 属性来显示标题文本。许多html 使用 "altKey" 属性从资源文件里获得alternate(替代)文本。

Actions

你还可以在Action 类中使用消息资源文件。Action 类有两个方法得到一个MessageResource 类的实例:

// 返回一个request里的资源文件

protected MessageResources getResources(HttpServletRequest request);

// 返回一个request里的资源文件,

// 该资源文件的标志上<message-resources/> 元素的内容

protected MessageResources getResources(javax.servlet.http.HttpServletRequest request, java.lang.String key);

MessageResources类可以让你从资源文件中得到本地化的消息。The API for MessageResources 可以在资源中找到。比较常用的方法有:

// these methods load a resources key for the given locale

public String getMessage(java.util.Locale locale, java.lang.String key);

public String getMessage(java.util.Locale locale, java.lang.String key,

       java.lang.Object arg0);

public String getMessage(java.util.Locale locale, java.lang.String key,

       java.lang.Object[] args);

public String getMessage(java.util.Locale locale, java.lang.String key,

       java.lang.Object arg0, java.lang.Object arg1)

public String getMessage(java.util.Locale locale, java.lang.String key,

       java.lang.Object arg0, java.lang.Object arg1, java.lang.Object arg2);

public String getMessage(java.util.Locale locale, java.lang.String key, java.lang.Object arg0,

       java.lang.Object arg1, java.lang.Object arg2, java.lang.Object arg3);

// these methods load a resources key for the locale retrieved

// from the HttpServletRequest

public String getMessage(java.lang.String key);

public String getMessage(java.lang.String key, java.lang.Object arg0);

public String getMessage(java.lang.String key, java.lang.Object[] args);

public String getMessage(java.lang.String key, java.lang.Object arg0,

       java.lang.Object arg1);

public String getMessage(java.lang.String key, java.lang.Object arg0,

       java.lang.Object arg1, java.lang.Object arg2);

public String getMessage(java.lang.String key, java.lang.Object arg0,

       java.lang.Object arg1, java.lang.Object arg2, java.lang.Object arg3);

这些返回的字符串可以被设置成request 或 session 的参数并串会表现层。你可能注意到了一些重载方法getMessage(...) 选择了参数Object,而另外一些采用了参数arg0...arg3。这和 bean:message arg0...arg3 属性等价。

除了MessageResources 类,还有一些类使用了资源文件。ActionMessage类被用来从action 向JSP之间传递消息资源中的keys 。消息被用来作为bean 的属性。ActionError, ActionMessage的子类,使用消息资源中的keys 存储验证失败后的错误信息。

国际化

从资源文件中提取一个本地化信息可以由类MessageResources 来处理,或者由它的直接子类PropertyMessageResources类处理。既然类PropertyMessageResources 等经常地被用到,那么我们就来看看它是怎样使用getMessage(Locale, String) 方法来从资源文件中读取消息的。

举例说明:

1. 如果你在ApplicationResources_pt_br.properties (Brazilian Portuguese)中没有发现消息的定义,系统将在ApplicationResources_pt.properties 文件中找,如果ApplicationResources_pt.properties 文件不存在或者也没有该消息,那就去ApplicationResources.properties 文件里查找了。

2. 如果消息找到了,它就被加到本地化的缓存中并返回java.lang.String型数据。

3. 如果消息没有找到,此时如果returnNull 属性被为默认的true,将返回 null。 否则将返回类似 ???key??? 的字符串,key 就是那个被传递的参数。

JSTL

JSTL (JavaServer Pages Standard Tag Library) 的fmt标签最近开始流行起来,用来向JSP中显示资源文件的信息。它能很好地和Struts结合在一起。使用它非常简单,只要下载JSTL 的jar 文件和TLDs 并把它们拷贝到你的应用的正确的位置,然后在web.xml文件中加入下面的内容:

<context-param>

  <param-name>javax.servlet.jsp.jstl.fmt.localizationContext</param-name>

  <param-value>ApplicationResources</param-value>

</context-param>

上面的配置是假定你的ApplicationResources.properties文件是放在/WEB-INF/classes 目录下的。 参见above 更多情况。

然后将这个标签库直接放在你的JSP中:

<%@ taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt" %>

最后,下面的标签将显示资源文件的内容:

<fmt:message key="label.first.name"/>

还有一个使用fmt 标签获得资源的例子。(注意: 该段程序取自Jakarta JSTL examples。)

// loading a resource from a specific bundle and populating a parameter

<fmt:message key="currentTime" bundle="${deBundle}">

 <fmt:param value="${currentDateString}"/>

</fmt:message>

// using the forEach iterator to populate paramters

<fmt:message key="serverInfo" bundle="${deBundle}">

 <c:forEach var="arg" items="${serverInfoArgs}">

  <fmt:param value="${arg}"/>

 </c:forEach>

</fmt:message>

结论

在向JSP文件方便地传递消息的同时,Struts使用消息资源文件还帮助我们创建国际化的Web应用。我们既可以使用正在快速发展中的JSTL标签,也可以使用Struts标签,或直接在action中得到一条具体的消息。我希望这篇文章为您阐明了一些Struts中常用的但有时会混淆的东西。

关于作者

Nick Heudecker 是一位软件开发人员,具有6年的企业应用的开发经验。 他所在的公司, System Mobile, Inc.,专门从事应用集成,定制软件开发和无线应用。 他还是Sun认证JAVA程序员,现在居住在Ann Arbor, Michigan。

资源

下面的资源也许对您了解更多的关于Struts资源文件有帮助:

JavaDoc for the classes of interest:

java.util.ResourceBundle

java.util.Locale

org.apache.struts.util.MessageResources

org.apache.struts.action.ActionError

org.apache.struts.action.ActionMessage

Ted Husted's Struts Tips: Very handy advice, especially for resource bundle usage.

Struts Home Page

Struts-User Mailing List Archive: Many people use Struts, so there is a very good chance that your question has be answered already. Please use all available resources before asking your question on the mailing list.

JSTL Homepage and the Jakarta JSTL Implementation

注意事项

Packages are just directory structures used to avoid naming conflicts. If you put a message bundle in a directory named resources under /WEB-INF/classes, this is the equivalent of putting the file in a package named resrouces. While basic, this point seems to trip up many new programmers.

 

 

  

 

页面功能  【加入收藏】 【推荐给朋友】 【字体:大 中 小】 【关闭】    

 

 

Copyright © 2003 - 2005 JavaFan.NET All Rights Reserved