内容


评论专栏:Scott Johnson

通过 HTTP 加载 Java 资源包

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: 评论专栏:Scott Johnson

敬请期待该系列的后续内容。

此内容是该系列的一部分:评论专栏:Scott Johnson

敬请期待该系列的后续内容。

使用资源包的另一个方法

请设想一下这种情况:您必须提供一个显示小部件,它能够从数据库中提取消息键(keys)和消息替代参数,在 Java 资源包中查找键,然后格式化消息并显示出来。惟一的问题是这个资源包位于 Web 应用程序中,而不是您的小部件所在的位置。事实上,Web 应用程序位于不同的服务器中。

这并不是 Java 资源包的典型使用情况,而可能是一场噩梦。您如何来实现这一点呢?为了不进行空泛的解释,本文包括了一个示例应用程序,供你下载运行。它将更轻松地向您演示并帮助您做到这点。

示例应用程序和资源文件

为运行 Java Platform Standard Edition(Java SE)6 的 IBM® WebSphere® Application Server V7 和 Apache Tomcat 6 提供了这种情况下的工作样例。之所以需要 Java SE 6,是因为该样例使用了 Java 类 java.util.ResourceBundle.Control 来实现通过 HTTP 进行的资源包加载,而这个类在 Java SE 6 以前都不可用。

安装示例应用程序

本文中包含的 下载 样例材料包括:

  • sj_Tomcat.zip 包含:
    • RemoteResourceBundle.war
    • AutoParts.war
    • AutoSales.war
  • sj_WAS.zip 包含:
    • RemoteResourceBundleEAR.ear
    • AutoPartsEAR.ear
    • AutoSalesEAR.ear
  • sj_Source.zip 包含了 Java 资源和构成样例的其他文件。用于 RemoteResourceBundle.war 中的资源包括:
    • src/remote/bundle/example/RemoteResourceBundleLoader.java
    • src/remote/bundle/example/ProductContextRoots.java
    • src/remote/bundle/example/MessagesBundle.java
    • src/remote/bundle/example/MessagesBean.java
    • src/remote/bundle/example/productContextRoots.properties
    • WebContent/RemoteBundleDisplay.jsp

用于 AutoParts.war 中的资源包括:WebContent/remote/bundle/example/autoparts.properties

用于 AutoSales.war 中的资源包括:WebContent/remote/bundle/example/autosales.properties

要安装样例材料:

  1. 部署 Tomcat WAR 文件

    把 sj_Tomcat.zip 解压并保存到 Tomcat 安装的根目录的 webapps 目录中。如果已经运行了 Tomcat 服务器,它将自动部署应用程序。如果还没有运行 Tomcat 服务器的话,那么现在就运行它,它将会自动部署应用程序。

  2. 部署 WebSphere Application Server EAR 文件
    1. 把 sj_WAS.zip 解压并保存到一个方便的 temp 目录中。
    2. 登录 Integrated Solutions Console,然后在菜单中选择 Applications => New Application => New Enterprise Application
    3. 单击 Browse 按钮,找到您解压 extracted sj_WAS.zip 的 temp 目录,然后选择 RemoteResourceBundleEAR.ear
    4. 单击 Next 按钮直到您看到 Summary 页面(接受所有面板中的所有默认值),然后单击 Finish 按钮开始安装应用程序。
    5. 安装完应用程序后,单击 Save 链接,把应用程序保存到主机配置中。
    6. 对 AutoPartsEAR.ear 和 AutoSalesEAR.ear 重复这些步骤。
    7. 这三个应用程序安装成功后,导航到 Applications => Application Types => WebSphere enterprise applications
    8. 在应用程序清单中,您可以看到刚刚安装的三个应用程序,在每个应用程序后面的 Application Status 栏中标着红色的 X。选择这三个应用程序左边的复选框,然后单击 Start 按钮。
    9. 运行这些应用程序后,您会发现每个应用程序后面的 Application Status 栏出现了一个绿色的箭头,表示已经启动了这些应用程序。
  3. 运行样例

    在浏览器地址栏中输入 http://<host>:<port>/RemoteResourceBundle/RemoteBundleDisplay.jsp,其中 <host>:<port> 是安装了示例应用程序的正在运行的服务器的主机和端口。例如,在 WebSphere Application Server 中,这些内容类似如下所示:

    http://localhost:9080/RemoteResourceBundle/RemoteBundleDisplay.jsp

    或者在 Tomcat 中,这些内容类似如下所示:

    http://localhost:8080/RemoteResourceBundle/RemoteBundleDisplay.jsp

    图 1 显示了这个样例的非常简单的浏览器输出。

    图 1. 样例的显示输出
    图 1. 样例的显示输出
    图 1. 样例的显示输出

