Servlet 与 MVC

什么是Spring MVC 其实应该说 什么是 MVC ?

Model 数据,View 视图,Controller 控制器。啪!三个东西合在一起,MVC就出来了。

这么简单? 没错,其实就是这么简单。

当然如果你对MVC不太熟悉的话还是乖乖往下看吧。

其实MVC就是处理Web请求的一种框架模式,我们思考一下用户请求的流程:

  1. 输入url
  2. 发送请求
  3. 接受响应

对于用户来说其实也就这三个步骤,但是对于服务端来说需要做很多,这里我画了一张图供大家理解。这里其实忽略了很多 Tomcat 本身已经为我们做的,而且 Tomcat 并不仅仅只有 Host,Context。

用户请求

我来解释一下,用户发送请求的 url 其实对应着很多东西。

比如说 localhost ,当然这个就是 ip 地址。这个 ip 地址对应着 Tomcat 里面的 Host (站点) 层。

Context 代表着一个 web 应用,还记得当初写 Servlet 项目的时候有一个 webapp 文件夹(里面还有个WEB-INF,最里面是web.xml)吗?也可以理解为当初写的 servlet 项目就是一个 web 应用,而用户通过 ip 地址的端口映射去找到了这个应用。

这时候我们已经通过 ip 和端口寻找到了指定的 web 应用,我们知道一个 web 应用中存在多个 servlet ,而我们如何去寻找每个请求对应的 servlet 呢? 答案还是 url ,我们通过后面的 /news 去web.xml里面寻找已经注册到应用中的 Servlet 类。

具体我再配合图中解释一下: 找到了指定的 web 应用之后,通过请求的路径 /news 去 web.xml 中寻找是否有对应的 标签,其中这个标签的子标签 标签的值需要匹配到请求的路径,这个时候 标签的值为 /news 正好匹配到了,所以我们获取了上面的标签的值然后再寻找是否有 标签的子标签 和这个值相等,如果有则获取到底下的 对应的类 并通过这个类去解析请求

总结来说就是通过 url 从 web.xml 文件中寻找到匹配的 servlet 的类

其实这就是原生的 servlet ,那么 MVC 的影子在哪呢?

别急,你要记住的是 MVC 就是对 Servlet 的封装,想要理解 MVC 就必须理解 Servlet 和 MVC 与 Servlet 的关系

SpringMVC中的DispatcherServlet

DispatcherServlet的继承结构

有没有发现这个 DispatcherServlet 其实就是一个 Servlet。也就是说 Spring MVC中最核心的部分其实就是一个 Servlet 。

我来简单解释一下相应的部分(先简单了解一下)

  • FrameworkServlet : 是 DispatcherServlet 的一个抽象父类。其中提供了加载某个对应的 web 应用程序环境的功能,还有将 GET、POST、DELETE、PUT等方法统一交给 DispatcherServlet 处理。
  • Servlet : 一个规范,用来解决 HTTP服务器和业务代码之间的耦合问题
  • GenericServlet : 提升了 ServletConfig 的作用域,在init(servletConfig)方法中调用了init()无参方法,子类可以重写这个无参初始化方法来做一些初始化自定义工作(后面分析源码中会讲到)。
  • HttpServletBean : 可以将 Servlet 配置信息作为 Bean 的属性 自动赋值给 Servlet 的属性。
  • DispatcherServlet :整个继承链中的最后一个也是最重要的一个类,是SpringMVC 实现的核心类。MVC 通过在 web.xml 中配置 DispatcherServlet 来拦截所有请求,然后通过这个 DispatcherServlet 来进行请求的分发,并调用相应的处理器去处理请求和响应消息。

有没有想起来在 SSM 框架配置的时候在 web.xml 中的配置

  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <!-- 把所以请求都交给DispatcherServlet处理-->
  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <!-- 拦截所有 -->
    <url-pattern>/</url-pattern>
  </servlet-mapping>

复制代码

好的,现在我们知道了 springMVC 中使用了一个 DispatcherServlet 去处理所有请求,而我们知道真正处理的肯定不是 DispatcherServlet ,而是具体我们在 Controller 层中写的带有 @Controller @RequestMapping 注解的类和底下的方法。DispatcherServlet 只是一个为我们分发请求到具体处理器的一个分发Servlet

那么,这个 DispatcherServlet 具体怎么工作的呢?它是如何分发请求的呢? 且听我慢慢道来。

和 DispatcherServlet 一起工作的一些组件

首先我先将这些组件简单化,并把一些不必要的先省略为了便于理解。

