接上一篇:Spring MVC源碼解讀一
- DispatchServlet:
源碼位置:
DispatcherServlet是前置控制器喧锦,配置在web.xml文件中的涧狮。攔截匹配的請求洼裤,Servlet攔截匹配規(guī)則要自已定義蓬坡,把攔截下來的請求骏啰,依據(jù)相應(yīng)的規(guī)則分發(fā)到目標(biāo)Controller來處理离斩,是配置spring MVC的第一步
類繼承關(guān)系:
時(shí)序圖:
- servlet初始化鏈路:
通過時(shí)序圖可以看到银舱,HttpServletBean中調(diào)用了initServletBean()方法,通過源代碼可以看到是在HttpServletBean的init()中調(diào)用了initServletBean()
HttpServletBean的init()方法源碼如下捐腿,重寫了HttpServlet的init()方法:
/**
* Map config parameters onto bean properties of this servlet, and
* invoke subclass initialization.
* @throws ServletException if bean properties are invalid (or required
* properties are missing), or if subclass initialization fails.
*/
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// Set bean properties from init parameters.
try {
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
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();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
問題: 1、HttpServlet的init()是什么時(shí)候調(diào)用的柿顶? 解答: 在前面解讀spring mvc在web.xml中的load-on-startup配置時(shí)已經(jīng)回答過了
FrameworkServlet中的initServletBean()源碼
/**
* Overridden method of {@link HttpServletBean}, invoked after any bean properties
* have been set. Creates this servlet's WebApplicationContext.
*/
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
if (this.logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
elapsedTime + " ms");
}
}
可以看到initServletBean()中具體的處理都是通過調(diào)用initWebApplicationContext()方法實(shí)現(xiàn)的茄袖,具體源碼如下:
/**
* Initialize and publish the WebApplicationContext for this servlet.
* <p>Delegates to {@link #createWebApplicationContext} for actual creation
* of the context. Can be overridden in subclasses.
* @return the WebApplicationContext instance
* @see #FrameworkServlet(WebApplicationContext)
* @see #setContextClass
* @see #setContextConfigLocation
*/
protected WebApplicationContext initWebApplicationContext() {
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
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.
onRefresh(wac);
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
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;
}
initWebApplicationContext()正如方法名的直觀含義所表示的,它的作用就是初始化webApplicationContext嘁锯,主要步驟有:
1宪祥、通過servletcontext獲取ContextLoaderListener中初始化的根WebApplicationContext
2、調(diào)用onRefresh(rootContext)來對webApplicationContext進(jìn)行初始化(主要是加載mvc相關(guān)的BeanDefinition到webApplicationContext中)
問題: ContextLoaderListener和FrameworkServlet類中都有initWebApplicationContext()方法家乘,二者有什么區(qū)別蝗羊? 解答: a) ContextLoaderListener是監(jiān)聽Servlet的啟動(dòng)/結(jié)束,在啟動(dòng)時(shí)調(diào)用initWebApplicationContext()初始化web的根應(yīng)用上下文仁锯; b) FrameworkServlet調(diào)用initWebApplicationContext()時(shí)耀找,會(huì)先取ContextLoaderListener初始化的web根應(yīng)用上下文,對其進(jìn)行mvc相關(guān)的初始化:FrameworkServlet的initWebApplicationContext()方法通過調(diào)用DispatcherServlet的onRefresh()和initStrategies()方法實(shí)現(xiàn)對mvc相關(guān)的BeanDefinition加載(具體需要接下來看initStrategies()方法源碼)
由于DispatcherSerlvet的onRefresh直接調(diào)用了initStrategies()方法业崖,我們看initStrategies()的源碼:
/**
* 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);
}
我們逐個(gè)解讀這些初始化方法,他們都是從webApplicationContext中通過getBean()方法獲取BeanDefinition的實(shí)例:
1.以initMultipartResolver為例:
/**
* Initialize the MultipartResolver used by this class.
* <p>If no bean is defined with the given name in the BeanFactory for this namespace,
* no multipart handling is provided.
*/
private void initMultipartResolver(ApplicationContext context) {
try {
this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("Using MultipartResolver [" + this.multipartResolver + "]");
}
}
catch (NoSuchBeanDefinitionException ex) {
// Default is no multipart resolver.
this.multipartResolver = null;
if (logger.isDebugEnabled()) {
logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME +
"': no multipart request handling provided");
}
}
}
initMultipartResolver從webApplicationContext獲取名為“multipartResolver”的Bean定義野芒,如果spring的配置中沒有配置的話,webApplicationContext也獲取不到該Bean定義双炕,則multipartResolver不生效
這些初始化方法分別對應(yīng)spring mvc配置中的這些Bean:
multipartResolver
localeResolver
themeResolver
handlerMapping:指定handlerMapping的處理類
handlerAdapter
handlerExceptionResolver
viewNameTranslator
viewResolver:指定視圖解析類
flashMapManager
例如:viewResolver配置
我們重點(diǎn)看下initHandlerMappings()的源碼狞悲,他是從mvc中重要的url mapping的基礎(chǔ):
/**
* Initialize the HandlerMappings used by this class.
* <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
* we default to BeanNameUrlHandlerMapping.
*/
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
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 {
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) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
}
}
}
這段源碼的主要作用是指定用于handlerMapping的具體處理類,并將其賦值給DispatcherServlet的handlerMappings字段妇斤,可以單步調(diào)試看下HandlerMapping的具體值:
可以看到handleMappings默認(rèn)值有3個(gè): 1.RequestMappingHandlerMapping 2.BeanNameUrlHandlerMapping 3.SimpleUrlHandlerMapping