探索SpringMVC之容器初始化

本文详细介绍了SpringMVC的初始化过程,包括DispatcherServlet的生命周期、容器初始化、配置解析、注解驱动、静态资源处理、拦截器、视图解析等关键步骤。文章探讨了Spring容器与MVC容器的关系,解析了web.xml配置,并阐述了如何通过不同解析器和处理器来支持文件上传、地区解析和主题解析等功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

dispatcherservlet
springmvc的核心是DispatcherServlet,所以整个初始化也是围绕它展开的。他是springmvc的所有类的父类,一系列的功能都是围绕这个核心类展开。(上图是DispatcherServlet类图)

servlet标准定义了init()方法是它的生命周期的初始化方法:
HttpServletBean.init

@Override
public final void init() throws ServletException {
    // Set bean properties from init parameters.
    PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
    //包装DispatcherServlet,准备放入容器
    BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
    //用以加载spring-mvc配置文件
    ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
    bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
    //没有子类实现此方法
    initBeanWrapper(bw);
    bw.setPropertyValues(pvs, true);
    // Let subclasses do whatever initialization they like.
    initServletBean();
}

逻辑已经注释的很清晰。其中,setPropertyValues方法会导致对DispatcherServlet相关setter方法的调用,所以当进行容器初始化时从init-param中读取的参数已被设置到DispatcherServlet的相关字段(filed)中。

容器初始化

FrameworkServlet.initServletBean简略版源码:

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

FrameworkServlet.initWebAppplicationContext

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"

容器创建

FrameworkContext.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>

configureAndRefrashWebApplicationContext核心源码:

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>

applyInitializer方法是要触发这些坏事儿。
ApplicationContextInitializer类图:
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-mvc来说,其容器默认为XmlWebApplicationContext,部分类图:
在这里插入图片描述XmlWebApplicationContext通过重写loadBeanDefinitions方法改变了bean加载行为,使其指向spring-servlet.xml。

spring-servlet.xml中不同于spring-core的地方便在于引入了mvc命名空间,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: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。

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

MVC初始化

入口位于DispatcherServlet的initStrategies方法(经由onRefresh调用):

protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}

显然,这里就是spring-mvc的核心了。

文件上传支持

initMultipartResolver核心源码:

private void initMultipartResolver(ApplicationContext context) {
    try {
        this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
    } catch (NoSuchBeanDefinitionException ex) {
        // Default is no multipart resolver.
        this.multipartResolver = null;
    }
}

MultipartResolver用于开启Spring MVC文件上传功能,其类图:
在这里插入图片描述也就是说,如果我们要使用文件上传功能,须在容器中注册一个MultipartResolver bean。当然,默认是没有的。

地区解析器

LocaleResolver接口定义了Spring MVC如何获取客户端(浏览器)的地区,initLocaleResolver方法在容器中寻找此bean,如果没有,注册AcceptHeaderLocaleResolver,即根据request的请求头Accept-Language获取地区。

spring-mvc采用了属性文件的方式配置默认策略(即bean),此文件位于spring-mvc的jar包的org.springframework.web.servlet下。

主题解析器

ThemeResolver接口配合Spring标签库使用可以通过动态决定使用的css以及图片的方式达到换肤的效果,其类图:
在这里插入图片描述如果容器中不存在叫做themeResolver的bean,initThemeResolver方法将向容器中注册FixedThemeResolver,此bean只能提供一套默认的主题,名为theme。

HandlerMapping检查

initHandlerMappings方法用于确保容器中至少含有一个HandlerMapping对象。从前面配置解析-注解驱动一节中可以看出,注解驱动导致已经注册了两个此对象。

如果没有开启注解驱动,那么将会使用默认的HandlerMapping,相关源码:

if (this.handlerMappings == null) {
    this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
    if (logger.isDebugEnabled()) {
        logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
    }
}

前面提到了,默认的策略由DispatcherServlet.properties决定,目前是BeanNameUrlHandlerMapping和DefaultAnnotationHandlerMapping。

HandlerAdapter检查

套路和上面完全一样,默认使用HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter和AnnotationMethodHandlerAdapter。

HandlerExceptionResolver检查

套路和上面完全一样,默认使用AnnotationMethodHandlerExceptionResolver、ResponseStatusExceptionResolver、DefaultHandlerExceptionResolver。

RequestToViewNameTranslator

initRequestToViewNameTranslator方法回向容器中注册一个DefaultRequestToViewNameTranslator对象,此接口用以完成从HttpServletRequest到视图名的解析,其使用场景是给定的URL无法匹配任何控制器时。

DefaultRequestToViewNameTranslator的转换例子:
http://localhost:8080/gamecast/display.html -> display(视图)
其类图:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值