其实要分发请求处理请求并相应,我们可以肯定的是 我们需要使用一个映射关系Mapping 来表示 url 和对应的 处理器,使用一个 处理器Handler 来处理对应的请求。这样,我们就出来了两个最根本的角色: HandlerMappingHandler

我们再来强调一下这两者的工作。

  • HandlerMapping : 建立请求和处理器的映射关系,即我们可以通过请求去获取对应的 handler。
  • Handler : 处理请求的处理器。

这样,我们就可以再画出一个简单的流程图了。

简单的处理流程

有没有疑惑,这个 HandlerMapping 集合从哪来?HandlerMapping 的类结构是啥样的?Handler的类结构又是什么样的?

如果有,那么就带着这些问题往下看。

首先,这个 handlerMapping 的集合从哪来的?甭说集合,连单个你都不知道从哪来。 那么我们就从源码中找答案吧。为了你省力,我直接告诉你,DispatcherServlet 中的 doDispatch 方法中进行了 分发的主要流程。

这里我给出了简化版的 doDispatch 方法

public void doDispatcher(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        // 通过request在处理器映射HandlerMapping中获取相应处理器
        Object handler = getHandler(req);
        if (handler != null) {
            ... 调用handler的处理方法
        }
    }
复制代码

那么这个 getHandler(request) 方法又是什么样的呢?这里我直接放 DispatcherServlet 类的源码

@Nullable
// 这里返回的是 HandlerExecutionChain 
// 其实这是一个处理器和拦截器链的组合
// 现在你就理解为返回的是一个 handler
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	if (this.handlerMappings != null) {
	        // 遍历 handlerMapping 调用它的getHanlder获取处理器
	        // 如果不为空直接返回
		for (HandlerMapping mapping : this.handlerMappings) {
			HandlerExecutionChain handler = mapping.getHandler(request);
			if (handler != null) {
				return handler;
			}
		}
	}
	return null;
}
复制代码

我们继续追踪 HandlerMapping 的getHandler(request) 方法。

其实进入源码你会发现,HandlerMapping 是一个接口,故这里给出一个简单的 HandlerMapping 接口代码,如果有能力可以去看源码。

public interface HandlerMapping {

    /**
     * 获取请求对应的处理器
     * @param request 请求
     * @return 处理器
     * @throws Exception 异常
     */
    Object getHandler(HttpServletRequest request) throws Exception;

}
复制代码

那么,具体的实现类又是什么呢?我们思考一下,这个mapping是一个请求和处理器的映射,它是如何存的?我们当初怎么做的?

想必,你已经有答案了,在我们使用 SSM 框架的时候我们是通过 给类和方法 配置相应的注解(@Controller,@ReqeustMapping)来建立相应的 url 和 处理器方法的映射关系的。

我们再回来看源码 在idea中 可以使用 ctrl+alt+B 来查看方法实现和类实现继承。我们查看 HandlerMapping 接口的 getHandler 方法的实现,我们会发现直接跳到了 AbstractHandlerMapping 这个抽象类的方法,我们查看该方法的源码

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        // 获取 handler 这其实是一个抽象方法 
        // 子类可以通过不同实现来获取handler
        // 例如通过 url 和 name的映射逻辑
        // 通过 requestMapping 中的 url 和对应方法的映射逻辑
	Object handler = getHandlerInternal(request);
	if (handler == null) {
	        // 如果获取为null 的获取默认的处理器
	        // 这里子类也可以设置自己的默认处理器
		handler = getDefaultHandler();
	}
	// 如果还是没有则返回 这时候 DispatcherServlet会返回 404
	if (handler == null) {
		return null;
	}
	// Bean name or resolved handler?
	// 如果返回的处理器是字符串 则认为它是一个beanName
	if (handler instanceof String) {
		String handlerName = (String) handler;
		// 通过beanName从IOC容器中获取相应的处理器
		handler = obtainApplicationContext().getBean(handlerName);
	}
	// 下面是将处理器 和 拦截器封装成处理器执行链
	HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

	if (logger.isTraceEnabled()) {
		logger.trace("Mapped to " + handler);
	}
	else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
		logger.debug("Mapped to " + executionChain.getHandler());
	}

	if (CorsUtils.isCorsRequest(request)) {
		CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);
		CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
		CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
		executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
	}
	// 返回处理器执行链
	return executionChain;
}
复制代码

如果其他的你看不懂,你只要理解我注释区域的代码就行了。我们从上面得到最重要的信息就是:真正的handler获取是在子类实现的getHandlerInternal(request)中,那我们来看一下有哪些子类。

我们可以看到其中有 AbstractHandlerMethodMapping、AbstractUrlHandlerMapping、WelcomeHandlerMapping。

