Spring IOC 父子容器(注解方式启动)

SpringBoot 启动默认没有父子容器,只有一个容器

一、调试环境

依赖使用 Maven 管理,只用导入 spring-context 即可,这里的版本为 5.2.7

通常使用 spring 有两种配置方式:注解和配置文件

public static void main(String[] args) {
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
    // ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");

    Person bean = applicationContext.getBean(Person.class);
    System.out.println(bean);

    // 打印 IOC 容器中所有 bean 的名称
    String[] namesForType = applicationContext.getBeanDefinitionNames();
    for (String name : namesForType) {
        System.out.println(name);
    }
}

两种配置方式都可以启动 IOC 容器

注解

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration // 告诉 Spring 这是一个配置类
@ComponentScan // 默认扫描当前包(com.test.config)及其子包(com.test.config.*)
public class MainConfig {

    // 给容器中注册一个 Bean,类型为返回值的类型,id 默认是用方法名作为 id
    @Bean("person123")
    public Person person() {
        return new Person("lisi", 20, "nice");
    }
}
View Code

配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 包扫描、只要标注了@Controller、@Service、@Repository,@Component,禁用默认规则才可自定义扫描规则 -->
    <context:component-scan base-package="com.test" use-default-filters="false"/>

    <bean id="person" class="com.test.bean.Person" scope="prototype">
        <property name="age" value="29"></property>
        <property name="name" value="zhangsan"></property>
    </bean>
</beans>
View Code

一般情况下,我们不会像这样手动去初始化 IOC 容器,而是在 Web 环境下使用,它会自动初始化 IOC 容器,附上调试环境:https://www.cnblogs.com/jhxxb/p/10512553.html

二、开始源码

Tomcat 启动后会自动加载”配置类“:https://www.cnblogs.com/jhxxb/p/13596565.html

第一个被执行的 Spring 程序类应该就是 SpringServletContainerInitializer  类了

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    @Override
    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
        List<WebApplicationInitializer> initializers = new LinkedList<>();

        if (webAppInitializerClasses != null) {
            for (Class<?> waiClass : webAppInitializerClasses) {
                // 只处理实体类,接口和抽象类一概不管
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        initializers.add((WebApplicationInitializer) ReflectionUtils.accessibleConstructor(waiClass).newInstance());
                    } catch (Throwable ex) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
                    }
                }
            }
        }

        if (initializers.isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
            return;
        }

        servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
        // 排序后,循环调用 onStartup 方法进行初始化
        AnnotationAwareOrderComparator.sort(initializers);
        for (WebApplicationInitializer initializer : initializers) {
            initializer.onStartup(servletContext);
        }
    }
}

可以看到这里只有我们写的一个配置类,抽象类和接口都被过滤掉了

往下走,onStartup 的实现在 AbstractDispatcherServletInitializer 类

public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        super.onStartup(servletContext);
        // 注册 DispatcherServlet,让它去初始化 Spring MVC 的子容器
        registerDispatcherServlet(servletContext);
    }


public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
    // 父类方法,super.onStartup(servletContext)
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        // 注册 ContextLoaderListener 监听器,去初始化 Spring 父容器
        registerContextLoaderListener(servletContext);
    }

这里可以看出分两步,先初始化 Spring IOC,再初始化 Spring MVC IOC

注册 ContextLoaderListener,为了后续初始化 Spring IOC

