上篇文章写了SpringMVC启动时的加载源码,这篇文章我会介绍一下SpringMVC的请求过程,从前端发送请求到后端相应、处理、转发、返回、渲染的全流程。
一、SpringMVC的请求转换
我们知道一个web服务一定符合标准的servlet规范,前端发送请求时必定也是交给servlet处理的,要想知道servlet如何处理请求的,我们可以从servlet接口入手。
由图上可知,servlet接口中一共有五个方法,根据翻译即可大致知道方法的功能:
- init:servlet初始化方法;
- getServletConfig:获取servlet的配置;
- service:处理请求;
- getServletInfo:获取servlet的信息;
- destroy:servlet销毁方法。
所以在servlet中必定是由service方法去处理的来自前端的请求的,我们再来看下谁是真正实现这个方法的。
从继承关系图上我们可以知道,第一个实现的是GenericServlet,但是这个方法是抽象的,GenericServlet并没有真正实现。
public abstract void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
我们继续往下找,下一个实现是HttpServlet
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException
{
HttpServletRequest request;
HttpServletResponse response;
if (!(req instanceof HttpServletRequest &&
res instanceof HttpServletResponse)) {
throw new ServletException("non-HTTP request or response");
}
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
service(request, response);
}
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < lastModified) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
我们可以看到,HttpServlet中有两个service实现方法,看入参和继承关系我们应该知道servlet的request类型是ServletRequest(response一样),第一个service会先将属于是servlet的ServletRequest转换成HttpServletRequest,然后再调用第二个service方法。
第二个service方法的作用是什么呢?我们根据源码可以看出,第二个service的主要目的是拿到request的请求方式,根据不同的请求方式调用不同的处理方法,我们这次就以get方法为例,探究SpringMVC是如何处理来自前端的请求的。
二、SpringMVC请求的入口
上面我们说了servlet会在HttpServlet中将ServletRequest转换成HttpServletRequest,并且根据不同的请求方式找到对应的处理方法,这个是servlet提供的功能,那么整个SpringMVC的处理入口在哪呢?
通过servlet的继承关系图我们可以知道,诸如doGet这些方法最早是HttpServlet定义的,但是FrameworkServlet重写了他的方法,而且由于FrameworkServlet的doGet方法是有final修饰,因此get请求的最终入口就走到了FrameworkServlet的doGet方法去了,它的源码如下:
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
我们继续点进去看processRequest里面具体做了哪些东西:
// 重点关注:doService
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获取系统当前时间戳
long startTime = System.currentTimeMillis();
// 申明一个异常顶层对象Throwable
Throwable failureCause = null;
// 本地化、国际化处理
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
// 构建一个本地国际化上下文,SimpleLocaleContex
LocaleContext localeContext = buildLocaleContext(request);
// 获取当前绑定到线程的RequestAttributes
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
// 构建ServletRequestAttributes
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
// 将localeContext和requestAttributes放入当前线程中,
// request作用域每个请求线程都不一样,参数,attribute需要上下文直接传递
// (回顾:request,session,application)
initContextHolders(request, localeContext, requestAttributes);
try {
//前面都是一堆准备工作,重点在这里!跳到DispatcherServlet 类中(子类重写) 【关键点!】
doService(request, response);
} catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
} catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
} finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
if (logger.isDebugEnabled()) {
if (failureCause != null) {
this.logger.debug("Could not complete request", failureCause);
} else {
if (asyncManager.isConcurrentHandlingStarted()) {
logger.debug("Leaving response open for concurrent processing");
} else {
this.logger.debug("Successfully completed request");
}
}
}
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
可以看到processRequest主要做了下面的事情;
- 记录请求时间;
- 处理本地化和国际化问题;
- 构造localeContext和requestAttributes,设置当前request的生命周期;
- 最后调用doService()方法,这个动作最为重要,是当前方法的核心功能。
执行doService()方法的时候我们可以看到,实际上调用的是DispatcherServlet的doService(),这样,请求就交给了DispatcherServlet进行处理了,那么DispatcherServlet的doService()方法里又做了些什么东西呢?
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
" processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
}
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects
// 扔一堆东西进request.
// web上下文,可以在后面的请求链路上通过request.getAttribute取出来使用
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); // 本地化处理器
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); // 主题处理器,2003年的产物
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); // 主题资源文件
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
try {
//重点关注,核心
doDispatch(request, response);
} finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
我们可以看到DispatcherServlet的doService()方法又set了一些参数,但是里面有一个核心的方法doDispatch(),这个是Spring MVC的核心代码,Spring MVC的所有步骤都在这个方法,所以我们要重点关注这个方法。
// Spring MVC的最核心代码
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
//创建视图对象
ModelAndView mv = null;
Exception dispatchException = null;
try {
//请求检查,是否文件上传请求(二进制请求)
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 根据当前的请求去拿一个Handler.查找处理器映射器,进入!!!!!!!!!!!
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
// 如果当前请求没有handler进行处理,那么就抛出异常
noHandlerFound(processedRequest, response);
return;
}
// handler适配器,找到:RequestMappingHandlerAdapter ,可以通过debug变量查看类型
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// get方法
String method = request.getMethod();
//get方法为true
boolean isGet = "GET".equals(method);
//method为get
if (isGet || "HEAD".equals(method)) { // 处理last-modified , 浏览器缓存时间
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
//循环调拦截器对应的 pre 方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) { // ===> applyPreHandle
return;
}
// 让handlerAdapter去执行业务控制器方法, 真正进入controller的入口!
// com.spring.mvc.test.MvcController.getModeAndView
// 让它来返回modelAndView对象! debug仔细查看 mv 变量的值
mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // ===> controller入口,走起~
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 设置默认视图,当返回了mav,但是又没设置具体view的时候
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv); // ===> 拦截器后置调用 postHandle
} catch (Exception ex) {
dispatchException = ex;
} catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
//1、请求视图解析器,解析成view
//2、执行页面渲染(jsp)
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); // ===> go!
} catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
} catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
至此,Spring MVC的全部请求流程源码就跟踪完了,具体更加详细的实现可以自己点进去看看。
三、SpringMVC根据url获取到对应的controller流程
我们根据上面SpringMVC地方核心源码可以知道,url获取到对应的controller方法是在doDispatch()中,其入口如下:
//根据当前的请求去拿一个Handler.查找处理器映射器
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
// 如果当前请求没有handler进行处理,那么就抛出异常
noHandlerFound(processedRequest, response);
return;
}
那么这个方法里面又做了哪些东西呢,我们可以进去看一下。
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//不止一个,比如BeanNameHandlerMapping、SimpleUrlHandlerMapping,
// 还有我们需要的RequestHandlerMapping
if (this.handlerMappings != null) {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
//这个就是执行器链,里面包含了需要处理这个请求的controller(handler),以及所有的拦截器(interceptor),即根据url取到controller的过程
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
里面都是一些校验,我们再点进去看 getHandler()方法,其实现过程在org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler下,源码如下:
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request); // ===> 找到controller和方法的地方!
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) { // 如果是字符串,从factory取出对应的bean来
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
//所谓的handler链就是这里!其实就是包装一下,加上拦截器列表,详情看 HandlerExecutionChain 的源码结构!
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); //封装成chain对象
if (CorsUtils.isCorsRequest(request)) { //如果是跨域请求(从header里加了Origin的话)
CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config); //就给它加一个拦截器
}
return executionChain; // handler链返回给 DS
}
可以看到,根据url获取controller的方法主要是在第一步getHandlerInternal(request)中,后面都是对找到的controller进行的一些处理,而getHandlerInternal的实现是在org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); // 请求的url值
if (logger.isDebugEnabled()) {
logger.debug("Looking up handler method for path " + lookupPath);
}
this.mappingRegistry.acquireReadLock(); //加一把读锁,防止后期有修改的线程造成数据不一致!
try {
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); // ===> 藏在这里!
if (logger.isDebugEnabled()) {
if (handlerMethod != null) {
logger.debug("Returning handler method [" + handlerMethod + "]");
}
else {
logger.debug("Did not find handler method for [" + lookupPath + "]");
}
}
//return之前处理一下。如果是string的话,找到对应的bean返回去
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); // ===>
}
finally {
this.mappingRegistry.releaseReadLock(); //释放掉!
}
}
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
//定义一个集合,存储所有匹配上的方法
List<Match> matches = new ArrayList<>();
// mappingRegistry , 映射关系 component-scan扫进去的!debug一下,留心mappingRegistry里面的 mappingLookUp……
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) { // directPathMatches,所有匹配上的,可能有多个符合条件的
addMatchingMappings(directPathMatches, matches, request); // 把他们整理一下,塞到matches这个list里
}
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));
Collections.sort(matches, comparator); //根据定义的比较器,对多个匹配的method排序
if (logger.isTraceEnabled()) {
logger.trace("Found " + matches.size() + " matching mapping(s) for [" +
lookupPath + "] : " + matches);
}
Match bestMatch = matches.get(0); // 取匹配优先级最高的那个
if (matches.size() > 1) {
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) { // 如果第1 第 2 两个匹配优先级相同
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
request.getRequestURL() + "': {" + m1 + ", " + m2 + "}"); // 扔出异常,提示重复
}
}
handleMatch(bestMatch.mapping, lookupPath, request); // ===> 没干啥事,映射关系在request里设置了个attribute
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
至此,SpringMVC就完成了根据url查找对应的处理器的过程。
其他的比如SpringMVC是如何完成处理过程以及视图渲染,文件上传等功能的,大家可以根据Spring源码参考查看对应的解决源码。