我们主要关注 AbstractHandlerMethodMapping (提供方法处理器) 和 AbstractUrlHandlerMapping(提供url对应处理器映射),这里为了不耽误时间,我们直接分析 AbstractHandlerMethodMapping ,它是注解方法的映射的一个抽象类。

@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    // 获取请求的路径
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    this.mappingRegistry.acquireReadLock();
    try {
        // 通过 lookupPath 来从中获取 HandlerMethod 
        // 这个HandlerMethod 又是什么?
        // 先不用管 我们继续看lookupHandlerMethod源码
    	HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
    	return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    }
    finally {
    	this.mappingRegistry.releaseReadLock();
    }
}

// 这里的逻辑稍微有些复杂 
// 你只要知道它通过请求来匹配返回处理器方法
// 如果有多个处理器方法可以处理当前Http请求 那么返回最佳匹配的处理器
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
	List<Match> matches = new ArrayList<>();
	List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
	if (directPathMatches != null) {
		addMatchingMappings(directPathMatches, matches, request);
	}
	if (matches.isEmpty()) {
		// No choice but to go through all mappings...
		addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
	}

	if (!matches.isEmpty()) {
		Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
		matches.sort(comparator);
		Match bestMatch = matches.get(0);
		if (matches.size() > 1) {
			if (logger.isTraceEnabled()) {
				logger.trace(matches.size() + " matching mappings: " + matches);
			}
			if (CorsUtils.isPreFlightRequest(request)) {
				return PREFLIGHT_AMBIGUOUS_MATCH;
			}
			Match secondBestMatch = matches.get(1);
			if (comparator.compare(bestMatch, secondBestMatch) == 0) {
				Method m1 = bestMatch.handlerMethod.getMethod();
				Method m2 = secondBestMatch.handlerMethod.getMethod();
				String uri = request.getRequestURI();
				throw new IllegalStateException(
						"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
			}
		}
		request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
		handleMatch(bestMatch.mapping, lookupPath, request);
		// 返回最佳匹配
		return bestMatch.handlerMethod;
	}
	else {
		return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
	}
}
复制代码

到现在逻辑慢慢变得复杂起来,我们做一个小结:在 DispatcherServlet 中我们通过遍历 handlerMapping 集合并调用它的 getHandler 方法来获取handler ,这个handler 是一个 Object (因为spring会整合其他框架的处理器,并使用这些处理器处理请求 所以这里选择Object)。而 HandlerMapping 仅仅是一个接口 为了方便 抽象类 AbstractHandlerMapping 实现了这个方法并且为子类提供了自定义获取handler的 getHandlerInternal(request) 方法。 对于我们通用方式注解来标识控制器方法和url请求路径的映射是通过 AbstractHandlerMethodMapping 来获取请求对应的 HandlerMethod

那么,疑问又来了,HandlerMethod是什么?

还记得刚刚上面的问题么,这个 HandlerMapping 集合从哪来?HandlerMapping 的类结构是啥样的?Handler的类结构又是什么样的

我们现在可以来回答一下Handler的类结构了,Handler是一个Object,为了第三方框架的处理器能够接入来处理请求,spring使用了Object,而对于注解形式来说 一个处理器是一个 HandlerMethod。这里我给出 HandlerMethod 的简单实现形式,如果有能力可以查看源码。

@Data
public class HandlerMethod {
    // bean 其实这个是标识 Controller 注解的类的对象
    private Object bean;
    // 该对象的类型
    private Class<?> beanType;
    // 该类上被标识RequestMapping注解的方法
    private Method method;
}
复制代码

在 HandlerMethod 中存放了控制器类和对应的方法。为什么要存放他们?你想一下,我们用@RequestMapping注解标识的方法不就是处理方法吗,HandlerMethod 中存放他们,到时候调用处理方法只需要通过反射调用这个bean的method就行了。如果不理解可以看一下我下面写的代码。

// 这里先不用管 ModelAndView
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    ModelAndView modelAndView = null;
    HandlerMethod handlerMethod = ((HandlerMethod) handler);
    // 获取HandlerMethod的method
    Method method = handlerMethod.getMethod();
    if (method != null) {
        // 通过反射调用方法并返回视图对象(这就是处理方法)
        modelAndView = (ModelAndView)method.invoke(BeanFactory.getBean(handlerMethod.getBeanType()));
    }
    return modelAndView;
}
复制代码

再来看看上面的问题。这个 HandlerMapping 集合从哪来?HandlerMapping 的类结构是啥样的?Handler的类结构又是什么样的

第三个问题解决了,第二个问题上面也解决了,那么第一个问题来了。

我们从一开始就只讨论了如何在HandlerMapping中取出handler 并且调用handler的处理方法,那么我们一开始遍历的这个handlerMappings集合到底从哪儿来,或者说它是什么时候被初始化的

