无法将转换JSON数组传递给spring控制器中的对象List

时间:2014-08-07 07:30:43

标签: java json spring spring-mvc

我正在尝试实现一个场景,我希望能够将一个JSON对象数组作为请求体发送到Spring MVC控制器。

我已经阅读了以下帖子

  1. Custom HttpMessageConverter with @ResponseBody to do Json things
  2. Howto get rid of <mvc:annotation-driven />?
  3. http://prasanthnath.wordpress.com/2013/08/23/type-conversion-in-spring/#more-199
  4. http://gerrydevstory.com/2013/08/14/posting-json-to-spring-mvc-controller/
  5. Spring-Returning json with @ResponseBody when the Accept header is */* throws HttpMediaTypeNotAcceptableException
  6. 但这些建议都没有奏效。如果我错过任何其他帖子,我会道歉。

    这个想法是有两个控制器功能

    1. 从数据库中获取数据控制器将查询数据库并返回要序列化的对象列表。
    2. 接受JSON响应并将其转换为要插入数据库的对象列表。
    3. 第一个工作没有任何明确的序列化。

      @RequestMapping("/config")
      public class ConfigController {
      
        @Autowired
        private final Service service;
      
         // This works. I don't know why.
         @RequestMapping("/fetch", method=RequestMethod.GET)
         @ResponseBody
         @ResponseStatus(HttpStatus.OK)
         public String readConfigProperties() throws Exception {
           ImmutableList<Config> configObjects = this.service.readConfiguration();
           return configObjects;
         }
      }
      

      我无法在请求正文中传递JSON响应,并将它们作为对象列表提供。控制器函数似乎传递了一个链接的哈希映射列表,这不是我想要的。这引发了ClassCastException。功能设置如下:

      更新:我在此帖的先前版本中使用了@ResponseBody注释。我将帖子更改为使用@RequestBody,但没有任何影响。

      @RequestMapping(method=RequestMethod.POST, consumes={"application/json"}, value="/update}
      @ResponseStatus(HttpStatus.OK)
      public void updateConfig(@RequestBody List<Config> configList) throws Exception {
        this.service.updateConfiguration(configList);
      
      }
      

      在这种情况下, configList 是LinkedHashMap对象的列表,因此会引发ClassCastException。我不知道为什么。

      我的标题如下:

      Content-Type: application/json; charset=utf-8
      

      堆栈追踪:

      java.lang.ClassCastException: java.util.LinkedHashMap incompatible with com.kartik.springmvc.model.Config
          com.kartik.springmvc.service.ConfigPropertyService.upsertConfigurationProperties(ConfigPropertyService.java:56)
          com.kartik.springmvc.controller.ConfigController .upsertConfigurationProperties(ConfigController .java:86)
          sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
          sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:88)
          sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:55)
          java.lang.reflect.Method.invoke(Method.java:613)
          org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:213)
          org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:126)
          org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:96)
          org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:617)
          org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:578)
          org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
          org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:923)
          org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:852)
          org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:882)
          org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:789)
          javax.servlet.http.HttpServlet.service(HttpServlet.java:646)
      

      我的转换器和控制器特定配置

         <context:annotation-config />
          <context:component-scan base-package="com.kartik.springmvc.controller" />
          <bean class="com.kartik.springmvc.controller.AppConfigPropertiesConverter" id="appConfigPropertiesConverter"/>
      
          <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
               <property name="messageConverters">
                  <list>
                      <ref bean="appConfigPropertiesConverter" />
                      <bean  class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
                  </list>
              </property>
          </bean>
          <mvc:annotation-driven/>
      

      我的转换器实现如下。 更新:不调用此类。

      public class AppConfigPropertiesConverter extends AbstractHttpMessageConverter<Object> {
      
        public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
      
        private Gson gson = new Gson();
        /**
         * Construct a new {@code GsonHttpMessageConverter}.
         */
        public AppConfigPropertiesConverter() {
          super(new MediaType("application", "json", DEFAULT_CHARSET), new MediaType(
              "application", "*+json", DEFAULT_CHARSET));
        }
      
        /** Supports only {@link Config} instances. */
        @Override
        protected boolean supports(Class<?> clazz) {
          // TODO Auto-generated method stub
          return true;
        }
      
        /**
         * Converts to a list of {@Config}
         */
        @Override
        protected Object readInternal(
            Class<?> clazz, HttpInputMessage inputMessage)
                throws IOException, HttpMessageNotReadableException {
          TypeToken<?> token = TypeToken.get(clazz);
          System.out.println("#################################################################3");
          Reader jsonReader =
              new InputStreamReader(inputMessage.getBody(), DEFAULT_CHARSET.displayName());
          System.out.println("####################################################################");
          try {
            return this.gson.fromJson(jsonReader, token.getType());
          } catch (JsonParseException ex) {
            throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
          }
        }
      
        /**
         * Write the json reprsentation to {@link OutputStream}.
         * 
         * @param config object to be serialized
         * @param output http output message
         */
        @Override
        protected void writeInternal(
            Object config, HttpOutputMessage outputMessage)
                throws IOException, HttpMessageNotWritableException {
          outputMessage.getBody().write(
              this.gson.toJson(config).getBytes(DEFAULT_CHARSET.displayName()));
        }
      
      }
      

      更新:添加了服务层

         public class ConfigPropertyService implements IConfigPropertyService {
      
        private final Log LOG = LogFactory.getLog(ConfigPropertyService.class);
      
        private final IConfigPropertyDao<Config> configPropertyDao;
      
        /**
         * Returns the instance of Dao.
         */
        @Override
        public IConfigPropertyDao<Config> getConfigPropertyDao() {
          return this.configPropertyDao;
        }
      
        /**
         * Initializes the data access tier.
         */
      
            public ConfigPropertyService(IConfigPropertyDao<Config> configPropertyDao) {
              this.configPropertyDao = Objects.requireNonNull(configPropertyDao);
            }
      
        /**
         * {@inheritDoc}
         * @throws ConfigServiceException if the resources can't be cleaned up successfully
         */
        @Override
        public void upsertConfigurationProperties(
            ImmutableList<Config> configModels) {
      
          for (Config config: configModels) {
               // This for loop throws an exception.
          }
      
          // Step # 2: Updating the properties. 
          this.configPropertyDao.upsertProperties(configModels);
        }
      
      
        /**
         * {@inheritDoc}
         */
        @Override
        public ImmutableList<ConfigModel> readConfigProperties() {
          return this.configPropertyDao.readProperties();
      
        }
      
      }
      

      我的要求机构如下。它是一个带有Content-Type: application/json; charset=UTF-8

      的String主体
      [{"propertyName":"anchorIndexingFilter.deduplicate","propertyValue":"false","propertyDescription":"With this enabled the indexer will case-insensitive deduplicate hanchors\n  before indexing. This prevents possible hundreds or thousands of identical anchors for\n  a given page to be indexed but will affect the search scoring (i.e. tf=1.0f).\n  ","editable":true}]   
      

