只有光头才能变强。文本已收录至个人GitHub精选文章,欢迎Star:https://github.com/ZhongFuCheng3y/3yhtml
这篇SpringMVC
被催了好久了,这阵子因为作整合系统的事,因此很是很是地忙。这周末早早就回了公司肝这篇文章了。java
若是关注三歪的同窗会发现,三歪最近写的不少文章都是结合了现有的系统去写的。这些问题都是真实开发场景会遇到的、用的上的,这些案例对未工做的同窗帮助应该仍是蛮大的。git
很少BB了,仍是进入今天的正题吧「SpringMVC」github
若是大家玩知乎,极可能会看到个人身影。我常常会去知乎水回答。在知乎有不少初学者都会问的一个问题:「我学习SpringMVC须要什么样的基础」web
我必定会让他们先学Servlet,再学SpringMVC的。虽说咱们在现实开发中几乎不会写原生Servlet的代码了,但我始终认为学完Servlet再学SpringMVC,对理解SpringMVC是有好处的。面试
三歪题外话:我当时在学SpringMVC以前其实已经接触过另一个web框架(固然了Servlet也是学了的),那就是「大名鼎鼎」的Struts2。只要是Struts2有的功能,SpringMVC都会有。当时初学Struts2的时候用的是XML配置的方式去开发的,再转到SpringMVC注解的时候,以为SpringMVC真香。算法
Struts2在2020年已经不用学了,学SpringMVC的基础是Servlet,只要Servlet基础还行,上手SpringMVC应该不成问题。spring
从Servlet到SpringMVC,你会发现SpringMVC帮咱们作了不少的东西,咱们的代码确定是没之前多了。json
Servlet:后端
咱们之前可能须要将传递进来的参数手动封装成一个Bean,而后继续往下传:
SpringMVC:
如今SpringMVC自动帮咱们将参数封装成一个Bean
Servlet:
之前咱们要导入其余的jar
包去手动处理文件上传的细节:
SpringMVC:
如今SpringMVC上传文件用一个MultipartFile对象都给咱们封装好了
........
说白了,在Servlet时期咱们这些活都能干,只不过SpringMVC把不少东西都给屏蔽了,因而咱们用起来就更加舒心了。
在学习SpringMVC的时候实际上也是学习这些功能是怎么用的而已,并不会太难。此次整理的SpringMVC电子书其实也是在讲SpringMVC是如何使用的
如今「电子书」已经放出来了,可是别急,重头戏在后面。显然,经过上面的电子书是能够知道SpringMVC是怎么用的。
可是这在面试的时候人家是不会问你SpringMVC的一些用法的,而SpringMVC面试问得最多的就是:SpringMVC请求处理的流程是怎么样的。
其实也很简单,流程就是下面这张图:
再简化一点,能够发现流程不复杂
在面试的时候甚至能一句话就讲完了,但这够吗,这是面试官想要的吗?那确定不是。那咱们想知道SpringMVC是作了什么吗?想的吧(无论大家想不想,反正三歪想看)。
因为想要主流程更加清晰一点,我会在源码添加部分注释以及删减部分的代码
以@ResponseBody和@RequestBody的Controller代码讲解为主,这是线上环境用得最多的
首先咱们看看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; }
再进去getHandler
里边看看呗,里边又有几层,咱们最后能够看到它根据路径去匹配,走到了lookupHandlerMethod
这么一个方法
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList<Match>(); // 获取路径 List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); // 对匹配的排序,找到最佳匹配的 if (!matches.isEmpty()) { Comparator<Match> 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<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() { List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>(); 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); } }
处理请求的方法咱们进去看看invokeForRequest
public 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 < parameters.length; 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 <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter param, Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException { // ...处理请求头 try { inputMessage = new EmptyBodyCheckingHttpInputMessage(inputMessage); // HttpMessageConverter实例去对参数转换 for (HttpMessageConverter<?> converter : this.messageConverters) { Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) 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; }
看到这里,有没有看不懂,想要退出的感受了??别慌,三歪带大家看看这份熟悉的配置:
<!-- 启动JSON返回格式 --> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="messageConverters"> <list> <ref bean="jacksonMessageConverter" /> </list> </property> </bean> <bean id="jacksonMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>text/html;charset=UTF-8</value> <value>application/json;charset=UTF-8</value> <value>application/x-www-form-urlencoded;charset=UTF-8</value> </list> </property> <property name="objectMapper" ref="jacksonObjectMapper" /> </bean> <bean id="jacksonObjectMapper" class="com.fasterxml.jackson.databind.ObjectMapper" />
咱们在SpringMVC想要使用@ResponseBody
返回JSON格式都会在配置文件上配置上面的配置,RequestMappingHandlerAdapter
这个适配器就是上面所说的那个,内置了RequestResponseBodyMethodProcessor
解析器,而后MappingJackson2HttpMessageConverter
实际上就是HttpMessageConverter
接口的实例
而后在返回的时候也通过HttpMessageConverter去将参数转换后,写给HTTP响应报文。转换的流程大体如图所示:
视图解析器后面就不贴了,大概的流程就如上面的源码,我再画个图来加深一下理解吧:
SpringMVC咱们使用的时候很是简便,在内部实际上帮咱们作了不少(有各类的HandlerAdaptor),SpringMVC的请求流程面试的时候仍是面得不少的,仍是能够看看源码它帮咱们作了什么,过一遍可能会发现本身能看懂之前的配置了。
参考资料:
若是你们想要实时关注我更新的文章以及分享的干货的话,微信搜索Java3y。