重點:java
1. Spring MVC中的dispatcherServletweb
2. 8個組件(handleMapping & handleAdaper & viewResolver)spring
3. MVC的關係,理解URL與handler的對應關係存放,handlerExecutionChain對象,modelAndMap對象數組
---------------------------- 原文 -----------------------------spring-mvc
地址:https://my.oschina.net/bosscheng/blog/129282session
在一個工程中若是想要使用 spring MVC的話,只須要兩個步驟mvc
須要配置一個org.springframework.web.servlet.DispatcherServlet的servlet。app
在這個配置文件裏面咱們只須要擴展一個路徑映射關係,定義一個視圖解析器,再定義一個業務邏輯的處理流程規則。框架
這樣就能夠搞定一個最基本的Spring MVC的應用了。jsp
對於DispatcherServlet初始化的時候初始了哪些東西,這些能夠在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) { //初始化MultipartResolver,主要是處理文件上傳服務。 initMultipartResolver(context); //用於處理應用的國際化問題 initLocaleResolver(context); //用於定義一個主題 initThemeResolver(context); //用於定義用戶設置的請求映射關係 initHandlerMappings(context); //用於根據Handler的類型定義不一樣的處理規則 initHandlerAdapters(context); //當Handler處理錯誤的時候,經過這個handler來作統一的處理 initHandlerExceptionResolvers(context); //將指定的ViewName按照定義的RequestToViewNameTranslator替換成想要的格式。 initRequestToViewNameTranslator(context); //用於將view解析成頁面 initViewResolvers(context); //用於映射flash管理的。 initFlashMapManager(context); }
小結: 對於spring MVC框架中,有三個組件是用戶必須定義和擴展的:
看看DispatcherServlet啓用的時候作了哪些工做?
/** * 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, this.environment)); initBeanWrapper(bw); bw.setPropertyValues(pvs, true); } catch (BeansException ex) { 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"); } }
這樣:spring MVC的初始化工做就完成了。這樣就能夠接受你的http請求了。
Spring MVC 的Control主要是由HandlerMapping(interface)和HandlerAdapter(interface)兩個組件提供。
對於HandlerMapping:主要負責映射用戶的URL和對應的處理類,HandlerMapping並無規定這個URL與應用的處理類如何映射,HandlerMapping接口中只定義了根據一個URL必須返回一個由HandlerExceptionChain表明的處理鏈,咱們能夠在這個處理鏈中添加任意的HandlerAdapter實例來處理這個URL對應的請求,這個設計思路和Servlet規範中的Filter處理是相似的。
/** * Return a handler and any interceptors for this request. The choice may be made * on request URL, session state, or any factor the implementing class chooses. * <p>The returned HandlerExecutionChain contains a handler Object, rather than * even a tag interface, so that handlers are not constrained in any way. * For example, a HandlerAdapter could be written to allow another framework's * handler objects to be used. * <p>Returns <code>null</code> if no match was found. This is not an error. * The DispatcherServlet will query all registered HandlerMapping beans to find * a match, and only decide there is an error if none can find a handler. * @param request current HTTP request * @return a HandlerExecutionChain instance containing handler object and * any interceptors, or <code>null</code> if no mapping found * @throws Exception if there is an internal error */ HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
------------------------------------邪惡的分割線-------------------------------------------
Spring MVC自己也提供了不少HandlerMapping 的實現,默認使用的是BeanNameUrlHandlerMapping,也能夠根據Bean的name屬性映射到URL中。
public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping { /** * Checks name and aliases of the given bean for URLs, starting with "/". */ @Override protected String[] determineUrlsForHandler(String beanName) { List<String> urls = new ArrayList<String>(); if (beanName.startsWith("/")) { urls.add(beanName); } String[] aliases = getApplicationContext().getAliases(beanName); for (String alias : aliases) { if (alias.startsWith("/")) { urls.add(alias); } } return StringUtils.toStringArray(urls); } }
對於HandlerMapping,能夠幫助咱們管理URL和處理類的映射關係,簡單的說就是能夠幫助咱們將一個或者多個URL映射到一個或者多個spring Bean中。
下面總結下spring MVC是如何將請求的URL映射到咱們定義的bean中的。
對於HandlerMapping是如何初始化的。spring MVC提供了一個HandlerMapping的抽象類 AbstractHandlerMapping。
public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport implements HandlerMapping, Ordered { }
能夠經過讓HandlerMapping設置setOrder方法提升優先級和經過覆蓋initApplicationContext方法實現初始化的工做。
@Override protected void initApplicationContext() throws BeansException { extendInterceptors(this.interceptors); detectMappedInterceptors(this.mappedInterceptors); initInterceptors(); }
對於SimpleUrlHandlerMapping類是如何初始化的:
public final void setApplicationContext(ApplicationContext context) throws BeansException { if (context == null && !isContextRequired()) { // Reset internal context state. this.applicationContext = null; this.messageSourceAccessor = null; } else if (this.applicationContext == null) { // Initialize with passed-in context. if (!requiredContextClass().isInstance(context)) { throw new ApplicationContextException( "Invalid application context: needs to be of type [" + requiredContextClass().getName() + "]"); } this.applicationContext = context; this.messageSourceAccessor = new MessageSourceAccessor(context); initApplicationContext(context); } else { // Ignore reinitialization if same context passed in. if (this.applicationContext != context) { throw new ApplicationContextException( "Cannot reinitialize with different application context: current one is [" + this.applicationContext + "], passed-in one is [" + context + "]"); } } }
/** * Calls the {@link #registerHandlers} method in addition to the * superclass's initialization. */ @Override public void initApplicationContext() throws BeansException { super.initApplicationContext(); registerHandlers(this.urlMap); }
/** * Initializes the interceptors. * @see #extendInterceptors(java.util.List) * @see #initInterceptors() */ @Override protected void initApplicationContext() throws BeansException { extendInterceptors(this.interceptors); detectMappedInterceptors(this.mappedInterceptors); initInterceptors(); }
初始化initInterceptors()方法:將SimpleUrlHandlerMapping中定義的interceptors包裝成handlerInterceptor對象保存在adaptedInterceptors數組中。
在方法registerHandlers中,調用了AbstractUrlHandlerMapping的registerHandler(url, handler)方法;
在方法registerHandler(url, handler)中,將在SimpleUrlHandlerMapping中定義的mappings註冊到handlerMap集合中。
protected void registerHandlers(Map<String, Object> urlMap) throws BeansException { if (urlMap.isEmpty()) { logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping"); } else { for (Map.Entry<String, Object> entry : urlMap.entrySet()) { String url = entry.getKey(); Object handler = entry.getValue(); // Prepend with slash if not already present. if (!url.startsWith("/")) { url = "/" + url; } // Remove whitespace from handler bean name. if (handler instanceof String) { handler = ((String) handler).trim(); } registerHandler(url, handler); } } }
在registerHandler方法中;
/** * Register the specified handler for the given URL path. * @param urlPath the URL the bean should be mapped to * @param handler the handler instance or handler bean name String * (a bean name will automatically be resolved into the corresponding handler bean) * @throws BeansException if the handler couldn't be registered * @throws IllegalStateException if there is a conflicting handler registered */
----------------------------------邪惡的分割線-----------------------------------
小結: HandlerMapping初始化工做完成的兩個最重要的工做就是:
HandlerMapping 能夠完成URL與Handler的映射關係,那麼HandlerAdapter就能夠幫助自定義各類handler了。由於SpringMVC首先幫助咱們把特別的URL對應到一個Handler,那麼這個Handler一定要符合某種規則,最多見的方法就是咱們的全部handler都繼承某個接口,而後SpringMVC 天然就調用這個接口中定義的特性方法。
對於spring MVC提供了三種典型的handlerAdapter實現類。
對於handlerAdapter的初始化沒有什麼特別之處,只是簡單的建立一個handlerAdapter對象,將這個對象保存在DispatcherServlet的HandlerAdapters集合中。當Spring MVC將某個URL對應到某個Handler時候,在handlerAdapters集合中查詢那個handlerAdapter對象supports這個Handler,handlerAdapter對象將會被返回,用了調用這個handlerAdapter接口對應的方法。
------------------------------------邪惡的分割線-------------------------------------------
整個Spring MVC的調用是從DispatcherServlet的doService方法開始的,在doService方法中會將ApplicationContext、localeResolver、themeResolver等對象添加到request中便於在後面使用,接着就調用doDispatch方法,這個方法是主要的處理用戶請求的地方。
對於control的處理關鍵就是:DispatcherServlet的handlerMappings集合中根據請求的URL匹配每個handlerMapping對象中的某個handler,匹配成功以後將會返回這個handler的處理鏈接handlerExecutionChain對象。而這個handlerExecutionChain對象中將會包含用戶自定義的多個handlerInterceptor對象。
/** * Return the HandlerExecutionChain for this request. * <p>Tries all handler mappings in order. * @param request current HTTP request * @return the HandlerExecutionChain, or <code>null</code> if no handler could be found */ protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { for (HandlerMapping hm : this.handlerMappings) { if (logger.isTraceEnabled()) { logger.trace( "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'"); } HandlerExecutionChain handler = hm.getHandler(request); if (handler != null) { return handler; } } return null; }
而對於handlerInterceptor接口中定義的三個方法中,preHandler和postHandler分別在handler的執行前和執行後執行,afterCompletion在view渲染完成、在DispatcherServlet返回以前執行。
PS:這麼咱們須要注意的是:當preHandler返回false時,當前的請求將在執行完afterCompletion後直接返回,handler也將不會執行。
在類HandlerExecutionChain中的getHandler()方法是返回object對象的;
/** * Return the handler object to execute. * @return the handler object */ public Object getHandler() { return this.handler; }
這裏的handler是沒有類型的,handler的類型是由handlerAdapter決定的。dispatcherServlet會根據handler對象在其handlerAdapters集合中匹配哪一個HandlerAdapter實例支持該對象。接下來去執行handler對象的相應方法了,若是該handler對象的相應方法返回一個ModelAndView對象接下來就是去執行View渲染了。
/** * Return the handler object to execute. * @return the handler object */ public Object getHandler() { return this.handler; }
若是handler兌現返回了ModelAndView對象,那麼說明Handler須要傳一個Model實例給view去渲染模版。除了渲染頁面須要model實例,在業務邏輯層一般也有Model實例。
ModelAndView對象是鏈接業務邏輯層與view展現層的橋樑,對spring MVC來講它也是鏈接Handler與view的橋樑。ModelAndView對象顧名思義會持有一個ModelMap對象和一個View對象或者View的名稱。ModelMap對象就是執行模版渲染時候所須要的變量對應的實例,如jsp的經過request.getAttribute(String)獲取的JSTL標籤名對應的對象。velocity中context.get(String)獲取$foo對應的變量實例。
public class ModelAndView { /** View instance or view name String */ private Object view; /** Model Map */ private ModelMap model; /** Indicates whether or not this instance has been cleared with a call to {@link #clear()} */ private boolean cleared = false; ..... }
ModelMap其實也是一個Map,Handler中將模版中須要的對象存在這個Map中,而後傳遞到view對應的ViewResolver中。
public interface ViewResolver { View resolveViewName(String viewName, Locale locale) throws Exception; }
不一樣的ViewResolver會對這個Map中的對象有不一樣的處理方式;
-----------------------邪惡的分割線-----------------------------------------------
在spring MVC中,view模塊須要兩個組件來支持:RequestToViewNameTranslator和ViewResolver
public interface RequestToViewNameTranslator { /** * Translate the given {@link HttpServletRequest} into a view name. * @param request the incoming {@link HttpServletRequest} providing * the context from which a view name is to be resolved * @return the view name (or <code>null</code> if no default found) * @throws Exception if view name translation fails */ String getViewName(HttpServletRequest request) throws Exception; }
對於 ViewResolver,前面有寫到了,就不寫了;
RequestToViewNameTranslator:主要支持用戶自定義對viewName的解析,如將請求的ViewName加上前綴或者後綴,或者替換成特定的字符串等。
ViewResolver:主要是根據用戶請求的viewName建立適合的模版引擎來渲染最終的頁面,ViewResolver會根據viewName建立一個view對象,調用view對象的Void render方法渲染出頁面;
public interface View { void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception; }
下面來總結下 Spring MVC解析View的邏輯:
/** * Translate the supplied request into a default view name. * @param request current HTTP servlet request * @return the view name (or <code>null</code> if no default found) * @throws Exception if view name translation failed */ protected String getDefaultViewName(HttpServletRequest request) throws Exception { return this.viewNameTranslator.getViewName(request); }
public interface RequestToViewNameTranslator { /** * Translate the given {@link HttpServletRequest} into a view name. * @param request the incoming {@link HttpServletRequest} providing * the context from which a view name is to be resolved * @return the view name (or <code>null</code> if no default found) * @throws Exception if view name translation fails */ String getViewName(HttpServletRequest request) throws Exception; }
Locale resolveLocale(HttpServletRequest request);
View resolveViewName(String viewName, Locale locale) throws Exception;