SpringMVC初始化流程(事件监听机制、父子容器关系)

常用的SSM框架=Spring+SpringMVC(表现层)+Mybatis,其中Spring为父容器ApplicationContext、SpringMVC为子容器WebApplicationContext,Spring容器的初始化由ContextLoaderListener负责,SpringMVC的初始化由DispatcherServlet负责(web.xml中Listener优先于Servlet初始化)。下面将以问答模式叙述。
1.springMVC初始化流程是什么?
2.springMVC为什么要单独扫描Controller?

一、SpringMVC初始化流程是什么?

1.SpringMVC容器初始化的入口为DispatcherServlet的父类FrameworkServlet的父类HttpServletBean#init方法,里面调用了FrameworkServlet#initServletBean方法,调用了FrameworkServlet#initWebApplicationContext

FrameworkServlet.class
protected WebApplicationContext initWebApplicationContext() {
    
    
		// 获取Spring父容器
		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
			// 创建SpringMVC容器并注入父容器
			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;
	}

2.进入createWebApplicationContext的createWebApplicationContext

FrameworkServlet.class
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);
		// 获取web.xml中为DispatcherServlet设置的init-param参数,指定了要加载的springmvc.xml位置
		String configLocation = getContextConfigLocation();
		if (configLocation != null) {
    
    
			wac.setConfigLocation(configLocation);
		}
		// 初始化SpringMVC容器
		configureAndRefreshWebApplicationContext(wac);

		return wac;
	}

3.进入configureAndRefreshWebApplicationContext

FrameworkServlet.class
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());
		// 将监听器ContextRefreshListener注入到springMVC容器,用来监听容器上下文刷新事件ContextRefreshedEvent,当此事件被发布时
		// ContextRefreshListener会调用springMVC九大组件初始化逻辑(此处运用了观察模式)
		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);
		// SpringMVC容器刷新初始化
		wac.refresh();
	}

4.进入AbstractApplicationContext#refresh方法这里是springIOC容器初始化的主要步骤,就不逐步骤介绍了

