在如下图所示的添加Portlet的文本显示,有一个非常有趣的特性:

当我们吧文本的代码替换成小写的"add",则最终显示出来的是大写的"Add",而我们把文本替换成小写的"modify",则原封不动显示的是"modify",这是什么特性呢?

 

为此,我们可以进行一系列的研究,首先我们定位到显示这个文字的代码,它位于/html/portlet/layout_configuration/view_category.jsp的第138行:

 
  1. .... 
  2.                 <div 
  3.                         class="lfr-portlet-item <c:if test="<%= portletLocked %>">lfr-portlet-used</c:if> <c:if test="<%= portletInstanceable %>">lfr-instanceable</c:if>
  4.                         id="<portlet:namespace />portletItem<%= portlet.getPortletId() %>" 
  5.                         instanceable="<%= portletInstanceable %>" 
  6.                         plid="<%= plid %>" 
  7.                         portletId="<%= portlet.getPortletId() %>" 
  8.                         title="<%= PortalUtil.getPortletTitle(portlet, application, locale) %>" 
  9.                     > 
  10.                         <p><%= PortalUtil.getPortletTitle(portlet, application, locale) %> <a href="javascript:;"><liferay-ui:message key="modify" /></a></p> 
  11.                     </div> 
  12.  
  13. ....

 

然后这个标记<liferay-ui>是定义在liferay-ui.tld文件中:

我们这里可以看到key是一个必须的属性,并且这个标记对应的java类是com.liferay.taglib.ui.MessageTag:

 
  1. <tag> 
  2.         <name>message</name> 
  3.         <tag-class>com.liferay.taglib.ui.MessageTag</tag-class> 
  4.         <body-content>JSP</body-content> 
  5.         <attribute> 
  6.             <name>arguments</name> 
  7.             <required>false</required> 
  8.             <rtexprvalue>true</rtexprvalue> 
  9.         </attribute> 
  10.         <attribute> 
  11.             <name>key</name> 
  12.             <required>true</required> 
  13.             <rtexprvalue>true</rtexprvalue> 
  14.         </attribute> 
  15.         <attribute> 
  16.             <name>translateArguments</name> 
  17.             <required>false</required> 
  18.             <rtexprvalue>true</rtexprvalue> 
  19.         </attribute> 
  20.         <attribute> 
  21.             <name>unicode</name> 
  22.             <required>false</required> 
  23.             <rtexprvalue>true</rtexprvalue> 
  24.         </attribute> 
  25.     </tag> 

 

我们去MessageTag中去找寻对这个key参数的处理,在doEndTag方法中:

 
  1. public int doEndTag() throws JspException { 
  2.         try { 
  3.             String value = StringPool.BLANK; 
  4.  
  5.             if (_arguments == null) { 
  6.                 if (_unicode) { 
  7.                     value = UnicodeLanguageUtil.get(pageContext, _key); 
  8.                 } 
  9.                 else { 
  10.                     value = LanguageUtil.get(pageContext, _key); 
  11.                 } 
  12.             } 
  13.             else { 
  14.                 if (_unicode) { 
  15.                     value = UnicodeLanguageUtil.format( 
  16.                         pageContext, _key, _arguments, _translateArguments); 
  17.                 } 
  18.                 else { 
  19.                     value = LanguageUtil.format( 
  20.                         pageContext, _key, _arguments, _translateArguments); 
  21.                 } 
  22.             } 
  23.  
  24.             JspWriter jspWriter = pageContext.getOut(); 
  25.  
  26.             jspWriter.write(value); 
  27.  
  28.             return EVAL_PAGE; 
  29.         } 
  30.         catch (Exception e) { 
  31.             throw new JspException(e); 
  32.         } 
  33.         .. 
  34.     } 

我们看出来,liferay-ui的最终替换的文本是value值,见26行。所以我们的重心在于我们传入的key如何转为这个value.

因为页面上只有key属性,其他属性都没设置,所以这个value的值来自于第11行:

它会去调用LanguageUtil的get方法:

 
  1. public static String get(PageContext pageContext, String key) { 
  2.         return getLanguage().get(pageContext, key); 
  3.     } 

 

进而调用LanguageImpl的 get(pageContext, key)方法,最终调用get(pageContext,key,key)方法:

 
  1. public String get( 
  2.         PageContext pageContext, String key, String defaultValue) { 
  3.  
  4.         try { 
  5.             return _get(pageContext, nullnull, key, defaultValue); 
  6.         } 
  7.         catch (Exception e) { 
  8.             if (_log.isWarnEnabled()) { 
  9.                 _log.warn(e, e); 
  10.             } 
  11.  
  12.             return defaultValue; 
  13.         } 
  14.     } 

 