public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
    protected void registerContextLoaderListener(ServletContext servletContext) {
        WebApplicationContext rootAppContext = createRootApplicationContext();
        if (rootAppContext != null) {
            // 创建 listener,并且把已经创建好的容器放进去
            ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
            // 放入监听器需要的一些上下文,此处没有。一般都为 null 即可。若有需要(自己定制),子类复写此方法即可
            listener.setContextInitializers(getRootApplicationContextInitializers());
            // 把监听器加入进来  这样该监听器就能监听 ServletContext 了,并且执行 contextInitialized 方法
            servletContext.addListener(listener);
        }

createRootApplicationContext 创建了一个 AnnotationConfigWebApplicationContext,并把配置文件注册了进去

public abstract class AbstractAnnotationConfigDispatcherServletInitializer extends AbstractDispatcherServletInitializer {
    @Override
    @Nullable // 返回值允许为 null,这里不分析为 null 的执行流程
    protected WebApplicationContext createRootApplicationContext() {
        Class<?>[] configClasses = getRootConfigClasses();
        if (!ObjectUtils.isEmpty(configClasses)) {
            AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
            // 配置文件可以有多个,会以累加的形式添加进去
            context.register(configClasses);
            return context;
        } else {
            return null;
        }
    }

注册 DispatcherServlet,为了后续初始化 Spring MVC IOC

public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
    protected void registerDispatcherServlet(ServletContext servletContext) {
        // Servlet 名称,一般用系统默认的即可,否则自己复写此方法也成
        String servletName = getServletName();
        Assert.hasLength(servletName, "getServletName() must not return null or empty");

        // 创建 web 的子容器。创建的代码和上面差不多,也是使用调用者提供的配置文件,创建 AnnotationConfigWebApplicationContext,注:此处不可能为 null
        WebApplicationContext servletAppContext = createServletApplicationContext();
        Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");

        // 创建 DispatcherServlet,并且把子容器传进去了。其实就是 new 一个出来,最后加到容器里,就能够执行一些 init 初始化方法了
        FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
        Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");

        // 同样的 getServletApplicationContextInitializers(),一般也为 null 即可
        dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

        // 注册 servlet 到 web 容器里,这样就可以接收 HTTP 请求了
        ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
        if (registration == null) {
            throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. Check if there is another servlet registered under the same name.");
        }

        // 1 表示立马执行
        registration.setLoadOnStartup(1);
        // 调用者必须实现
        registration.addMapping(getServletMappings());
        // 默认就是开启了支持异步的
        registration.setAsyncSupported(isAsyncSupported());

        // 处理自定义的 Filter 进来,一般我们 Filter 不这么加进来,而是自己 @WebFilter,或者借助 Spring。注:这里添加进来的 Filter 都仅仅只拦截过滤上面注册的 dispatchServlet
        Filter[] filters = getServletFilters();
        if (!ObjectUtils.isEmpty(filters)) {
            for (Filter filter : filters) {
                registerServletFilter(servletContext, filter);
            }
        }

        // 调用者若对 dispatcherServlet 有自己更个性化的参数设置,复写此方法即可
        customizeRegistration(registration);
    }

初始化 Spring IOC

继续执行,就来到了 ContextLoaderListener 的初始化方法,附带会初始化 Spring 容器(注:到了此处,就和 web.xml 方式启动一模一样了)

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent event) {
        initWebApplicationContext(event.getServletContext());
    }

和 web.xml 不一样的是,使用注解驱动启动的此时,ContextLoaderListener 对象已经持有 WebApplicationContext 的引用(但是还没有放进 ServletContext 里面去)

往下走,进入父类的 initWebApplicationContext 方法

public class ContextLoader {
    public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        // 虽然注解驱动传进来的监听器对象持有 WebApplicationContext 的引用,但是并没有放进 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 {
            // 这句特别重要,兼容了 web.xml 的方式以及注解驱动的方式。这里是注解驱动的方式,所以此处不会为 null。用 web.xml 的方式的时候,会去详细看 createWebApplicationContext(servletContext) 方法
            if (this.context == null) {
                this.context = createWebApplicationContext(servletContext);
            }
            // 从上图可以看出:XmlWebApplicationContext(xml驱动) 和 AnnotationConfigWebApplicationContext(注解驱动) 是复合的,都会进来
            if (this.context instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
                // 一般刚创建的 context 不会处于激活状态,所以会进来完善一些更多的容器信息。比如刷新容器、设置父容器等
                if (!cwac.isActive()) {
                    if (cwac.getParent() == null) {
                        // Spring 5.0 开始,默认实现只是返回 null。绝大多数情况下,Spring 容器不用再给设置父容器。这里主要是想多个 Web 应用可以有一个共同的父容器,例如 EJB、EAR
                        ApplicationContext parent = loadParentContext(servletContext);
                        cwac.setParent(parent);
                    }
                    // 读取相应的配置并且刷新 context 对象,这一步极其重要了,因为刷新容器做了太多的事,属于容器的最最最核心逻辑(这里不做分析)
                    configureAndRefreshWebApplicationContext(cwac, servletContext);
                }
            }
            // 放进 ServletContext 上下文,避免再次被初始化,也让我们能更加方便的获取到容器
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

            // 此处把容器和当前线程绑定,public static WebApplicationContext getCurrentWebApplicationContext() 这样就可以更加方便的得到容器,类为:ContextLoader
            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;
        }
    }


    // 最重要的一个方法
    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
        // 一般此处为真,给 ApplicationContext 设置一个 id
        if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
            // 获取 servletContext 中的 contextId 属性 contextId,可在 web.xml 里配置,一般也不用配置,采用 else 里的默认值即可
            String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
            if (idParam != null) {
                // 存在则设为指定的 id 名
                wac.setId(idParam);
            } else {
                // 生成默认 id,一般为 org.springframework.web.context.WebApplicationContext:${contextPath}
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath()));
            }
        }

        // 让 Spring 容器关联上 servlet 上下文
        wac.setServletContext(sc);
        // 读取 contextConfigLocation 属性(在 web.xml 配置,但是注解驱动里没有,因此为 null)
        String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
        if (configLocationParam != null) {
            // 设置指定的 spring 文件所在地,支持 classpath 前缀并多文件,以 ,; 为分隔符
            wac.setConfigLocation(configLocationParam);
        }