public void refresh() throws BeansException, IllegalStateException {
    
    
		synchronized (this.startupShutdownMonitor) {
    
    
			// Prepare this context for refreshing.
			// 第一步 刷新前的预处理
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			// 第二步 1.创建BeanFactory实例,默认实现是DefaultListableBeanFactory
			//       2.解析XML中的<bean>为BeanDefition 并注册到 BeanDefitionRegistry
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			// 第三步 BeanFactory的预准备⼯作(BeanFactory进⾏⼀些设置,⽐如context的类加载器等)
			prepareBeanFactory(beanFactory);

			try {
    
    
				// Allows post-processing of the bean factory in context subclasses.
				// 第四步 BeanFactory准备工作完成后的后置处理工作,钩子方法,等子类重写
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				// 第五步 实例化并调⽤实现了BeanFactoryPostProcessor接⼝的Bean
				// 提前初始化工厂后置处理器bean,并调用postProcessBeanFactory方法
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				// 第六步 注册BeanPostProcessor(Bean的后置处理器),在创建bean的前后等执
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				// 第七步 初始化MessageSource组件(做国际化功能;消息绑定,消息解析);
				initMessageSource();

				// Initialize event multicaster for this context.
				// 第八步 初始化事件派发器
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				// 第九步 ⼦类重写这个⽅法,在容器刷新的时候可以⾃定义逻辑,钩子方法
				onRefresh();

				// Check for listener beans and register them.
				// 第十步 注册应⽤的监听器。就是注册实现了ApplicationListener接⼝的监听器bean
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				// 第十一步 初始化所有剩下的⾮懒加载的单例bean
				//1).初始化创建⾮懒加载⽅式的单例Bean实例(未设置属性)
				//2).填充属性
				//3) .如果bean实现了Aware相关接口,则调用Aware接口的实现方法
				//4) .调用BeanPostProcessor处理器的前置方法
				//5).初始化⽅法调⽤(⽐如调⽤afterPropertiesSet⽅法、init-method⽅法)
				//6).调⽤BeanPostProcessor(后置处理器)对实例bean进⾏后置处
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				// 第十二步 完成context的刷新。主要是调⽤LifecycleProcessor的onRefresh()⽅法,并且发布事件 (ContextRefreshedEvent)
				finishRefresh();
			}

			catch (BeansException ex) {
    
    
				if (logger.isWarnEnabled()) {
    
    
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
    
    
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
	}

**5.此处关注refresh方法的第12步骤finishRefresh(); 完成context的刷新。主要是调⽤LifecycleProcessor的onRefresh()⽅法,并且发布事件 ContextRefreshedEvent(本质是遍历监听ContextRefreshedEvent事件的监听器调用其onApplicationEvent方法),此处先看下步骤三为springMVC容器注入监听器的地方 wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));,进去监听器FrameworkServlet.ContextRefreshListener (FrameworkServlet的内部类)
**

ContextRefreshListener.class

```private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
    
    

		@Override
		public void onApplicationEvent(ContextRefreshedEvent event) {
    
    
			FrameworkServlet.this.onApplicationEvent(event);
		}
	}

进入onApplicationEvent()中的onRefresh方法

DispatcherServlet.class
protected void onRefresh(ApplicationContext context) {
    
    
		initStrategies(context);
}

	/**
	 * Initialize the strategy objects that this servlet uses.
	 * <p>May be overridden in subclasses in order to initialize further strategy objects.
	 */
	protected void initStrategies(ApplicationContext context) {
    
    
		// 初始化文件上传解析器(用于处理文件上传请求)
		initMultipartResolver(context);
		// 初始化国际化解析器(实现翻译语言切换功能)
		initLocaleResolver(context);
		// 初始化主题解析器(切换主题)
		initThemeResolver(context);
		// 初始化处理器映射器(维护url与handler关系)
		initHandlerMappings(context);
		// 初始化处理器适配器(反射执行对应类型的handler)
		initHandlerAdapters(context);
		// 初始化异常解析器(handler异常优雅捕捉处理)
		initHandlerExceptionResolvers(context);
		// 初始化默认视图名称转换器(当为指定逻辑视图名称时,根据url获取默认逻辑视图名称)
		initRequestToViewNameTranslator(context);
		// 初始化视图解析器(根据逻辑视图名解析创建真实视图)
		initViewResolvers(context);
		// 初始化FlashMap管理器(负责springmvc重定时参数的传递处理)
		initFlashMapManager(context);
	}

6.挑选一个组件HandlerMapping看一下初始化细节,其他组件大同小异
进入initHandlerMappings()

private void initHandlerMappings(ApplicationContext context) {
    
    
		this.handlerMappings = null;

		if (this.detectAllHandlerMappings) {
    
    
			// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
			// 在SpringMVC容器及其父容器中查找HandlerMapping
			Map<String, HandlerMapping> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
			if (!matchingBeans.isEmpty()) {
    
    
				this.handlerMappings = new ArrayList<>(matchingBeans.values());
				// We keep HandlerMappings in sorted order.
				AnnotationAwareOrderComparator.sort(this.handlerMappings);
			}
		}
		else {
    
    
			try {
    
    
				// 查找beanName=handlerMapping的处理器映射器,如果需要自定义处理器可以将beanName定义为handlerMapping,再在web.xml
				//中配置DispatcherServlet的detectAllHandlerMappings参数=false
				HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
				this.handlerMappings = Collections.singletonList(hm);
			}
			catch (NoSuchBeanDefinitionException ex) {
    
    
				// Ignore, we'll add a default HandlerMapping later.
			}
		}

		// Ensure we have at least one HandlerMapping, by registering
		// a default HandlerMapping if no other mappings are found.
		if (this.handlerMappings == null) {
    
    
			// 如果未发现HandlerMapping则创建默认的处理器映射器
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
			if (logger.isTraceEnabled()) {
    
    
				logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
						"': using default strategies from DispatcherServlet.properties");
			}
		}
	}

进入getDefaultStrategies

DispatcherServlet.class
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
    
    
		String key = strategyInterface.getName();
		// 在properties中加载指定的handlerMapping
		String value = defaultStrategies.getProperty(key);
		if (value != null) {
    
    
			String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
			List<T> strategies = new ArrayList<>(classNames.length);
			for (String className : classNames) {
    
    
				try {
    
    
					Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
					// 将默认的handlerMapping注册到SpringMVC容器
					Object strategy = createDefaultStrategy(context, clazz);
					strategies.add((T) strategy);
				}
				catch (ClassNotFoundException ex) {
    
    
					throw new BeanInitializationException(
							"Could not find DispatcherServlet's default strategy class [" + className +
							"] for interface [" + key + "]", ex);
				}
				catch (LinkageError err) {
    
    
					throw new BeanInitializationException(
							"Unresolvable class definition for DispatcherServlet's default strategy class [" +
							className + "] for interface [" + key + "]", err);
				}
			}
			return strategies;
		}
		else {
    
    
			return new LinkedList<>();
		}
	}

看下defaultStrategies的初始化
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

由上面三个图可以看到,在DispatcherServlet.properties里配置了默认的两个处理器映射器。其他组件如处理器适配器HandlerAdapter、视图解析器viewResolver都在此处配置了默认类。
到此SpringMVC的初始化完成,有了这九大组件,足以支持springMVC的绝大部分功能。

二、SpringMVC为什么要单独扫描Controller?

答:关键点就在于DefaultListableBeanFactory#getBeanNamesForType方法,此方法为根据类型在容器中获取beanName集合,此处仅仅在当前容器中的bean取,不会取入容器中的bean。但是DefaultListableBeanFactory#getBean方法会从父容器中获取bean。

解析:在SSM中Spring容器作为父容器管理service层+dao层的bean,SpringMVC作为子容器管理着web层的bean,子容器拥有父容器的引用,即可以获取父容器中的bean,父容器无法获取子容器中的bean。
首先解释下划分父子容器的原因
父子容器的作用主要是划分框架边界。在J2EE三层架构中,在service层我们一般使用spring框架, 而在web层则有多种选择,如spring mvc、struts等。因此,通常对于web层我们会使用单独的配置文件。一开始我们使用spring-servlet.xml来配置web层,使用applicationContext.xml来配置service、dao层。如果现在我们想把web层从spring mvc替换成struts,那么只需要将spring-servlet.xml替换成Struts的配置文件struts.xml即可,而applicationContext.xml不需要改变。
事实上,如果你的项目确定了只使用spring和spring mvc的话,你甚至可以将service 、dao、web层的bean都放到spring-servlet.xml中进行配置,并不是一定要将service、dao层的配置单独放到applicationContext.xml中,然后使ContextLoaderListener来加载。在这种情况下,就没有了Root WebApplicationContext(为null),只有Servlet WebApplicationContext。

所以Spring和SpringMVC分两个配置文件进行配置可以降低耦合性,方便表现层框架切换,而且逻辑更为清晰。但是此处有个,相信大家很多人都遇到过在Spring配置文件里扫描了全部类,在SpringMVC配置文件里没有进行Controller扫描,导致请求访问的时候找不到handler出现404的情况,不是说**子容器拥有父容器的引用,即可以获取父容器中的bean嘛?为什么会出现这种情况呢下面看一下HandlerMapping的初始化源码。
一、SpringMVC初始化流程是什么?**中我们可以看到默认的两个处理器BeanNameUrlHandlerMapping和RequestMappingHandlerMapping,我们目前主流的handler声明方式是通过@RequestMapping注解,那么url和handler的关系维护在RequestMappingHandlerMapping中。
进入RequestMappingHandlerMapping#afterPropertiesSet方法,再进入 super.afterPropertiesSet();中的initHandlerMethods();

AbstractHandlerMethodMapping.class
protected void initHandlerMethods() {
    
    
		// 获取springMVC中全部@Controller标注的bean的BeanName,遍历解析维护url和handler关系
		for (String beanName : getCandidateBeanNames()) {
    
    
			if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
    
    
				processCandidateBean(beanName);
			}
		}
		handlerMethodsInitialized(getHandlerMethods());
	}

进入getCandidateBeanNames()

protected String[] getCandidateBeanNames() {
    
    
		return (this.detectHandlerMethodsInAncestorContexts ?
				BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
				obtainApplicationContext().getBeanNamesForType(Object.class));
	}

进入obtainApplicationContext().getBeanNamesForType(Object.class),在springMVC容器中获取类型为Object的bean
关键点就在于DefaultListableBeanFactory#getBeanNamesForType方法,此方法为根据类型在容器中获取beanName集合,此处仅仅在当前容器中的bean取,不会取父容器中的bean。但是DefaultListableBeanFactory#getBean方法会优先从父容器中获取bean,所以@Controller注解的扫描最后放置SpringMVC配置文件中,Spring的配置文件中只扫描@Repository、@Component、@Service的包路径

猜你喜欢

转载自blog.csdn.net/yangxiaofei_java/article/details/113092111