这个时候,我们又得回到根源。我再来放这张图,不知道你们是否还记得

你能想到什么呢?我这里假设你对servlet还是有一些了解的。

我们知道 DispatcherServlet 是一个 servlet 。一个 servlet 肯定有init()方法 (还记得我上面讲的GenericServlet的作用吗?现在来了,如果不是很懂init(),建议去了解一下servlet的生命周期)。

我们可以大胆的猜测,对 handlerMappings 的初始化就是在 servlet 的初始化方法中进行的。

很遗憾我们没有能在 DispatcherServlet 中找到 init 方法,那么就找他爹,找不到再找他爷爷,曾爷爷。我们知道因为 DispatcherServlet 继承了 GenericServlet 所以我们需要找到 实现的 init() 无参方法。所以我们找到了 HttpServletBean 中重写的 init() 方法了

@Override
// 这家伙还不允许被重写 final
public final void init() throws ServletException {

	// Set bean properties from init parameters.
	// 将servlet配置信息存入bean的属性中
	PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
	if (!pvs.isEmpty()) {
		try {
			BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
			ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
			bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
			initBeanWrapper(bw);
			bw.setPropertyValues(pvs, true);
		}
		catch (BeansException ex) {
			if (logger.isErrorEnabled()) {
				logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
			}
			throw ex;
		}
	}
	// 重点在这里 这里子类才可以自由发挥
	// 该方法不能被重写是因为 上面的步骤是必须的
	// 别忘了上面的步骤是 HttpServletBean 的职责
	// 接下去继续看
	// Let subclasses do whatever initialization they like.
	initServletBean();
}

// 进入FrameworkServlet 查看实现的initServletBean方法
@Override
protected final void initServletBean() throws ServletException {
        // log不用管
	getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
	if (logger.isInfoEnabled()) {
		logger.info("Initializing Servlet '" + getServletName() + "'");
	}
	long startTime = System.currentTimeMillis();
	// 重点来了
	try {
	        // 初始化容器和上下文
	        // 我们要记得现在在 FrameworkServlet中执行呢
	        // 我们进入initWebApplicationContext方法
		this.webApplicationContext = initWebApplicationContext();
		// 初始化FrameworkServlet 这里没给实现 子类也没给
		// 所以不用管
		initFrameworkServlet();
	}
	// log不用管
	catch (ServletException | RuntimeException ex) {
		logger.error("Context initialization failed", ex);
		throw ex;
	}

	if (logger.isDebugEnabled()) {
		String value = this.enableLoggingRequestDetails ?
				"shown which may lead to unsafe logging of potentially sensitive data" :
				"masked to prevent unsafe logging of potentially sensitive data";
		logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
				"': request parameters and headers will be " + value);
	}

	if (logger.isInfoEnabled()) {
		logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
	}
}
// 初始化容器和上下文
protected WebApplicationContext initWebApplicationContext() {
        // 查找是否有专门的根环境 先不用管
	WebApplicationContext rootContext =
			WebApplicationContextUtils.getWebApplicationContext(getServletContext());
	WebApplicationContext wac = null;
	// 如果不存在专用根环境 通常我们不会走到这 先不用管
	if (this.webApplicationContext != null) {
		// A context instance was injected at construction time -> use it
		wac = this.webApplicationContext;
		if (wac instanceof ConfigurableWebApplicationContext) {
			ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
			if (!cwac.isActive()) {
				// The context has not yet been refreshed -> provide services such as
				// setting the parent context, setting the application context id, etc
				if (cwac.getParent() == null) {
					// The context instance was injected without an explicit parent -> set
					// the root application context (if any; may be null) as the parent
					cwac.setParent(rootContext);
				}
				configureAndRefreshWebApplicationContext(cwac);
			}
		}
	}
	// 如果为空 
	if (wac == null) {
	// 查看是否在servlet中已经注册
		// No context instance was injected at construction time -> see if one
		// has been registered in the servlet context. If one exists, it is assumed
		// that the parent context (if any) has already been set and that the
		// user has performed any initialization such as setting the context id
		wac = findWebApplicationContext();
	}
	if (wac == null) {
	// 自己创建一个
		// No context instance is defined for this servlet -> create a local one
		wac = createWebApplicationContext(rootContext);
	}
	// 判断这个环境是否支持刷新 如果不支持 下面手动刷新 
	// 如果支持则前面已经刷新了
	if (!this.refreshEventReceived) {
		// Either the context is not a ConfigurableApplicationContext with refresh
		// support or the context injected at construction time had already been
		// refreshed -> trigger initial onRefresh manually here.
		synchronized (this.onRefreshMonitor) {
		    // !!!!!!!!!!!!!!!!!!!!!重点
		    // DispacherServlet 就是在这里实现的
			onRefresh(wac);
		}
	}

	if (this.publishContext) {
		// Publish the context as a servlet context attribute.
		String attrName = getServletContextAttributeName();
		getServletContext().setAttribute(attrName, wac);
	}

	return wac;
}
// DispatcherServlet 重写了该方法
@Override
protected void onRefresh(ApplicationContext context) {
	initStrategies(context);
}