// 这里有一个注意的地方,ConfigurableEnvironment 生成的地方 // wac.setConfigLocation(configLocationParam); 时根据 configLocationParam 设置配置参数路径时就会初始化 StandardServletEnvironment(ConfigurableEnvironment 的子类) // StandardServletEnvironment 符合条件,因此会执行 initPropertySources 方法 ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(sc, null); } // 检查 web.xml 是否有一些其余初始化类的配置,极大多数情况都不需要,所以粗暴理解为没什么用 customizeContext(sc, wac); // 容器的核心方法,也是最难的一个方法 // 这里先理解为就是初始化容器,比如加载 bean、拦截器、各种处理器的操作就够了(也是最耗时的一步操作) wac.refresh(); }

该方法完成之后,看控制台日志:

Root WebApplicationContext initialized in 435212 ms

就说明 Spring 根容器初始化完成了

初始化 Spring MVC IOC

继续执行,就来到了 DispatcherServlet 的初始化方法,附带会初始化 Spring 子容器(Web 容器),首先要知道 Servlet 初始化的大致步骤:

  1. Servlet 容器加载 Servlet 类,把类的 .class 文件中的数据读到内存中
  2. Servlet 容器中创建一个 ServletConfig 对象。该对象中包含了 Servlet 的初始化配置信息
  3. Servlet 容器创建一个 Servlet 对象(我们也可以手动 new,然后手动添加进去)
  4. Servlet 容器调用 Servlet 对象的 init() 方法进行初始化

由于设置了 registration.setLoadOnStartup(1),在容器启动完成后就会调用 servlet 的 init()

DispatcherServlet 继承 FrameworkServlet 继承 HttpServletBean 继承 HttpServlet,在 HttpServletBean 实现了 init()

先看 HttpServletBean 的 init 方法

