在看Spring的IOC源码时,看到了一个接口HierarchicalBeanFactory,该接口是为了获取父级容器。因此就去了解了一下spring为啥会用到父子容器。其中一个很典型的例子就是Spring和SpringMVC整合的web项目。
首先,需要了解一下什么是父子容器: 简单来说,父子容器中都有很多初始化的bean,子容器可以通过getBea()获取到父层级的bean,而父容器获取不到子容器的bean(类似于java的继承)。这样做的好处就是依赖分层明确,职责不混乱。 其次,我们看一下在整合项目中是如何实现父子容器的。在springMVC项目中,web.xml中需要配置如下一段代码
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext*.xml</param-value>
</context-param>
<!--创建父容器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 创建子容器-->
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-*.xml</param-value>
</init-param>
</servlet>
web项目启动的顺序是context-param->listener->filter->servlet
在ContextLoaderListener类中有一个initWebApplicationContext方法,该方法是为了创建父容器。
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// 判断是否有根容器,如果有,则报错
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
...
try {
// 创建根容器
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
// 判断线程上下文类加载器是否是WebAppClassLoader
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
...
return this.context;
}
...
}
之后在DispatcherServlet的初始化的时候,它会调FrameworkServlet#initWebApplicationContext方法,在该方法中进行了子容器的创建。
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// context实例在构造时被注入
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
//设置父上下文
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
//查找web容器
wac = findWebApplicationContext();
}
if (wac == null) {
// 创建web容器,在该方法中也设置了容器的父
wac = createWebApplicationContext(rootContext);
}
...
return wac;
}
最后,也去思考了几个问题。
- springMVC的加载需要DispatcherServlet,但是一定需要ContextLoaderListener吗? 不一定需要(参考blog.csdn.net/gzu_imis/ar… 但是为了更好的区分各层级bean的业务含义,一般是用子容器去加载controller层的bean;父容器去加载service和dao层的bean,这样可以保证在service层不会乱用controller层的bean。