先简单聊聊SpringMVC
如果你们玩知乎,很可能会看到我的身影。我经常会去知乎水回答。在知乎有很多初学者都会问的一个问题:「我学习SpringMVC需要什么样的基础」
我一定会让他们先学Servlet,再学SpringMVC的。虽然说我们在现实开发中几乎不会写原生Servlet的代码了,但我始终认为学完Servlet再学SpringMVC,对理解SpringMVC是有好处的。
三歪题外话:我当时在学SpringMVC之前其实已经接触过另外一个web框架(当然了Servlet也是学了的),那就是「大名鼎鼎」的Struts2。只要是Struts2有的功能,SpringMVC都会有。
当时初学Struts2的时候用的是XML配置的方式去开发的,再转到SpringMVC注解的时候,觉得SpringMVC真香。
Struts2在2020年已经不用学了,学SpringMVC的基础是Servlet,只要Servlet基础还行,上手SpringMVC应该不成问题。
从Servlet到SpringMVC,你会发现SpringMVC帮我们做了很多的东西,我们的代码肯定是没以前多了。
Servlet:
我们以前可能需要将传递进来的参数手动封装成一个Bean,然后继续往下传:
SpringMVC:
现在SpringMVC自动帮我们将参数封装成一个Bean
Servlet:
以前我们要导入其他的jar
包去手动处理文件上传的细节:
SpringMVC:
现在SpringMVC上传文件用一个MultipartFile对象都给我们封装好了
........
说白了,在Servlet时期我们这些活都能干,只不过SpringMVC把很多东西都给屏蔽了,于是我们用起来就更加舒心了。
在学习SpringMVC的时候实际上也是学习这些功能是怎么用的而已,并不会太难。这次整理的SpringMVC电子书其实也是在讲SpringMVC是如何使用的
- 比如说传递一个日期字符串来,SpringMVC默认是不能转成日期的,那我们可以怎么做来实现。
- SpringMVC的文件上传是怎么使用的
- SpringMVC的拦截器是怎么使用的
- SpringMVC是怎么对参数绑定的
- ......
现在「电子书」已经放出来了,但是别急,重头戏在后面。显然,通过上面的电子书是可以知道SpringMVC是怎么用的。
但是这在面试的时候人家是不会问你SpringMVC的一些用法的,而SpringMVC面试问得最多的就是:SpringMVC请求处理的流程是怎么样的。
其实也很简单,流程就是下面这张图:
再简化一点,可以发现流程不复杂
在面试的时候甚至能一句话就讲完了,但这够吗,这是面试官想要的吗?那肯定不是。那我们想知道SpringMVC是做了什么吗?想的吧(不管你们想不想,反正三歪想看)。
由于想要主流程更加清晰一点,我会在源码添加部分注释以及删减部分的代码
以@ResponseBody和@RequestBody的Controller代码讲解为主,这是线上环境用得最多的
DispatcherServlet源码
首先我们看看DispatcherServlet的类结构,可以清楚地发现实际DispatcherServlet就是Servlet接口的一个子类(这也就是为什么网上这么多人说DispatcherServlet的原理实际上就是Servlet)
我们在DispatcherServlet类上可以看到很多熟悉的成员变量(组件),所以看下来,我们要的东西,DispatcherServlet可全都有。
// 文件处理器 private MultipartResolver multipartResolver; // 映射器 private List<HandlerMapping> handlerMappings; // 适配器 private List<HandlerAdapter> handlerAdapters; // 异常处理器 private List<HandlerExceptionResolver> handlerExceptionResolvers; // 视图解析器 private List<ViewResolver> viewResolvers;
然后我们会发现它们在initStrategies()
上初始化:
protected void initStrategies(ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); }
请求进到DispatcherServlet,其实全部都会打到doService()
方法上。我们看看这个doService()
方法做了啥:
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { // 设置一些上下文...(省略一大部分) request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager); try { // 调用doDispatch doDispatch(request, response); } finally { if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { if (attributesSnapshot != null) { restoreAttributesAfterInclude(request, attributesSnapshot); } } } }
所以请求会走到doDispatch(request, response);
里边,我们再进去看看:
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); // 找到HandlerExecutionChain mappedHandler = getHandler(processedRequest); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } // 得到对应的hanlder适配器 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // 拦截前置处理 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // 真实处理请求 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // 视图解析器处理 applyDefaultViewName(processedRequest, mv); // 拦截后置处理 mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } } }
这里的流程跟我们上面的图的流程几乎是一致的了。我们从源码可以知道的是,原来SpringMVC的拦截器是在MappingHandler的时候一齐返回的,返回的是一个HandlerExecutionChain
对象。这个对象也不难,我们看看:
public class HandlerExecutionChain { private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class); // 真实的handler private final Object handler; // 拦截器List private HandlerInterceptor[] interceptors; private List<HandlerInterceptor> interceptorList; private int interceptorIndex = -1; }
OK,整体的流程我们是已经看完了,顺便要不我们去看看它是怎么找到handler的?三歪带着你们冲!我们点进去getHandler()
后,发现它就把默认实现的Handler遍历一遍,然后选出合适的:
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { // 遍历一遍默认的Handler实例,选出合适的就返回 for (HandlerMapping hm : this.handlerMappings) { HandlerExecutionChain handler = hm.getHandler(request); if (handler != null) { return handler; } } return null; }