【Spring MVC源码解析】(一)创建容器

Java web 项目在Tomcat、Jetty等服务器中运行,是因为实现了Servlet规范,即实现了服务器应用。SpringMVC是当下java web项目最流行的框架,我们一起来看看Spring MVC 是怎么和 Servlet 集成,怎么来初始化 Spring 容器的。

父子容器

首先来看下web应用初始化的核心文件,web.xml

<web-app version="3.0"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

    <!--Root-WebApplicationContext-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    <listener>
        <!--会初始化一个Root Spring WebApplicationContext 容器-->
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>


    <!--配置前端控制器DispatcherServlet Servlet-WebApplicationContext-->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--默认 加载/WEB-INF/[servlet-name]-servlet.xml -->
        <!--配置springmvc需要加载的文件-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring/spring-mvc.xml</param-value>
        </init-param>
        <!--通过配置文件加载处理器映射器、适配器、视图解析器等等-->
        <!--servlet在startup后立即加载-->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!--配置前端映射器-->
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>


</web-app>

一般情况下,在一个 Web 应用中会创建两个容器(即父子容器),因此我们的应用中会存在至少2个WebApplicationContext实例。

  • Servlet WebApplicationContext:这是对J2EE三层架构中的web层进行配置,如控制器(controller)、视图解析器(view resolvers)等相关的bean。通过spring mvc中提供的DispatchServlet来加载配置,通常情况下,配置文件的名称为spring-servlet.xml。
  • Root WebApplicationContext:这是对J2EE三层架构中的service层、dao层进行配置,如业务bean,数据源(DataSource)等。通常情况下,配置文件的名称为applicationContext.xml。在web应用中,其一般通过ContextLoaderListener来加载。

在容器初始化的过程中,Root容器会先于 Servlet容器进行初始化。Servlet容器初始化时,会将Root容器作为父容器。这样做的原因是,Servlet容器中的一些 bean 会依赖于业务容器中的 bean,另一方框架上也军训创建多个web子容器。比如我们的 controller 层接口通常会依赖 service 层的业务逻辑类。

Root WebApplicationContext 容器

在概述中,我们已经看到,Root WebApplicationContext 容器的初始化,通过 ContextLoaderListener 来实现。在 Servlet 容器启动时,例如 Tomcat、Jetty 启动,则会被 ContextLoaderListener 监听到,从而调用 #contextInitialized(ServletContextEvent event) 方法,初始化 Root WebApplicationContext 容器。

 ContextLoaderListener

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

    // 省略部分代码

    @Override
    public void contextInitialized(ServletContextEvent event) {
        // 初始化 WebApplicationContext
        initWebApplicationContext(event.getServletContext());
    }
}

p

调用父类 ContextLoader 的 #initWebApplicationContext(ServletContext servletContext) 方法,初始化 WebApplicationContext 对象。

ContextLoader

//ContextLoader 类
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    /*
     * 如果 ServletContext 中 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 属性值
     * 不为空时,表明有其他监听器设置了这个属性。Spring 认为不能替换掉别的监听器设置
     * 的属性值,所以这里抛出异常。
     */
    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!");
    }

    Log logger = LogFactory.getLog(ContextLoader.class);
    servletContext.log("Initializing Spring root WebApplicationContext");
    if (logger.isInfoEnabled()) {...}
    long startTime = System.currentTimeMillis();

    try {
        if (this.context == null) {
            // 创建 WebApplicationContext
            this.context = createWebApplicationContext(servletContext);
        }
        if (this.context instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
            //未刷新(未激活)
            if (!cwac.isActive()) {
                if (cwac.getParent() == null) {
                    /*
                     * 加载父 ApplicationContext,一般情况下,业务容器不会有父容器,
                     * 除非进行配置
                     */ 
                    ApplicationContext parent = loadParentContext(servletContext);
                    cwac.setParent(parent);
                }
                // 配置并刷新 WebApplicationContext
                configureAndRefreshWebApplicationContext(cwac, servletContext);
            }
        }

        // 设置 ApplicationContext 到 servletContext 中
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

        //记录到 currentContext 或 currentContextPerThread 中
        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()) {...}

        return this.context;
    }catch (RuntimeException | Error ex) {
			logger.error("Context initialization failed", ex);
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
			throw ex;
	}
}

概况逻辑有如下5步:

(1)检查若已经创建 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 对应的 WebApplicationContext 对象,则抛出 IllegalStateException 异常。例如,在 web.xml 中存在多个 ContextLoader 

(2)调用 #createWebApplicationContext(ServletContext sc) 方法,初始化 context ,即创建 WebApplicationContext 对象。

(3) 如果 context 是 ConfigurableWebApplicationContext 的子类,如果未刷新,则进行配置和刷新。

(4)记录 context 在 ServletContext 中。这样,如果 web.xml 如果定义了多个 ContextLoader ,就会在 <1> 处报错。

