明天的内容是SpringMVC的初始化过程,其实也就是DispatcherServilet的初始化过程。
Special Bean Types
DispatcherServlet委托如下一些非凡的bean来解决申请、并渲染正确的返回。这些非凡的bean是Spring MVC框架治理的bean、依照Spring框架的约定解决相干申请,个别状况下是框架内置的,咱们当然也能够定制或扩大他们的性能。
这些非凡bean包含:
- HandlerMapping:依据肯定的规定把申请映射到对应的HandlerMapping去解决,HandlerMapping能够蕴含一系列拦截器,进行前置或后置解决。框架默认提供了RequestMappingHandlerMapping(解决@RequestMapping注解办法的)和SimpleUrlHandlerMapping两个HandlerMapping。
- HandlerAdapter:HandlerMapping匹配到申请之后,调用HandlerAdapter具体解决申请。
- HandlerExceptionResolver:产生异样后的异样处理器。
- ViewResolver:解决返回
- LocaleResolver, LocaleContextResolver:本地化处理器
- ThemeResolver:Theme渲染处理器
- MultipartResolver:Multipart处理器,文件上传下载的解决。
- 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的类构造:
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(); }
下面的代码是对以后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; }
该办法中有很多打印log的代码,疏忽掉,剩下的就是两个办法的调用:一个是创立webApplicationContext的,一个是initFrameworkServlet,这个initFrameworkServlet是空办法,所以,DispatcherServlet的初始化逻辑,要害就在这个initWebApplicationContext()办法中。
initWebApplicationContext办法很长,咱们分段剖析一下。
protected WebApplicationContext initWebApplicationContext() { WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; ...
首先获取以后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); } } }
判断如果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()); ...省略代码
所以如果是通过注解形式配置的话,会通过createServletApplicationContext()办法创立ServletContext:
@Override protected WebApplicationContext createServletApplicationContext() { AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); Class<?>[] configClasses = getServletConfigClasses(); if (!ObjectUtils.isEmpty(configClasses)) { context.register(configClasses); } return context; }
最终创立的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); }
如果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; }
首先调用getContextClass()办法获取contextClass:
public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class; private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;public Class<?> getContextClass() { return this.contextClass; }
能够看到,如果不是通过注解形式启动、而是通过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()); } }
为WebApplicationContext设置Id,无关紧要,持续看上面的代码:
wac.setServletContext(getServletContext()); wac.setServletConfig(getServletConfig()); wac.setNamespace(getNamespace()); wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
设置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(); }
设置环境变量,以及获取初始化参数,最初调用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<ContextRefreshedEvent> { @Override public void onApplicationEvent(ContextRefreshedEvent event) { FrameworkServlet.this.onApplicationEvent(event); } }
该监听器是定义在FrameworkServlet中的一个外部类,其onApplicationEvent办法会调用到FrameworkServlet的onApplicationEvent办法,这样,通过监听机制,代码逻辑就再次转回到了DispatcherServlet(确切说是他的父类FrameworkServlet)中来了:
public void onApplicationEvent(ContextRefreshedEvent event) { this.refreshEventReceived = true; synchronized (this.onRefreshMonitor) { onRefresh(event.getApplicationContext()); } }
最终会调用到DispatcherServlet中来:
@Override protected void onRefresh(ApplicationContext context) { initStrategies(context); }
查看DispatcherServlet代码咱们会发现,这个initStrategies正式咱们要找的办法,办法参数Context是通过事件传递过去的,因而,DispatcherSerlet在进行初始化的时候能够持有ApplicationContext对象,而后,得心应手地实现Spring MVC非凡bean的初始化。
篇幅起因,对于DispatcherServlet的具体初始化过程,咱们前面剖析。
上一篇 Spring MVC 五 - Spring MVC的配置和DispatcherServlet初始化过程