关于Spring返回json的问题

来源:互联网 发布:金庸x 知乎 编辑:程序博客网 时间:2024/06/14 05:37
今天测试了一下搭建一个新的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 causejava.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