說明:java
例子就不舉了,仍是直接進入主題,本文主要是以SpringMVC的Controller接口爲入點,來分析SpringMVC中C的具體實現和處理過程。web
public interface Controller { /** * Process the request and return a ModelAndView object which the DispatcherServlet * will render. A {@code null} return value is not an error: It indicates that * this object completed request processing itself, thus there is no ModelAndView * to render. * @param request current HTTP request * @param response current HTTP response * @return a ModelAndView to render, or {@code null} if handled directly * @throws Exception in case of errors */ ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception; }
這個接口只有一個方法handleRequest,留給實現的類實現。它接受DispatcherServlet傳遞的兩個參數request和response,而且返回給DispatcherServlet以ModelAndView ,以便進行視圖解析渲染,關於SpringMVC處理流程能夠自行查閱相關資料。spring
從上面的UML圖中能夠看出繼承實現關係,接下來依次分析每一個類的具體做用。express
/* * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.web.servlet.support; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.util.StringUtils; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.HttpSessionRequiredException; import org.springframework.web.context.request.WebRequest; import org.springframework.web.context.support.WebApplicationObjectSupport; public abstract class WebContentGenerator extends WebApplicationObjectSupport { /** HTTP method "GET" */ public static final String METHOD_GET = "GET"; /** HTTP method "HEAD" */ public static final String METHOD_HEAD = "HEAD"; /** HTTP method "POST" */ public static final String METHOD_POST = "POST"; private static final String HEADER_PRAGMA = "Pragma"; private static final String HEADER_EXPIRES = "Expires"; private static final String HEADER_CACHE_CONTROL = "Cache-Control"; /** Set of supported HTTP methods */ private Set<String> supportedMethods; private boolean requireSession = false; /** Use HTTP 1.0 expires header? */ private boolean useExpiresHeader = true; /** Use HTTP 1.1 cache-control header? */ private boolean useCacheControlHeader = true; /** Use HTTP 1.1 cache-control header value "no-store"? */ private boolean useCacheControlNoStore = true; private int cacheSeconds = -1; private boolean alwaysMustRevalidate = false; /** * Create a new WebContentGenerator which supports * HTTP methods GET, HEAD and POST by default. */ public WebContentGenerator() { this(true); } public WebContentGenerator(boolean restrictDefaultSupportedMethods) { if (restrictDefaultSupportedMethods) { this.supportedMethods = new HashSet<String>(4); this.supportedMethods.add(METHOD_GET); this.supportedMethods.add(METHOD_HEAD); this.supportedMethods.add(METHOD_POST); } } public WebContentGenerator(String... supportedMethods) { this.supportedMethods = new HashSet<String>(Arrays.asList(supportedMethods)); } public final void setSupportedMethods(String[] methods) { if (methods != null) { this.supportedMethods = new HashSet<String>(Arrays.asList(methods)); } else { this.supportedMethods = null; } } /** * Return the HTTP methods that this content generator supports. */ public final String[] getSupportedMethods() { return StringUtils.toStringArray(this.supportedMethods); } /** * Set whether a session should be required to handle requests. */ public final void setRequireSession(boolean requireSession) { this.requireSession = requireSession; } /** * Return whether a session is required to handle requests. */ public final boolean isRequireSession() { return this.requireSession; } public final void setUseExpiresHeader(boolean useExpiresHeader) { this.useExpiresHeader = useExpiresHeader; } /** * Return whether the HTTP 1.0 expires header is used. */ public final boolean isUseExpiresHeader() { return this.useExpiresHeader; } public final void setUseCacheControlHeader(boolean useCacheControlHeader) { this.useCacheControlHeader = useCacheControlHeader; } /** * Return whether the HTTP 1.1 cache-control header is used. */ public final boolean isUseCacheControlHeader() { return this.useCacheControlHeader; } /** * Set whether to use the HTTP 1.1 cache-control header value "no-store" * when preventing caching. Default is "true". */ public final void setUseCacheControlNoStore(boolean useCacheControlNoStore) { this.useCacheControlNoStore = useCacheControlNoStore; } /** * Return whether the HTTP 1.1 cache-control header value "no-store" is used. */ public final boolean isUseCacheControlNoStore() { return this.useCacheControlNoStore; } public void setAlwaysMustRevalidate(boolean mustRevalidate) { this.alwaysMustRevalidate = mustRevalidate; } /** * Return whether 'must-revaliate' is added to every Cache-Control header. */ public boolean isAlwaysMustRevalidate() { return alwaysMustRevalidate; } public final void setCacheSeconds(int seconds) { this.cacheSeconds = seconds; } /** * Return the number of seconds that content is cached. */ public final int getCacheSeconds() { return this.cacheSeconds; } protected final void checkAndPrepare( HttpServletRequest request, HttpServletResponse response, boolean lastModified) throws ServletException { checkAndPrepare(request, response, this.cacheSeconds, lastModified); } protected final void checkAndPrepare( HttpServletRequest request, HttpServletResponse response, int cacheSeconds, boolean lastModified) throws ServletException { // Check whether we should support the request method. String method = request.getMethod(); if (this.supportedMethods != null && !this.supportedMethods.contains(method)) { throw new HttpRequestMethodNotSupportedException( method, StringUtils.toStringArray(this.supportedMethods)); } // Check whether a session is required. if (this.requireSession) { if (request.getSession(false) == null) { throw new HttpSessionRequiredException("Pre-existing session required but none found"); } } // Do declarative cache control. // Revalidate if the controller supports last-modified. applyCacheSeconds(response, cacheSeconds, lastModified); } /** * Prevent the response from being cached. * See {@code http://www.mnot.net/cache_docs}. */ protected final void preventCaching(HttpServletResponse response) { response.setHeader(HEADER_PRAGMA, "no-cache"); if (this.useExpiresHeader) { // HTTP 1.0 header response.setDateHeader(HEADER_EXPIRES, 1L); } if (this.useCacheControlHeader) { // HTTP 1.1 header: "no-cache" is the standard value, // "no-store" is necessary to prevent caching on FireFox. response.setHeader(HEADER_CACHE_CONTROL, "no-cache"); if (this.useCacheControlNoStore) { response.addHeader(HEADER_CACHE_CONTROL, "no-store"); } } } protected final void cacheForSeconds(HttpServletResponse response, int seconds) { cacheForSeconds(response, seconds, false); } protected final void cacheForSeconds(HttpServletResponse response, int seconds, boolean mustRevalidate) { if (this.useExpiresHeader) { // HTTP 1.0 header response.setDateHeader(HEADER_EXPIRES, System.currentTimeMillis() + seconds * 1000L); } if (this.useCacheControlHeader) { // HTTP 1.1 header String headerValue = "max-age=" + seconds; if (mustRevalidate || this.alwaysMustRevalidate) { headerValue += ", must-revalidate"; } response.setHeader(HEADER_CACHE_CONTROL, headerValue); } } protected final void applyCacheSeconds(HttpServletResponse response, int seconds) { applyCacheSeconds(response, seconds, false); } protected final void applyCacheSeconds(HttpServletResponse response, int seconds, boolean mustRevalidate) { if (seconds > 0) { cacheForSeconds(response, seconds, mustRevalidate); } else if (seconds == 0) { preventCaching(response); } // Leave caching to the client otherwise. } }
WebContentGenerator做爲web 內容生成器的超類,能夠自定義處理器(handler),並且提供了HTTP緩存的控制,是否必須有 session 開啓、支持的請求方法類型(GET、HEAD、POST等)。緩存的控制同時提供了針對HTTP1.0和HTTP1.1的支持,代碼不難。其中requireSession、useExpiresHeader、useCacheControlHeader、useCacheControlNoStore、cacheSeconds、alwaysMustRevalidate均是可配置的。apache
AbstractController是一個抽象類,同時繼承了WebContentGenerator類而且實現了Controller接口。是一切具體controller處理類的超類,這個是按照模板設計模式(Template Method design pattern)來設計的。經過controller接口的代碼能夠知道只一個handler方法,因此具體的大量其餘功能確定是在器包括AbstractController在內的子類實現。AbstractController毫無疑問是最重要的一個類了,由於經過繼承關係能夠知道,全部的具體controller實現都是經過繼承AbstractController來完成的。它能夠控制頭部緩存的生成而且決定是否支持了GEP\POST請求方法。來看下具體代碼:設計模式
public abstract class AbstractController extends WebContentGenerator implements Controller { private boolean synchronizeOnSession = false; */ public final void setSynchronizeOnSession(boolean synchronizeOnSession) { this.synchronizeOnSession = synchronizeOnSession; } /** * Return whether controller execution should be synchronized on the session. */ public final boolean isSynchronizeOnSession() { return this.synchronizeOnSession; } public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { // Delegate to WebContentGenerator for checking and preparing. checkAndPrepare(request, response, this instanceof LastModified); // Execute handleRequestInternal in synchronized block if required. if (this.synchronizeOnSession) { HttpSession session = request.getSession(false); if (session != null) { Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) { return handleRequestInternal(request, response); } } } return handleRequestInternal(request, response); } /** * Template method. Subclasses must implement this. * The contract is the same as for {@code handleRequest}. * @see #handleRequest */ protected abstract ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception; }
來看下具體的handleRequest方法的實現,它作了兩件事,第一步經過委託給WebContentGenerator 的 checkAndPrepare()方法 進行緩存控制,而後呢判斷當前會話是否應串行化訪問,最後調用子類的handleRequestInternal方法返回具體的ModelAndView,注意handleRequestInternal方法是abstract的。緩存
這是一個與Servlet相關的控制器,還有一個與Servlet相關的控制器是ServletForwardingController。ServletWrappingController則是將當前應用中的某個 Servlet直接包裝爲一個Controller,全部到ServletWrappingController的請求其實是由它內部所包裝的這個 Servlet來處理的。也就是說內部封裝的Servlet實例對外並不開放,對於程序的其餘範圍是不可見的,適配全部的HTTP請求到內部封裝的Servlet實例進行處理。它一般用於對已存Servlet的邏輯重用上。ServletWrappingController是爲了Struts專門設計的,做用至關於代理Struts的ActionServlet 請注意,Struts有一個特殊的要求,由於它解析web.xml 找到本身的servlet映射。所以,你須要指定的DispatcherServlet做爲 「servletName」在這個控制器servlet的名字,認爲這樣的Struts的DispatcherServlet的映射 (它指的是ActionServlet的)。session
核心方法,實現了父類的抽象方法:app
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception {less
this.servletInstance.service(request, response);
return null;
}
它會直接將請求交給內部管理那個servlet來處理,具體來講就是調用servlet的service()方法來進行處理。
和ServletWrappingController相似,它也是一個Servlet相關的controller,他們都實現將HTTP請求適配到一個已存的Servlet實現。可是,簡單Servlet處理器適配器須要在Web應用程序環境中定義Servlet Bean,而且Servlet沒有機會進行初始化和析構。和ServletWrappingController不一樣的是,ServletForwardingController將全部的HTTP請求轉發給一個在web.xml中定義的Servlet。Web容器會對這個定義在web.xml的標準Servlet進行初始化和析構。來看下核心的handleRequestInternal()方法:
@Override protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { RequestDispatcher rd = getServletContext().getNamedDispatcher(this.servletName); if (rd == null) { throw new ServletException("No servlet with name '" + this.servletName + "' defined in web.xml"); } // If already included, include again, else forward. if (useInclude(request, response)) { rd.include(request, response); if (logger.isDebugEnabled()) { logger.debug("Included servlet [" + this.servletName + "] in ServletForwardingController '" + this.beanName + "'"); } } else { rd.forward(request, response); if (logger.isDebugEnabled()) { logger.debug("Forwarded to servlet [" + this.servletName + "] in ServletForwardingController '" + this.beanName + "'"); } } return null; }
它會首先獲取servlet配置對應的RequestDispatcher,若是獲取的爲空說明servlet未配置就會包servlet異常。若是不爲空則首先判斷請求是否已經處理過,或者response返回,那麼就會再次調用包含處理,不進行其餘處理了,不然就進行相應的跳轉。
可參數化視圖控制器(ParameterizableViewController),可參數化視圖控制器只是簡單的返回配置的視圖名。這個controller能夠選擇直接將一個request請求到JSP頁面。這樣作的好處就是不用向客戶端暴露具體的視圖技術而只是給出了具體的controller URL,而具體的視圖則由視圖解析器來決定。來看看具體的handleRequestInternal方法實現:
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { return new ModelAndView(getViewName(), RequestContextUtils.getInputFlashMap(request)); }
UrlFilenameViewController也是一個視圖解析控制器,不過它是經過將URL翻譯成爲視圖名,不須要功能處理,而且返回。UrlFilenameViewController的handler方法是在父類AbstractUrlViewController中實現的,以下所示:
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) { String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); String viewName = getViewNameForRequest(request); if (logger.isDebugEnabled()) { logger.debug("Returning view name '" + viewName + "' for lookup path [" + lookupPath + "]"); } return new ModelAndView(viewName, RequestContextUtils.getInputFlashMap(request)); } //更加請求對象獲取視圖名字 protected String getViewNameForRequest(HttpServletRequest request) { String uri = extractOperableUrl(request); return getViewNameForUrlPath(uri); } //根據uri地址獲取到視圖名字 protected String getViewNameForUrlPath(String uri) { String viewName = this.viewNameCache.get(uri); if (viewName == null) { viewName = extractViewNameFromUrlPath(uri); viewName = postProcessViewName(viewName); this.viewNameCache.put(uri, viewName); } return viewName; }
多動做控制器是用於處理多個HTTP請求的處理器。它根據HTTP請求URL映射獲得應該調用的處理器方法,經過反射調用處理器方法,而且封裝返回結果做爲模型和視圖返回給簡單控制器適配器。每一個處理器方法能夠有一個對應的最後修改方法,最後修改方法名是處理器方法名加上LastModified後綴構成的。最後修改方法也是經過反射調用而且返回結果的。首先看下具體的handler()方法:
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { try { String methodName = this.methodNameResolver.getHandlerMethodName(request); return invokeNamedMethod(methodName, request, response); } catch (NoSuchRequestHandlingMethodException ex) { return handleNoSuchRequestHandlingMethod(ex, request, response); } } protected final ModelAndView invokeNamedMethod( String methodName, HttpServletRequest request, HttpServletResponse response) throws Exception { Method method = this.handlerMethodMap.get(methodName); if (method == null) { throw new NoSuchRequestHandlingMethodException(methodName, getClass()); } try { Class[] paramTypes = method.getParameterTypes(); List<Object> params = new ArrayList<Object>(4); params.add(request); params.add(response); if (paramTypes.length >= 3 && paramTypes[2].equals(HttpSession.class)) { HttpSession session = request.getSession(false); if (session == null) { throw new HttpSessionRequiredException( "Pre-existing session required for handler method '" + methodName + "'"); } params.add(session); } // If last parameter isn't of HttpSession type, it's a command. if (paramTypes.length >= 3 && !paramTypes[paramTypes.length - 1].equals(HttpSession.class)) { Object command = newCommandObject(paramTypes[paramTypes.length - 1]); params.add(command); bind(request, command); } Object returnValue = method.invoke(this.delegate, params.toArray(new Object[params.size()])); return massageReturnValueIfNecessary(returnValue); } catch (InvocationTargetException ex) { // The handler method threw an exception. return handleException(request, response, ex.getTargetException()); } catch (Exception ex) { // The binding process threw an exception. return handleException(request, response, ex); } } protected ModelAndView handleNoSuchRequestHandlingMethod( NoSuchRequestHandlingMethodException ex, HttpServletRequest request, HttpServletResponse response) throws Exception { pageNotFoundLogger.warn(ex.getMessage()); response.sendError(HttpServletResponse.SC_NOT_FOUND); return null; }
經過handler方法的實現能夠知道,它首先根據反射獲取到了處理的方法名字,而後調用它。固然了若是沒有這樣的方法存在就會拋出異常見handleNoSuchRequestHandlingMethod()方法。
上面只是介紹了部份內容,還有不少須要繼續探索學習,繼續加油。關於Controller的設計實現是採用模板設計模式,這點是很值得咱們學習的。這樣帶來的靈活性和可擴展性是不言而喻的。多學習,學以至用。