说明:

  例子就不举了,还是直接进入主题,本文主要是以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. 泛泰A820L (高通公司MSM8660 cpu) 3.4内核CM10.1(Android 4.2.2) 测试版第二版

    欢迎关注泛泰非盈利专业第三方开发团队 VegaDevTeam  (本team 由 syhost suky zhaochengw(z大) xuefy(大星星) tenfar(R大师) loogeo cr ...

  2. 不同版本的SQL Server之间数据导出导入的方法及性能比较

    原文:不同版本的SQL Server之间数据导出导入的方法及性能比较 工作中有段时间常常涉及到不同版本的数据库间导出导入数据的问题,索性整理一下,并简单比较下性能,有所遗漏的方法也欢迎讨论.补充. 0 ...

  3. 【iOS发展-28】制造业UITabBarController标记控制器、定制UITabBarItem文字图像6途径和More评论

    一个.一个简单的制作过程(实际工程中不建议这样的方式,不要只展示所用原理的理解) 在AppDelegate.m在: - (BOOL)application:(UIApplication *)appli ...

  4. 解决Activity启动黑屏和设置android:windowIsTranslucent不兼容activity切换动画的问题

    在该项目中遇到开Activity之后,黑屏问题,解决的办法是在网上通过设置发现theme和style特性,可以实现. http://www.cnblogs.com/sunzn/p/3407078.ht ...

  5. thinkphp 删除该表的最后一行

    问题叙述性说明: 文章连接动态连接表格,因为有被添加.有必须删除.动态添加到表格这似乎有点不合理.它应该只被添加到表格行.而不是增加一个新表格. 发布完整的代码在这里,加入表格新行和删除表格最后一行. ...

  6. 《C++ Primer Plus》学习笔记10

    <C++ Primer Plus>学习笔记10 <<<<<<<<<<<<<<<<<&l ...

  7. 杭州电acm理工大舞台版

    我要参加全国软件设计大赛C/C++学生语言组,前一个假设<C训练和演习,并总结手>没看完,请阅读上述并根据所作的训练,然后做下面的练习. 门户:http://blog.csdn.net/l ...

  8. MySQL Scale Out

    原文:MySQL Scale Out 简介 MySQL复制中较常见的复制架构有“一主一从”.“一主多从”.“双主”.“多级复制”和“多主环形机构”等,见下图: 最常用,也最灵活的就要数“一主多从”复制 ...

  9. google面试题,男孩男女比例?

    Google面试题: 在一个重男轻女的国家里,每一个家庭都想生男孩.假设他们生的孩子是女孩.就再生一个,直到生下的是男孩为止,这种国家.男女比例会是多少? 答案:1:1 分析:  出生男女概率是50% ...

  10. Android中的“再按一次返回键退出程序”实现[转]

    用户退出应用前给出一个提示是很有必要的,因为可能是用户并不真的想退出,而只是一不小心按下了返回键,大部分应用的做法是在应用退出去前给出一个Dialog,我觉得这样不太友好,用户还得移动手指去按dial ...