(5) 记录到 currentContext 或 currentContextPerThread 中

接下来分析下第2步流程

createWebApplicationContext

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
		// 判断创建什么类型的容器,默认类型为 XmlWebApplicationContext
        Class<?> contextClass = determineContextClass(sc);
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
			throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
					"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
		}
        //通过反射创建容器
		return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
protected Class<?> determineContextClass(ServletContext servletContext) {
    /*
     * 读取用户自定义配置,比如:
     * <context-param>
     *     <param-name>contextClass</param-name>
     *     <param-value>XXXConfigWebApplicationContext</param-value>
     * </context-param>
     */
    String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
    if (contextClassName != null) {
        try {
            return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
        }
        catch (ClassNotFoundException ex) {
            throw new ApplicationContextException(
                    "Failed to load custom context class [" + contextClassName + "]", ex);
        }
    }
    else {
        /*
         * 若无自定义配置,则获取默认的容器类型,默认类型为 XmlWebApplicationContext。
         * defaultStrategies 读取的配置文件为 ContextLoader.properties,
         * 该配置文件内容如下:
         * org.springframework.web.context.WebApplicationContext =
         *     org.springframework.web.context.support.XmlWebApplicationContext
         */
        contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
        try {
            return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
        }
        catch (ClassNotFoundException ex) {
            throw new ApplicationContextException(
                    "Failed to load default context class [" + contextClassName + "]", ex);
        }
    }
}

回到第三步配置和刷新WebApplicationContext

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        // 从 ServletContext 中获取用户配置的 contextId 属性
        String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
        if (idParam != null) {
            // 设置容器 id
            wac.setId(idParam);
        }
        else {
            // 用户未配置 contextId,则设置一个默认的容器 id
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                    ObjectUtils.getDisplayString(sc.getContextPath()));
        }
    }

    wac.setServletContext(sc);
    /** 
    *  获取 contextConfigLocation 配置
    *  <context-param>
    *    <param-name>contextConfigLocation</param-name>
    *    <param-value>classpath:config/applicationContext.xml</param-value>
    *  </context-param>
    */
    String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
    if (configLocationParam != null) {
        wac.setConfigLocation(configLocationParam);
    }
    
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
    }

    customizeContext(sc, wac);

    // 刷新容器
    wac.refresh();
}

Servlet WebApplicationContext 容器

 Servlet WebApplicationContext 容器的初始化,是在 DispatcherServlet 初始化的过程中执行。

1. HttpServletBean

org.springframework.web.servlet.HttpServletBean ,实现 EnvironmentCapable、EnvironmentAware 接口,继承 HttpServlet 抽象类,负责将 ServletConfig 集成到 Spring 中。当然,HttpServletBean 自身也是一个抽象类。

//负责将 ServletConfig 设置到当前 Servlet 对象中
public final void init() throws ServletException {

		// Set bean properties from init parameters.
        // 解析 <init-param /> 标签,封装到 PropertyValues pvs 中
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		if (!pvs.isEmpty()) {
			try {
                // 将当前 Servlet 对象,转化成一个 BeanWrapper 对象
				BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
				ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
				bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
				initBeanWrapper(bw);
                //将配置的初始值(如contextLocation)设置到DispatchServlet中
				bw.setPropertyValues(pvs, true);
			}
			catch (BeansException ex) {
				if (logger.isErrorEnabled()) {
					logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
				}
				throw ex;
			}
		}

		//模板方法,子类初始化的入口方法
		initServletBean();
	}

2. FrameworkServlet

由HttpServletBean可知,FramworkServlet初始化容器入口是initServletBean()

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 {
	    // 初始化 WebApplicationContext 对象
		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");
	}
}

可以看到这里的核心代码只有一句话this.webApplicationContext = initWebApplicationContext();

protected WebApplicationContext initWebApplicationContext() {
    // 从 ServletContext 中获取容器,也就是 ContextLoaderListener 创建的容器
    WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;

    /*
     * 若下面的条件成立,则需要从外部设置 webApplicationContext。有两个途径可以设置 
     * webApplicationContext,以 DispatcherServlet 为例:
     *    1. 通过 DispatcherServlet 有参构造方法传入 WebApplicationContext 对象
     *    2. 将 DispatcherServlet 配置到其他容器中,由其他容器通过 
     *       setApplicationContext 方法进行设置
     *       
     * 途径1 可参考 AbstractDispatcherServletInitializer 中的 
     * registerDispatcherServlet 方法源码。一般情况下,代码执行到此处,
     * this.webApplicationContext 为 null,大家可自行调试进行验证。
     */
    if (this.webApplicationContext != null) {
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) {
                if (cwac.getParent() == null) {
                    // 设置 rootContext 为父容器
                    cwac.setParent(rootContext);
                }
                // 配置并刷新容器
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
        // 尝试从 ServletContext 中获取容器
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        // 创建容器,并将 rootContext 作为父容器
        wac = createWebApplicationContext(rootContext);
    }

    //如果未触发刷新事件,则主动触发刷新事件
    if (!this.refreshEventReceived) {
        onRefresh(wac);
    }

    if (this.publishContext) {
        String attrName = getServletContextAttributeName();
        // 将创建好的容器设置到 ServletContext 中
        getServletContext().setAttribute(attrName, wac);
        
    }

    return wac;
}

