【源码系列之springMVC】(一、servlet入口:DispatcherServlet统筹全局之初始化web上下文)

今天来说说深入一下SpringMVC,在学习SpringMVC源码之前,必须得了解javaEE原生的Servlet的工作流程,以tomcat为例子:在web.xml中配置servlet,指定servlet-class以及servlet-mapping。然后请求过来根据mapping映射到servlet的类,执行其中的doGet、doPost等方法。具体这里就不细说了。

先附上一张UML类图,可以先跳过:
在这里插入图片描述

一、从我们的web.xml入手

<!-- Spring mvc 配置的Servelt -->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <!-- Servlet的类,DispatcherServlet	-->
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <!--  配置初始化参数 	-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <!-- 配置文件的地址-->
            <param-value>classpath:application-context.xml</param-value>
        </init-param>
        <!-- 让web容器启动之后就加载此类,并且调用init方法	-->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <!--	配置拦截的路径 -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>

这是我们最常见的SpringMVC在web.xml中的配置了,从配置文件中我们可以看到核心类就是:DispatcherServlet
我们在初始化DispatcherServlet的时候,传入了参数contextConfigLocation,这个属性的意思是:本地上下文配置位置
我们后面看他是如何使用的。然后看mapping,是 / 也就是所有的请求都会过这个DispatcherServlet

二、web容器启动,调用init方法

进入DispatcherServlet类,发现并没有找到覆盖Servlet接口的init的方法。那么他肯定不是在DispatcherServlet进行初始化的。
然后我们继续往上面找:DispatcherServlet --> FrameworkServlet --> HttpServletBean最终在GenericServlet和HttpServletBean类中找到了覆盖Servlet接口的init方法(只有这个方法里面有内容,其他类下面的都是空方法),内容如下:
GenericServlet中的init方法:

	public void init(ServletConfig config) throws ServletException {
		this.config = config;
		// 这是config,这个类里面包含有我们配置文件中配置的init-param值。
		this.init();
    }

HttpServletBean中的init方法:

	@Override
	public final void init() throws ServletException {

		// 从init参数设置bean属性。
		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;
			}
		}

		// 让子类做他们喜欢的任何初始化。
		initServletBean();
	}
2.1 从init参数设置bean属性:ServletConfigPropertyValues方法
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);

首先看getServletConfig()方法,这个方法是GenericServlet的方法,返回config属性:

 	private transient ServletConfig config;


	public ServletConfig getServletConfig() {
		return config;
    }

ServletConfig 接口中有我们的web.xml文件中配置的init-paramkeyvalue

public interface ServletConfig {

    public String getServletName();
    public ServletContext getServletContext();
    public String getInitParameter(String name);
    // 此方法可以获取我们配置文件中的init-param配置
    public Enumeration<String> getInitParameterNames();

}

在这里插入图片描述
然后我们继续看ServletConfigPropertyValues的构造方法:参数一:ServletConfig ,参数二:requiredProperties设置必须传递的属性

	public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
				throws ServletException {

			Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ?
					new HashSet<>(requiredProperties) : null);
			// 这里就从ServletConfig 里面获取我们配置的值
			Enumeration<String> paramNames = config.getInitParameterNames();
			while (paramNames.hasMoreElements()) {
				String property = paramNames.nextElement();
				Object value = config.getInitParameter(property);
				addPropertyValue(new PropertyValue(property, value));
				if (missingProps != null) {
					missingProps.remove(property);
				}
			}

			// Fail if we are still missing properties.
			if (!CollectionUtils.isEmpty(missingProps)) {
				throw new ServletException(
						"Initialization from ServletConfig for servlet '" + config.getServletName() +
						"' failed; the following required properties were missing: " +
						StringUtils.collectionToDelimitedString(missingProps, ", "));
			}
		}
