SpringMVC4.x 番外(1):Spring MVC 上下文容器初始化

MVC 应用配置实现

  1. 在目前的实现中Spring的上下文容器可以称之为RootApplicationContext,而MVC的上下文容器称之为WebApplicationContext或者ServletApplicationContext。两者是父子容器的关系。

  2. Spring MVC 框架的核心类是DispatcherServlet,几乎所有的工作都是围绕DispatcherServlet展开的。每一个DispatcherServlet都有一个自己的WebApplicationContext。该上下文容器的创建与初始化是在 DispatcherServlet 类的创建和初始化过程中完成的。

  3. 由于Servlet3.x支持通过类完成应用容器初始化,创建DispatcherServlet一般会有两种不同的方式:

Web.xml 配置实现

  1. Web.xml 配置实现
  • 定义DispatcherServlet对象
  • 通过初始化参数contextConfigLocation自定义配置文件,多个配置文件可以使用逗号、空格等分隔。如spring-mvc.xml
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath*:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>0</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

  1. 在指定的配置文件中(spring-mvc.xml),定义需要的Bean组件实现具体的功能。

Servlet3.x 容器实现

  1. Servlet3.x 中可以不使用Web.xml,实现动态注册Servlet。MVC容器初始化以及DispatcherServlet 类的创建是在 AbstractDispatcherServletInitializer#registerDispatcherServlet方法中编码完成,省去了配置定义DispatcherServlet的步骤

  2. 在创建MVC的上下文容器中,有需要实现的getServletConfigClasses方法,可以设置Spring MVC 的配置类,需要自定义指定该 ServletServletMappings映射

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.learning.springmvc.base.javaconfig", useDefaultFilters = false,
        includeFilters = {@ComponentScan.Filter(classes = Controller.class)})
public class WebMvcConfig extends WebMvcConfigurerAdapter {


    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.jsp("/WEB-INF/pages/", ".jsp");
    }
}

# Servlet3.x 初始化 
public class WebMvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    
    # Spring 容器配置
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{ApplicationConfig.class};
    }

    # MVC 容器配置
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebMvcConfig.class};
    }

    # 配置Servlet 的Mappings映射
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

}

MVC 容器初始化

DispatcherServlet 创建

  1. 在Spring MVC 的设计中,每一个DispatcherServlet应该有属于自己的上下文容器,DispatcherServlet有两个构造函数,两种构造正好对应着Web.xml配置和SpringServletContainerInitializer初始化类配置两种不同的实现方式。
构造函数应用初始化方式解析
无参构造Web.xml创建DispatcherServlet对象,然后通过实现的接口方法initServletBean()创建和初始化容器WebApplicationContext
有参构造SpringServletContainerInitializer1、参数为WebApplicationContext对象,即先创建了容器对象WebApplicationContext
2、创建DispatcherServlet对象,通过实现的接口方法initServletBean()初始化容器WebApplicationContext
  1. SpringServletContainerInitializer 方式创建上下文容器会先调用AbstractDispatcherServletInitializer#registerContextLoaderListener方法创建Spring的上下文容器对象、ContextLoaderListener对象,然后创建MVC的上下文容器以及DispatcherServlet对象
	@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
	
	    # 创建Spring的上下文容器以及ContextLoaderListener对象
		super.onStartup(servletContext);
		# 创建 MVC 上下文容器以及 DispatcherServlet 对象
		registerDispatcherServlet(servletContext);
	}
  1. 在创建 MVC 上下文容器以及 DispatcherServlet 对象过程中,简单总结为如下步骤