public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
    @Override
    public final void init() throws ServletException {
        // 把 Servlet 的初始化参数封装进来...
        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);

        // 这里我们并没有给此 Servlet 初始化的一些参数,所以此处为空,为 false
        // 若进来了,可以看到里面会做一些处理:将这个 DispatcherServlet 转换成一个 BeanWrapper 对象,从而能够以 Spring 的方式来对初始化参数的值进行注入。这些属性如 contextConfigLocation、namespace 等等。
        // 同时注册一个属性编辑器,一旦在属性注入的时候遇到 Resource 类型的属性就会使用 ResourceEditor 去解析。再留一个 initBeanWrapper(bw) 方法给子类覆盖,让子类处真正执行 BeanWrapper 的属性注入工作。
        // 但是 HttpServletBean 的子类 FrameworkServlet 和 DispatcherServlet 都没有覆盖其 initBeanWrapper(bw) 方法,所以创建的 BeanWrapper 对象没有任何作用。

        // 备注:此部分把当前 Servlet 封装成一个 BeanWrapper,再把它交给 Spring 容器管理,这部分非常重要,使用 SpringBoot 启动的时候,会看出来这部分代码的重要性
        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;
            }
        }
        // Spring 把这个 init 方法给 final 掉了,然后开了这个口子,子类可以根据自己的需要,在初始化的时候复写这个方法,而不再是 init 方法
        initServletBean();
    }

 继续 initServletBean() 方法,它是由 FrameworkServlet 实现的

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 {
            // 重点,开始初始化 Spring 子容器
            this.webApplicationContext = initWebApplicationContext();
            // 留一个口,给子类去复写初始化所需要的操作,一般都为空实现即可,除非自己要复写 DispatcherServlet
            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);
        }

        // 当看到这句日志,dispatcherServlet 就已经初始化完成,Web 子容器也就初始化完成
        if (logger.isInfoEnabled()) {
            logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
        }
    }

继续 initWebApplicationContext 方法:创建一个 Web 子容器,并且和上面 Spring 已经创建好了的父容器关联上

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    protected WebApplicationContext initWebApplicationContext() {
        // 从 ServletContext 中把上面已经创建好的根容器拿到手
        WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        WebApplicationContext wac = null;

        // 上面注册 DispatcherServlet(createDispatcherServlet(servletAppContext)方法)的时候,已经传入了根据配置文件创建好了的子容器,因此,此处肯定是不为 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);
                }
            }
        }

        // 若是 web.xml 方式,会走这里,进而走 findWebApplicationContext()
        if (wac == null) {
            wac = findWebApplicationContext();
        }
        if (wac == null) {
            wac = createWebApplicationContext(rootContext);
        }

        // 此处注意:下面有解释,refreshEventReceived 属性保证了 onRefresh 方法不会被重复执行
        if (!this.refreshEventReceived) {
            synchronized (this.onRefreshMonitor) {
                onRefresh(wac);
            }
        }

        // 是否需要把我们的容器发布出去,作为 ServletContext 的一个属性值?默认值为 true,一般情况下就让为 true 即可
        if (this.publishContext) {
            // 这个 attr 的 key 的默认值,就是 FrameworkServlet.SERVLET_CONTEXT_PREFIX,保证了全局唯一性
            // 这么一来,我们的根容器、web 子容器其实就都放进 ServletContext 上下文里了,拿取都非常的方便。只是我们一般拿这个容器的情况较少,一般都是拿根容器,比如那个工具类就是获取根容器的
            String attrName = getServletContextAttributeName();
            getServletContext().setAttribute(attrName, wac);
        }

        return wac;
    }

FrameworkServlet 策略式的实现了监听方法,监听应用的刷新事件。

当我们刷新应用的时候(比如上面执行 refresh() 方法,这里就会执行,并且打上标记说已经执行过了),然而 onRefresh() 是一个模版方法,具体实现交给子类,这样子 DispatcherServlet 就可以做初始化 Web 组件的一些事情

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    public void onApplicationEvent(ContextRefreshedEvent event) {
        this.refreshEventReceived = true;
        synchronized (this.onRefreshMonitor) {
            onRefresh(event.getApplicationContext());
        }
    }

    private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
        @Override
        public void onApplicationEvent(ContextRefreshedEvent event) {
            FrameworkServlet.this.onApplicationEvent(event);
        }
    }

这就是为何会抽象出 FrameworkServlet 的原因,因为它设计的初衷不仅仅只想支持到 Servlet

所以此处就不得不说一下,子类自己实现的 onRefresh() 方法:

public class DispatcherServlet extends FrameworkServlet {
    @Override
    protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
    }

    // 初始化 Spring MVC 的 9 大组件(至此,才算全部初始化完成了)
    protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }

该方法完成之后,看控制台日志:

INFO (FrameworkServlet.java:547)- Completed initialization in 2015760 ms

就说明整个 Spring 父子容器全部初始化、启动完成了

三、其他

SpringMVC 中的 Servlet 的三个层次:

  1. HttpServletBean 直接继承自 java 的 HttpServlet,其作用是将 Servlet 中配置的参数设置到相应的 Bean 属性上
  2. FrameworkServlet 初始化了 WebApplicationContext
  3. DispatcherServlet 初始化了自身的 9 个组件(重点)

在任何地方获取 Spring 容器(根容器):

@Autowired
private HttpServletRequest request;

@Override
public Object hello() {
    ApplicationContext ctx1 = WebApplicationContextUtils.getWebApplicationContext(request.getSession().getServletContext());
    WebApplicationContext ctx2 = ContextLoader.getCurrentWebApplicationContext();
    System.out.println(ctx1 == ctx2); // true

    return "service hello";
}

用了 ContextLoader 的静态方法

public static WebApplicationContext getCurrentWebApplicationContext() {
    ClassLoader ccl = Thread.currentThread().getContextClassLoader();
    if (ccl != null) {
        WebApplicationContext ccpt = currentContextPerThread.get(ccl);
        if (ccpt != null) {
            return ccpt;
        }
    }
    return currentContext;
}

关于 DispatcherServlet:

DispatcherServlet 创建自己的 WebApplicationContext 并管理这个 WebApplicationContext 里面的 handlers/controllers/view-resolvers

FrameworkServlet 实现(Spring 4.0 之后才实现的)了 ApplicationContextAware 接口的 setApplicationContext() 方法,可知 DispatcherServlet 的 applicationContext 来自 FrameworkServlet

setApplicationContext 方法只用于嵌入式的 Servlet 环境。war 环境这里是不会执行的,war 环境下 WebApplicationContext 不会以 Bean 的形式存在于 Spring 容器

在 DispatcherServlet 的 doService 方法里都有这样的一段代码,方便我们非常方便获取到一些参数,比如 web 子容器等等

public class DispatcherServlet extends FrameworkServlet {
    @Override
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // Make framework objects available to handlers and view objects.
        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
        request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

关于父子容器:

优点:能让 web 环境和普通的 Spring 环境达到隔离的效果。web 容器专注于管理 web 相关 Bean,其余 bean 的交给父容器。 

缺点:父子容器的设计提高了 Spring 初始化、管理 Bean 的复杂度(虽然对使用者一般无感),但万一要用到相关功能时,若不理解原理会有莫名其妙的一些问题,提高了复杂性

理论上可以有任意多个容器(只是我们一般其它的 Bean 都只放进主容器统一管理,但 Spring 是提供了这样的功能的),比如:

  • 主容器:applicationContext.xml(主文件,包括 JDBC 配置,hibernate.cfg.xml,与所有的 Service 与 DAO 基类)
  • web 子容器:application-servlet.xml(管理 Spring MVC 9 大组件以及相关的Bean)
  • cache 子容器:applicationContext-cache.xml(cache 策略配置,管理和缓存相关的Bean)
  • JMX 子容器:applicationContext-jmx.xml(JMX 相关的 Bean)

值得注意的是,SpringMVC 在调用 HandlerMapper 进行 url 到 controller 函数方法映射解析的时候,HandlerMapper 会在 SpringMVC 容器中寻找 controller,也就是在子容器中寻找,不会去父容器 spring 容器中寻找的。

所以如果用父容器来管理 controller 的话,子容器不去管理,在访问页面的时候会出现 404 错误。

Spring Boot 中只采用一个容器,也就没有这个问题


https://blog.csdn.net/f641385712/article/details/87883205

https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-servlet-context-hierarchy

原文地址:https://www.cnblogs.com/jhxxb/p/13598074.html