关于Spring返回json的问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011548068/article/details/73459732
今天测试了一下搭建一个新的SpringMVC项目
然后测试Controller返回String类型,加上了@ResponseBody,访问之后是可以正常访问到返回内容
然后我改成返回Map类型,访问之后直接报错。
在这之前首先我是:
1.没有配置StringHttpMessageConvertor
2.maven没有把json包引入
严重: Servlet.service() for servlet [dispatcher] in context with path [/ElecEmp] threw exception [Request processing failed; nested exception is java.lang.IllegalArgumentException: No converter found for return value of type: class java.util.HashMap] with root cause
java.lang.IllegalArgumentException: No converter found for return value of type: class java.util.HashMap
      at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:187)
      at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:174)
      at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:81)
      at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:132)
      at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
      at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
      at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
      at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)
      at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
      at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
      at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
      at javax.servlet.http.HttpServlet.service(HttpServlet.java:622)


错误信息里看到没有converter用来处理HashMap
      /**
       * Writes the given return type to the given output message.
       * @param value the value to write to the output message
       * @param returnType the type of the value
       * @param inputMessage the input messages. Used to inspect the {@code Accept} header.
       * @param outputMessage the output message to write to
       * @throws IOException thrown in case of I/O errors
       * @throws HttpMediaTypeNotAcceptableException thrown when the conditions indicated by {@code Accept} header on
       * the request cannot be met by the message converters
       */
      @SuppressWarnings("unchecked")
      protected <T> void writeWithMessageConverters(T value, MethodParameter returnType,
                  ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
                  throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

            Object outputValue;
            Class<?> valueType;
            Type declaredType;

            if (value instanceof CharSequence) {
                  outputValue = value.toString();
                  valueType = String.class;
                  declaredType = String.class;
            }
            else {
                  outputValue = value;
                  valueType = getReturnValueType(outputValue, returnType);
                  declaredType = getGenericType(returnType);
            }

            HttpServletRequest request = inputMessage.getServletRequest();
            List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
            List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);

            if (outputValue != null && producibleMediaTypes.isEmpty()) {
                  throw new IllegalArgumentException("No converter found for return value of type: " + valueType);
            }
..............
看到报错的地方是因为producibleMediaTypes的值为空。getProducibleMediaTypes是获取produces的,看下源码:

      /**
       * Returns the media types that can be produced:
       * <ul>
       * <li>The producible media types specified in the request mappings, or
       * <li>Media types of configured converters that can write the specific return value, or
       * <li>{@link MediaType#ALL}
       * </ul>
       * @since 4.2
       */
      @SuppressWarnings("unchecked")
      protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> valueClass, Type declaredType) {
            Set<MediaType> mediaTypes = (Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
            if (!CollectionUtils.isEmpty(mediaTypes)) {
                  return new ArrayList<MediaType>(mediaTypes);
            }
            else if (!this.allSupportedMediaTypes.isEmpty()) {
                  List<MediaType> result = new ArrayList<MediaType>();
                  for (HttpMessageConverter<?> converter : this.messageConverters) {
                        if (converter instanceof GenericHttpMessageConverter && declaredType != null) {
                              if (((GenericHttpMessageConverter<?>) converter).canWrite(declaredType, valueClass, null)) {
                                    result.addAll(converter.getSupportedMediaTypes());
                              }
                        }
                        else if (converter.canWrite(valueClass, null)) {
                              result.addAll(converter.getSupportedMediaTypes());
                        }
                  }
                  return result;
            }
            else {
                  return Collections.singletonList(MediaType.ALL);
            }
      }



请求的mediaType是从request中获取的,用google浏览器查看请求,看到请求头默认是:
Accept:  text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
因此mediaTypes的值不为空,到了下面的逻辑,是先获取全部支持的mediaType,我这边获取到
[application/octet-stream, text/plain, application/xml, text/xml, application/x-www-form-urlencoded, application/*+xml, multipart/form-data, */*]
而且messageConverter的值如下:

这些值如何获取呢,看源码:
      /**
       * Return the media types supported by all provided message converters sorted
       * by specificity via {@link MediaType#sortBySpecificity(List)}.
       */
      private static List<MediaType> getAllSupportedMediaTypes(List<HttpMessageConverter<?>> messageConverters) {
            Set<MediaType> allSupportedMediaTypes = new LinkedHashSet<MediaType>();
            for (HttpMessageConverter<?> messageConverter : messageConverters) {
                  allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());
            }
            List<MediaType> result = new ArrayList<MediaType>(allSupportedMediaTypes);
            MediaType.sortBySpecificity(result);
            return Collections.unmodifiableList(result);
      }


