说明:

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

Spring之SpringMVC的Controller(源码)分析的更多相关文章

  1. Spring之SpringMVC的RequestToViewNameTranslator(源码)分析

    前言 SpringMVC如果在处理业务的过程中发生了异常,这个时候是没有一个完整的ModelAndView对象返回的,它应该是怎么样处理呢?或者说应该怎么去获取一个视图然后去展示呢.下面就是要讲的Re ...

  2. Spring之SpringMVC的MethodNameResolver(源码)分析

    前言 在介绍SpringMVC  的Controller的具体实现中,我们讲到了MultiActionController.在获取处理请求对于的方法的时候我们用到了下面的代码,来自于MultiActi ...

  3. Spring Boot 揭秘与实战 源码分析 - 工作原理剖析

    文章目录 1. EnableAutoConfiguration 帮助我们做了什么 2. 配置参数类 – FreeMarkerProperties 3. 自动配置类 – FreeMarkerAutoCo ...

  4. Spring Boot 揭秘与实战 源码分析 - 开箱即用,内藏玄机

    文章目录 1. 开箱即用,内藏玄机 2. 总结 3. 源代码 Spring Boot提供了很多”开箱即用“的依赖模块,那么,Spring Boot 如何巧妙的做到开箱即用,自动配置的呢? 开箱即用,内 ...

  5. Spring Environment(二)源码分析

    Spring Environment(二)源码分析 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) Spring Envi ...

  6. kubernetes垃圾回收器GarbageCollector Controller源码分析(二)

    kubernetes版本:1.13.2 接上一节:kubernetes垃圾回收器GarbageCollector Controller源码分析(一) 主要步骤 GarbageCollector Con ...

  7. SpringMVC由浅入深day01_6源码分析(了解)

    6 源码分析(了解) 通过前端控制器源码分析springmvc的执行过程. 入口 第一步:前端控制器接收请求 调用doDiapatch 第二步:前端控制器调用处理器映射器查找 Handler 第三步: ...

  8. Spring Security(四) —— 核心过滤器源码分析

    摘要: 原创出处 https://www.cnkirito.moe/spring-security-4/ 「老徐」欢迎转载,保留摘要,谢谢! 4 过滤器详解 前面的部分,我们关注了Spring Sec ...

  9. Spring Cloud Eureka服务注册源码分析

    Eureka是怎么work的 那eureka client如何将本地服务的注册信息发送到远端的注册服务器eureka server上.通过下面的源码分析,看出Eureka Client的定时任务调用E ...

随机推荐

  1. 随着MapReduce job实现去加重,多种输出文件夹

    总结以往的工作中遇到的一个问题. 背景: 操作和维护与scribe从apacheserver一再被推到日志记录,所以在这里ETL处理正在进行的重.有根据业务的输出类型是用于多文件夹一个需求.方便挂分区 ...

  2. A*寻路算法lua实现

    前言:并在相当长的时间没有写blog该,我觉得有点"颓废"该,最近认识到各种同行,也刚刚大学毕业,我认为他们是优秀的.认识到与自己的间隙,有点自愧不如.我没有写blog当然,部分原 ...

  3. NYoj-Binary String Matching-KMP算法

    Binary String Matching 时间限制:3000 ms  |  内存限制:65535 KB 难度:3 描写叙述 Given two strings A and B, whose alp ...

  4. 【JAVA】【NIO】5、Java NIO Scatter / Gather

    标题手段Java NIO该分散体浓缩 Java NIO内置支持分散与收集.的概念主要用于信道分散聚集的读写. 读出的分散体的一个通道被读多个数据buffer在.因此.数据分散到多个buffer中. 对 ...

  5. ORDER BY RAND()

    大概是因为需要研究了一下MYSQL随机样本实现.例如:离tablename表随机抽取了创纪录,我们一般的写法是:SELECT * FROM tablename ORDER BY RAND() LIMI ...

  6. 2014年辛星Javascript解读第四节 流程控制语句

    上一节我们介绍了函数,本小节我们介绍一下流程控制语句,对于不论什么一门编程语言来说,流程控制都是很重要的,也就是我们常说的顺序结构.选择结构和循环结构. ************选择结构******* ...

  7. Windows 2008 卸载 IIS7 批处理

    @echo offcolor 0aecho 正在卸载IIS功能,这可能需要几分钟时间...start /w pkgmgr /uu:IIS-WebServerRole;WAS-WindowsActiva ...

  8. C#开发人员能够可视化操作windows服务

    使用C#开发自己的定义windows服务是一个很简单的事.因此,当.我们需要发展自己windows它的服务.这是当我们需要有定期的计算机或运行某些程序的时候,我们开发.在这里,我有WCF监听案例,因为 ...

  9. Maven+struts2+spring4+hibernate4的环境搭建

    搭建Maven+struts2+spring4+hibernate4其实并不难!但开始弄的时候还是费了我好大的力气,老是出现这样那样的错误!好了,废话不多说,开始搭建开发环境. 一.Myeclipse ...

  10. C#二维码生成

    C#二维码生成,这里使用开源的ThoughtWorks.QRCode.dll库. 1.下载ThoughtWorks.QRCode.dll库文件,并引用到项目中. 2.创建QRCodeHandler.c ...