在讲述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的父类AbstractApplicationContext的refresh()方法,容器的加载过程是该方法再通过调用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); } }
AbstractApplicationContext的refresh()方法执行完成后,标志着Spring IOC容器 加载完成。
Spring IOC容器 加载完成后,才开始Spring mvc容器的初始化。上面已经提到Spring mvc容器是通过DispatcherServlet加载的,DispatcherServlet本质上是一个Servlet,容器的初始化过程是在FrameworkServlet的initWebApplicationContext()方法中完成:
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层对Servvice、Dao层可见,但反之则不行,这也就是事务处理在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容器中的类型为ResourceBundleMessageSource,DelegatingMessageSource肯定不是我们期望。再深入分析,messageSource是在Controller层使用,Controller属于Spring MVC容器,其依赖的bean优先从Spring MVC容器中查找,如果找不到再到Spring IOC容器中查找。
根据异常信息发现找到的是messageSource是DelegatingMessageSource类型,可以推测下,应该是在Spring MVC容器中有一个messageSource类型为DelegatingMessageSource,首先被找到后就返回了,没有继续到父容器中查找。带着这样个想法,又读了一遍容器初始化的源码,在AbstractApplicationContext的refresh()方法中,发现其调用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的名称看起来不雅观。
方法三:把资源文件拆分成两份:testMVC和testIOC,Controller层使用的资源文件为testMVC,其他层使用的资源文件为testIOC, 分别在spring-mvc.xml和spring-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层都会使用同样的配置值。
这三种方式,可以根据实际项目情况进行选择。
转载请注明出处:
相关推荐
在mySpring框架中,我们可以按照上述思路,逐步实现IOC容器和MVC模型。首先,构建一个简单的配置解析器,然后实现bean的实例化和依赖注入。接着,设计Controller的注册和调用机制,以及视图解析过程。最后,将这些...
总的来说,Spring框架通过其IoC容器实现了对象的管理和依赖注入,带来了诸多好处,同时Eclipse作为强大的开发工具,为创建和管理Spring Web应用提供了便利。了解和掌握这些知识,对于提升Java开发效率和代码质量具有...
在Spring框架中,ApplicationContext是IoC容器的核心,它不仅负责管理Bean,还提供了实现国际化(i18n)的功能。国际化使应用程序能够适应不同语言和地区的用户,无需修改代码即可提供多语言支持。Spring通过实现...
Spring框架的IOC容器负责创建对象、管理对象之间的关系以及执行对象的生命周期方法。例如,`SpringIOC`目录中的配置文件(如`applicationContext.xml`)用于定义bean的定义和它们之间的依赖关系。通过XML或注解方式...
Spring MVC是一种基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,使用了IoC容器,支持RESTful风格的应用程序开发。Spring MVC通过分离模型(Model)、视图(View)和控制器(Controller)来简化Web开发...
Spring Ioc容器是整个Spring框架的基石,它负责创建、配置和管理对象。容器通过读取XML、Java注解或Java配置类等方式,获取对象的定义信息,然后根据这些信息实例化对象并进行依赖注入。 **三、依赖注入(DI,...
7. **Jackson 或 Gson**:用于 JSON 数据的序列化和反序列化,例如 `jackson-databind.jar`、`jackson-core.jar` 和 `jackson-annotations.jar`,或者 `gson.jar`。 8. **Dojo 或 jQuery**:如果在前端使用 ...
Spring的IOC容器是其核心特性之一,它负责管理对象的生命周期和依赖关系。开发者定义Bean的配置,而IOC容器负责创建、装配和管理这些Bean。使用XML、注解或者Java配置,可以声明Bean及其依赖。这种方式使得代码更加...
BeanFactory提供了基础的IoC支持,而ApplicationContext是BeanFactory的子接口,为Spring应用提供配置服务,并且支持国际化和资源访问。 通过这部分内容的学习,可以了解到Spring框架如何帮助开发者从编写大量样板...
IoC容器是Spring框架的重要组成部分,它负责管理对象的生命周期、配置和装配过程。通过Spring IoC容器,开发者可以将对象间的依赖关系交给Spring容器进行管理,从而实现对象间的解耦。 #### 二、Spring IoC容器的...
它以强大的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 IoC(Inversion of Control)容器,它提供了Model-View-Controller(MVC)架构模式的实现,使得开发者可以将业务逻辑、数据处理和用户界面分离,从而提高代码的复用性和模块化。 在"Spring MVC ...
它扩展了核心容器(由spring-core和spring-beans组成),增加了对国际化、资源加载、事件处理等功能的支持。 3. **spring-core-4.1.5.RELEASE.jar**:Spring的核心库,包含了IoC(Inversion of Control)和依赖注入...
Spring MVC 是Spring框架的一部分,它基于Spring IoC(Inversion of Control,控制反转)容器,简化了配置并提供了强大的功能。框架将应用程序分为Controller、View和Model三层,使得业务逻辑、用户界面和数据模型...
MVC 模式是一种将业务逻辑、数据和用户界面分离的设计模式,使得开发过程更加模块化,易于维护。 在 Spring MVC 中,Controller 负责处理 HTTP 请求,Model 代表应用程序数据,而 View 负责渲染用户界面。Spring ...
然后,配置Spring的IoC容器,包括定义bean的实例化方式、初始化参数等。接着,设置Spring MVC的配置,如处理器映射器、视图解析器等。最后,配置Hibernate连接数据库的相关信息,包括数据源、实体类、映射文件等。 ...
在本教程中,Spring将作为基础架构层,帮助开发者实现松耦合和控制反转(IoC),使得应用程序更加模块化和易于测试。 Spring MVC是Spring框架的一部分,专门用于构建Web应用程序的Model-View-Controller(MVC)架构...
9. **国际化与本地化**:Spring MVC支持多语言环境,通过视图解析器和国际化资源文件,可以轻松实现内容的本地化。 10. **测试支持**:Spring MVC提供了MockMVC工具,方便进行单元测试和集成测试,确保应用的正确性...
Spring的IOC(Inversion of Control,控制反转)容器是其核心...虽然这个简单的实现可能没有Spring框架那样全面和高效,但它可以帮助我们理解Spring IOC容器的工作原理,为进一步学习和研究Spring框架打下坚实的基础。