我正在尝试实现一个场景,我希望能够将一个JSON对象数组作为请求体发送到Spring MVC控制器。
我已经阅读了以下帖子
但这些建议都没有奏效。如果我错过任何其他帖子,我会道歉。
这个想法是有两个控制器功能
第一个工作没有任何明确的序列化。
@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
[{"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}]
答案 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参考手册 - 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()));
}
}