// 一系列的初始化工作
protected void initStrategies(ApplicationContext context) {
        // 前面一些不用管
	initMultipartResolver(context);
	// 地域
	initLocaleResolver(context);
	// 主题
	initThemeResolver(context);
	// 重点来了!!!!
	// 初始化HandlerMapping
	initHandlerMappings(context);
	// 初始化适配器
	initHandlerAdapters(context);
	// 初始化异常处理
	initHandlerExceptionResolvers(context);
	initRequestToViewNameTranslator(context);
	initViewResolvers(context);
	initFlashMapManager(context);
}
复制代码

我们先暂停一下,理一下思路。

HttpServletBean 中重写了 GenericServletinit() 无参方法开始初始化动作,其中HttpServletBean中先实现了 servlet 配置信息到 bean 属性信息的赋值,然后调用 initServletBean() 该方法是子类进行自定义初始化的方法。FrameworkServlet 实现了该方法并且调用了 initWebApplicationContext() 方法进行了容器和上下文的初始化工作,并且其中调用了 onRefresh(ApplicationContext context) 方法。 这里FrameworkServlet没有做任何操作而是子类 DispatcherServlet 在其中调用了 initStrategies(context) 进行初始化工作。

好了我们继续看初始化 handlerMappings方法。

private void initHandlerMappings(ApplicationContext context) {
	this.handlerMappings = null;

	if (this.detectAllHandlerMappings) {
		// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
		// 在应用上下文中寻找 handlerMappings
		Map<String, HandlerMapping> matchingBeans =
				BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
		if (!matchingBeans.isEmpty()) {
			this.handlerMappings = new ArrayList<>(matchingBeans.values());
			// We keep HandlerMappings in sorted order.
			AnnotationAwareOrderComparator.sort(this.handlerMappings);
		}
	}
	else {
		try {
			HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
			this.handlerMappings = Collections.singletonList(hm);
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Ignore, we'll add a default HandlerMapping later.
		}
	}
	
	// Ensure we have at least one HandlerMapping, by registering
	// a default HandlerMapping if no other mappings are found.
	// 前面不用管 其实一般我们使用默认的
	if (this.handlerMappings == null) {
	        // 这里是获取默认的handlerMappings
		this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
		if (logger.isTraceEnabled()) {
			logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
					"': using default strategies from DispatcherServlet.properties");
		}
	}
}

protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
	String key = strategyInterface.getName();
	// 获取defaultStrategies的内容
	String value = defaultStrategies.getProperty(key);
	if (value != null) {
	        // 解析相应内容并初始化 handlerMappings
	        // 获取内容中的类名数组
		String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
		List<T> strategies = new ArrayList<>(classNames.length);
		for (String className : classNames) {
			try {
			     //通过反射创建并加入数组中取返回给上面
				Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
				Object strategy = createDefaultStrategy(context, clazz);
				strategies.add((T) strategy);
			}
			catch (ClassNotFoundException ex) {
				throw new BeanInitializationException(
						"Could not find DispatcherServlet's default strategy class [" + className +
						"] for interface [" + key + "]", ex);
			}
			catch (LinkageError err) {
				throw new BeanInitializationException(
						"Unresolvable class definition for DispatcherServlet's default strategy class [" +
						className + "] for interface [" + key + "]", err);
			}
		}
		return strategies;
	}
	else {
		return new LinkedList<>();
	}
}
复制代码

事情马上明了了,我们现在已经知道了 handlerMapping 是怎么加入队列中了(获取到 defaultStrategies 的资源内容 遍历内容获取类名 并通过反射创建对象加入队列),所以我们可以大胆猜测 defaultStrategies 中藏着秘密,它肯定已经定义好了默认的 handlerMapping 的类名。

果不其然,我们来看代码

	private static final Properties defaultStrategies;
	// 在静态块中已经加载了defaultStrategies
	static {
		// Load default strategy implementations from properties file.
		// This is currently strictly internal and not meant to be customized
		// by application developers.
		try {
		    // 通过资源初始化defaultStrategies
		    // 这里的资源路径 很重要!!!!
			ClassPathResource resource = new
			ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
			defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
		}
		catch (IOException ex) {
			throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
		}
	}
	// 在这里呀 DispatcherServlet.properties
	private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
