这篇文章主要介绍了 SpringMVC 解析 JSON 请求数据问题解析, 小编觉得挺不错的, 现在分享给大家, 也给大家做个参考一起跟随小编过来看看吧
这几年都在搞前后端分离 RESTful 风格, 我们项目中也在这样用前几天有人遇到了解析 JSON 格式的请求数据的问题, 然后说了一下解析的方式, 今天就写篇文章简单的分析一下后台对于 JSON 格式请求数据是怎么解析的
先把例子的代码贴出来:
前端
- <input type="button" value="测试 JSON 数据" onclick="testJSON()" />
- <script type="text/javascript">
- function testJSON() {
- $.ajax({
- type: "POST",
- url: "/testJson",
- contentType: "application/json",
- dataType: "json",
- data: JSON.stringify({"name":"张三"}),
- success: function (jsonResult) {
- alert(jsonResult);
- }
- });
- }
- </script>
后台处理代码如下:
- @RequestMapping(value ="testJson")
- public String testJson(@RequestBody Map name, HttpServletRequest request){
- System.out.println(name);
- return "jsonp";
- }
这里需要注意的是: 要在参数对象上加上 @RequestBody 注解, 这个一定不能少, 后台在接收 JSON 数据的时候一定要用自定义的对象或者 Map 对象去接收, 不要用 JDK 中的简单对象 (String/Integer/Long) 来接收
接下来我再把抓出来的 http 请求贴一下:
Content-Type:application/json
这里需要注意的是: Request Payload 中的格式一定要和上图一致, 其他格式 SpringMVC 会解析不出来
OK, 如上的代码就可以搞定一个 JSON 请求数据的解析了下面我们来分析一下 SpringMVC 是怎么处理 JSON 请求的
SpringMVC 处理请求的简单时序图如下:
正常情况下, 一个请求在 SpringMVC 中一般会调用 doDispatch 这个方法, 我们进入到这个方法中直接跳到
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
这一行, 这一行上面的内容我们以后再找机会分析
ha.handle 这个方法会调用 org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter 中的 handle 方法, 这个方法里面很简单, 就是调用了 handleInternal 这个方法, 代码如下:
- public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
- throws Exception {
- return handleInternal(request, response, (HandlerMethod) handler);
- }
而 handleInternal 这个方法调用的是 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter 中的 handleInternal 方法, 我们进入到这个方法中看看这个方法中都干了一些什么事:
- @Override
- protected ModelAndView handleInternal(HttpServletRequest request,
- HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
- ModelAndView mav;
- checkRequest(request);// 检查是不是所支持的请求类型是不是要求 session
- // Execute invokeHandlerMethod in synchronized block if required.
- if (this.synchronizeOnSession) {//session 中是不是要求同步执行
- HttpSession session = request.getSession(false);
- if (session != null) {
- Object mutex = WebUtils.getSessionMutex(session);
- synchronized (mutex) {// 同步执行方法调用
- mav = invokeHandlerMethod(request, response, handlerMethod);
- }
- }
- else {
- // No HttpSession available -> no mutex necessary
- mav = invokeHandlerMethod(request, response, handlerMethod);
- }
- }
- else {
- // No synchronization on session demanded at all...
- mav = invokeHandlerMethod(request, response, handlerMethod);// 这三个 invokeHandlerMethod 调用的是同一个方法
- }// 缓存的设置
- if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
- if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
- applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
- }
- else {
- prepareResponse(response);
- }
- }
- return mav;
- }
在上面的这个方法中我们需要关注的是 invokeHandlerMethod 这个方法 invokeHandlerMethod 这个方法有点复杂, 这个方法中干了很多的事, 像创建数据验证类创建方法处理类模型视图容器等在这里我们先忽略这些, 直接跳到
invocableMethod.invokeAndHandle(webRequest, mavContainer);
这里这个方法在 org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod 中在这个方法中我们只关注第一句话:
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
invokeForRequest 这个方法在 org.springframework.web.method.support.InvocableHandlerMethod 中, 同样在这个方法中我们也只关注第一句话:
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
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)) { //(1) 支持的参数类型
- try {
args[i] = this.argumentResolvers.resolveArgument( // (2) 给参数赋值校验的一些操作
- parameter, mavContainer, request, this.dataBinderFactory);
- continue;
- }
- catch (Exception ex) {
- if (logger.isDebugEnabled()) {
- logger.debug(getArgumentResolutionErrorMessage("Error resolving argument", i), ex);
- }
- throw ex;
- }
- }
- if (args[i] == null) {
- String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i);
- throw new IllegalStateException(msg);
- }
- }
- return args;
- }
我们先来看看上面的代码中 (1) 的地方这个地方是给方法中的参数匹配一个合适的解析器这个方法的真正调用的是
- org.springframework.web.method.support.HandlerMethodArgumentResolverComposite#getArgumentResolver 这个方法
- private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
- HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);// 如果缓存中已经存在了, 则从缓存中取
- if (result == null) {
- for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {// 遍历所有的参数解析器
- if (logger.isTraceEnabled()) {
- logger.trace("Testing if argument resolver [" + methodArgumentResolver + "] supports [" +
- parameter.getGenericParameterType() + "]");
- }
- if (methodArgumentResolver.supportsParameter(parameter)) {// 匹配合适的参数解析器并放入到缓存中
- result = methodArgumentResolver;
- this.argumentResolverCache.put(parameter, result);
- break;
- }
- }
- }
- return result;
- }
那 SpringMVC 种提供了多少参数解析器呢? 看下图所示:
大概有 30 来个, 瞬间觉得 SpringMVC 好强大啊, 给人一种无论你在 Header 里 Cookie 里 Body 里还是 Path 里, 无论是什么类型的参数我都能给你解析了的霸气我们这里的匹配到的参数解析器是 org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor 这个类我们接着来看上面代码中的(2)resolveArgument 这个方法真的调用的就是 RequestResponseBodyMethodProcessor 这个类中的 resolveArgument 的方法我们进入到这个方法中看一下:
- public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
- NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
- // 这里是对参数的解析赋值
- Object arg = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType());//[1]
- String name = Conventions.getVariableNameForParameter(parameter);
- // 获取参数校验的具体类
- WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
- if (arg != null) {
- validateIfApplicable(binder, parameter);// 进行参数校验
- if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
- throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
- }
- }
- mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
- return arg;
- }
我们重点看上面代码中 [1] 的地方方法中的代码如下:
- @Override
- protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter methodParam,
- Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
- // 获取请求对象
- HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
- ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
- // 从请求输入流中解析出参数的值
- Object arg = readWithMessageConverters(inputMessage, methodParam, paramType);
- if (arg == null) {
- if (checkRequired(methodParam)) {// 校验参数是不是必须的
- throw new HttpMessageNotReadableException("Required request body is missing:" +
- methodParam.getMethod().toGenericString());
- }
- }
- return arg;
- }
我们重点要看的是 org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver 中的 readWithMessageConverters 方法
这个方法很长, 在这个方法中会获取 ContentType 参数的类型 Method 重新封装 Request 等等的操作我们需要关注这三行代码:
- inputMessage = getAdvice().beforeBodyRead(inputMessage, param, targetType, converterType);
- body = genericConverter.read(targetType, contextClass, inputMessage);[1]
- body = getAdvice().afterBodyRead(body, inputMessage, param, targetType, converterType);
为参数赋值的是 [1] 这行代码这里调用的是 org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter 中的 read 方法, 代码如下:
- @Override
- public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage)
- throws IOException, HttpMessageNotReadableException {
/ 获取 Java 中的类型
- JavaType javaType = getJavaType(type, contextClass);
- return readJavaType(javaType, inputMessage);// 按照 Java 的类型, 为参数赋值
- }
- private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) {
- try {
- if (inputMessage instanceof MappingJacksonInputMessage) {
- Class<?> deserializationView = ((MappingJacksonInputMessage) inputMessage).getDeserializationView();
- if (deserializationView != null) {
- return this.objectMapper.readerWithView(deserializationView).forType(javaType).
- readValue(inputMessage.getBody());
- }
- }
- return this.objectMapper.readValue(inputMessage.getBody(), javaType);//[1]调用 Jackson 中的方法, 解析 Body 的内容, 赋值为 java 的类型
- }
- catch (IOException ex) {
- throw new HttpMessageNotReadableException("Could not read document:" + ex.getMessage(), ex);
- }
- }
this.objectMapper.readValue 这个方法会掉到 Jackson 相关的 jar 中再往下跟的话还有很深, 说实在的里面有很多的方法我还没看明白, 所以我们就不继续往下走了这个方法中大致干的事是按照相应的编码读取 HTTP 请求中请求体里的内容, 由于是 JSON 格式的, 所以又会把 JSON 格式的数据转换为传入进去的 Java 类型对象
后记:
如果我们知道请求格式是 JSON 的话, 我们可以自己写一个简单的请求体的解析, 但是在项目中最好别这样做代码如下:
- @RequestMapping(value ="testJson")
- public String testJson(HttpServletRequest request){
- try {
- InputStream inputStream = request.getInputStream();
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- byte[] bytes = new byte[1024];
- int flag = 0;
- while ((flag = inputStream.read(bytes))> 0){
- byteArrayOutputStream.write(bytes,0,flag);
- }
- System.out.println(new String(byteArrayOutputStream.toByteArray(),request.getCharacterEncoding()));
- } catch (IOException e) {
- e.printStackTrace();
- }
- return "jsonp";
- }
请求信息如下:
后台输出结果如下:
来源: http://www.phperz.com/article/18/0316/357689.html