3 个答案:

答案 0 :(得分:0)

stacktrace说....LinkedHashMap incompatible with com.kartik.springmvc.model.Config

错误发生在for (Config config: configModels) configModels来自控制器public void updateConfig(@RequestBody List<Config> configList)

现在有意义:Spring在请求体中看到一个json字符串,并在地图列表中完全爆炸。您要求List并获得List。由于Java通过类型擦除来管理泛型,因此所有列表在运行时都是兼容的。

您有两种主要方法可以解决它:

  • 手动方式(丑陋但简单而健壮):将json String作为... String并手动转换它。 知道它应该是什么以及您想要什么,这样您就可以轻松完成转换。

    public void updateConfig(@RequestBody String jsonString ) {
        ... do actual conversion
    

    如果转换为字符串会导致问题,那么转换为ByteArray总是可行的,因为你将在引擎盖下使用ByteArrayHttpMessageConverter

  • 聪明的方法:找到弹簧绑定中转换的方式,并设法让它生成一个真正的List<Config>。由于Spring原生支持Jackson或Jackson2,您可以尝试自定义它。或者,您可以使用自己的转换器:

    • 创建一个HttpMessageConverter bean - 好吧,你有一个
    • 声明一个明确的RequestMappingHandlerAdapter并在其中注入您的消息转换器
    • 不要忘记注入您可以在应用程序中使用的任何其他消息转换器,因为您正在覆盖Spring默认转换

    它更干净但配置相当先进。

参考资料:Spring参考手册 - Wev MVC框架/使用@RequestBody注释映射请求体 - 使用Spring / HTTP消息转换的远程处理和Web服务

答案 1 :(得分:0)

问题在于您的方法签名是否将LinkedHashMap作为参数传递,但在您的方法中,您正在接收配置对象列表,这会导致类强制转换异常

public void updateConfig(@RequestBody List<Config> configList) 

将上述更改为不完全

public void updateConfig(@RequestBody LinkedHashMap<K,V> configList)

答案 2 :(得分:0)

经过多次排列和组合后,我使用了自定义解串器。它的工作原理如下

public class AppConfigDeserializer implements JsonDeserializer<List<Config>> {

 /**
  * Creates an collection of {@link Config}.
  * 
  * <p>If the stringified json element represents a json array as in {@code [{'foo': 'bar'}]}, then
  * the serialized instance will be an instance of {@link JsonArray} comprising of
  * {@link JsonObject}
  * 
  * <p>If th stringified json element represents a json object as in {@code {'foo': 'bar'}}, then
  * the serialized instance will be an instance of {@link JsonObject). 
  * 
  */
  @Override
  public List<Config> deserialize(JsonElement json, Type typeOfT,
      JsonDeserializationContext context) throws JsonParseException {
    json = Objects.requireNonNull(json);
    List<Config> configs = new ArrayList<>();
    if (JsonArray.class.isAssignableFrom(json.getClass())) {
        JsonArray jsonArray = (JsonArray) json;
        for (JsonElement jsonElement : jsonArray) {
          JsonObject jsonObject = (JsonObject) jsonElement.getAsJsonObject();
          // Initialize the list of models with values to defaulting to primitive values.
          configs.add(
          new Config(
              jsonObject.get("appName") != null ?
                  jsonObject.get("appName").getAsString() : null,
              jsonObject.get("propertyName") != null ?
                  jsonObject.get("propertyName").getAsString() : null,
              jsonObject.get("propertyValue") != null ?
                  jsonObject.get("propertyValue").getAsString() : null,
              jsonObject.get("propertyDescription") != null ?
                  jsonObject.get("propertyDescription").getAsString() : null,
              jsonObject.get("editable") != null ?
                  jsonObject.get("editable").getAsBoolean() : false,
              jsonObject.get("updated") != null ?
                jsonObject.get("updated").getAsBoolean() : false));
        }
    } else if (JsonObject.class.isAssignableFrom(json.getClass())) {
      // Just a simple json string.
      JsonObject jsonObject = (JsonObject) json.getAsJsonObject();
      configs.add(new Config(
                jsonObject.get("appName") != null ?
                    jsonObject.get("appName").getAsString() : null,
                jsonObject.get("propertyName") != null ?
                    jsonObject.get("propertyName").getAsString() : null,
                jsonObject.get("propertyValue") != null ?
                    jsonObject.get("propertyValue").getAsString() : null,
                jsonObject.get("propertyDescription") != null ?
                    jsonObject.get("propertyDescription").getAsString() : null,
                jsonObject.get("editable") != null ?
                    jsonObject.get("editable").getAsBoolean() : false,
                jsonObject.get("updated") != null ?
                  jsonObject.get("updated").getAsBoolean() : false));
  }
    return configs;
  }

}

在我的转换器实现中,我引用了转换器clas。

public class AppConfigPropertiesConverter
    extends AbstractHttpMessageConverter<List<Config>> {

  public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

  private Gson gson = null;

  private final GsonBuilder gsonBuilder = new GsonBuilder();

  private final TypeToken<List<Config>> token = new TypeToken<List<Config>>() {};
  /**
   * Construct a new {@code GsonHttpMessageConverter}.
   */
  public AppConfigPropertiesConverter() {
    super(new MediaType("application", "json", DEFAULT_CHARSET), new MediaType(
        "application", "*+json", DEFAULT_CHARSET));
  }

  /**
   * Initializes the type adapters to inorder to deserialize json arrays or json objects.
   */
  public void initializeAdapters() {
   this.gsonBuilder.registerTypeAdapter(token.getType(), new AppConfigDeserializer());
   this.gson = this.gsonBuilder.create();
  }

  /** Supports only {@link L} instances. */
  @Override
  protected boolean supports(Class<?> clazz) {
    // TODO Auto-generated method stub
    return List.class.isAssignableFrom(clazz);
  }

  /**
   * Converts the serialized input to a list of objects.
   * 
   * @param clazz class to be serialized into
   * @param inputMessage message to be read from
   */
  @Override
  protected List<Config> readInternal(
      Class<? extends List<Config>> clazz, HttpInputMessage inputMessage)
      throws IOException, HttpMessageNotReadableException {

    Reader jsonReader =
        new InputStreamReader(inputMessage.getBody(), DEFAULT_CHARSET.displayName());
    return this.gson.fromJson(jsonReader, this.token.getType());
  }

  /**
   * Converts an instance of immutable list to json response.
   * 
   * @param configs list of objects to be serialized
   * @param outputMessage output message to write to
   * @throws IOException thrown if the object can not be serialized
   * @throws HttpMessageNotWritableException if the object can not be written
   */
  @Override
  protected void writeInternal(
      List<Config> configs, HttpOutputMessage outputMessage)
      throws IOException, HttpMessageNotWritableException {
    outputMessage.getBody().write(
        this.gson.toJson(
            configs, this.token.getType()).getBytes(DEFAULT_CHARSET.displayName()));
  }
}

这对我有用。虽然我应该使用https://github.com/spring-projects/spring-android/blob/master/spring-android-rest-template/src/main/java/org/springframework/http/converter/json/GsonHttpMessageConverter.java代替。