Spring之SpringMVC的Controller(源码)分析
说明:
例子就不举了,还是直接进入主题,本文主要是以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的设计实现是采用模板设计模式,这点是很值得我们学习的。这样带来的灵活性和可扩展性是不言而喻的。多学习,学以致用。