• Spring MVC 五 - DispatcherServlet初始化过程(续)


    今天的内容是SpringMVC的初始化过程,其实也就是DispatcherServilet的初始化过程。

    Special Bean Types

    DispatcherServlet委托如下一些特殊的bean来处理请求、并渲染正确的返回。这些特殊的bean是Spring MVC框架管理的bean、按照Spring框架的约定处理相关请求,一般情况下是框架内置的,我们当然也可以定制或扩展他们的功能。

    这些特殊bean包括:

    1. HandlerMapping:根据一定的规则把请求映射到对应的HandlerMapping去处理,HandlerMapping可以包含一系列拦截器,进行前置或后置处理。框架默认提供了RequestMappingHandlerMapping(处理@RequestMapping注解方法的)和SimpleUrlHandlerMapping两个HandlerMapping。
    2. HandlerAdapter:HandlerMapping匹配到请求之后,调用HandlerAdapter具体处理请求。
    3. HandlerExceptionResolver:发生异常后的异常处理器。
    4. ViewResolver:处理返回
    5. LocaleResolver, LocaleContextResolver:本地化处理器
    6. ThemeResolver:Theme渲染处理器
    7. MultipartResolver:Multipart处理器,文件上传下载的处理。
    8. FlashMapManager:跨请求存储和获取“input”和“output”的处理器
    Web MVC Config

    DispatcherServlet初始化过程中会根据WebApplicationContext的配置(xml或注解方式,前面两篇文章分析过)完成上述特殊bean的初始化,如果DispatcherServlet在WebApplicationContext中没有发现相应的配置,则采用DispatcherServlet.properties文件中的默认配置完成初始化。

    DispatcherServlet.properties文件在Spring web mvc包下:
    在这里插入图片描述

    我们猜想Spring MVC框架是通过DispatcherServlet的init方法完成上述各特殊bean的初始化的,下面我们要详细分析一下具体的初始化过程。

    Servlet Config

    通过注解方式、或通过xml方式初始化DispatcherServlet的具体方法,前面两篇文章已经做过分析,此处不在赘述。

    DispatcherServlet的初始化

    众所周知,Servlet容器(比如Tomcat)会通过调用Servlet的init方法完成Servlet的初始化。

    我们接下来看一下DispatcherServlet的初始化过程,也就是DispatcherServlet的init方法。

    先来看一眼DispatcherServlet的类结构:
    ![(https://img-blog.csdnimg.cn/bcd50bfa29b04ff3a49b607bc945a2bb.png)

    init方法在他的父类HttpServletBean中:

    	@Override
    	public final void init() throws ServletException {
    
    		// Set bean properties from init parameters.
    		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;
    			}
    		}
    
    		// Let subclasses do whatever initialization they like.
    		initServletBean();
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    上面的代码是对当前Servlet属性的处理,与我们的目标无关,初始化逻辑在最下面的方法initServletBean中,在他的子类(也是DispatcherServlet的直接父类)FrameworkServlet中:

    	protected final void initServletBean() throws ServletException {
            ...省略部分代码
    		try {
    			this.webApplicationContext = initWebApplicationContext();
    			initFrameworkServlet();
    		}
    		catch (ServletException | RuntimeException ex) {
    			logger.error("Context initialization failed", ex);
    			throw ex;
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    该方法中有很多打印log的代码,忽略掉,剩下的就是两个方法的调用:一个是创建webApplicationContext的,一个是initFrameworkServlet,这个initFrameworkServlet是空方法,所以,DispatcherServlet的初始化逻辑,关键就在这个initWebApplicationContext()方法中。

    initWebApplicationContext方法很长,我们分段分析一下。

    	protected WebApplicationContext initWebApplicationContext() {
    		WebApplicationContext rootContext =
    				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    		WebApplicationContext wac = null;
            ...
    
    • 1
    • 2
    • 3
    • 4
    • 5

    首先获取当前ServletContext的RootContext,有关RootContext,参见前面的文章 Spring MVC 四:Context层级

    然后:

    		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);
    				}
    			}
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    判断如果DispatcherServlet对象创建的时候,如果在构造方法中已经初始化过WebApplicationContext了,那么就使用该WebApplicationContext,设置上面获取到的RootContext为当前WebApplicationContext的父容器。并且判断该Context是否已经刷新过,如果没有刷新过的话,调用configureAndRefreshWebApplicationContext方法配置并刷新该Context。

    前面文章Spring MVC 三 :基于注解配置中我们分析过DispatcherServlet的创建过程,确实在创建的时候就通过构造函数的参数传过来已经创建好的ServletContext了:

    protected void registerDispatcherServlet(ServletContext servletContext) {
            String servletName = getServletName();
            Assert.hasLength(servletName, "getServletName() must not return null or empty");
    
            WebApplicationContext servletAppContext = createServletApplicationContext();
            Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
    
            FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
            Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
            dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
    
       ...省略代码
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    所以如果是通过注解方式配置的话,会通过createServletApplicationContext()方法创建ServletContext:

    	@Override
    	protected WebApplicationContext createServletApplicationContext() {
    		AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
    		Class[] configClasses = getServletConfigClasses();
    		if (!ObjectUtils.isEmpty(configClasses)) {
    			context.register(configClasses);
    		}
    		return context;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    最终创建的ServletContext是AnnotationConfigWebApplicationContext。

    所以如果通过注解方式配置,那就是要走到上面这段逻辑中来的。

    否则,如果不是通过注解、而是通过xml配置,也就是说DispactherServlet创建的时候并没有ServletContext,会走到下面的逻辑中:

    		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);
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    如果wac为空(DispatcherServlet创建的时候没有设置),那么就判断容器中是否已经注册进来了,如果已经注册了的话,那么Spring framework就会认为其父容器已经设置过了,也做过初始化以及refresh了,直接拿过来用就OK。(我们的应用如果不主动注册的话,就不会有注册进来的Context,所以这段代码就跑不到)。

    然后看下面的代码,如果没有发现,就调用createWebApplicationContext创建,createWebApplicationContext方法在创建WebApplicationContext之后,也会设置其父容器为RootContext,之后也会调用configureAndRefreshWebApplicationContext配置和刷新容器,走到和上面第一步(通过注解方式配置,DispatcherServlet创建的时候已经通过构造器设置了一个Context)一致的逻辑中了。

    createWebApplicationContext:

    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");
    		}
    		ConfigurableWebApplicationContext wac =
    				(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    
    		wac.setEnvironment(getEnvironment());
    		wac.setParent(parent);
    		String configLocation = getContextConfigLocation();
    		if (configLocation != null) {
    			wac.setConfigLocation(configLocation);
    		}
    		configureAndRefreshWebApplicationContext(wac);
    
    		return wac;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    首先调用getContextClass()方法获取contextClass:

    	public static final Class DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
    	private Class contextClass = DEFAULT_CONTEXT_CLASS;
    
    public Class getContextClass() {
    		return this.contextClass;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    可以看到,如果不是通过注解方式启动、而是通过xml配置方式启动的话,创建的ServletContext应该就是这个XmlWebApplicationContext。

    创建ServletContext之后,与xml配置方式一样:设置父容器,然后调用configureAndRefreshWebApplicationContext方法配置及刷新容器。

    接下来我们看configureAndRefreshWebApplicationContext方法。

    configureAndRefreshWebApplicationContext

    目前为止,我们前面的猜测:通过DispatcherServlet的init方法初始化各个特殊bean。尚未的到证实 — 在DispatcherServlet的init方法中,我们尚未看到相关的初始化代码。

    不过代码还没分析完,还有一个configureAndRefreshWebApplicationContext,我们继续分析。

    代码比较长,我们还是分段分析:

    	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());
    			}
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    为WebApplicationContext设置Id,无关紧要,继续看下面的代码:

    		wac.setServletContext(getServletContext());
    		wac.setServletConfig(getServletConfig());
    		wac.setNamespace(getNamespace());
    		wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    设置ServletContext、ServletConfig、以及namespace,之后新增了一个监听器: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);
    		wac.refresh();
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    设置环境变量,以及获取初始化参数,最后调用WebApplicationContext的refresh方法。

    依然没有看到DispatcherServlet对特殊bean的初始化!而且现在的代码逻辑是转到了ApplicationContext中,是Spring Framework的内容、并不是Spring MVC的内容。

    别急,马上就要摸到开关了!

    目前的代码确实是转悠到Spring Framework中来了。所以说Spring全家桶,不管是Spring MVC、还是SpringBoot、还是Spring Security,统统都是以Spring Framework为基础的。掌握Spring Framework是掌握Spring全家桶的基础。

    ApplicationContext的refresh方法我们很熟悉了,是Spring Framework的关键方法,在AbstractApplicationContext类中实现,该方法最后会调用到finishRefresh()方法:
    在这里插入图片描述

    finishRefresh()方法最后会发布ContextRefreshedEvent事件。

    没错,前面代码分析过程中,我们确实是在WebApplicationContext容器中注册了一个针对该事件的监听器ContextRefreshListener:

    	private class ContextRefreshListener implements ApplicationListener {
    
    		@Override
    		public void onApplicationEvent(ContextRefreshedEvent event) {
    			FrameworkServlet.this.onApplicationEvent(event);
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    该监听器是定义在FrameworkServlet中的一个内部类,其onApplicationEvent方法会调用到FrameworkServlet的onApplicationEvent方法,这样,通过监听机制,代码逻辑就再次转回到了DispatcherServlet(确切说是他的父类FrameworkServlet)中来了:

    	public void onApplicationEvent(ContextRefreshedEvent event) {
    		this.refreshEventReceived = true;
    		synchronized (this.onRefreshMonitor) {
    			onRefresh(event.getApplicationContext());
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    最终会调用到DispatcherServlet中来:

    	@Override
    	protected void onRefresh(ApplicationContext context) {
    		initStrategies(context);
    	}
    
    • 1
    • 2
    • 3
    • 4

    查看DispatcherServlet代码我们会发现,这个initStrategies正式我们要找的方法,方法参数Context是通过事件传递过来的,因此,DispatcherSerlet在进行初始化的时候可以持有ApplicationContext对象,然后,随心所欲地完成Spring MVC特殊bean的初始化。

    篇幅原因,关于DispatcherServlet的具体初始化过程,我们后面分析。

    上一篇 Spring MVC 五 - Spring MVC的配置和DispatcherServlet初始化过程

  • 相关阅读:
    MMoE论文中Synthetic Data生成代码(控制多任务学习中任务之间的相关性)
    【Java】函数式接口
    从零配置一台linux主机
    Vue:(四)数据代理
    一个讲座监控软件
    Android 反射在Android开发中的巧妙使用基础篇(二)
    单点登录SSO的含义
    【Teams】Teams的组织名称变更
    【OpenCV】红绿灯检测C++Demo实现
    动态sql和分页
  • 原文地址:https://blog.csdn.net/weixin_44612246/article/details/132679252