返回介绍

1.1 容器初始化

发布于 2025-10-03 18:08:52 字数 9370 浏览 0 评论 0 收藏

FrameworkServlet.initServletBean 简略版源码:

@Override
protected final void initServletBean() {
    this.webApplicationContext = initWebApplicationContext();
    //空实现,且没有子类覆盖
    initFrameworkServlet()
}

FrameworkServlet.initWebApplicationContext:

protected WebApplicationContext initWebApplicationContext() {
    //根容器查找
    WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;
    if (this.webApplicationContext != null) {
        //有可能 DispatcherServlet 被作为 Spring bean 初始化,且 webApplicationContext 已被注入进来
        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) {
        //是否已经存在于 ServletContext 中
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        wac = createWebApplicationContext(rootContext);
    }
    if (!this.refreshEventReceived) {
        onRefresh(wac);
    }
    if (this.publishContext) {
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }
    return wac;
}

下面分部分展开。

根容器查找

spring-mvc 支持 Spring 容器与 MVC 容器共存,此时,Spring 容器即根容器,mvc 容器将根容器视为父容器。

Spring 容器(根容器) 以下列形式进行配置(web.xml):

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

根据 Servlet 规范,各组件的加载 顺序如下:

listener -> filter -> servlet

WebApplicationContextUtils.getWebApplicationContext:

String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
    return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}

两参数方法:

public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
    Object attr = sc.getAttribute(attrName);
    if (attr == null) {
        return null;
    }
    return (WebApplicationContext) attr;
}

可以得出结论:

如果 Spring 根容器存在,那么它被保存在 ServletContext 中,其 key 为 WebApplicationContext.class.getName() + ".ROOT"

容器创建

FrameworkServlet.createWebApplicationContext:

protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
    Class<?> contextClass = getContextClass();
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException();
    }
    ConfigurableWebApplicationContext wac =
            (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    wac.setEnvironment(getEnvironment());
    wac.setParent(parent);
    wac.setConfigLocation(getContextConfigLocation());
    configureAndRefreshWebApplicationContext(wac);
    return wac;
}

通过对 getContextClass 方法的调用,Spring 允许我们自定义容器的类型,即我们可以在 web.xml 中如下配置:

<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-servlet.xml</param-value>
    </init-param>
    <!-- 容器类型 -->
    <init-param>
        <param-name>contextClass</param-name>
        <param-value>java.lang.Object</param-value>
    </init-param>
</servlet>

configureAndRefreshWebApplicationContext 核心源码:

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
    applyInitializers(wac);
    wac.refresh();
}

ApplicationContextInitializer

ApplicationContextInitializer 允许我们在 Spring(mvc) 容器初始化之前干点坏事,可以通过 init-param 传入:

<init-param>
    <param-name>contextInitializerClasses</param-name>
    <param-value>坏事儿</param-value>
</init-param>

applyInitializers 方法正是要触发这些坏事儿。类图:

ApplicationContextInitializer 类图

配置解析

"配置"指的便是 spring-servlet.xml:

<context:component-scan base-package="controller"/>
<mvc:annotation-driven/>
<!-- 启用对静态资源使用默认 servlet 处理,非 REST 方式不需要 -->
<mvc:default-servlet-handler/>
<!-- 配置视图 -->
<bean class="org.springframework.web.servlet.view.UrlBasedViewResolver">
    <!-- viewClass 属性必不可少 -->
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"></property>
    <property name="prefix" value="/WEB-INF/"></property>
    <property name="suffix" value=".jsp"></property>
</bean>

而解析的入口便在于对 refresh 方法的调用,此方法位于 AbstractApplicationContext,这一点在 spring-core 时已经见过了,下面我们重点关注不同于 spring-core 的地方。

对于 spring-mvc 来说,其容器默认为 XmlWebApplicationContext,部分类图:

XmlWebApplicationContext 类图

XmlWebApplicationContext 通过重写 loadBeanDefinitions 方法改变了 bean 加载行为,使其指向 spring-servlet.xml。

spring-servlet.xml 中不同于 spring-core 的地方便在于引入了 mvc 命名空间,正如 spring-core 中笔记中所说的那样, Spring 用过 jar 包/META-INFO 中的.handlers 文件定义针对不同的命名空间所使用的解析器

mvc 命名空间的解析器为 MvcNamespaceHandler,部分源码:

@Override
public void init() {
    registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
    registerBeanDefinitionParser("default-servlet-handler", 
                                 new DefaultServletHandlerBeanDefinitionParser());
    registerBeanDefinitionParser("interceptors", new IanterceptorsBeanDefinitionParser());
    registerBeanDefinitionParser("view-resolvers", new ViewResolversBeanDefinitionParser());
}

老样子,按部分展开。

注解驱动

其 parse 方法负责向 Sprng 容器注册一些必要的组件,整理如下图:

mvc-annotation

静态资源处理

即:

<mvc:default-servlet-handler/>

DefaultServletHandlerBeanDefinitionParser.parse 负责向容器注册以下三个组件:

  • DefaultServletHttpRequestHandler
  • SimpleUrlHandlerMapping
  • HttpRequestHandlerAdapter

拦截器

InterceptorsBeanDefinitionParser.parse 方法负责 将每一项 mvc:interceptor 配置解析为一个 MappedInterceptor bean 并注册到容器中

视图

有两种方式向 Spring 容器注册视图:

  • 以前采用较土的方式:
    <bean class="org.springframework.web.servlet.view.UrlBasedViewResolver">
      <!-- viewClass 属性必不可少 -->
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"></property>
        <property name="prefix" value="/WEB-INF/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>
    
  • 通过特定的标签:
    <mvc:view-resolvers>
      <mvc:jsp view-class="" />
    </mvc:view-resolvers>
    

从这里可以推测出: 拦截器同样支持第一种方式,Spring 在查找时应该会查询某一接口的子类。

ViewResolversBeanDefinitionParser.parse 方法的作用便是将每一个视图解析为 ViewResolver 并注册到容器。

Scope/处理器注册

AbstractRefreshableWebApplicationContext.postProcessBeanFactory:

@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    beanFactory.addBeanPostProcessor(
        new ServletContextAwareProcessor(this.servletContext, this.servletConfig));
    beanFactory.ignoreDependencyInterface(ServletContextAware.class);
    beanFactory.ignoreDependencyInterface(ServletConfigAware.class);
    WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext);
    WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, 
        this.servletContext, this.servletConfig);
}

ServletContextAwareProcessor 用以向实现了 ServletContextAware 的 bean 注册 ServletContext。

registerWebApplicationScopes 用以注册"request", "session", "globalSession", "application"四种 scope,scope 是个什么东西以及如何自定义,在 spring-core 中已经进行过说明了。

registerEnvironmentBeans 用以将 servletContext、servletConfig 以及各种启动参数注册到 Spring 容器中。

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。