说明:

  例子就不举了,还是直接进入主题,本文主要是以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. How use Instruments and display the console in Command Lines applications

    I'm using Xcode on OSX to develop command line C applications. I would also like to use Instruments ...

  2. SAP ABAP第一,两,三代出口型BADI实现 解释的概念

    BADI这是第三代用户出口型.让我们来看看如何实现的细节. 一个,用户出口的类型 1,第一代 sap提供了一个空的子程序代码.在这个过程分,用户可以添加自己的代码.为了控制自己的需求.这样的改进是需要 ...

  3. UVa 208 - Firetruck 回溯+剪枝 数据

    题意:构造出一张图,给出一个点,字典序输出所有从1到该点的路径. 裸搜会超时的题目,其实题目的数据特地设计得让图稠密但起点和终点却不相连,所以直接搜索过去会超时. 只要判断下起点和终点能不能相连就行了 ...

  4. 应用spss可靠性分析软件

    问卷调查的可靠性分析 一.概念:     信度是指依据測验工具所得到的结果的一致性或稳定性,反映被測特征真实程度的指标. 一般而言,两次或两个測验的结果愈是一致.则误差愈小,所得的信度愈高,它具有下面 ...

  5. Codeforces Round #271 (Div. 2) E题 Pillars(线段树维护DP)

    题目地址:http://codeforces.com/contest/474/problem/E 第一次遇到这样的用线段树来维护DP的题目.ASC中也遇到过,当时也非常自然的想到了线段树维护DP,可是 ...

  6. 设计模式之享元模式(Flyweight)摘录

    23种GOF设计模式一般分为三大类:创建型模式.结构型模式.行为模式. 创建型模式抽象了实例化过程,它们帮助一个系统独立于怎样创建.组合和表示它的那些对象.一个类创建型模式使用继承改变被实例化的类,而 ...

  7. JMeter怎么在get URL请求、POST请求中添加动态参数用于服务器段安全验证

    从前一个页面(含有服务器段返回给客户端的参数,用于在下一次请求时验证)中添加后置处理器中的Regular Expression Extractor,使用正则表达式对参数进行提取. 在用到这些变量时可以 ...

  8. 大约SQL/NoSQL数据库搜索/思考查询

    转载请注明出处:jiq•钦's technical Blog Hbase特征: 近期在学习Hbase.Hbase基于行健是建立了索引的,查询速度会很快,全然实时. 可是Hbase要基于行健之外的字段进 ...

  9. Xcode-5.1.1更改文件盯作者

    原来的文件默认是用户开机时的username ,网上说什么改通讯录事实上都是不正确的. 1.首先打开偏好设置,选择用户群组 2.进入用户界面 改动全名.此时要求你输入用户的password才干改动us ...

  10. Qt Quick 组件和动态创建的对象具体的解释

    在<Qt Quick 事件处理之信号与槽>一文中介绍自己定义信号时,举了一个简单的样例.定义了一个颜色选择组件,当用户在组建内点击鼠标时,该组件会发出一个携带颜色值的信号,当时我使用 Co ...