說明:
例子就不舉了,還是直接進入主題,本文主要是以SpringMVC的Controller接口為入點,來分析SpringMVC中C的具體實現和處理過程。
1.Controller接口
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處理流程可以自行查閱相關資料。
2..Controller接口繼承實現層次

從上面的UML圖中可以看出繼承實現關系,接下來依次分析每個類的具體作用。
3.實現接口相關類分析
3.1 WebContentGenerator
/*
* 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均是可配置的。
3.2 AbstractController
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的。
3.3 ServletWrappingController實現
這是一個與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的)。
核心方法,實現了父類的抽象方法:
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception {
this.servletInstance.service(request, response);
return null;
}
它會直接將請求交給內部管理那個servlet來處理,具體來說就是調用servlet的service()方法來進行處理。
3.4 ServletForwardingController
和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返回,那么就會再次調用包含處理,不進行其他處理了,否則就進行相應的跳轉。
3.5 ParameterizableViewController
可參數化視圖控制器(ParameterizableViewController),可參數化視圖控制器只是簡單的返回配置的視圖名。這個controller可以選擇直接將一個request請求到JSP頁面。這樣做的好處就是不用向客戶端暴露具體的視圖技術而只是給出了具體的controller URL,而具體的視圖則由視圖解析器來決定。來看看具體的handleRequestInternal方法實現:
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception {
return new ModelAndView(getViewName(), RequestContextUtils.getInputFlashMap(request));
}
3.6 UrlFilenameViewController
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;
}
3.7 MultiActionController
多動作控制器是用於處理多個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()方法。
4.總結
上面只是介紹了部分內容,還有很多需要繼續探索學習,繼續加油。關於Controller的設計實現是采用模板設計模式,這點是很值得我們學習的。這樣帶來的靈活性和可擴展性是不言而喻的。多學習,學以致用。
