常用的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的包路径