Мне нужно опубликовать объект (например, не MultiValueMap) через RestTemplate с типом контента application/x-www-form-urlencoded. Когда я пытаюсь это сделать...
HttpHeaders headers = new HttpHeaders();
HttpEntity request;
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED)
// data is some generic type
request = new HttpEntity<>(data, headers);
// clazz is the Class<T> being returned
restTemplate.exchange(url, method, request, clazz)
... Я получаю следующую ошибку:
org.springframework.web.client.RestClientException: Could not write request: no suitable HttpMessageConverter found for request type [com.whatever.MyRequestPayload] and content type [application/x-www-form-urlencoded]
Вот что я вижу внутри restTemplate.getMessageConverters():
Почему я не хочу ставить MultiValueMap? Две причины:
x-www-form-urlencoded только усложнит ситуацию.x-www-form-urlencoded



Вы пробовали что-то вроде добавления MappingJackson2HttpMessageConverter к RestTemplate
restTemplate.getMessageConverters().add(getMappingJackson2HttpMessageConverter());
public MappingJackson2HttpMessageConverter getMappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_FORM_URLENCODED));
return mappingJackson2HttpMessageConverter;
}
Поддерживаемые носители в сопоставлении по умолчаниюJackson2HttpMessageConverter поддерживают только json. Ответ Мохамеда добавляет еще один конвертер, поддерживающий MediaType.APPLICATION_FORM_URLENCODED. Но я сомневаюсь, что ObjectMapper может конвертировать объекты в тело x-www-form-urlencoded или нет.
Я думаю, вам нужно написать класс для реализации HttpMessageConverter<MyRequestPayload>
Правильно, я сделал это, и в теле появилась строка JSON, а не www-form-urlencoded.
С другой стороны, мне весьма любопытно, почему вы просто не пишете статический метод util для преобразования экземпляра MyRequestPayload в экземпляр MultiValueMap?
Это только один пример полезной нагрузки. Я не хочу писать преобразователь для каждого типа полезной нагрузки, он должен быть универсальным.
Можно ли написать метод util, используя отражение, чтобы получить все имена полей и значения объекта, а затем установить их в MultiValueMap?
@Duncan Да, я только что сделал это и собираюсь опубликовать ответ.
В итоге мне пришлось написать собственный преобразователь HTTP-сообщений, который берет любой объект и записывает его как содержимое с www-form-urlencoded в тело запроса:
RestTemplate template = new RestTemplate(...);
template.getMessageConverters().add(new ObjectToUrlEncodedConverter(mapper));
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.List;
public class ObjectToUrlEncodedConverter implements HttpMessageConverter
{
private static final String Encoding = "UTF-8";
private final ObjectMapper mapper;
public ObjectToUrlEncodedConverter(ObjectMapper mapper)
{
this.mapper = mapper;
}
@Override
public boolean canRead(Class clazz, MediaType mediaType)
{
return false;
}
@Override
public boolean canWrite(Class clazz, MediaType mediaType)
{
return getSupportedMediaTypes().contains(mediaType);
}
@Override
public List<MediaType> getSupportedMediaTypes()
{
return Collections.singletonList(MediaType.APPLICATION_FORM_URLENCODED);
}
@Override
public Object read(Class clazz, HttpInputMessage inputMessage) throws HttpMessageNotReadableException
{
throw new NotImplementedException();
}
@Override
public void write(Object o, MediaType contentType, HttpOutputMessage outputMessage) throws HttpMessageNotWritableException
{
if (o != null)
{
String body = mapper
.convertValue(o, UrlEncodedWriter.class)
.toString();
try
{
outputMessage.getBody().write(body.getBytes(Encoding));
}
catch (IOException e)
{
// if UTF-8 is not supporter then I give up
}
}
}
private static class UrlEncodedWriter
{
private final StringBuilder out = new StringBuilder();
@JsonAnySetter
public void write(String name, Object property) throws UnsupportedEncodingException
{
if (out.length() > 0)
{
out.append("&");
}
out
.append(URLEncoder.encode(name, Encoding))
.append(" = ");
if (property != null)
{
out.append(URLEncoder.encode(property.toString(), Encoding));
}
}
@Override
public String toString()
{
return out.toString();
}
}
}
пожалуйста, можете ли вы добавить пример для объекта картографа?
@Esameldean просто создайте экземпляр ObjectMapper следующим образом: ObjectMapper mapper = new ObjectMapper(); restTemplate.getMessageConverters().add (новый ObjectToUrlEncodedConverter (сопоставитель));
Если вы используете WebMvcConfigurerAdapter, вы можете получить такие ошибки. Также WebMvcConfigurerAdapter устарел с Spring 5.x.x. Попробуйте использовать WebMvcConfigurer. После этого вам придется переопределить:
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {}
Вы можете попробовать использовать https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/http/converter/FormHttpMessageConverter.html
RestTemplate template = new RestTemplate(...);
template.getMessageConverters().add(new org.springframework.http.converter.FormHttpMessageConverter.FormHttpMessageConverter());
Вы можете использовать FormHttpMessageConverter для типа содержимого «application/x-www-form-urlencoded», как показано ниже, и отправлять данные в методе публикации RestTemplate для тела как MultiValueMap<String, String>
<bean id = "restTemplate" class = "org.springframework.web.client.RestTemplate" lazy-init = "true">
<constructor-arg name = "requestFactory" ref = "bufferingClientHttpRequestFactory" />
<property name = "errorHandler" ref = "responeErrorHandler" />
<property name = "messageConverters">
<list>
<bean class = "org.springframework.http.converter.FormHttpMessageConverter" />
<bean class = "org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
</list>
</property>
</bean>
Версия конфигурации Java
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new FormHttpMessageConverter());
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
return restTemplate
}
Причина: нет конвертера, который мог бы преобразовать ваш java-объект в тело запроса в формате x-www-form-urlencoded.
Решение 1: создайте такой конвертер, как пишет @Josh M..
Решение 2: преобразуйте ваш java-объект в MultiValueMap, и в весенней загрузке уже есть преобразователь с именем FormHttpMessageConverter, который автоматически преобразует MultiValueMap в тело запроса в формате x-www-form-urlencoded.
Итак, в решении 2 все, что вам нужно, это преобразовать ваш объект Java в MultiValueMap:
MultiValueMap<String, String> bodyPair = new LinkedMultiValueMap();
bodyPair.add(K1, V1);
bodyPair.add(K1, V2);
bodyPair.add(K2, V2);
...
K1, V1, K2, V2, ..., означает имена полей и соответствующие значения в вашем java-объекте. Все поля, которые вы объявили в своем классе Java, должны быть добавлены. Если полей слишком много, рассмотрите возможность использования Java Reflection.
Он уже есть, посмотрите на скриншот. Я попробую добавить его для определенного типа носителя.