文章目录
1. 介绍
Spring父子容器是指Spring IOC容器中存在另一个IOC容器,其两者并不是继承关系,而是在AbstractApplicationContext
类中存在一个ApplicationContext
类型的成员变量,变量名为parent
,所以称之为父子容器;其特点是子容器可以访问父容器中的BeanDefinition实例
和Bean实例
,反过来父容器访问子容器则不行,如Spring MVC
和Spring
就是一对父子容器;在Spring官方中称父子容器为Servlet WebApplicationContext
和Root WebApplicationContext
;
1.1 为什么需要父子容器?
父子容器的主要作用是划分框架边界。因为在日常J2EE三层架构中,service
和dao
层的实例一般是交由Spring来管理的,而web
层则有多种选择,如Spring MVC
、Struts
、Struts2
等等,因此对于web
层我们一般使用单独的配置文件来管理其实例,在对web
层的框架进行更换时也不用去修改service
和dao
层的代码和配置;而且在J2EE架构中,web
层通常需要注入service
层的实例,所以web
层作为子容器,service
层作为父容器,也符合子容器可以访问父容器的实例的规则;
1.2 是否可以把所有的类通过父容器来管理?
不行,Spring MVC
在解析@RequestMapping
注解时只会对子容器中的Bean实例进行处理,并没有去查找父容器的Bean实例,所以在请求接口时会产生404
;
1.3 是否可以把所有的类通过子容器来管理?
可以,但不推荐;而且如果在父容器中有AOP或者事务配置,也得一起迁移到子容器的配置文件中来,否则将产生AOP或者事务不生效的情况;
1.4 是否可以把所有的类在父子容器中同时管理?
一般不采用这种方式配置,原因一是同样的实例在父子容器中都存在一份造成了资源浪费,原因二是子容器在注入service
层实例时不会采用父容器中的实例,这样如果在父容器中配置有AOP或者事务也会导致其失效;
1.5 Spring Boot中是否存在父子容器?
不存在,Spring Boot
中所有的Bean是由其源码中的AnnotationConfigEmbeddedWebApplicationContext
容器管理的,这个类是WebApplicationContext
接口的子类;
2. 源码
2.1 父容器的创建过程
父容器的配置
<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>
入口
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
// 这个方法会被Servlet调用
@Override
public void contextInitialized(ServletContextEvent event) {
// 初始化RootWebApplicationContext
initWebApplicationContext(event.getServletContext());
}
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
初始化RootWebApplicationContext
public class ContextLoader {
// ......
@Nullable
private WebApplicationContext context;
// ......
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!");
}
servletContext.log("Initializing Spring root WebApplicationContext");
Log logger = LogFactory.getLog(ContextLoader.class);
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
if (this.context == null) {
/*
* 创建RootWebApplicationContext实例
* 实例Class类型默认从Servlet上下文的初始化参数contextClass中获取
* 若未获取到则从默认策略(ContextLoader.properties配置文件)中获取
*/
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
// 配置并刷新RootWebApplicationContext, Bean实例创建就是在这里
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
// 把RootWebApplicationContext以org.springframework.web.context.WebApplicationContext.ROOT为名称存入Servlet上下文中(重要)
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException | Error ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
}
}
2.2 子容器的创建过程
DispatcherServlet继承体系
初始化方法init()
在HttpServletBean
类中
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
// ......
@Override
public final void init() throws ServletException {
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// 初始化ServletBean
initServletBean();
}
protected void initBeanWrapper(BeanWrapper bw) throws BeansException {
}
// 由子类实现
protected void initServletBean() throws ServletException {
}
// ......
}
初始化ServletBean
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
// ......
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
long startTime = System.currentTimeMillis();
try {
// 初始化ServletWebApplicationContext
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
if (logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ?
"shown which may lead to unsafe logging of potentially sensitive data" :
"masked to prevent unsafe logging of potentially sensitive data";
logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
"': request parameters and headers will be " + value);
}
if (logger.isInfoEnabled()) {
logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}
// ......
protected WebApplicationContext initWebApplicationContext() {
// 从Servlet上下文中获取RootWebApplicationContext, 与RootWebApplicationContext创建后存入Servlet上下文相互呼应
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
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) {
wac = findWebApplicationContext();
}
if (wac == null) {
// 创建ServletWebApplicationContext, 并将RootWebApplicationContext注入作为父容器
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
synchronized (this.onRefreshMonitor) {
// 刷新ServletWebApplicationContext
onRefresh(wac);
}
}
if (this.publishContext) {
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
// ......
}
2.3 子容器获取父容器Bean实例过程
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
// ......
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
String beanName = transformedBeanName(name);
Object beanInstance;
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
// ......
else {
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// 获取父容器
BeanFactory parentBeanFactory = getParentBeanFactory();
// 如果在当前容器中未找到BeanDefinition且父容器不为空, 则去父容器中获取实例
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
String nameToLookup = originalBeanName(name);
if (parentBeanFactory instanceof AbstractBeanFactory) {
return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
nameToLookup, requiredType, args, typeCheckOnly);
}
else if (args != null) {
return (T) parentBeanFactory.getBean(nameToLookup, args);
}
else if (requiredType != null) {
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
else {
return (T) parentBeanFactory.getBean(nameToLookup);
}
}
// ......
}
return adaptBeanInstance(name, beanInstance, requiredType);
}
// ......
}