复制代码

我们去寻找一下 DispatcherServlet.properties 这个文件, 原来都给我们定义好了,我们可以看见默认的handlerMapping有两个。
BeanNameUrlHandlerMapping 和 RequestMappingHandlerMapping。

好了,我们现在终于可以总结一下了。

在我定义的简单的 DispatcherServlet 的 “同事”中,主要有 HandlerMapping 和 Handler。HandlerMapping 会在 DispatcherServlet 初始化的时候被在加载 ,然后在 DispatcherServlet 调用到执行方法 doDispatch() 的时候,会遍历 handlerMappings 集合获取对应的 handler。handler 是一个 Object(因为需要适配其他框架的处理器),在注解方式中是一个 HandlerMethod (里面存了Controller类的实例和method,在处理方法的时候使用反射调用该实例的method方法)。获取完 handler之后通过 处理器的处理方法返回一个视图对象并渲染页面。

再来一个组件 Adapter

其实对于“正宗”的MVC流程中,在遍历 handlerMappings 获取到相应的 handler 之后,其实并不是直接通过 handler 来执行处理方法的,而是通过 HandlerAdapter 来执行处理方法的。

这里我写了一个简单的适配器接口,源码也不复杂 你可以直接看源码

public interface HandlerAdapter {
    ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
    boolean support(Object handler);
}
复制代码

handleRequest 不必说,用来执行处理的,里面传进去一个 handler 肯定最终调用的 handler的执行方法。这是典型的适配器模式。

support 判断该handler是否被该适配器支持。

其实理解上面的过程了之后再加入一个适配器就不难了,我们主要思考一下 为什么要加入适配器,我们知道 handler 是一个 Object 它的处理方法是不固定的,如果我们要在 DispatcherServlet 中通过 Handler 执行处理方法,那么就要做很多类型判断,这对于 DispatcherServlet 是非常难受的,所以需要通过适配器扩展。

这样我们可以写出一个简单的 doDispatch 方法了,有能力的可以查看源码

public void doDispatcher(HttpServletRequest req, HttpServletResponse resp) throws Exception {
    // 通过request在处理器映射HandlerMapping中获取相应处理器
    Object handler = getHandler(req);
    if (handler != null) {
        // 通过handler获取对应的适配器
        HandlerAdapter handlerAdapter = getHandlerAdapter(handler);
        if (handlerAdapter != null) {
            // 通过适配器调用处理器的处理方法并返回ModelAndView视图对象
            ModelAndView modelAndView = handlerAdapter.handleRequest(req, resp, handler);
            ... 处理视图并渲染
        }
    }
}
复制代码

视图解析

我们知道在 doDispatch 方法调用完 HandlerAdapter 的处理方法后统一返回的是一个 ModelAndView 对象,那么这个 ModelAndView 对象是什么呢?

字面意思,模型和视图。也就是 MVC 的 Model 和 View。在 SpringMVC 中 ModelAndView 是给 框架本身支持的网页生成器使用的,它是用来连接后台和网页的类,而如今在前后端分离的趋势下,基本不怎么使用了,这里我只简单提一下。

我们知道在 HandlerAdapter 调用处理方法之后会返回一个视图对象 ModelAndView ,而在这之后,doDispatch方法会调用 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException) 去处理视图信息,这个方法又会调用一个 render() 方法进行真正的视图渲染。

非常有用的RequestResponseBodyMethodProcessor

还记不记得 @RequestBody @ResponseBody @RestController 这些注解。没错,现在我们大部分都用它们,那么它们是如何工作的呢?

奥秘要从 doDispatch() 方法中的

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
复制代码

这条语句开始。 这个语句就是调用 相应的适配器的 handle 方法并返回 ModelAndView 对象。

当然通过前面的学习,我们知道最终调用到的是 RequestMappingHandlerAdapter 类的 handleInternal方法。

// 查看这个方法的源码 你会发现 除了处理一些 session 的问题
// 最终都会调用 处理器方法 invokeHandlerMethod
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
    	HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    
    ModelAndView mav;
    checkRequest(request);
    
    // Execute invokeHandlerMethod in synchronized block if required.
    // 如果配置了 session 同步
    if (this.synchronizeOnSession) {
        // 则获取 session
    	HttpSession session = request.getSession(false);
    	if (session != null) {
    		Object mutex = WebUtils.getSessionMutex(session);
    		synchronized (mutex) {
    			mav = invokeHandlerMethod(request, response, handlerMethod);
    		}
    	}
    	else {
    		// No HttpSession available -> no mutex necessary
    		mav = invokeHandlerMethod(request, response, handlerMethod);
    	}
    }
    else {
        // 如果没有配置 session 内同步 或者还没有创建 session 直接调用处理器方法
    	// No synchronization on session demanded at all...
    	mav = invokeHandlerMethod(request, response, handlerMethod);
    }
    
    if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
    	if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
    		applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
    	}
    	else {
    		prepareResponse(response);
    	}
    }

	return mav;
}
复制代码

