Spring MVC_多IOC容器整合
多 IOC 容器整合
为了实现更好的解耦,我们在实际开发中往往还是需要将数据源、Service、Dao 等组件配置到传统的 Spring 配置文件中,并通过ContextLoaderListener启动这个 IOC 容器。 而在表述层负责处理请求的 handler 组件则使用 SpringMVC 自己来启动。
这会导致一个问题:同样的组件会被创建两次。
1、多个 IOC 容器之间的关系
① 启动日志中的关键信息
五月 13, 2021 9:06:39 上午 org.springframework.web.context.ContextLoader initWebApplicationContext
信息: Root WebApplicationContext: initialization started
五月 13, 2021 9:06:39 上午 org.springframework.web.context.support.XmlWebApplicationContext prepareRefresh
信息: Refreshing Root WebApplicationContext: startup date [Thu May 13 09:06:39 CST 2021]; root of context hierarchy
五月 13, 2021 9:06:39 上午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [spring-ioc.xml]
五月 13, 2021 9:06:40 上午 org.springframework.web.context.ContextLoader initWebApplicationContext
信息: Root WebApplicationContext: initialization completed in 804 ms
五月 13, 2021 9:06:40 上午 org.springframework.web.servlet.DispatcherServlet initServletBean
信息: FrameworkServlet ‘dispatcherServlet’: initialization started
五月 13, 2021 9:06:40 上午 org.springframework.web.context.support.XmlWebApplicationContext prepareRefresh
信息: Refreshing WebApplicationContext for namespace ‘dispatcherServlet-servlet’: startup date [Thu May 13 09:06:40 CST 2021]; parent: Root WebApplicationContext
五月 13, 2021 9:06:40 上午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [spring-mvc.xml]
五月 13, 2021 9:06:40 上午 org.springframework.web.servlet.DispatcherServlet initServletBean
信息: FrameworkServlet ‘dispatcherServlet’: initialization completed in 544 ms
结论:ContextLoaderListener 创建的是父容器;DispatcherServlet 创建的是子容器。
② 源码
[1]设置父容器的源码位置
org.springframework.web.servlet.FrameworkServlet 类
createWebApplicationContext(ApplicationContext parent)方法
关键代码:wac.setParent(parent);
1 | protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) { |
[2]将根级别容器存入应用域
ContextLoaderListener 创建 IOC 容器对象后,将 IOC 容器对象存入了 ServletContext 的属性域。
属性名是 org.springframework.web.context.WebApplicationContext 类中定义的常量:ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
源码证据:
所在类:org.springframework.web.context.ContextLoader
所在方法:initWebApplicationContext(ServletContext servletContext)
关键代码:servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
[3]把父容器赋值给 parent 属性
所在类:org.springframework.context.support.AbstractApplicationContext
所在方法:setParent(ApplicationContext parent)
关键代码:this.parent = parent;
结论:SpringMVC 的 IOC 容器对象把 Spring 的 IOC 容器对象赋值给了自己的 parent 属性。也就是说通过 parent 属性能够获取到父容器对象的引用。当有需要获取父容器时,调用 getParent()方法即可。
③ 子容器访问父容器中资源
结论:子容器能够访问到父容器中的资源(bean);父容器没法访问到子容器中的资源。
原因:子容器可以通过自己的 parent 属性找到父容器对象,但是父容器对象并没有属性存放所有子容器。
2、重复创建对象
① 证据
在各个组件的无参构造器中,编写打印代码,然后查看启动日志:
信息: Loading XML bean definitions from class path resource [spring-ioc.xml]
EmpControler 对象创建了
EmpService 对象创建了
EmpDao 对象创建了……
信息: Loading XML bean definitions from class path resource [spring-mvc.xml]
EmpControler 对象创建了
EmpService 对象创建了
EmpDao 对象创建了
② 重复创建对象的危害
- 危害 1:浪费内存
- 危害 2:由 SpringMVC 的 IOC 容器创建的 EmpService 是没有事务的。而此时 EmpController 会就近装配这个没有事务的 EmpService,从而导致程序运行时调用是 Service 是一个没有事务功能的 Service
③ 解决方案 1[建议]
[1]Spring 扫描的包
1 | <!-- 配置自动扫描的包 --> |
[2]SpringMVC 扫描的包
1 | <!-- 配置自动扫描的包 --> |
④ 解决方案 2
[1]Spring 扫描的包
1 | <!-- 重复创建对象的解决方案二:在两个IOC容器都扫描同一个包的前提下,Spring的IOC容器排除@Controller注解标记的类 --> |
[2]SpringMVC 扫描的包
1 | <!-- 重复创建对象的解决方案二:在两个IOC容器都扫描同一个包的前提下,SpringMVC的IOC容器仅扫描@Controller注解标记的类 --> |
注意:两种解决方案只可以选择其中的一种,推荐解决方案 1。