springmvc 传对象报400_那么火的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自动帮我们将参数封装成一个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 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); } }
处理请求的方法我们进去看看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 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 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-8value> <value>application/json;charset=UTF-8value> <value>application/x-www-form-urlencoded;charset=UTF-8value> 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的请求流程面试的时候还是面得很多的,还是可以看看源码它帮我们做了什么,过一遍可能会发现自己能看懂以前的配置了。
关注我
觉得有点东西就点一下“赞和在看”吧!感谢大家的支持了!
springmvc 传对象报400_那么火的SpringMVC到底有什么过人之处呢相关推荐
- springmvc 传对象报400_源码导读:深入理解SpringMVC报400时的流程
相信很多同学都了解过(或者面试前都会复习过)springMVC的执行流程,如下图: 网图 转载请注明出处:Michael孟良 这里我想细节地理解下springMVC报400(也就是上图第5步拿到Han ...
- springmvc 传对象报400_springmvc 通过对象来接收参数,为什么默认会返回该对象?
我是忘记加@RequestBody也出现问题,现在解决了,解决如下: 原来的代码: @RequestMapping("login") public ModelAndView log ...
- Ajax传JSON对象报错:JSON parse error: Unrecognized token ‘ids‘: was expecting (‘true‘, ‘false‘ or ‘null‘);
org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Unrecognized t ...
- Spring/Boot/Cloud系列知识:SpringMVC 传参详解(下)
(接上文<Spring/Boot/Cloud系列知识:SpringMVC 传参详解(上)>) 2.3.通过@PathVariable注解基于URL匹配符接收请求传参 为了便于开发人员实现更 ...
- spring cloud feign 上传文件报not a type supported by this encoder解决方案
上传文件调用外部服务报错: not a type supported by this encoder 查看SpringFormEncoder类的源码: 1 public class SpringFor ...
- 06Java第六课 获取输入框内容(传对象,参数,值)
在上一课中实现了对 按钮被点击的监听,接下来要实现 对输入框内容的获取 由之前的界面中可知,账号和密码在输入框对象中,也就是nameInput和pwdInput对象中.当用户输入了账号和密码并点击按钮 ...
- spring boot ajax 415,解决@RequestBody接收json对象报错415的问题
@RequestBody接收json对象报错415 前端请求: $.ajax({ url: basePath() + "/index/login.do", type : " ...
- SpringMVC对HTTP报文体的处理
客户端和服务端HTTP报文传递消息,而HTTP报文包含报文头和报文体.通常,解析请求参数以及返回页面都不需要我们关心HTTP报文体的读取和生成过程.但在某些特定场景下需要直接到请求报文中读取报文体,或 ...
- java找不到对象报错_java找不到对象报错
java找不到对象报错 [2021-02-04 02:18:22] 简介: php去除nbsp的方法:首先创建一个PHP代码示例文件:然后通过"preg_replace("/(\ ...
最新文章
- MyBatis学习笔记(六)动态sql
- 【一周入门MySQL—4】数据库进阶练习
- SAP系统和微信集成的系列教程之四:如何将SAP C4C主数据变化推送给微信公众号的关注者
- 计算机广告制作未来发展还行吗,计算机多媒体设计专业和广告设计制作那个好...
- “ 紫手环的力量 ” :我想,美好的生活应该是自已造就的...
- C语言 指针数组 - C语言零基础入门教程
- xgboost参数_XGBoost实战和参数详解
- 圆形界面 开启相机_「基础篇三」手机摄影拍照界面详解
- 【鲲鹏来了】手把手教你在鲲鹏上使用编程语言——C语言
- 4.FreeRTOS学习笔记-消息队列
- 声谱图,梅尔语谱,倒谱,梅尔倒谱系数
- java不带括号_java – 打印数组,不带括号和逗号
- SpringBoot2.0之整合Dubbo
- 定时重启php,linux系统定时重启
- Java快逸报表展现demo_快逸报表操作积累.docx
- ubuntu下锐捷客户端连接校园网
- 发电厂及电力系统类毕业论文文献都有哪些?
- 二维傅里叶变换的意义
- css玻璃雨滴效果,纯css实现窗户玻璃雨滴逼真效果
- Qtxlsx操作Excel之使用
热门文章
- .NET5全面拥抱Azure云,微软市值重回巅峰,那些年吹过的牛,都实现了!
- 使用 Visual Studio 2019 批量添加代码文件头
- ASP.NET Core中的响应压缩
- 平台or职位,你怎么选?
- 用.NET进行客户端Web开发?看这个Bootstrap风格的BlazorUI组件库
- Sql Server之旅——第九站 看看DML操作对索引的影响
- Istio 1.5 发布——拥抱变化,爱上单体
- asp.net core 3.x Endpoint终结点路由1-基本介绍和使用
- 依赖注入在 dotnet core 中实现与使用:2 使用 Extensions DependencyInjection
- Natasha v2.5.4 版与运行时实战