我们来看一下 invokeHandlerMethod 中干了什么事, 看上去好密密麻麻,其实我们只要关注重点就行了,关注我注释的地方。

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
		HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
	// 构造 Web 请求 其实就是一个代理类 封装了 请求和响应
	ServletWebRequest webRequest = new ServletWebRequest(request, response);
	try {
		WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
		ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
		// 重点来了!!!!!
		// 将handlerMethod 封装成 ServletInvocableHandlerMethod类
		ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
		if (this.argumentResolvers != null) {
			invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
		}
		if (this.returnValueHandlers != null) {
			invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
		}
		// 为invocableMethod 做一些配置
		invocableMethod.setDataBinderFactory(binderFactory);
		invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

		ModelAndViewContainer mavContainer = new ModelAndViewContainer();
		mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
		modelFactory.initModel(webRequest, mavContainer, invocableMethod);
		mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

		AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
		asyncWebRequest.setTimeout(this.asyncRequestTimeout);

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		asyncManager.setTaskExecutor(this.taskExecutor);
		asyncManager.setAsyncWebRequest(asyncWebRequest);
		asyncManager.registerCallableInterceptors(this.callableInterceptors);
		asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

		if (asyncManager.hasConcurrentResult()) {
			Object result = asyncManager.getConcurrentResult();
			mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
			asyncManager.clearConcurrentResult();
			LogFormatUtils.traceDebug(logger, traceOn -> {
				String formatted = LogFormatUtils.formatValue(result, !traceOn);
				return "Resume with async result [" + formatted + "]";
			});
			invocableMethod = invocableMethod.wrapConcurrentResult(result);
		}
		// 重点来了!!!!! 调用 ServletInvocableHandlerMethod 的 invokeAndHandle 方法
		invocableMethod.invokeAndHandle(webRequest, mavContainer);
		if (asyncManager.isConcurrentHandlingStarted()) {
			return null;
		}

		return getModelAndView(mavContainer, modelFactory, webRequest);
	}
	finally {
		webRequest.requestCompleted();
	}
}
复制代码

总结一下上面的方法就是:将 HandlerMethod 对象封装成 ServletInvocableHandlerMethod 然后做一些配置并调用它的 invokeAndHandle 方法

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
		Object... providedArgs) throws Exception {
	// 这一步很重要 执行请求并获取返回值
	// 这里里面就涉及到了 RequestBody 注解了
	Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
	setResponseStatus(webRequest);
	// 处理返回值
	if (returnValue == null) {
		if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
			disableContentCachingIfNecessary(webRequest);
			mavContainer.setRequestHandled(true);
			return;
		}
	}
	else if (StringUtils.hasText(getResponseStatusReason())) {
		mavContainer.setRequestHandled(true);
		return;
	}

	mavContainer.setRequestHandled(false);
	Assert.state(this.returnValueHandlers != null, "No return value handlers");
	try {
		this.returnValueHandlers.handleReturnValue(
				returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
	}
	catch (Exception ex) {
		if (logger.isTraceEnabled()) {
			logger.trace(formatErrorForReturnValue(returnValue), ex);
		}
		throw ex;
	}
}
复制代码

我们首先来解析一下 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs) 方法 ,这里涉及到了 @RequestBody 注解的使用。

@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
		Object... providedArgs) throws Exception {
	// 方法参数的解析 这里可以做很多关于 参数和请求 的事情
	// 这是我们需要深入查看源码的
	Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
	if (logger.isTraceEnabled()) {
		logger.trace("Arguments: " + Arrays.toString(args));
	}
	// 返回调用结果 很简单 就是通过反射调用方法 这里不做赘述
	return doInvoke(args);
}

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
		Object... providedArgs) throws Exception {
	// 很简单 获取到参数
	MethodParameter[] parameters = getMethodParameters();
	if (ObjectUtils.isEmpty(parameters)) {
		return EMPTY_ARGS;
	}

	Object[] args = new Object[parameters.length];
	// 遍历参数
	for (int i = 0; i < parameters.length; i++) {
		MethodParameter parameter = parameters[i];
		parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
		args[i] = findProvidedArgument(parameter, providedArgs);
		if (args[i] != null) {
			continue;
		}
		if (!this.resolvers.supportsParameter(parameter)) {
			throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
		}
		try {
		    // 重点来了!!!!
		    // 将请求中的信息通过参数解析器解析到对应的参数
		    // 最终遍历完之后将参数数组返回
			args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
		}
		catch (Exception ex) {
			// Leave stack trace for later, exception may actually be resolved and handled...
			if (logger.isDebugEnabled()) {
				String exMsg = ex.getMessage();
				if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
					logger.debug(formatArgumentError(parameter, exMsg));
				}
			}
			throw ex;
		}
	}
	return args;
}

