从本篇开始分析SpringMvc的源码,版本5.3.6。SpringMvc提供一个类DispatcherServlet,是处理http请求入口。该类本质上是一个Servlet,可通过UML得知,既然是Servlet那么就按照Servlet生命周期去分析源码就好了。
一、初始化整体流程图
初始化整体流程,入口肯定是Servlet#init(ServletConfig)方法,沿着这个思路去看源码,可以得大体框架图:
注:DispatcherServlet继承了FrameworkServlet类,所以很多东西是父类中完后的
以xml配置方式,进行源码解析,web.xml配置如下:
<!-- servlet -->
<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:springmvc04.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
二、HttpServletBean#init
该方法代码如下所示:
@Override
public final void init() throws ServletException {
// Set bean properties from init parameters.
// 解析web.xml中init-param标签,存储到PropertyValues
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {//通过xml方式配置springmvc,会进入这个if。如果是注解方式则不进入
try {
//这里的this指向的DispatcherServlet,下面的代码是给DispatcherServlet中contextLocation属性赋值
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(); //子类实现
}
基于xml方式配置springmvc,会进入if分支。PropertyValues是一个键值对,内容就是xml配置init-param标签,key=contextConfigLocation,value=classpath:springmvc04.xml。
这段代码就是保存springmvc配置的,了解到这种程度即可。
三、FrameworkServlet#initServletBean
这个方法大部分都是日志检查,最核心方法就是initWebApplicationContext,初始化IOC容器。initFrameworkServlet目前是空方法。
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
long startTime = System.currentTimeMillis();
try {
//初始化IOC容器 ApplicationContext
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet(); // 这方法是空
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
if (logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ?
"shown which may lead to unsafe logging of potentially sensitive data" :
"masked to prevent unsafe logging of potentially sensitive data";
logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
"': request parameters and headers will be " + value);
}
if (logger.isInfoEnabled()) {
logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
//如果是注解方式的springmvc进入if分支,xml方式不进入
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
wac = createWebApplicationContext(rootContext); //这里创建ioc容器
}
//spring异步事件,在创建ioc容器时会产生一个异步事件,去刷新ioc容器
//这里简单起见,假设没有接收到事件,刷新操作会在当前线程完成
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;
}
这里有几点说明:
1)spring异步事件,是单独的,不去深究,这里假设ioc容器ApplicationContext创建之后,刷新操作可以看成是在当前线程中完成
2)attrName比较长,通过断点查看到其值是,org.springframework.web.servlet.FrameworkServlet.CONTEXT.DispatcherServlet,红色是我们在xml配置文件中指定servlet-name属性
四、DispatcherServlet#onRefresh
本质是初始化各种解析器
/**
* This implementation calls {@link #initStrategies}.
*/
@Override
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);
initHandlerMappings(context); //初始化处理器映射
initHandlerAdapters(context);
initHandlerExceptionResolvers(context); //异常解析器
initRequestToViewNameTranslator(context);
initViewResolvers(context); //视图解析器
initFlashMapManager(context);
}
五、总结
初始化流程相对清晰一些,当然并没有深入分析每个方法,只是把大体流程搞清楚了,下一篇介《SpringMvc源码分析-DispatchServlet处理流程》。