2.2 如果我们在web.xml文件中设置了init-param,那么执行如下代码:
	// 从web.xml文件中获取配置
	PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
	// 如果不为空,
	if (!pvs.isEmpty()) {
			try {
				// BeanWrapper 获取BeanWrapper ,这是装饰者模式的一种体现
				BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
				// 获取ServletConfig,将其赋值给ServletContextResourceLoader的属性:servletContext
				ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
				// 将bean注册自定义属性编辑器:ResourceEditor
				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;
			}
			// 执行子类自定义初始化bean的方法,他的子类就包括:FrameworkServlet和DispatcherServlet
			initServletBean();
		}
2.3 initServletBean()方法:初始化bean,这个方法只有其子类FrameworkServlet进行了实现,DispatcherServlet没有实现
@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 {
			// 初始化应用上下文
			this.webApplicationContext = initWebApplicationContext();
			// 这个是一个空方法,
			// 此方法将在设置任何bean属性之后调用
			// 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);
		}

		if (logger.isInfoEnabled()) {
			logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
		}
	}
2.4 initWebApplicationContext()方法:初始化web应用上下文,委托createWebApplicationContext来创建
	// 为此servlet初始化并发布WebApplicationContext
	protected WebApplicationContext initWebApplicationContext() {
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;

		if (this.webApplicationContext != null) {
			// A context instance was injected at construction time -> use it
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) {
					// The context has not yet been refreshed -> provide services such as
					// setting the parent context, setting the application context id, etc
					if (cwac.getParent() == null) {
						// The context instance was injected without an explicit parent -> set
						// the root application context (if any; may be null) as the parent
						cwac.setParent(rootContext);
					}
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		if (wac == null) {
			// No context instance was injected at construction time -> see if one
			// has been registered in the servlet context. If one exists, it is assumed
			// that the parent context (if any) has already been set and that the
			// user has performed any initialization such as setting the context id
			wac = findWebApplicationContext();
		}
		if (wac == null) {
			// No context instance is defined for this servlet -> create a local one
			wac = createWebApplicationContext(rootContext);
		}

		if (!this.refreshEventReceived) {
			// Either the context is not a ConfigurableApplicationContext with refresh
			// support or the context injected at construction time had already been
			// refreshed -> trigger initial onRefresh manually here.
			synchronized (this.onRefreshMonitor) {
				onRefresh(wac);
			}
		}

		if (this.publishContext) {
			// Publish the context as a servlet context attribute.
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
		}

		return wac;
	}

此方法上面的注释说委托createWebApplicationContext来创建,那么我们打个断点:
在这里插入图片描述
果然,走到了那一步,wac变量还是为null,所以会走createWebApplicationContext方法。

2.5 createWebApplicationContext()方法:创建web上下文。
	protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
		Class<?> contextClass = getContextClass();
		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");
		}
		// 使用默认的构造函数实例化bean
		ConfigurableWebApplicationContext wac =
				(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
		// 设置环境
		wac.setEnvironment(getEnvironment());
		// 将ApplicationContext设置为此应用程序上下文的父级
		wac.setParent(parent);
		// 获取本地上下文配置:这个配置就是我们当初web.xml中配置的属性:contextConfigLocation
		String configLocation = getContextConfigLocation();
		if (configLocation != null) {
			wac.setConfigLocation(configLocation);
		}
		// 配置并且刷新web应用程序上下文,
		configureAndRefreshWebApplicationContext(wac);

		return wac;
	}
2.6 configureAndRefreshWebApplicationContext()方法:配置并且刷新web应用程序上下文,到这里,sringmvc将加载bean,刷新上下文交给了spring方法了,然后spring就会将其加入到spring容器中。
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
		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
			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.setServletContext(getServletContext());
		wac.setServletConfig(getServletConfig());
		wac.setNamespace(getNamespace());
		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());
		}

		postProcessWebApplicationContext(wac);
		applyInitializers(wac);
		// 交给spring
		wac.refresh();
	}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值