整個spring mvc的架構以下圖所示:html
上篇文件講解了DispatcherServlet經過request獲取控制器Controller的過程,如今來說解DispatcherServletDispatcherServlet的第二步:經過request從Controller獲取ModelAndView。java
DispatcherServlet調用Controller的過程:web
DispatcherServlet.javaspring
doService()--->doDispatch()--->handlerAdapter的handle()方法api
try {// Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); } finally { if (asyncManager.isConcurrentHandlingStarted()) { return; } }
最經常使用的實現了HandlerAdapter接口是SimpleControllerHandlerAdapter類,該類將安全
兩個不兼容的類:DispatcherServlet 和Controller 類鏈接到一塊兒。
Adapter to use the plain {@link Controller} workflow interface with the generic {@link org.springframework.web.servlet.DispatcherServlet}. Supports handlers that implement the {@link LastModified} interface. <p>This is an SPI class, not used directly by application code.
類之間的轉換代碼以下所示,調用了Controller類的handleRequest()方法來處理請求:session
@Override public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return ((Controller) handler).handleRequest(request, response); }
重量級人物控制器Controller開始閃亮登場,Controller是一個基本的接口,它接受request和response,從這點上來講,它有點像servlet,但不一樣之處在於它在mvc模式流程中起做用,它和struts中的Action做用相似。繼承該接口的控制器或者類應該保證是線程安全的,可複用的,可以在一個應用生命週期中處理大量的request。爲了使Controller的配置更便捷,一般使用javaBeans來繼承Controller。架構
/** * Base Controller interface, representing a component that receives * {@code HttpServletRequest} and {@code HttpServletResponse} * instances just like a {@code HttpServlet} but is able to * participate in an MVC workflow. Controllers are comparable to the * notion of a Struts {@code Action}. * * <p>Any implementation of the Controller interface should be a * <i>reusable, thread-safe</i> class, capable of handling multiple * HTTP requests throughout the lifecycle of an application. To be able to * configure a Controller easily, Controller implementations are encouraged * to be (and usually are) JavaBeans. * </p> * * <p><b><a name="workflow">Workflow</a></b></p> * * <p> * After a <cde>DispatcherServlet</code> has received a request and has * done its work to resolve locales, themes and suchlike, it then tries * to resolve a Controller, using a * {@link org.springframework.web.servlet.HandlerMapping HandlerMapping}. * When a Controller has been found to handle the request, the * {@link #handleRequest(HttpServletRequest, HttpServletResponse) handleRequest} * method of the located Controller will be invoked; the located Controller * is then responsible for handling the actual request and - if applicable - * returning an appropriate * {@link org.springframework.web.servlet.ModelAndView ModelAndView}. * So actually, this method is the main entrypoint for the * {@link org.springframework.web.servlet.DispatcherServlet DispatcherServlet} * which delegates requests to controllers.</p> * * <p>So basically any <i>direct</i> implementation of the Controller interface * just handles HttpServletRequests and should return a ModelAndView, to be further * interpreted by the DispatcherServlet. Any additional functionality such as * optional validation, form handling, etc should be obtained through extending * one of the abstract controller classes mentioned above.</p> * * <p><b>Notes on design and testing</b></p> * * <p>The Controller interface is explicitly designed to operate on HttpServletRequest * and HttpServletResponse objects, just like an HttpServlet. It does not aim to * decouple itself from the Servlet API, in contrast to, for example, WebWork, JSF or Tapestry. * Instead, the full power of the Servlet API is available, allowing Controllers to be * general-purpose: a Controller is able to not only handle web user interface * requests but also to process remoting protocols or to generate reports on demand.</p> * * <p>Controllers can easily be tested by passing in mock objects for the * HttpServletRequest and HttpServletResponse objects as parameters to the * {@link #handleRequest(HttpServletRequest, HttpServletResponse) handleRequest} * method. As a convenience, Spring ships with a set of Servlet API mocks * that are suitable for testing any kind of web components, but are particularly * suitable for testing Spring web controllers. In contrast to a Struts Action, * there is no need to mock the ActionServlet or any other infrastructure; * HttpServletRequest and HttpServletResponse are sufficient.</p> * * <p>If Controllers need to be aware of specific environment references, they can * choose to implement specific awareness interfaces, just like any other bean in a * Spring (web) application context can do, for example:</p> * <ul> * <li>{@code org.springframework.context.ApplicationContextAware}</li> * <li>{@code org.springframework.context.ResourceLoaderAware}</li> * <li>{@code org.springframework.web.context.ServletContextAware}</li> * </ul> * * <p>Such environment references can easily be passed in testing environments, * through the corresponding setters defined in the respective awareness interfaces. * In general, it is recommended to keep the dependencies as minimal as possible: * for example, if all you need is resource loading, implement ResourceLoaderAware only. * Alternatively, derive from the WebApplicationObjectSupport base class, which gives * you all those references through convenient accessors - but requires an * ApplicationContext reference on initialization. * * <p>Controllers can optionally implement the {@link LastModified} interface.
*/
Controller的handleRequest()方法處理請求,並返回ModelAndView給DispatcherServlet去渲染render。mvc
Controller接口的抽象實現類爲:AbstractController,它經過互斥鎖(mutex)來保證線程安全。app
/** * Set if controller execution should be synchronized on the session, * to serialize parallel invocations from the same client. * <p>More specifically, the execution of the {@code handleRequestInternal} * method will get synchronized if this flag is "true". The best available * session mutex will be used for the synchronization; ideally, this will * be a mutex exposed by HttpSessionMutexListener. * <p>The session mutex is guaranteed to be the same object during * the entire lifetime of the session, available under the key defined * by the {@code SESSION_MUTEX_ATTRIBUTE} constant. It serves as a * safe reference to synchronize on for locking on the current session. * <p>In many cases, the HttpSession reference itself is a safe mutex * as well, since it will always be the same object reference for the * same active logical session. However, this is not guaranteed across * different servlet containers; the only 100% safe way is a session mutex. * @see AbstractController#handleRequestInternal * @see org.springframework.web.util.HttpSessionMutexListener * @see org.springframework.web.util.WebUtils#getSessionMutex(javax.servlet.http.HttpSession) */
線程安全實現:
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); }
handleRequestInternal()爲抽象方法,留待具體實現類來實現。它的直接子類有:
簡單Controller實現
在web.xml中有時候定義節點<welcome-list>index.html</welcome-list>等,這種簡單的請,Controller是如何實現的呢?咱們來看看UrlFilenameViewController,它是Controller的一個間接實現,實現了AbstractUrlViewController。它把url的虛擬路徑轉換成一個view的名字,而後返回這個view。
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)); }
複雜Controller實現
一個能夠處理多種請求類型的Controller實現:MultiActionController。它相似於struts中的DispatcherAction,但更靈活,並且支持代理。
/** * {@link org.springframework.web.servlet.mvc.Controller Controller} * implementation that allows multiple request types to be handled by the same * class. Subclasses of this class can handle several different types of * request with methods of the form * * <pre class="code">public (ModelAndView | Map | String | void) actionName(HttpServletRequest request, HttpServletResponse response, [,HttpSession] [,AnyObject]);</pre> * * A Map return value indicates a model that is supposed to be passed to a default view * (determined through a {@link org.springframework.web.servlet.RequestToViewNameTranslator}). * A String return value indicates the name of a view to be rendered without a specific model. * * <p>May take a third parameter (of type {@link HttpSession}) in which an * existing session will be required, or a third parameter of an arbitrary * class that gets treated as the command (that is, an instance of the class * gets created, and request parameters get bound to it) * * <p>These methods can throw any kind of exception, but should only let * propagate those that they consider fatal, or which their class or superclass * is prepared to catch by implementing an exception handler. * * <p>When returning just a {@link Map} instance view name translation will be * used to generate the view name. The configured * {@link org.springframework.web.servlet.RequestToViewNameTranslator} will be * used to determine the view name. * * <p>When returning {@code void} a return value of {@code null} is * assumed meaning that the handler method is responsible for writing the * response directly to the supplied {@link HttpServletResponse}. * * <p>This model allows for rapid coding, but loses the advantage of * compile-time checking. It is similar to a Struts {@code DispatchAction}, * but more sophisticated. Also supports delegation to another object. * * <p>An implementation of the {@link MethodNameResolver} interface defined in * this package should return a method name for a given request, based on any * aspect of the request, such as its URL or an "action" parameter. The actual * strategy can be configured via the "methodNameResolver" bean property, for * each {@code MultiActionController}. * * <p>The default {@code MethodNameResolver} is * {@link InternalPathMethodNameResolver}; further included strategies are * {@link PropertiesMethodNameResolver} and {@link ParameterMethodNameResolver}. * * <p>Subclasses can implement custom exception handler methods with names such * as: * * <pre class="code">public ModelAndView anyMeaningfulName(HttpServletRequest request, HttpServletResponse response, ExceptionClass exception);</pre> * * The third parameter can be any subclass or {@link Exception} or * {@link RuntimeException}. * * <p>There can also be an optional {@code xxxLastModified} method for * handlers, of signature: * * <pre class="code">public long anyMeaningfulNameLastModified(HttpServletRequest request)</pre> * * If such a method is present, it will be invoked. Default return from * {@code getLastModified} is -1, meaning that the content must always be * regenerated. * * <p><b>Note that all handler methods need to be public and that * method overloading is <i>not</i> allowed.</b> * * <p>See also the description of the workflow performed by * {@link AbstractController the superclass} (in that section of the class * level Javadoc entitled 'workflow'). * * <p><b>Note:</b> For maximum data binding flexibility, consider direct usage of a * {@link ServletRequestDataBinder} in your controller method, instead of relying * on a declared command argument. This allows for full control over the entire * binder setup and usage, including the invocation of {@link Validator Validators} * and the subsequent evaluation of binding/validation errors.*/
根據方法名決定處理的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); }
處理返回結果,要麼返回null要麼返回ModelAndView實例。當返回一個Map類型時,ModelAndView實例包裝的Map類型。
/** * Processes the return value of a handler method to ensure that it either returns * {@code null} or an instance of {@link ModelAndView}. When returning a {@link Map}, * the {@link Map} instance is wrapped in a new {@link ModelAndView} instance. */ @SuppressWarnings("unchecked") private ModelAndView massageReturnValueIfNecessary(Object returnValue) { if (returnValue instanceof ModelAndView) { return (ModelAndView) returnValue; } else if (returnValue instanceof Map) { return new ModelAndView().addAllObjects((Map<String, ?>) returnValue); } else if (returnValue instanceof String) { return new ModelAndView((String) returnValue); } else { // Either returned null or was 'void' return. // We'll assume that the handle method already wrote the response. return null; } }
小結:
DispatcherServlet接受一個請求,而後解析完locales, themes等後,經過HadlerMapping解析控制器Controller去處理請求。
找到Controller後,出發當前controller的handleRequest()方法,此controller負責真正處理請求,而後一個ModelAndView實例。
DispatcherServlet 代理此Controller,接收返回結果,而後進行渲染。