这个输出的有趣之处在于 RemoteResourceBundle Web 应用程序中的 RemoteBundleDisplay.jsp,它能够从两个完全独立的 Web 应用程序(AutoParts 和 AutoSales)中提取资源包,然后格式化来自资源包的消息并进行显示。

示例应用程序的运行方式

参考图 2,下面是示例应用程序的运行方式概览:

图 2. 示例概览
图 2. 示例概览
图 2. 示例概览
  1. 请求 RemoteBundleDisplay.jsp。它把类 MessagesBean 实例化并访问名为 messagesMap 的 MessageBean 属性,而这将为 JSP 返回格式化的消息以进行显示。
  2. MessagesBean 的方法 getMessagesMap() 遍历一些硬编码的消息键,而且对每个键都调用私有方法 getString()。
  3. MessagesBean 的 getString() 方法调用静态的 Java 方法 ResourceBundle.getBundle(),以便为当前的消息键检索资源包。ResourceBundle.getBundle() 的其中一个参数是示例类 RemoteResourceBundleLoader 的实例,它扩展 Java 类 ResourceBundle.Control 并实现方法 newBundle()。
  4. RemoteResourceBundleLoader.newBundle() 创建一个 HttpURLConnection,用于从 Web 应用程序中提取所需的资源包。定位资源包后,它被加载到名为 MessagesBundle 的示例类的新实例中。RemoteResourceBundleLoader 返回 MessagesBundle 实例。(如果资源包是由前面的 ResourceBundle.getBundle() 调用检索,Java ResourceBundle 类将返回一个资源包的缓存版本。)
  5. MessagesBean 的 getString() 方法使用 MessagesBundle 实例检索正在处理的消息键的字符串。MessagesBean 格式化字符串并且把它储存在一个映射中,当所有的消息都检索完毕时,这个映射会被返回到 JSP。
  6. JSP 显示格式化的消息。

如何通过 HTTP 加载资源包

关键在于 Java class ResourceBundle.Control。在 Java SE 6 中引入了类 java.util.ResourceBundle.Control。Control 类使您能够比以往任何 Java 版本都能够更好地控制资源包加载。

请看一下示例类 RemoteResourceBundleLoader。您可以在 remote.bundle.example 包(清单 1)中可下载的 sj_Source.zip 文件中找到它。

ResourceBundle 使用 getFormats() 方法来决定资源包应该使用什么格式。在示例应用程序中,您只需要加载类型属性包;例如,autoparts.properties。

清单 1. 为资源包指定格式
private static String propertiesType = "properties";