编号操作解析
1创建Servlet 名称默认名称为 dispatcher
2创建 MVC 上下文容器1、在创建容器对象的方法中,可以获取容器相关的配置类。其方法getServletConfigClasses,框架中没有实现,需要应用扩展使用。
2、创建的该上下文容器,类型为AnnotationConfigWebApplicationContext,没有设置任何属性,如contextClasscontextConfigLocation
3创建DispatcherServlet对象1、可以获取DispatcherServlet对应的映射,其方法getServletMappings,框架中没有实现,需要应用扩展使用
2、把创建的容器对象作为参数创建 DispatcherServlet对象,并且把创建的该对象动态注册到ServletContext
4自定义容器设置获取自定义接口ApplicationContextInitializer的实现,目前该功能没有任何实现。
5动态注册过滤器以及其他自定义组件ServletContext中动态注册Filter、自定义组件等。框架本身没有具体的实现,需要通过覆盖getServletFilterscustomizeRegistration方法自定义实现
	protected void registerDispatcherServlet(ServletContext servletContext) {
		# 默认servlet 名称为 dispatcher
		String servletName = getServletName();
		
        # 创建MVC上下文容器
		WebApplicationContext servletAppContext = createServletApplicationContext();
        
        # 创建`DispatcherServlet`对象
		FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
		
		# 设置初始化类 ApplicationContextInitializer。没有任何实现
		dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

        # 动态把该Servlet 注册到 `servletContext` 上下文
		ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
		Assert.notNull(registration,
				"Failed to register servlet with name '" + servletName + "'." +
				"Check if there is another servlet registered under the same name.");

		registration.setLoadOnStartup(1);
		registration.addMapping(getServletMappings());# 映射
		registration.setAsyncSupported(isAsyncSupported());

        # 动态注册 Filter,可以重写 getServletFilters 实现
		Filter[] filters = getServletFilters();
		if (!ObjectUtils.isEmpty(filters)) {
			for (Filter filter : filters) {
				registerServletFilter(servletContext, filter);
			}
		}
        # 动态自定义注册其他的,重写 customizeRegistration 方法实现
		customizeRegistration(registration);
	}

DispatcherServlet 初始化

在这里插入图片描述

  1. HttpServlet初始化调用了HttpServletBean#init方法,该方法在Servlet初始化中被调用,主要工作就是MVC容器的初始化工作
  • 该方法还会获取Servlet中的init参数,并且创建一个BeanWrapper对象,然后由子类真正执行BeanWrapper的初始化工作。由于HttpServletBean的子类都没有覆盖其initBeanWrapper方法,所以创建的BeanWrapper对象没有任何作用
	public final void init() throws ServletException {

		// 设置初始化参数 
		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);
			}
			... ...
		}

		// 初始化Web 上下文容器,初始化 Servlet这个Bean
		initServletBean();
        ... ...
	}
	
	@Override
	protected final void initServletBean() throws ServletException {
	    # 初始化Web 上下文容器
		this.webApplicationContext = initWebApplicationContext();
		
		# 初始化 Servlet,这个方法暂时实现为空。
		initFrameworkServlet();
	}
	
  1. 在方法initWebApplicationContext()中完成的MVC上下文容器创建初始化过程,简单总结为如下4个步骤:
编号步骤分析
1设置父容器如果MVC容易已经存在,则从 ServletContext 中获取key为org.springframework.web.context.WebApplicationContext.ROOT的Spring上下文容器,并把他设置为MVC上下文容器的父容器
2初始化容器如果MVC容易已经存在,并判断容器是否成功初始化(isActive状态),如未初始化,则执行容器的初始化工作
3创建容器判断上下文容器对象是否已经创建,如果没有则创建一个新的容器对象
4Serevlet个性化刷新工作该方法内部用来初始化MVC框架的核心组件,由于在MVC上下文容器初始化时使用spring 的事件机制完成了个性化刷新工作(refreshEventReceived状态),所以这边一般不会再次执行。
5保存容器把初始化的MVC容器作为一个属性存入ServletContext中,key为 org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcherdispatcher 为默认的Servlet的名称。
	protected WebApplicationContext initWebApplicationContext() {
	    # 从 ServletContext 中获取Spring上下文容器
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;
        
        # 如果MVC容易已经存在
		if (this.webApplicationContext != null) {
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) {
					if (cwac.getParent() == null) {
						# 把应用上下文容器设置成 Web 上下文容器的父容器
						cwac.setParent(rootContext);
					}
					# 配置和刷新(初始化)Web 上下文容器
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		# 如果MVC容易不存在
		if (wac == null) {
			wac = findWebApplicationContext();
		}
		if (wac == null) {
			wac = createWebApplicationContext(rootContext);
		}

		if (!this.refreshEventReceived) {
            // 该方法在DispatcherServlet中覆盖重写,用于初始化mvc内部核心组件.其核心方法在于 DispatcherServlet#initStrategies() 方法
            // 其实在 刷新(初始化)MVC上下文容器时利用spring的事件机制,已经初始化过一遍,这边不会走。
			onRefresh(wac);
		}

		if (this.publishContext) {
			# 把Web上下文容器,存在 ServletContext 中
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
            ... ...
		}

		return wac;
	}
	
  1. 容器创建方法createWebApplicationContext只有在使用Web.xml 配置方式时才会被调用。
  • 先获取创建上下文容器的类型,默认是XmlWebApplicationContext,然后利用反射直接创建。
  • 设置上下文容器相关的属性,如父容器、contextConfigLocation
  • 初始化刷新该容器对象
	protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
		# 获取创建上下文容器的类型
		Class<?> contextClass = getContextClass();
		... ...
		
		# 反射创建
		ConfigurableWebApplicationContext wac =
				(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

        # 设置各种属性参数
		wac.setEnvironment(getEnvironment());
		wac.setParent(parent);
		wac.setConfigLocation(getContextConfigLocation());

        # 初始化容器
		configureAndRefreshWebApplicationContext(wac);

		return wac;
	}
  1. 上下文容器配置与初始化方法configureAndRefreshWebApplicationContext()是整个过程的核心。所有初始化的工作部分都是在这边完成的,主要有:
编号任务名称具体事项
1设置容器的唯一Id1、可以使用初始化参数contextId自定义
2、默认值为一个组合值,为WebApplicationContext类的全路径名称加上ServletName的值。默认容器ID为org.springframework.web.context.WebApplicationContext:/dispatcher
2设置一般属性属性包括 ServletContextServletConfigNamespaceApplicationListener,值得关注的为添加了自定义的ContextRefreshListener容器刷新监听器。
3初始化占位符属性源这个操作在容器初始化(刷新操作)始终会被执行。这边执行的原因在于确保servlet属性源在刷新操作之前的任何后处理或初始化中都处于适当的位置
4后置处理在刷新并激活给定的上下文容器作为此该Seervlet的上下文容器之前,对其进行后处理。默认实现为空,需要子类扩展实现
5自定义容器设置1、在容器初始化刷新之前自定义创建的容器
2、该自定义扩展功能专门由一个接口ApplicationContextInitializer实现
3、通过初始化属性contextInitializerClassesglobalInitializerClasses,指定自定义的ApplicationContextInitializer接口实现类器
4、通过这个功能可以在容器对象初始化之前,扩展容器的功能
6上下文初始化调用AbstractApplicationContext#refresh方法完成初始化。具体见 Spring4.x 笔记(6):Ioc 容器高级-内部工作机制
	protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
	
	    # 设置 contextId
		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
			if (this.contextId != null) {
				wac.setId(this.contextId);
			}
			else {
				// 默认值
				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()));

		# 初始化占位符属性源。
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
		}
        
        # 自定义后置处理
		postProcessWebApplicationContext(wac);
		# 自定义容器设置
		applyInitializers(wac);
		# 上下文初始化刷新
		wac.refresh();
	}
  1. 上下文容器初始化刷新后,会提交一个容器刷新事件。而在DispatcherServlet父类自定义的容器刷新事件监听中ContextRefreshListener,直接调用FrameworkServlet#onRefresh方法初始负责初始化内部组件,并且设置refreshEventReceived属性为true
	private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

		@Override
		public void onApplicationEvent(ContextRefreshedEvent event) {
			FrameworkServlet.this.onApplicationEvent(event);
		}
	}
	
	public void onApplicationEvent(ContextRefreshedEvent event) {
		this.refreshEventReceived = true;
		onRefresh(event.getApplicationContext());
	}
	

至此整个DispatcherServlet以及其所属的MVC上下文容器完成创建和初始化,可以接收HTTP请求了。

参考

  1. SpringMVC4.x 笔记(1):框架体系概述
  2. Spring4.x 番外(1):Spring 上下文容器初始化
  3. Spring4.x 笔记(2):Spring 的Ioc容器
  4. Spring4.x 笔记(6):Ioc 容器高级-内部工作机制
  5. Servlet 笔记(12):Servlet3.X 版本新特性
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值