Spring .ResourceBundleMessageResource和ApplicationContext的联系与区别

 最近美国那边的工程师提了个问题,关于 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.*;

String ResourceBundleMessageSource.getMessage(String key  
// message key
                                              String[], // message args
                                              String,   // default value
                                              Locale)

引用原文,

于是我就开始寻找怎么得到ResourceBundleMessageResource如何实例化,大家都知道 ResourceBundleMessageResource是在国际化的时候使用的,我们通常在spring的bean.xml里面这样配置来加载我们 的资源文件:

      <bean id="messageSource"
            class
="org.springframework.context.support.ResourceBundleMessageSource">
            
<property name="basenames">
                  
<list>
                        
<value>Messages</value>
                  
</list>
            
</property>
      
</bean>
Spring IOC会帮我们管理ResourceBundleMessageResource对象的实例话和资源文件的加载.

也就是说在我们的应用启动的时候这些文件已经被加载了,对象已经被实例化了,我们要怎么拿到这个对象,使得我们在java code里面可以使用这个对象呢?

查遍了所有的Spring source code没有发现好的方法,可以这样做么?

      ResourceBundleMessageSource ms  = new ResourceBundleMessageSource();            
         //
Actually, This object had been created when the bean.xml is loaded.
         //Shall I should create another?
     
//not singleton? Too many object will be created?
      ms.setBasename("MessageFileName");
     
//load message file again?
看我的comment.

这个办法不行..new始终难以保证单例,会造成performance的问题.继续寻找其他方法吧.
继续寻找的时候发现下面的类层次:(MessageResource)


会不会ResourceBundleMessageResource只是做文件的load而读取显示实由ApplicationContext来做的呢, 查找之后发现了这样个代码片段:位于AbstractApplicationContext

 
 
 
 
 / **
 * Initialize the MessageSource.
 * Use parent's if none defined in this context.
 
*/

private   void  initMessageSource()  throws  BeansException  {
        
if (containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
                
this.messageSource = (MessageSource) getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
    
// Make MessageSource aware of parent MessageSource.
            if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource) {
                                       HierarchicalMessageSource hms 
= (HierarchicalMessageSource) this.messageSource;
           
if (hms.getParentMessageSource() == null{
          
// Only set parent context as parent MessageSource if no parent MessageSource
          
// registered already.
          hms.setParentMessageSource(getInternalParentMessageSource());
            }

        }

        
if (logger.isInfoEnabled()) {
            logger.info(
"Using MessageSource [" + this.messageSource + "]");
        }

    }

    
else {
        
// Use empty MessageSource to be able to accept getMessage calls.
        DelegatingMessageSource dms = new DelegatingMessageSource();
        dms.setParentMessageSource(getInternalParentMessageSource());
        
this.messageSource = dms;
        
if (logger.isInfoEnabled()) {
        logger.info(
"Unable to locate MessageSource with name '" + MESSAGE_SOURCE_BEAN_NAME +
            
"': using default [" + this.messageSource + "]");
        }

    }

}

分析之后发现这样一个判断语句:this.parent != null && this.messageSource instanceof HierarchicalMessageSource,这个initMessageSource方法只做个这一个loop,也就是说spring只知道是来自HierarchicalMessageSource子类的实例而不接受其它的对象.ResourceBundleMessageResource就是HierarchicalMessageSource实例之一. 也就是说.ResourceBundleMessageResource这个类就是用来加载资源的,而显示却放在了ApplicationContext这个分支在做.
另外,public static final String MESSAGE_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了呢?经过测试,果然,有用.
    protected static ApplicationContext getApplicationContext(PortletRequest portletRequest){
        PortletContext pc 
= portletRequest.getPortletSession().getPortletContext();
        ApplicationContext ac 
= PortletApplicationContextUtils.getRequiredWebApplicationContext(pc);
        
return ac;
    }

这是我简单写的拿到ApplicationContext对象的static method.由于我做的是portlet开发,所以用的
Object attr = pc.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);

public static final String DEFAULT_VIEW_RENDERER_URL = "/WEB-INF/servlet/view";

private String viewRendererUrl = DEFAULT_VIEW_RENDERER_URL;

// Expose Portlet ApplicationContext to view objects.
request.setAttribute(ViewRendererServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, getPortletApplicationContext());

// These attributes are required by the ViewRendererServlet.
request.setAttribute(ViewRendererServlet.VIEW_ATTRIBUTE, view);
request.setAttribute(ViewRendererServlet.MODEL_ATTRIBUTE, mv.getModel());
// Unclude the content of the view in the render response.
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
这一句很重要,现在你应该明白究竟"因为
public static final String WEB_APPLICATION_CONTEXT_ATTRIBUTE = DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE;

DispatcherPortlet 在以 DispatcherServlet 相同的方式暴露当前的本地化信息"是什么意思了吧.

<完>

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值