最近美国那边的工程师提了个问题,关于 ResourceBundleMessageSource初始化的问题,
HLD的详情是这样的,他想使用 ResourceBundleMessageSource这个对象在Java code里面实现国际化,(Java code是一个Helper class,不继承任何类,用于jsp显示辅助,为让jsp干净做的),但是怎么得到ResourceBundleMessageSource这个对象 呢,他给的HLD的这样的:
For access in Java: with the above bean.xml, any of the Spring ResourceBundleMessageSource methods will find and use the named bundle. Those Spring methods can be used directly (upon Getting Locale as above) – for example:
结 论1:可以把resource 文件交给applicationContext来管理,但是不是通过DispatcherPortlet的applicationContext.是用过 root applicationContext.所以,我们在写资源文件加载的时候一定药注意到底是在那个context里面的,Spring提供了两个 context.一个root的, 一个 child的.
那么portlet.xml的呢.由他管理的资源文件到哪儿去了..
找啊找,实在找不到更好的方式了:决定看spring 的帮助文档了,居然在帮助文档看见这样一句话:
"Portlet MVC不支持本地化解析和主题解析 - 它们是portal/portlet容器 的范畴,并不适合放在Spring框架里。但是,Spring里所有依赖本地化(比如消息的 国际化)仍旧可以工作,因为DispatcherPortlet在以 DispatcherServlet相同的方式暴露当前的本地化信息。"
晕死,弄了半天,Spring 自己实现的问题啊,以前没有注意到
大意啊.....
看了这句话,当然要去找下spring是怎么实现:"DispatcherPortlet在以 DispatcherServlet相同的方式暴露当前的本地化信息"
大 家都知道spring在执行显示的时候会把PortletRequest&PortletResponse转换成 HttpServletRequest&HttpServletResponse.这个动作是通过一个ViewRenderServlet来完成 的,当Spring组织好了显示的数据和用于显示的view,就会去调用(DispacherPortlet)
org.springframework.context.support.*;
StringResourceBundleMessageSource.getMessage(Stringkey//messagekey
String[],//messageargs
String,//defaultvalue
Locale)
引用原文,
于是我就开始寻找怎么得到ResourceBundleMessageResource如何实例化,大家都知道 ResourceBundleMessageResource是在国际化的时候使用的,我们通常在spring的bean.xml里面这样配置来加载我们 的资源文件:
<beanid="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<propertyname="basenames">
<list>
<value>Messages</value>
</list>
</property>
</bean>
Spring IOC会帮我们管理ResourceBundleMessageResource对象的实例话和资源文件的加载.
也就是说在我们的应用启动的时候这些文件已经被加载了,对象已经被实例化了,我们要怎么拿到这个对象,使得我们在java code里面可以使用这个对象呢?
查遍了所有的Spring source code没有发现好的方法,可以这样做么?
ResourceBundleMessageSourcems=newResourceBundleMessageSource();
//Actually,Thisobjecthadbeencreatedwhenthebean.xmlisloaded.
//ShallIshouldcreateanother?
//notsingleton?Toomanyobjectwillbecreated?
ms.setBasename("MessageFileName");
//loadmessagefileagain?
看我的comment.
这个办法不行..new始终难以保证单例,会造成performance的问题.继续寻找其他方法吧.
继续寻找的时候发现下面的类层次:(MessageResource)
会不会ResourceBundleMessageResource只是做文件的load而读取显示实由ApplicationContext来做的呢, 查找之后发现了这样个代码片段:位于AbstractApplicationContext
/
**
*InitializetheMessageSource.
*Useparent'sifnonedefinedinthiscontext.
*/
private
void
initMessageSource()
throws
BeansException
...
{
if(containsLocalBean(MESSAGE_SOURCE_BEAN_NAME))...{
this.messageSource=(MessageSource)getBean(MESSAGE_SOURCE_BEAN_NAME,MessageSource.class);
//MakeMessageSourceawareofparentMessageSource.
if(this.parent!=null&&this.messageSourceinstanceofHierarchicalMessageSource)...{
HierarchicalMessageSourcehms=(HierarchicalMessageSource)this.messageSource;
if(hms.getParentMessageSource()==null)...{
//OnlysetparentcontextasparentMessageSourceifnoparentMessageSource
//registeredalready.
hms.setParentMessageSource(getInternalParentMessageSource());
}
}
if(logger.isInfoEnabled())...{
logger.info("UsingMessageSource["+this.messageSource+"]");
}
}
else...{
//UseemptyMessageSourcetobeabletoacceptgetMessagecalls.
DelegatingMessageSourcedms=newDelegatingMessageSource();
dms.setParentMessageSource(getInternalParentMessageSource());
this.messageSource=dms;
if(logger.isInfoEnabled())...{
logger.info("UnabletolocateMessageSourcewithname'"+MESSAGE_SOURCE_BEAN_NAME+
"':usingdefault["+this.messageSource+"]");
}
}
}
分析之后发现这样一个判断语句:this.parent!=null&&this.messageSourceinstanceofHierarchicalMessageSource,这个initMessageSource方法只做个这一个loop,也就是说spring只知道是来自HierarchicalMessageSource子类的实例而不接受其它的对象.ResourceBundleMessageResource就是HierarchicalMessageSource实例之一. 也就是说.ResourceBundleMessageResource这个类就是用来加载资源的,而显示却放在了ApplicationContext这个分支在做.
另外,publicstaticfinalStringMESSAGE_SOURCE_BEAN_NAME="messageSource";这个也表明为什么我么在写bean id 的时候只能写成messageSource而不实其他.
所以我继续寻找看能否够拿到ApplicationContext相关的对象.
最后发现了spring 的一些util发放可以做到这个:
PortletApplicationContextUtils, 传统的web application应该有相似的方式可以得到的.
个人见解,谢谢
--------------------------------------------------------------------------------------------------
写到这里,原来以为大功告成...结果不是那样的.我发现在我的java code里面要使用这个静态方法来实现国际化始终找不到资源. 没有理由啊,一切都顺理成章.但是的确实拿不到,于是静下心来仔细查找.发现getRequiredWebApplicationContext(PortletContext)这个方法里面的applicationContext的获取很诡异:
ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,不是指的web.xml的context么, 于是,我打印了一些context的类容.发现还真是web.xml的. 晕....
通常web.xml我们用来加载一些common的文件,比如, 我们用来加载applicationContext.xml.(WEB-INF/context/applicationContext) 也就是说要是我们把messageSource 写到applicationContext.xml.是不是就可以拿到resource了呢?经过测试,果然,有用.
protectedstaticApplicationContextgetApplicationContext(PortletRequestportletRequest)...{
PortletContextpc=portletRequest.getPortletSession().getPortletContext();
ApplicationContextac=PortletApplicationContextUtils.getRequiredWebApplicationContext(pc);
returnac;
}
这是我简单写的拿到ApplicationContext对象的static method.由于我做的是portlet开发,所以用的
Objectattr=pc.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
publicstaticfinalStringDEFAULT_VIEW_RENDERER_URL="/WEB-INF/servlet/view";
privateStringviewRendererUrl=DEFAULT_VIEW_RENDERER_URL;
//ExposePortletApplicationContexttoviewobjects.
request.setAttribute(ViewRendererServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE,getPortletApplicationContext());
//TheseattributesarerequiredbytheViewRendererServlet.
request.setAttribute(ViewRendererServlet.VIEW_ATTRIBUTE,view);
request.setAttribute(ViewRendererServlet.MODEL_ATTRIBUTE,mv.getModel());
//Uncludethecontentoftheviewintherenderresponse.
getPortletContext().getRequestDispatcher(this.viewRendererUrl).include(request,response);
ViewRenderServlet会被配置在web.xml里面
<servlet>
<servlet-name>ViewRendererServlet</servlet-name>
<servlet-class>
org.springframework.web.servlet.ViewRendererServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>ViewRendererServlet</servlet-name>
<url-pattern>/WEB-INF/servlet/view</url-pattern>
</servlet-mapping>
当检测到有/WEB-INF/servlet/view开始的页面请求,就会被ViewRendererServlet拦截.那么看看ViewRendererServlet具体做了什么呢?
分析ViewRendererServlet可以看出 ViewRendererServlet extends HttpServlet
这一句很重要,现在你应该明白究竟"因为
publicstaticfinalStringWEB_APPLICATION_CONTEXT_ATTRIBUTE=DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE;
DispatcherPortlet
在以 DispatcherServlet
相同的方式暴露当前的本地化信息"是什么意思了吧.
<完>