欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

关于Spring返回json的问题

程序员文章站 2022-07-12 19:42:18
...
今天测试了一下搭建一个新的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的值如下:
关于Spring返回json的问题
这些值如何获取呢,看源码:
      /**
       * 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