以上就是创建 Web 容器过程。如下:

(1)从 ServletContext 中获取 ContextLoaderListener 创建的Root WebApplicationContext容器

(2)若 this.webApplicationContext != null 条件成立,仅设置父容器和刷新容器即可

(3)尝试从 ServletContext 中获取容器,若容器不为空,则无需执行步骤4

(4)创建容器,并将 rootContext 作为父容器

(5)触发onRefresh()刷新事件,子类DispatchServer实现初始化九大组件

(6)设置容器到 ServletContext 中

进一步看一下第三步 #findWebApplicationContext() 方法,从 ServletContext 获取对应的 WebApplicationContext 对象

protected WebApplicationContext findWebApplicationContext() {
	String attrName = getContextAttribute();
	// 需要配置了 contextAttribute 属性下,才会去查找
	if (attrName == null) {
		return null;
	}
	// 从 ServletContext 中,获得属性名对应的 WebApplicationContext 对象
	WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
	// 如果不存在,则抛出 IllegalStateException 异常
	if (wac == null) {
		throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
	}
	return wac;
}

第四步调用 #createWebApplicationContext(WebApplicationContext parent) 方法,创建一个 WebApplicationContext 对象。代码如下:

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
    //获取容器类型,默认为 XmlWebApplicationContext.class
	Class<?> contextClass = getContextClass();
	// 如果非 ConfigurableWebApplicationContext 类型,抛出 ApplicationContextException 异常
	if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
		throw new ApplicationContextException(
				"Fatal initialization error in servlet with name '" + getServletName() +
				"': custom WebApplicationContext class [" + contextClass.getName() +
				"] is not of type ConfigurableWebApplicationContext");
	}
	// 通过反射实例化容器
	ConfigurableWebApplicationContext wac =
			(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

	// 设置 environment、parent、configLocation 属性
	wac.setEnvironment(getEnvironment());
	wac.setParent(parent);
	String configLocation = getContextConfigLocation();
	if (configLocation != null) {
		wac.setConfigLocation(configLocation);
	}

	//配置和初始化 wac
	configureAndRefreshWebApplicationContext(wac);

	return wac;
}


protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
	//如果 wac 使用了默认编号,则重新设置 id 属性
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
		// The application context id is still set to its original default value
		// -> assign a more useful id based on available information
        // 情况一,使用 contextId 属性
		if (this.contextId != null) {
			wac.setId(this.contextId);
        // 情况二,自动生成
		} else {
			// Generate default id...
			wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
					ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
		}
	}

	//设置 wac 的 servletContext、servletConfig、namespace 属性
	wac.setServletContext(getServletContext());
	wac.setServletConfig(getServletConfig());
	wac.setNamespace(getNamespace());

	//添加监听器 ontextRefreshListener() 到 wac 中,用于context初始化完后,触发DispatchServlet.onRefresh()
	wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

	// The wac environment's #initPropertySources will be called in any case when the context
	// is refreshed; do it eagerly here to ensure servlet property sources are in place for
	// use in any post-processing or initialization that occurs below prior to #refresh
	ConfigurableEnvironment env = wac.getEnvironment();
	if (env instanceof ConfigurableWebEnvironment) {
		((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
	}

	// 执行处理完 WebApplicationContext 后的逻辑。目前是个空方法,暂无任何实现
	postProcessWebApplicationContext(wac);

	// 执行自定义初始化
	applyInitializers(wac);

	// 刷新 wac ,从而初始化 wac
	wac.refresh();
}

3.DispatcherServlet 

onRefresh()是DispathcerServlet 初始化入口

protected void onRefresh(ApplicationContext context) {
    initStrategies(context);
}

protected void initStrategies(ApplicationContext context) {
    // 初始化 MultipartResolver
	initMultipartResolver(context);
	// 初始化 LocaleResolver
	initLocaleResolver(context);
	// 初始化 ThemeResolver
	initThemeResolver(context);
	// 初始化 HandlerMappings
	initHandlerMappings(context);
	// 初始化 HandlerAdapters
	initHandlerAdapters(context);
	// 初始化 HandlerExceptionResolvers 
	initHandlerExceptionResolvers(context);
	// 初始化 RequestToViewNameTranslator
	initRequestToViewNameTranslator(context);
	// 初始化 ViewResolvers
	initViewResolvers(context);
	// 初始化 FlashMapManager
	initFlashMapManager(context);
}

 

 

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wonder ZH

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值