如何规划建设一个企业网站,谁会建设网站,怎么做网站的自然排名,简历电子模版免费下载先简单聊聊SpringMVC如果你们玩知乎#xff0c;很可能会看到我的身影。我经常会去知乎水回答。在知乎有很多初学者都会问的一个问题#xff1a;「我学习SpringMVC需要什么样的基础」我一定会让他们先学Servlet#xff0c;再学SpringMVC的。虽然说我们在现实开发中几乎不会写… 先简单聊聊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自动帮我们将参数封装成一个BeanServlet:以前我们要导入其他的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 handlerMappings;// 适配器private List handlerAdapters;// 异常处理器private List handlerExceptionResolvers;// 视图解析器private List 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 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;}再进去getHandler里边看看呗里边又有几层我们最后可以看到它根据路径去匹配走到了lookupHandlerMethod这么一个方法protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { List matches new ArrayList();// 获取路径 List directPathMatches this.mappingRegistry.getMappingsByUrl(lookupPath);// 对匹配的排序找到最佳匹配的if (!matches.isEmpty()) { Comparator comparator new MatchComparator(getMappingComparator(request)); Collections.sort(matches, comparator);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) { 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);return bestMatch.handlerMethod; }else {return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request); } }找拦截器大概也是上面的一个过程于是我们就可以顺利拿到HandlerExecutionChain了找到HandlerExecutionChain后我们是先去拿对应的HandlerAdaptor。我们也去看看里边做了什么// 遍历HandlerAdapter实例找到个合适的返回protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { for (HandlerAdapter ha : this.handlerAdapters) { if (ha.supports(handler)) { return ha; } } }我们看一个常用HandlerAdapter实例RequestMappingHandlerAdapter会发现他会初始化很多的参数解析器其实我们经常用的ResponseBody解析器就被内置在里边private List getDefaultArgumentResolvers() { List resolvers new ArrayList(); resolvers.add(new MatrixVariableMethodArgumentResolver()); resolvers.add(new MatrixVariableMapMethodArgumentResolver()); resolvers.add(new ServletModelAttributeMethodProcessor(false));// ResponseBody Requestbody解析器 resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice)); resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), t// 等等return resolvers; }得到HandlerAdaptor后随之而行的就是拦截器的前置处理然后就是真实的mv ha.handle(processedRequest, response, mappedHandler.getHandler())。这里边嵌套了好几层我就不一一贴代码了我们会进入ServletInvocableHandlerMethod#invokeAndHandle方法我们看一下这里边做了什么public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 处理请求 Object returnValue invokeForRequest(webRequest, mavContainer, providedArgs); setResponseStatus(webRequest); if (returnValue null) { if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) { mavContainer.setRequestHandled(true); return; } } //.. mavContainer.setRequestHandled(false); try { // 处理返回值 this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } }处理请求的方法我们进去看看invokeForRequestpublic Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 得到参数 Object[] args getMethodArgumentValues(request, mavContainer, providedArgs); // 调用方法 Object returnValue doInvoke(args); if (logger.isTraceEnabled()) { logger.trace(Method [ getMethod().getName() ] returned [ returnValue ]); } return returnValue; }我们看看它是怎么处理参数的getMethodArgumentValues方法进去看看private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 得到参数 MethodParameter[] parameters getMethodParameters(); Object[] args new Object[parameters.length]; for (int i 0; i MethodParameter parameter parameters[i]; parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); GenericTypeResolver.resolveParameterType(parameter, getBean().getClass()); args[i] resolveProvidedArgument(parameter, providedArgs); if (args[i] ! null) { continue; } // 找到适配的参数解析器 if (this.argumentResolvers.supportsParameter(parameter)) { try { args[i] this.argumentResolvers.resolveArgument( parameter, mavContainer, request, this.dataBinderFactory); continue; } //..... } return args; }这些参数解析器实际上在HandlerAdaptor内置的那些这里不好放代码所以我截个图吧针对于RequestResponseBodyMethodProcessor解析器我们看看里边做了什么public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { // 通过Converters对参数转换 Object arg readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType()); String name Conventions.getVariableNameForParameter(parameter); WebDataBinder binder binderFactory.createBinder(webRequest, arg, name); // ... mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX name, binder.getBindingResult()); return arg; }再进去readWithMessageConverters里边看看protected Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter param, Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {// ...处理请求头try { inputMessage new EmptyBodyCheckingHttpInputMessage(inputMessage);// HttpMessageConverter实例去对参数转换for (HttpMessageConverter converter : this.messageConverters) { Class converterType (Class) converter.getClass();if (converter instanceof GenericHttpMessageConverter) { GenericHttpMessageConverter genericConverter (GenericHttpMessageConverter) converter;if (genericConverter.canRead(targetType, contextClass, contentType)) {if (logger.isDebugEnabled()) { logger.debug(Read [ targetType ] as \ contentType \ with [ converter ]); }if (inputMessage.getBody() ! null) { inputMessage getAdvice().beforeBodyRead(inputMessage, param, targetType, converterType); body genericConverter.read(targetType, contextClass, inputMessage); body getAdvice().afterBodyRead(body, inputMessage, param, targetType, converterType); }else { body null; body getAdvice().handleEmptyBody(body, inputMessage, param, targetType, converterType); }break; } }//...各种判断return body; }看到这里有没有看不懂想要退出的感觉了别慌三歪带你们看看这份熟悉的配置 bean classorg.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter property namemessageConverters list ref beanjacksonMessageConverter / list property bean bean idjacksonMessageConverterclassorg.springframework.http.converter.json.MappingJackson2HttpMessageConverter property namesupportedMediaTypes list valuetext/html;charsetUTF-8value valueapplication/json;charsetUTF-8value valueapplication/x-www-form-urlencoded;charsetUTF-8value list property property nameobjectMapper refjacksonObjectMapper / bean bean idjacksonObjectMapper classcom.fasterxml.jackson.databind.ObjectMapper /我们在SpringMVC想要使用ResponseBody返回JSON格式都会在配置文件上配置上面的配置RequestMappingHandlerAdapter这个适配器就是上面所说的那个内置了RequestResponseBodyMethodProcessor解析器然后MappingJackson2HttpMessageConverter实际上就是HttpMessageConverter接口的实例然后在返回的时候也经过HttpMessageConverter去将参数转换后写给HTTP响应报文。转换的流程大致如图所示img视图解析器后面就不贴了大概的流程就如上面的源码我再画个图来加深一下理解吧最后SpringMVC我们使用的时候非常简便在内部实际上帮我们做了很多(有各种的HandlerAdaptor)SpringMVC的请求流程面试的时候还是面得很多的还是可以看看源码它帮我们做了什么过一遍可能会发现自己能看懂以前的配置了。关注我觉得有点东西就点一下“赞和在看”吧感谢大家的支持了