@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
		NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
	// 获取对应的参数解析器
	HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
	if (resolver == null) {
		throw new IllegalArgumentException("Unsupported parameter type [" +
				parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
	}
	// 通过解析器解析参数 重点就在这了 因为 @RequestBody注解的存在
	// 我们会调用到 RequestResponseBodyProcessor 类的这个方法
	return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}

@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
		NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

	parameter = parameter.nestedIfOptional();
	// 主要这里面通过 MessageConverters 消息转换器 来实现了 @RequestBody 的功能
	// 由于篇幅有限 这里不再深入分析 如果想找到答案 顺着往下查看源码就行
	Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
	String name = Conventions.getVariableNameForParameter(parameter);

	if (binderFactory != null) {
		WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
		if (arg != null) {
			validateIfApplicable(binder, parameter);
			if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
				throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
			}
		}
		if (mavContainer != null) {
			mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
		}
	}

	return adaptArgumentIfNecessary(arg, parameter);
}
复制代码

下面我还放了一下 readWithMessageConverters 方法的代码,其实里面主要就是遍历消息转换器,然后通过转换器执行HTTP报文到参数的转换。

for (HttpMessageConverter<?> converter : this.messageConverters) {
	Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
	GenericHttpMessageConverter<?> genericConverter =
			(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
	if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
			(targetClass != null && converter.canRead(targetClass, contentType))) {
		if (message.hasBody()) {
			HttpInputMessage msgToUse =
					getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
			body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
					((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
			body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
		}
		else {
			body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
		}
		break;
	}
}
复制代码

知道了 @RequestBody 注解的原理,@ResponseBody 注解的原理也马上浮出水面了。答案就在 ServletInvocableHandlerMethod 类中的 invokeAndHandle 方法获取了 returnValue 之后的步骤

// 答案就在这里
try {
	this.returnValueHandlers.handleReturnValue(
			returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
		ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

	HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
	if (handler == null) {
		throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
	}
	handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
复制代码

上面两个方法你可以追踪源码,其实最终调用的还是在 RequestResponseBodyMethodProcessor 这个类中。

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
		ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
		throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

	mavContainer.setRequestHandled(true);
	// 封装web请求
	ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
	ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
	// 通过消息解析器解析返回的value
	// Try even with null return value. ResponseBodyAdvice could get involved.
	writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

// 这里我贴出了writeWithMessageConverters方法的主要代码 因为这个方法有点长。。
// 这里遍历 Http消息转换器集合
for (HttpMessageConverter<?> converter : this.messageConverters) {
	GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
			(GenericHttpMessageConverter<?>) converter : null);
	if (genericConverter != null ?
			((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
			converter.canWrite(valueType, selectedMediaType)) {
		body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
				(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
				inputMessage, outputMessage);
		if (body != null) {
			Object theBody = body;
			LogFormatUtils.traceDebug(logger, traceOn ->
					"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
			addContentDispositionHeader(inputMessage, outputMessage);
			// 通过转换器来输出  重点。。。
			if (genericConverter != null) {
				genericConverter.write(body, targetType, selectedMediaType, outputMessage);
			}
			else {
				((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
			}
		}
		else {
			if (logger.isDebugEnabled()) {
				logger.debug("Nothing to write: null body");
			}
		}
		return;
	}
}
复制代码

我们来总结一下: 在调用 HandlerAdapter 的 处理方法的时候 会跳转调用到 RequestMappingHandlerAdapter 的 handleInternal 方法。这里面会将 原本的处理器 HandlerMethod 封装成 ServletInvocableHandlerMethod,然后会调用这个类中的 invokeAndHandle 方法,这个方法中主要进行了相应方法处理器的方法的调用,在调用之前,会将Http报文中的内容转换为对应的参数内容。在调用完成返回 returnValue 之后,会调用相应 HttpMessageConvert 的转换方法 然后返回。

最终变成什么样了呢?

现在,你理解 Spring MVC了么?

GankRobot转载声明

原文出处:掘金后端

原文作者:FrancisQ

原文地址:https://juejin.im/post/5da18c746fb9a04e37316a9e