我们继续跟进第05行,发现它调用_get方法:

 
  1. private String _get( 
  2.             PageContext pageContext, PortletConfig portletConfig, Locale locale, 
  3.             String key, String defaultValue) 
  4.         throws Exception { 
  5.  
  6.         if (PropsValues.TRANSLATIONS_DISABLED) { 
  7.             return key; 
  8.         } 
  9.  
  10.         if (key == null) { 
  11.             return null
  12.         } 
  13.  
  14.         String value = null
  15.  
  16.         if (pageContext != null) { 
  17.             HttpServletRequest request = 
  18.                 (HttpServletRequest)pageContext.getRequest(); 
  19.  
  20.             ThemeDisplay themeDisplay = (ThemeDisplay)request.getAttribute( 
  21.                 WebKeys.THEME_DISPLAY); 
  22.  
  23.             locale = themeDisplay.getLocale(); 
  24.  
  25.             portletConfig = (PortletConfig)request.getAttribute( 
  26.                 JavaConstants.JAVAX_PORTLET_CONFIG); 
  27.         } 
  28.  
  29.         if (portletConfig != null) { 
  30.             ResourceBundle resourceBundle = portletConfig.getResourceBundle( 
  31.                 locale); 
  32.  
  33.             value = ResourceBundleUtil.getString(resourceBundle, key); 
  34.  
  35.             // LEP-7393 
  36.  
  37.             String portletName = portletConfig.getPortletName(); 
  38.  
  39.             if (((value == null) || (value.equals(defaultValue))) && 
  40.                 (portletName.equals(PortletKeys.PORTLET_CONFIGURATION))) { 
  41.  
  42.                 value = _getPortletConfigurationValue(pageContext, locale, key); 
  43.             } 
  44.  
  45.             if (value != null) { 
  46.                 value = LanguageResources.fixValue(value); 
  47.             } 
  48.         } 
  49.  
  50.         if ((value == null) || value.equals(defaultValue)) { 
  51.             value = LanguageResources.getMessage(locale, key); 
  52.         } 
  53.  
  54.         if ((value == null) || value.equals(defaultValue)) { 
  55.             if ((key.length() > 0) && 
  56.                 (key.charAt(key.length() - 1) == CharPool.CLOSE_BRACKET)) { 
  57.  
  58.                 int pos = key.lastIndexOf(CharPool.OPEN_BRACKET); 
  59.  
  60.                 if (pos != -1) { 
  61.                     key = key.substring(0, pos); 
  62.  
  63.                     return _get( 
  64.                         pageContext, portletConfig, locale, key, defaultValue); 
  65.                 } 
  66.             } 
  67.         } 
  68.  
  69.         if (value == null) { 
  70.             value = defaultValue; 
  71.         } 
  72.  
  73.         return value; 
  74.     } 

首先,在第06行进行translation_disabled判断。这个值最终设置在portal.properties文件中:

 
  1.    # Set this to true to disable language translations. When a translation is 
  2.    # requested for the key "first-name", instead of returning "First Name" in 
  3.    # English (or in its relevant locale), it will return "first-name"
  4.    # 
  5.    translations.disabled=false 

所以,默认是启用了translations的而且key是有值的,比如 “add",所以跳过06-12行:

因为PageContext不为null,所以进入第16行:

我们断点跟踪,假如传入的参数为"key",那么第20,23行都执行了,其中第20行的ThemeDisplay,第23行的locale,第25行的portletConfig都不为null:

所以,我们进入第30行:

而第30行的ResourceBundle我们使用的类是com.liferay.portlet.StrutsResourceBundle,

 

最终把resourceBundle设置进去,这是通过PortletConfigImpl的getResourceBundle(Locale)来完成的:

所以看出来,resourceBundleId为33enUs,而被设置进去的resourceBundle是PortletResourceBundle的实例,其父类是 StrutsResourceBundle:

 

所以我们终于找到了value与key关联的桥梁:

 
  1. value = ResourceBundleUtil.getString(resourceBundle, key); 

 

我们继续跟进到ResourceBundleUtil的getString方法:

 
  1. public static String getString(ResourceBundle resourceBundle, String key) { 
  2.         ResourceBundleThreadLocal.setReplace(true); 
  3.  
  4.         String value = null
  5.  
  6.         try { 
  7.             value = resourceBundle.getString(key); 
  8.         } 
  9.         finally { 
  10.             ResourceBundleThreadLocal.setReplace(false); 
  11.         } 
  12.  
  13.         if (NULL_VALUE.equals(value)) { 
  14.             value = null
  15.         } 
  16.  
  17.         return value; 
  18.     } 
  19.  

这时会进入ResourceBundle的getString()方法:

 
  1. public final String getString(String key) { 
  2.        return (String) getObject(key); 
  3.    } 

因为我们的locale是en_US是默认的Locale,所以它会去找资源包文件Language.properties,如果在上面的就使用对应的value,不在上面的就使用默认值,也就是原来的value的值。

所以最终,当我们传入的key为小写的"search"时,

我们在这个资源包中找到:

所以value就为大写的"Search"。

这刚好和我们调试出来的信息匹配:

 

证明:

我们再举个例子,如果传入key 为"add",则返回value 为"Add",因为资源文件中有add=Add

如果传入key为“modify",则返回value为”modify",因为资源文件中没匹配项