在SpringMVC中處理請求的核心類是DispatcherServlet; SpringMVC在DispatcherServlet類的doDispatch()中維護着請求處理流程的主要邏輯
DispatcherServlet的繼承關係圖:
SpringMVC初始化的觸發點:
SpringMVC的初始化是從HttpServletBean#init()開始的;Tomcat在啓動時最終會調用GenericServlet#init(javax.servlet.ServletConfig), 在該方法中又調用了GenericServlet#init()方法; 由於HttpServletBean繼承了GenericServlet並重寫了init()方法, 所以此時調用的是HttpServletBean#init(), 從這裏便開始了SpringMVC的初始化歷程
GenericServlet#init(javax.servlet.ServletConfig)方法實現:
@Override
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
GenericServlet#init()方法實現(實際上執行的是HttpServletBean#init()方法):
public void init() throws ServletException {
// NOOP by default
}
HttpServletBean#init()方法實現:
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
//PropertyValues: 獲取Web.xml裏面的servlet的init-param(web.xml)
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
//BeanWrapper: 封裝了bean的行爲,提供了設置和獲取屬性值,它有對應的BeanWrapperImpl
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
//ResourceLoader: 可以根據一個資源地址加載文件資源。classpath:這種方式指定SpringMVC框架bean配置文件的來源
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;
}
}
/**
* 由子類實現, 進行子類的初始化
* {@link FrameworkServlet#initServletBean()}
*/
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
Tomcat部分講解:
Tomcat在啓動後會逐步加載各個組件, 最終會加載到Wrapper容器, 因此會調用StandardWrapper#load()方法; 在此之前的啓動邏輯感興趣的可以看這篇博客:Tomcat源碼解析五(容器的啓動過程解析)
StandardWrapper#load()方法實現:
@Override
public synchronized void load() throws ServletException {
/**
* 加載Servlet
*/
instance = loadServlet();
if (!instanceInitialized) {
initServlet(instance);
}
if (isJspServlet) {
StringBuilder oname = new StringBuilder(getDomain());
oname.append(":type=JspMonitor");
oname.append(getWebModuleKeyProperties());
oname.append(",name=");
oname.append(getName());
oname.append(getJ2EEKeyProperties());
try {
jspMonitorON = new ObjectName(oname.toString());
Registry.getRegistry(null, null).registerComponent(instance, jspMonitorON, null);
} catch (Exception ex) {
log.warn("Error registering JSP monitoring with jmx " + instance);
}
}
}
StandardWrapper#loadServlet()方法實現:
public synchronized Servlet loadServlet() throws ServletException {
// 如果我們已經有實例或實例池,則無需執行任何操作
if (!singleThreadModel && (instance != null))
return instance;
PrintStream out = System.out;
if (swallowOutput) {
SystemLogHandler.startCapture();
}
Servlet servlet;
try {
long t1=System.currentTimeMillis();
// Complain if no servlet class has been specified
if (servletClass == null) {
unavailable(null);
throw new ServletException
(sm.getString("standardWrapper.notClass", getName()));
}
InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
try {
/**
* 實例化Servlet
*/
servlet = (Servlet) instanceManager.newInstance(servletClass);
} catch (ClassCastException e) {
. . . . . .
}
if (multipartConfigElement == null) {
MultipartConfig annotation =
servlet.getClass().getAnnotation(MultipartConfig.class);
if (annotation != null) {
multipartConfigElement =
new MultipartConfigElement(annotation);
}
}
if (servlet instanceof ContainerServlet) {
((ContainerServlet) servlet).setWrapper(this);
}
classLoadTime=(int) (System.currentTimeMillis() -t1);
if (servlet instanceof SingleThreadModel) {
if (instancePool == null) {
instancePool = new Stack<>();
}
singleThreadModel = true;
}
/**
* 初始化Servlet
*/
initServlet(servlet);
fireContainerEvent("load", this);
loadTime=System.currentTimeMillis() -t1;
} finally {
. . . . . .
}
return servlet;
}
分析:
- 通過反射實例化Servlet (Servlet ==> DispatcherServlet)
- 初始化Servlet
(1) Tomcat是如何獲取到SpringMVC的核心類DispatcherServlet然後進行初始化的呢?
在web.xml中有這樣的一個配置:
<servlet>
<servlet-name>spring-mvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring-mvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
Tomcat中在Context容器啓動時會發送消息通知各個觀察者, 其中有一個觀察者就是WebConfig, WebConfig在監聽到消息之後,開始解析web.xml, 並對其中配置的Servlet逐個進行實例化, 其中就包括我們配置的DispatcherServlet路徑信息; 所以Tomcat就可以通過反射去實例化該類;
解析web.xml配置信息以及實例化DispatcherServlet可以看org.apache.catalina.startup.ContextConfig#webConfig() == > org.apache.catalina.startup.ContextConfig#configureContext(WebXml webxml) 相關邏輯
調用鏈如下:
org.apache.catalina.core.StandardWrapper#setServletClass(String servletClass)方法是將DispatcherServlet類信息維護到StandardWrapper實例中(每一個StandardWrapper對應一個Servlet)
(2) 初始化Servlet
StandardWrapper#initServlet(Servlet servlet)方法實現: (Servlet => DispatcherServlet)
private synchronized void initServlet(Servlet servlet) throws ServletException {
if (instanceInitialized && !singleThreadModel) return;
// 調用此Servlet的初始化方法
try {
if( Globals.IS_SECURITY_ENABLED) {
boolean success = false;
try {
Object[] args = new Object[] { facade };
/**
* 通過反射執行該Servlet的init()方法
*/
SecurityUtil.doAsPrivilege("init", servlet, classType, args);
success = true;
} finally {
if (!success) {
// destroy() will not be called, thus clear the reference now
SecurityUtil.remove(servlet);
}
}
} else {
servlet.init(facade);
}
instanceInitialized = true;
} catch (UnavailableException f) {
. . . . . .
}
}
GenericServlet#init(javax.servlet.ServletConfig)方法實現:
@Override
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
HttpServletBean#init()方法實現:
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
//PropertyValues: 獲取Web.xml裏面的servlet的init-param(web.xml)
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
//BeanWrapper: 封裝了bean的行爲,提供了設置和獲取屬性值,它有對應的BeanWrapperImpl
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
//ResourceLoader: 可以根據一個資源地址加載文件資源。classpath:這種方式指定SpringMVC框架bean配置文件的來源
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;
}
}
/**
* 由子類實現, 進行子類的初始化
* {@link FrameworkServlet#initServletBean()}
*/
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
調用鏈如下:
FrameworkServlet#initServletBean()方法實現: (注意: FrameworkServlet是HttpServletBean的子類)
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
/**
* 初始化應用上下文
*/
this.webApplicationContext = initWebApplicationContext();
//初始化FrameworkServlet
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
elapsedTime + " ms");
}
}
分析:
- 初始化應用上下文
- 初始化FrameworkServlet(爲空方法)
FrameworkServlet#initWebApplicationContext()方法實現:
protected WebApplicationContext initWebApplicationContext() {
//獲取根節點上下文,通過ContextLoaderListener加載,服務器啓動便加載
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
//設置根節點上下文爲父上下文
cwac.setParent(rootContext);
}
//設置id等等
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
/**
* 查找servletContext中已有的WebApplicationContext作爲上下文
*/
wac = findWebApplicationContext();
}
if (wac == null) {
/**
* 嘗試獲取本地已有上下文失敗, 創建一個本地上下文
*/
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
if (this.publishContext) {
// 將DispatcherServlet的上下文放入servlet上下文中
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
分析:
- 嘗試從ServletContext中獲取已有的WebApplicationContext作爲上下文
- 嘗試獲取本地已有上下文失敗, 創建一個本地上下文
FrameworkServlet#createWebApplicationContext(org.springframework.context.ApplicationContext)方法實現:
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
Class<?> contextClass = getContextClass();
. . .
//通過反射創建mvc容器
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
//設置環境
wac.setEnvironment(getEnvironment());
//設置根上下文爲父上下文
wac.setParent(parent);
//設置springmvc.xml的路徑
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
/**
* 初始化springmvc容器
*/
configureAndRefreshWebApplicationContext(wac);
return wac;
}
分析: 利用反射創建一個WebApplicationContext實例, 並進行相關配置設置, 最後調用初始化方法, 開始SpringMVC容器的初始化
FrameworkServlet#configureAndRefreshWebApplicationContext()方法實現:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
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());
/**
* 向容器中添加監聽器, 監聽容器初始化結束事件(事件對象:ContextRefreshListener) <=== 重點
* ContextRefreshListener爲FrameworkServlet的內部類
*/
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
/**
* 調用Spring中的refresh()方法, 刷新應用環境
* {@link AbstractApplicationContext#refresh()}
*/
wac.refresh();
}
分析:
- 註冊監聽器, 用於監聽Spring中IOC容器初始化結束事件, 監聽到事件後, 從容器中獲取相關組件Bean, 註冊到SpringMVC上下文中
- 對ConfigurableWebApplicationContext進行相關配置, 然後調用AbstractApplicationContext#refresh()開始SpringMVC相關配置文件的解析(注:WebApplicationContext與AbstractApplicationContext都繼承於ApplicationContext); Spring在refresh()方法執行結束, 也就是容器加載完成, 發送結束事件, 由SimpleApplicationEventMulticaster激活監聽器SourceFilteringListener的相關方法, 最終會觸發DispatcherServlet的initStrategies(ApplicationContext context)方法, 開始各個組件的註冊
_1. FrameworkServlet的內部監聽器類ContextRefreshListener實現:
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
FrameworkServlet.this.onApplicationEvent(event);
}
}
分析: 當監聽者獲取到事件後調用FrameworkServlet#onApplicationEvent方法進行處理;
_2.AbstractApplicationContext#finishRefresh實現(refresh()方法實現已省略, 需要看該方法內相關邏輯的可以看Spring源碼解析專欄):
protected void finishRefresh() {
//清除上下文級別的資源緩存(例如來自掃描的ASM元數據)
clearResourceCaches();
//爲此上下文初始化生命週期處理器
initLifecycleProcessor();
//首先將刷新傳播到生命週期處理器
getLifecycleProcessor().onRefresh();
/**
* 發佈結束事件(事件對象:ContextRefreshedEvent)
*/
publishEvent(new ContextRefreshedEvent(this));
// Participate in LiveBeansView MBean, if active.
LiveBeansView.registerApplicationContext(this);
}
AbstractApplicationContext#publishEvent(org.springframework.context.ApplicationEvent)
==> AbstractApplicationContext#publishEvent(java.lang.Object, org.springframework.core.ResolvableType)
==> SimpleApplicationEventMulticaster#multicastEvent
==> SimpleApplicationEventMulticaster#invokeListener
==> SimpleApplicationEventMulticaster#doInvokeListener
==> SourceFilteringListener#onApplicationEvent
==> SourceFilteringListener#onApplicationEventInternal實現:
protected void onApplicationEventInternal(ApplicationEvent event) {
if (this.delegate == null) {
throw new IllegalStateException(
"Must specify a delegate object or override the onApplicationEventInternal method");
}
/**
* 通過監聽適配器調用對應監聽器的onApplicationEvent()方法
*/
this.delegate.onApplicationEvent(event);
}
GenericApplicationListenerAdapter#onApplicationEvent實現:
@Override
public void onApplicationEvent(ApplicationEvent event) {
/**
* GenericApplicationListenerAdapter適配器中維護着真正的監聽器
*/
this.delegate.onApplicationEvent(event);
}
FrameworkServlet#onApplicationEvent實現:
public void onApplicationEvent(ContextRefreshedEvent event) {
this.refreshEventReceived = true;
synchronized (this.onRefreshMonitor) {
onRefresh(event.getApplicationContext());
}
}
DispatcherServlet#onRefresh實現:
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
DispatcherServlet#initStrategies實現:
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
分析: 在這個方法中完成了SpringMVC各個組件的註冊, 注意參數是應用上下文ApplicationContext, SpringMVC便可以通過ApplicationContext從容器中獲取到自己的組件對象, 然後進行註冊; 關於各個組件的初始化就比較簡單了, 這裏不再進行解析;
調用鏈如下:
問題: 爲什麼要這麼做呢?
答: 因爲IOC, Spring的控制反轉, SpringMVC將對象初始化相關操作交由Spring容器去管理, 等容器初始化完成後, 直接從容器中獲取需要的組件對象, 再註冊到SpringMVC的中容器中即可
SpringMVC宏觀啓動過程如下:
Tomcat ==> SpringMVC ==> Spring ==> SpringMVC
流程圖(Tomcat啓動流程+SpringMVC啓動流程):
至此, SpringMVC初始化過程解析完成;
相關文章:
SpringMVC源碼解析一(在Spring源碼項目中搭建SpringMVC源碼模塊)
SpringMVC源碼解析三(處理映射器HandlerMapping的解析)
SpringMVC源碼解析四(處理適配器HandlerAdapter的解析)
SpringMVC源碼解析五(HandlerMethod執行過程解析)
SpringMVC源碼解析六(ModelAndView解析)