很明显,意思是获取messageConverters支持的mediatype,关键是看messageConverters是什么值,我看了一下源码,调用了此方法的往上追溯是Resolver,Adapter方法
也就是适配器,用来查询适配的controller,再往上到顶,是WebMvcConfigurationSupport(带有@Bean),调用了
      /**
       * Provides access to the shared {@link HttpMessageConverter}s used by the
       * {@link RequestMappingHandlerAdapter} and the
       * {@link ExceptionHandlerExceptionResolver}.
       * This method cannot be overridden.
       * Use {@link #configureMessageConverters(List)} instead.
       * Also see {@link #addDefaultHttpMessageConverters(List)} that can be
       * used to add default message converters.
       */
      protected final List<HttpMessageConverter<?>> getMessageConverters() {
            if (this.messageConverters == null) {
                  this.messageConverters = new ArrayList<HttpMessageConverter<?>>();
                  configureMessageConverters(this.messageConverters);
                  if (this.messageConverters.isEmpty()) {
                        addDefaultHttpMessageConverters(this.messageConverters);
                  }
                  extendMessageConverters(this.messageConverters);
            }
            return this.messageConverters;
      }


configureMessageConverters是抽象方法,我觉得应该是实现了此方法的类可以对messageConverters做新的配置。
假如没有做配置,那么就还是空,因为开头我说了我没有配置messageConverters,所以这里拿到的messageConverters是空的。所以会调用addDefaultHttpMessageConverters
源码:
      /**
       * Adds a set of default HttpMessageConverter instances to the given list.
       * Subclasses can call this method from {@link #configureMessageConverters(List)}.
       * @param messageConverters the list to add the default message converters to
       */
      protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
            StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
            stringConverter.setWriteAcceptCharset(false);

            messageConverters.add(new ByteArrayHttpMessageConverter());
            messageConverters.add(stringConverter);
            messageConverters.add(new ResourceHttpMessageConverter());
            messageConverters.add(new SourceHttpMessageConverter<Source>());
            messageConverters.add(new AllEncompassingFormHttpMessageConverter());

            if (romePresent) {
                  messageConverters.add(new AtomFeedHttpMessageConverter());
                  messageConverters.add(new RssChannelHttpMessageConverter());
            }

            if (jackson2XmlPresent) {
                  ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.xml().applicationContext(this.applicationContext).build();
                  messageConverters.add(new MappingJackson2XmlHttpMessageConverter(objectMapper));
            }
            else if (jaxb2Present) {
                  messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
            }

            if (jackson2Present) {
                  ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().applicationContext(this.applicationContext).build();
                  messageConverters.add(new MappingJackson2HttpMessageConverter(objectMapper));
            }
            else if (gsonPresent) {
                  messageConverters.add(new GsonHttpMessageConverter());
            }
      }


所以默认的话MessageConverter有5个ByteArrayHttpMessageConverter,stringConverter,ResourceHttpMessageConverter,SourceHttpMessageConverter,AllEncompassingFormHttpMessageConverter
源码里也看到了:
private static boolean romePresent =
                  ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", WebMvcConfigurationSupport.class.getClassLoader());

      private static final boolean jaxb2Present =
                  ClassUtils.isPresent("javax.xml.bind.Binder", WebMvcConfigurationSupport.class.getClassLoader());

      private static final boolean jackson2Present =
                  ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", WebMvcConfigurationSupport.class.getClassLoader()) &&
                              ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", WebMvcConfigurationSupport.class.getClassLoader());

      private static final boolean jackson2XmlPresent =
                  ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", WebMvcConfigurationSupport.class.getClassLoader());

      private static final boolean gsonPresent =
                  ClassUtils.isPresent("com.google.gson.Gson", WebMvcConfigurationSupport.class.getClassLoader());


.....................

      /**
       * Determine whether the {@link Class} identified by the supplied name is present
       * and can be loaded. Will return {@code false} if either the class or
       * one of its dependencies is not present or cannot be loaded.
       * @param className the name of the class to check
       * @param classLoader the class loader to use
       * (may be {@code null}, which indicates the default class loader)
       * @return whether the specified class is present
       */
      public static boolean isPresent(String className, ClassLoader classLoader) {
            try {
                  forName(className, classLoader);
                  return true;
            }
            catch (Throwable ex) {
                  // Class or one of its dependencies is not present...
                  return false;
            }
      }


这些值的定义,所以假如maven没有配json相关的jar包,MessageConverter也就没有相应的处理json的converter。
所以回到开始那里,因为没有一个converter可以写hashmap到响应体里,所以就会报错。

所以结论就是,只要配置了json相关的jar包,就可以返回json类型的数据。比如com.fasterxml.jackson

猜你喜欢

转载自blog.csdn.net/u011548068/article/details/73459732