`

Spring MVC容器和IOC容器引起的国际化问题

阅读更多

在讲述Spring国际化中遇到的问题之前,首先看下Spring MVC容器和Spring IOC容器,双容器的相关背景知识。

 

双容器

 

通常所说的spring 容器,只的是IOC容器。容器的主要作用是在程序启动时把所需的bean提前加载到内存(本质上是存储在一个ConcurrentHashMap里,DefaultListableBeanFactory

类的beanDefinitionMap字段),恰好如果web层使用的是Spring MVC,这时会产生另一个新的容—Spring MVC容器。这两个容器的启动入口都是在web.xml配置:

 

<!--  step1 Spring 容器启动监听器 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
<!-- spring IOC 容器配置 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-config.xml</param-value>
    </context-param>
 
    <!-- step2 Spring mvc 容器配置 -->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath*:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup> <!-- 程序启动时装在该servlet -->
    </servlet>
 
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
 

 

这份web.xml配置指出:Spring IOC容器是通过ContextLoaderListener启动,并根据spring-config.xml的配置读取对应的bean列表加载到容器;Spring MVC容器是通过DispatcherServlet启动,并根据spring-mvc.xml的配置读取对应的bean列表加载到容器。

 

两个容器的初始化过程

 

两个容器的初始化顺序为: Spring IOC容器àSpring mvc容器。首先看Spring IOC容器,通过阅读ContextLoaderListener的源码,可以得知其通过initWebApplicationContext方法创建容器,类型为XmlWebApplicationContext。创建完成后,调用容器自身的refresh()方法加载到容器,实际调用的是XmlWebApplicationContext的父类AbstractApplicationContextrefresh()方法,容器的加载过程是该方法再通过调用refreshBeanFactory()创建容器自己的beanFactory,并读取配置文件把bean加载到容器:

protected final void refreshBeanFactory() throws BeansException {
        if(this.hasBeanFactory()) {//如果容器已有beanFactory,先销毁
            this.destroyBeans();
            this.closeBeanFactory();
        }
 
        try {
            //创建一个DefaultListableBeanFactory类型的BeanFactory
            DefaultListableBeanFactory ex = this.createBeanFactory();
            ex.setSerializationId(this.getId());
            this.customizeBeanFactory(ex);
           
            //通过该方法读取配置文件spring-config.xml中所有的bean加载到容器
            this.loadBeanDefinitions(ex);
            Object var2 = this.beanFactoryMonitor;
            synchronized(this.beanFactoryMonitor) {
                //把刚创建的DefaultListableBeanFactory赋值给容器
                this.beanFactory = ex;
            }
        } catch (IOException var5) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var5);
        }
    }

 

AbstractApplicationContextrefresh()方法执行完成后,标志着Spring IOC容器 加载完成。

 

Spring IOC容器 加载完成后,才开始Spring mvc容器的初始化。上面已经提到Spring mvc容器是通过DispatcherServlet加载的,DispatcherServlet本质上是一个Servlet,容器的初始化过程是在FrameworkServletinitWebApplicationContext()方法中完成:

protected WebApplicationContext initWebApplicationContext() {
        //获取父容器---spring ioc容器,通过ContextLoaderListener已经创建完毕
        WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
        WebApplicationContext wac = null;
 
        //省略部分代码
 
        if(wac == null) {
            //创建spring mvc容器类型为XmlWebApplicationContext,父容器为spring ioc容器
            wac = this.createWebApplicationContext(rootContext);
        }
 
        if(!this.refreshEventReceived) {
            this.onRefresh(wac);
        }
        //省略部分代码
        return wac;
    }
 

 

该方法通过调用createWebApplicationContext方法是创建容器,通过onRefresh方法把bean加载到容器。首先来看createWebApplicationContext方法:

protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
        //获取容器类型,这里还是XmlWebApplicationContext
        Class contextClass = this.getContextClass();
        if(this.logger.isDebugEnabled()) {
            this.logger.debug("Servlet with name \'" + this.getServletName() + "\' will try to create custom WebApplicationContext context of class \'" + contextClass.getName() + "\'" + ", using parent context [" + parent + "]");
        }
 
        if(!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Fatal initialization error in servlet with name \'" + this.getServletName() + "\': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext");
        } else {
            //根据contextClass 创建容器
            ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
            wac.setEnvironment(this.getEnvironment());
           
            //设置父容器,这里是Spring IOC容器
            wac.setParent(parent);
            wac.setConfigLocation(this.getContextConfigLocation());
           
            //调用AbstractApplicationContext的refresh()方法,加载bean到容器
            this.configureAndRefreshWebApplicationContext(wac);
            return wac;
        }
    }

 

这里contextClass是容器的类型,为XmlWebApplicationContext。然后根据contextClass创建Spring MVC容器,并设置之前已经创建好的Spring IOC容器为其父容器,也就是说Spring MVC容器和Spring IOC容器虽然是独立的,但也存在父子关系。最后的configureAndRefreshWebApplicationContext方法会调用容器自身的refresh()方法,加载bean到容器完成初始化,refresh()方法的主要执行过程在Spring Ioc容器加载过程中已有描述,这里不再累述。

 

两个容器的父子关系

 

再说下Spring MVC容器和Spring IOC容器的父子关系:子容器对父容器是可见的,但父容器对子容器不可见,这有点类似java类的父子关系。所谓可以见,就是在调用容器的getBean方法时,子容器会在自己的容器里先查找有没有对应的bean,如果没有就会到父容器中查找,具体代码实现参考AbstractBeanFactory类的doGetBean方法:

protected <T> T doGetBean(String name, Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException {
        final String beanName = this.transformedBeanName(name);
        //首先在自己的容器中查找
        Object sharedInstance = this.getSingleton(beanName);
        Object bean;
        if (sharedInstance != null && args == null) {
            if (this.logger.isDebugEnabled()) {
                if (this.isSingletonCurrentlyInCreation(beanName)) {
                    this.logger.debug("Returning eagerly cached instance of singleton bean \'" + beanName + "\' that is not fully initialized yet - a consequence of a circular reference");
                } else {
                    this.logger.debug("Returning cached instance of singleton bean \'" + beanName + "\'");
                }
            }
 
            bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, (RootBeanDefinition) null);
        } else {
            if (this.isPrototypeCurrentlyInCreation(beanName)) {
                throw new BeanCurrentlyInCreationException(beanName);
            }
 
            //如果自己的容器中没找到,在到父容器中查找
            BeanFactory ex = this.getParentBeanFactory();
            if (ex != null && !this.containsBeanDefinition(beanName)) {
                String var24 = this.originalBeanName(name);
                if (args != null) {
                    return ex.getBean(var24, args);
                }
 
                return ex.getBean(var24, requiredType);
            }
            //省略其他代码
        }
        //省略其他代码
}
 

 

换句话说,Spring MVC容器可以使用Spring IOC容器中的bean,反之则不行。通常spring mvc项目中分为 Controller层、Service层、Dao层,一般Controller层使用Spring MVC容器,其他的放到Spring IOC容器,Controller层对ServviceDao层可见,但反之则不行,这也就是事务处理在Controller层失效的原因。

 

Spring的双容器有时,会引发一些问题,比如 下面讲到的国际化资源 可见性问题。

 

Spring国际化中遇到的问题

 

最近做的项目需要满足国际化需求,直接使用Spring MVC视图渲染技术做页面国际化处理。但对于有些ajax的异步数据接口里的文字信息,由于没有视图页面,只能在接口数据返回时自行处理。处理方式为:

1、首先在xml中配置国际化资源文件,为了方便在Service层和Dao层使用,下列配置会配置到Spring IOC容器对应的spring-config.xml配置文件。

<!-- 国际化资源文件 -->
    <bean id="messageSource"
          class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames" >
            <list>
                <value>test</value>
            </list>
        </property>
        <property name="defaultEncoding" value="UTF-8"/>
</bean>

 

该配置会自动读取test开头的国际化资源文件列表,这里有三个:



 

为了测试,配置文件内容均为:my.name=lilei

 

2、在需要使用国际化的地方,直接从容器中获取messageSource,调用其getMessage方法。这里就发现一个奇怪的问题,在Service层、Dao层使用没有问题,但在Controller层使用却报异常:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'xxxxController': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeException:
Bean named 'messageSource' must be of type
[org.springframework.context.support.ResourceBundleMessageSource], but was actually of type [org.springframework.context.support.DelegatingMessageSource]

Controller中的代码为:

 

@Resource
private ResourceBundleMessageSource messageSource;
 
@ResponseBody
    @RequestMapping("/i18n")
    public String Testi18n(Locale locale){  // Locale 入参
        String val = messageSource.getMessage("my.name", null, locale);
        System.out.println(val);
        return val;
    }
 

 

通过分析异常日志可以发现,其大概意思是在通过容器的getBean方法获取messageSource bean时,期望messageSource的类型为ResourceBundleMessageSource类型,但得到的是DelegatingMessageSource类型。

 

由于上述配置在Spring IOC容器中的类型为ResourceBundleMessageSourceDelegatingMessageSource肯定不是我们期望。再深入分析,messageSource是在Controller层使用,Controller属于Spring MVC容器,其依赖的bean优先从Spring MVC容器中查找,如果找不到再到Spring IOC容器中查找。

 

根据异常信息发现找到的是messageSourceDelegatingMessageSource类型,可以推测下,应该是在Spring MVC容器中有一个messageSource类型为DelegatingMessageSource,首先被找到后就返回了,没有继续到父容器中查找。带着这样个想法,又读了一遍容器初始化的源码,在AbstractApplicationContextrefresh()方法中,发现其调用this.initMessageSource()方法进行资源文件加载,方法内容为:

protected void initMessageSource() {
        ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();
        //判断容器中是否已经包含名称为messageSource 的资源对象
        if(beanFactory.containsLocalBean("messageSource")) {
            this.messageSource = (MessageSource)beanFactory.getBean("messageSource", MessageSource.class);
            if(this.parent != null && this.messageSource instanceof HierarchicalMessageSource) {
                HierarchicalMessageSource dms = (HierarchicalMessageSource)this.messageSource;
                if(dms.getParentMessageSource() == null) {
                    dms.setParentMessageSource(this.getInternalParentMessageSource());
                }
            }
 
            if(this.logger.isDebugEnabled()) {
                this.logger.debug("Using MessageSource [" + this.messageSource + "]");
            }
        } else {
            //如果容器中不包含名为messageSource的资源对象,就新建一个DelegatingMessageSource类型的资源对象放入容器
            DelegatingMessageSource dms1 = new DelegatingMessageSource();
            dms1.setParentMessageSource(this.getInternalParentMessageSource());
            this.messageSource = dms1;
            beanFactory.registerSingleton("messageSource", this.messageSource);
            if(this.logger.isDebugEnabled()) {
                this.logger.debug("Unable to locate MessageSource with name \'messageSource\': using default [" + this.messageSource + "]");
            }
        }
 
}
 

 

果然在容器中如果没有名称为messageSource的资源对象,会自动创建一个名称为messageSource 类型为DelegatingMessageSource的资源对象,并放入容器。

 

这就是上述异常发生的根本原因。

 

解决办法

 

方法一:把下列资源对象配置从Spring IOC容器对应的spring-config.xml 迁移到Spring MVC容器对应的

spring-mvc.xml中,即:
<bean id="messageSource"
          class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames" >
            <list>
                <value>test</value>
            </list>
        </property>
        <property name="defaultEncoding" value="UTF-8"/>
</bean>
 

 

通过refresh()方法的源码发现,如果容器中已经存在名称为messageSource的资源对象,就不会再去创建DelegatingMessageSource类型的资源对象。

 

缺点:通过配置迁移,可以在Controller层中使用messageSource资源对象。但由于Sping IOC容器对该messageSource资源对象不可见,在Service层或Dao层就无法使用该对象获取国际化消息了。

 

方法二:资源对象配置还是Spring IOC容器对应的spring-config.xml中,只是把bean名称改下,不使用messageSource即可。

<bean id="messageSourceIoc"
          class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames" >
            <list>
                <value>test</value>
            </list>
        </property>
        <property name="defaultEncoding" value="UTF-8"/>
</bean>

这种方式Controller层、Service层、Dao层都可以使用,只是bean的名称看起来不雅观。

 

方法三:把资源文件拆分成两份:testMVCtestIOCController层使用的资源文件为testMVC,其他层使用的资源文件为testIOC 分别在spring-mvc.xmlspring-config.xml中配置资源对象:

spring-mvc.xml中配置为:

<bean id="messageSource"
          class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames" >
            <list>
                <value>testMVC</value>
            </list>
        </property>
        <property name="defaultEncoding" value="UTF-8"/>
</bean>
 

 spring-config.xml中配置为:

<bean id="messageSource"
          class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames" >
            <list>
                <value>testIOC</value>
            </list>
        </property>
        <property name="defaultEncoding" value="UTF-8"/>
</bean>
 

 

Bean的名称都是messageSource,但不会报错,也不会覆盖。因为他们分别属于两个独立的容器,在使用时也不会互相干扰。这种方式的缺点是:两个文件中有可能有重复配置,比如在Controller层、Service层都会使用同样的配置值。

 

这三种方式,可以根据实际项目情况进行选择。

 

转载请注明出处:

 

http://moon-walker.iteye.com/blog/2395925

  • 大小: 4.1 KB
0
0
分享到:
评论

相关推荐

    spring ioc+mvc代码

    在mySpring框架中,我们可以按照上述思路,逐步实现IOC容器和MVC模型。首先,构建一个简单的配置解析器,然后实现bean的实例化和依赖注入。接着,设计Controller的注册和调用机制,以及视图解析过程。最后,将这些...

    Spring实现原理及IoC容器的优点

    总的来说,Spring框架通过其IoC容器实现了对象的管理和依赖注入,带来了诸多好处,同时Eclipse作为强大的开发工具,为创建和管理Spring Web应用提供了便利。了解和掌握这些知识,对于提升Java开发效率和代码质量具有...

    09 Spring IoC容器ApplicationContext如何实现国际化慕课专栏1

    在Spring框架中,ApplicationContext是IoC容器的核心,它不仅负责管理Bean,还提供了实现国际化(i18n)的功能。国际化使应用程序能够适应不同语言和地区的用户,无需修改代码即可提供多语言支持。Spring通过实现...

    Spring IOC AOP MVC 简单例子

    Spring框架的IOC容器负责创建对象、管理对象之间的关系以及执行对象的生命周期方法。例如,`SpringIOC`目录中的配置文件(如`applicationContext.xml`)用于定义bean的定义和它们之间的依赖关系。通过XML或注解方式...

    Spring MVC 教程快速入门 深入分析

    Spring MVC是一种基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,使用了IoC容器,支持RESTful风格的应用程序开发。Spring MVC通过分离模型(Model)、视图(View)和控制器(Controller)来简化Web开发...

    springIoc实现原理

    Spring Ioc容器是整个Spring框架的基石,它负责创建、配置和管理对象。容器通过读取XML、Java注解或Java配置类等方式,获取对象的定义信息,然后根据这些信息实例化对象并进行依赖注入。 **三、依赖注入(DI,...

    Spring MVC所需jar包

    7. **Jackson 或 Gson**:用于 JSON 数据的序列化和反序列化,例如 `jackson-databind.jar`、`jackson-core.jar` 和 `jackson-annotations.jar`,或者 `gson.jar`。 8. **Dojo 或 jQuery**:如果在前端使用 ...

    Spring-MVC+Spring-IOC+Spring-JdbcTemple

    Spring的IOC容器是其核心特性之一,它负责管理对象的生命周期和依赖关系。开发者定义Bean的配置,而IOC容器负责创建、装配和管理这些Bean。使用XML、注解或者Java配置,可以声明Bean及其依赖。这种方式使得代码更加...

    Java EE 框架整合开发⼊⻔到实战——Spring+Spring MVC+MyBatis(微课版)课后习题答案.pdf

    BeanFactory提供了基础的IoC支持,而ApplicationContext是BeanFactory的子接口,为Spring应用提供配置服务,并且支持国际化和资源访问。 通过这部分内容的学习,可以了解到Spring框架如何帮助开发者从编写大量样板...

    spring ioc容器部署实现

    IoC容器是Spring框架的重要组成部分,它负责管理对象的生命周期、配置和装配过程。通过Spring IoC容器,开发者可以将对象间的依赖关系交给Spring容器进行管理,从而实现对象间的解耦。 #### 二、Spring IoC容器的...

    Spring MVC

    它以强大的Spring IoC容器为基础,并充分利用容器的特性来简化它的配置。 commons-logging-1.2.jar jackson-annotations-2.6.6.jar jackson-core-2.6.6.jar jackson-databind-2.6.6.jar jakarta-taglibs-standard-...

    Spring MVC简单例子

    这个框架基于Spring IoC(Inversion of Control)容器,它提供了Model-View-Controller(MVC)架构模式的实现,使得开发者可以将业务逻辑、数据处理和用户界面分离,从而提高代码的复用性和模块化。 在"Spring MVC ...

    Spring,Spring MVC所需的jar包

    它扩展了核心容器(由spring-core和spring-beans组成),增加了对国际化、资源加载、事件处理等功能的支持。 3. **spring-core-4.1.5.RELEASE.jar**:Spring的核心库,包含了IoC(Inversion of Control)和依赖注入...

    Spring MVC 学习记录总结1

    Spring MVC 是Spring框架的一部分,它基于Spring IoC(Inversion of Control,控制反转)容器,简化了配置并提供了强大的功能。框架将应用程序分为Controller、View和Model三层,使得业务逻辑、用户界面和数据模型...

    spring mvc例子

    MVC 模式是一种将业务逻辑、数据和用户界面分离的设计模式,使得开发过程更加模块化,易于维护。 在 Spring MVC 中,Controller 负责处理 HTTP 请求,Model 代表应用程序数据,而 View 负责渲染用户界面。Spring ...

    搭建Spring+Spring MVC+Hibernate开发框架

    然后,配置Spring的IoC容器,包括定义bean的实例化方式、初始化参数等。接着,设置Spring MVC的配置,如处理器映射器、视图解析器等。最后,配置Hibernate连接数据库的相关信息,包括数据源、实体类、映射文件等。 ...

    Java EE企业级应用开发教程(Spring+Spring MVC+MyBatis)所有实验及实验报告.zip

    在本教程中,Spring将作为基础架构层,帮助开发者实现松耦合和控制反转(IoC),使得应用程序更加模块化和易于测试。 Spring MVC是Spring框架的一部分,专门用于构建Web应用程序的Model-View-Controller(MVC)架构...

    Spring MVCSpring MVC基础.ppt

    9. **国际化与本地化**:Spring MVC支持多语言环境,通过视图解析器和国际化资源文件,可以轻松实现内容的本地化。 10. **测试支持**:Spring MVC提供了MockMVC工具,方便进行单元测试和集成测试,确保应用的正确性...

    Spring实现一个简单的SpringIOC容器

    Spring的IOC(Inversion of Control,控制反转)容器是其核心...虽然这个简单的实现可能没有Spring框架那样全面和高效,但它可以帮助我们理解Spring IOC容器的工作原理,为进一步学习和研究Spring框架打下坚实的基础。

Global site tag (gtag.js) - Google Analytics