// Only "properties" files are used (e.g., autoparts.properties)
   public List<String> getFormats(String baseName) {
   return Collections.singletonList(propertiesType);

RemoteResourceBundleLoader 中的 newBundle() 方法(清单 2)被工厂方法 ResourceBundle.getBundle() 调用,以实例化 ResourceBundle,用于基本的资源包名称、位置和格式。(要了解资源包加载流程的完整描述,请参见 ResourceBundle 的 Javadoc)。

清单 2. RemoteResourceBundleLoader 中的 newBundle() 方法
public ResourceBundle newBundle(String baseName, 
                                    Locale locale, 
                                    String format,
                                    ClassLoader loader, 
                                    boolean reload) 
                   throws IllegalAccessException, InstantiationException, 
                            IOException {

        ResourceBundle bundle = null;

        if ((baseName == null) || 
            (locale == null) || 
            (format == null) || 
            (loader == null)) {
            return null;
        }
        
        // format must be '.properties'
        if (!format.equals(propertiesType)) {
            return null;
        }

        // Create bundle name from baseName and locale (e.g., "autoparts" 
        //  (no locale), autoparts_fr (French)
        String bundleName = toBundleName(baseName, locale);
        
        // Create resource name (e.g., "autoparts.properties", 
        //                             "autoparts_fr.properties"
        String resourceName = toResourceName(bundleName, format);
        
        // get product context roots, if not already obtained
        Properties productContextRoots=ProductContextRoots.getProductContextRoots();
        if (productContextRoots==null) {
            ProductContextRoots pe = new ProductContextRoots();
            pe.loadProductContextRoots();
            productContextRoots = ProductContextRoots.getProductContextRoots();
            if (productContextRoots==null) {
                return null;
            }
        }
        // The last segment of the baseName indicates product 
        // (i.e., 'autosales', 'autoparts').
        // Use this string to find the product context root in productContextRoots.
        int dotIndex = baseName.lastIndexOf('.');
        if (dotIndex==-1) {
            return null;
        }
        String productName= baseName.substring(dotIndex+1);
        
        // Find product context root using productName
        String productContextRoot = productContextRoots.getProperty(productName);
        if (productContextRoot==null) {
            return null;
        }

	// Create the full name for this resource 
        String fullResourceName;
        if (!productContextRoot.startsWith("http")) {
        	if (this.refererHeader != null && this.refererHeader.length()>0) {
            	    // Create full resource name using Referer, 
                   //  context root and resource name:
            	    fullResourceName=this.refererHeader+
                   productContextRoot+resourceName;
        	} else {
	            // Create full resource name using scheme, 
                    // host, port, context root and resource name:
	            fullResourceName=this.scheme+"://"+
                    this.host+":"+this.port+productContextRoot+resourceName;
        	}
        } else {
        	// Create full resource name using context root and resource name:
        	fullResourceName=productContextRoot+resourceName;
        }

        // Create HttpURLConnection for the resource file
        URL proxy=new URL(fullResourceName);
        HttpURLConnection httpProxy = (HttpURLConnection)proxy.openConnection();
        if (httpProxy == null) {
          return null;
        }
        if (reload) {
          httpProxy.setUseCaches(false);
        }

        // Instantiate the input stream
        InputStream stream = httpProxy.getInputStream();
        if (stream == null) {
          return null;
        }
        BufferedInputStream bis = null;

        // Instantiate the bundle with the stream.
        try {
            bis = new BufferedInputStream(stream);
            bundle = new MessagesBundle(bis);
            bis.close();
        }finally{
            if (bis != null) try { bis.close (); } catch (Throwable ignore) {}
        }
        return bundle;
}

从一个消息键和基础资源包名称中创建一个 URL

您如何知道哪个 Web 应用程序包含针对特定消息键的资源包?清单 2 中的示例代码执行一个简单的模式,用于构建从 Web 应用程序检索资源包所需的 URL。这个模式是:

  • 消息键包含产品标识符

    消息键在其名称的第一部分包含了一个产品标识符。例如:autoparts.parts.received。在这个消息键中,autoparts 就是包含了资源包的 Web 应用程序。

  • 基本资源包名称包括一个固定的字符串和一个产品标识符

    所有样例资源包的名称都使用相同的基本字符串:remote.bundle.example

    当 MessagesBean 调用 ResourceBundle.getBundle() 时,它将传递这个固定的字符串,后面附加了来自消息键的产品标识符,例如:

    remote.bundle.example.autoparts

    这个字符串也被看作是 ResourceBundle.getBundle() 的 baseName 参数。

  • 产品上下文根

    一个名为 productContextRoots.properties 的属性文件包含了 Web 应用程序的上下文根,而该应用程序包含将被加载的资源包。属性文件的内容是:

    autoparts = /AutoParts/
    autosales = /AutoSales/

    第一次调用时,方法 RemoteResourceBundleLoader.newBundle() 将读取这个属性文件。

  • 把资源包 baseName 映射到产品上下文根中

    当调用方法 RemoteResourceBundleLoader.newBundle() 以加载一个新的资源包时,它将获取 baseName 参数的最后一部分并使用它作为消息键,在资源包所在的 Web 应用程序中查找上下文根。

    例如,资源包 baseName 的最后部分:

    remote.bundle.example.autoparts

    会被用来在 productContextRoots.properties 中为 autoparts Web 应用程序查找上下文根。在这个例子中,上下文根是:

    /AutoParts/

填充空缺部分

回忆一下示例应用程序概览,RemoteBundleDisplay.jsp 实例化类 MessagesBean。清单 3 显示 JSP 代码。

清单 3. RemoteBundleDisplay.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1" import="remote.bundle.example.MessagesBean"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    
<jsp:useBean id="messagesBean" scope="page" 
	class="remote.bundle.example.MessagesBean"></jsp:useBean> 
<jsp:setProperty name="messagesBean" property="locale" 
	value="${pageContext.request.locale}"></jsp:setProperty>    
<jsp:setProperty name="messagesBean" property="scheme" 
	value="${pageContext.request.scheme}"></jsp:setProperty>    
<jsp:setProperty name="messagesBean" property="port" 
	value="${pageContext.request.serverPort}"></jsp:setProperty>    
<jsp:setProperty name="messagesBean" property="host" 
	value="${pageContext.request.serverName}"></jsp:setProperty>    
<jsp:setProperty name="messagesBean" property="refererHeader" 
	value='${header["Referer"]}'></jsp:setProperty>    
<html>  
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> 
<title>Displays messages from resource bundles located in other Web 
Applications</title>
</head>
<body>
<p>The Messages:</p>

<ul>
<c:forEach var="msg" items="${messagesBean.messagesMap}">
  <li>${msg.value}</li>
</c:forEach>
</ul>
</body>
</html>

JSP 在 MessagesBean 实例中设置几个属性,然后遍历 MessagesBean 创建的 messagesMap,并显示格式化的消息(清单 4)。

清单 4. MessagesBean.java
public Map<String,String> getMessagesMap(){
        messages = new HashMap<String, Object[]>();
        // The message keys and the arguments are stored in
        // a HashMap for the purposes of this sample.

        // In the real world, both the message keys and their arguments could be
        // persisted to a database by event emitters living in many different
        // Web Applications which could be deployed on different hosts.
        // A single, centralized display widget, living on any server anywhere,
        // could retrieve the message keys and their arguments from the database, 
        // get the messages from the appropriate Web Application resource bundles, 
        // and then format and display the messages.
        messages.put("autoparts.parts.received", 
            new Object[]{"Detroit"});
        messages.put("autoparts.parts.shipped", 
            new Object[]{"Cleveland"});
        messages.put("autosales.invoice.received", 
            new Object[]{"TopNotch Motors", 10});
        messages.put("autosales.shipment.received", 
            new Object[]{"TopNotch Motors", "Lamborghini"});

        Set<String> messagesKeys = messages.keySet();
        Iterator<String> messagesIter = messagesKeys.iterator();
        messagesMap = new HashMap<String,String>();
        while(messagesIter.hasNext()) {
            String key = messagesIter.next();
            Object[] values = messages.get(key);
            int dotIndex = key.indexOf('.');
            if (dotIndex>1) {
                String productName= key.substring(0, dotIndex);
                String msg = getString(key,productName, this.locale );
                String formattedParams = MessageFormat.format(msg, values);

                messagesMap.put(key, formattedParams);
            }
        }
        return messagesMap;
    }

    /**
     * Returns the string in a resource bundle for key, product and locale
     *
     * @param messageKey a key
     * @param product the identifier for a specific resource bundle
     * @param locale the locale of the client
     * @return String a value which is assigned to the key
     */
    public String getString(String messageKey, String product, String locale) {
        try {
            // Load bundle using RemoteResourceBundleLoader.
            ResourceBundle bundle = ResourceBundle.getBundle(
                    BASE_BUNDLE_PACKAGE+"."+product,
                    new Locale(locale),
                    new RemoteResourceBundleLoader(this.scheme, this.host, 
                        this.port, this.refererHeader));
            String messageString=bundle.getString(messageKey);
            return messageString;
        } catch (MissingResourceException e) {
            return null;
        }
}

当 JSP 访问 bean 的 messagesMap 属性时,bean 为每个硬编码消息键调用 getString()。

在 JDK 6 中,ResourceBundle.getBundle() 方法把 ResourceBundle.Control 类作为一个参数。在清单 4 的 getString() 方法中,您把类 RemoteResourceBundleLoader 的一个新实例作为 ResourceBundle.getBundle() 的第三个参数进行传递。RemoteResourceBundleLoader 扩展 ResourceBundle.Control。对 ResourceBundle.getBundle() 的调用返回了一个资源包。通过调用 bundle.getString(messageKey),这个资源包被用来获取消息键的消息。

在您获得消息后,使用 MessageFormat.format() 格式化消息,然后把格式化的消息放到由 JSP 使用的 messagesMap 中。

结束语

本文向你演示了如何通过 HTTP 加载 Java 资源包。在真实的情景中,消息键和它们的参数都会被许多不同的 Web 应用程序(可能部署在不同的主机中)中的事件发送器持久化到数据库中。一个单一、集中化的显示小部件(位于任何地方的任何服务器)能够从数据库中检索消息键和它们的参数,从适当的 Web 应用程序资源包中获取消息,然后格式化消息并进行显示。

感谢

作者感谢 Varad Ramamoorthy 对本文作出的技术审校。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=WebSphere, Java technology
ArticleID=449610
ArticleTitle=评论专栏:Scott Johnson: 通过 HTTP 加载 Java 资源包
publish-date=11252009