Использовать RestTemplate с объектом в качестве данных и типом контента application/x-www-form-urlencoded?

Мне нужно опубликовать объект (например, не 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():

Использовать RestTemplate с объектом в качестве данных и типом контента application/x-www-form-urlencoded?

Почему я не хочу ставить MultiValueMap? Две причины:

  1. это код общего назначения, который используется для отправки запросов на несколько конечных точек, поэтому добавление перегрузки специально для x-www-form-urlencoded только усложнит ситуацию.
  2. не похоже, что мне нужно - я просто не знаю, какой HttpMessageConverter нужно использовать для поддержки преобразования объектов в строку x-www-form-urlencoded
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
16
0
21 449
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Вы пробовали что-то вроде добавления MappingJackson2HttpMessageConverter к RestTemplate

restTemplate.getMessageConverters().add(getMappingJackson2HttpMessageConverter());

public MappingJackson2HttpMessageConverter getMappingJackson2HttpMessageConverter() {
    MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
    mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_FORM_URLENCODED));
    return mappingJackson2HttpMessageConverter;
}

Он уже есть, посмотрите на скриншот. Я попробую добавить его для определенного типа носителя.

Josh M. 12.06.2019 16:47

Поддерживаемые носители в сопоставлении по умолчаниюJackson2HttpMessageConverter поддерживают только json. Ответ Мохамеда добавляет еще один конвертер, поддерживающий MediaType.APPLICATION_FORM_URLENCODED. Но я сомневаюсь, что ObjectMapper может конвертировать объекты в тело x-www-form-urlencoded или нет.

Duncan 12.06.2019 17:15

Я думаю, вам нужно написать класс для реализации HttpMessageConverter<MyRequestPayload>

Duncan 12.06.2019 17:17

Правильно, я сделал это, и в теле появилась строка JSON, а не www-form-urlencoded.

Josh M. 12.06.2019 17:18

С другой стороны, мне весьма любопытно, почему вы просто не пишете статический метод util для преобразования экземпляра MyRequestPayload в экземпляр MultiValueMap?

Duncan 12.06.2019 17:19

Это только один пример полезной нагрузки. Я не хочу писать преобразователь для каждого типа полезной нагрузки, он должен быть универсальным.

Josh M. 12.06.2019 17:23

Можно ли написать метод util, используя отражение, чтобы получить все имена полей и значения объекта, а затем установить их в MultiValueMap?

Duncan 12.06.2019 17:26

@Duncan Да, я только что сделал это и собираюсь опубликовать ответ.

Josh M. 12.06.2019 18:20
Ответ принят как подходящий

В итоге мне пришлось написать собственный преобразователь 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();
        }
    }
}

пожалуйста, можете ли вы добавить пример для объекта картографа?

Esam eldean 30.03.2021 11:43

@Esameldean просто создайте экземпляр ObjectMapper следующим образом: ObjectMapper mapper = new ObjectMapper(); restTemplate.getMessageConverters().add (новый ObjectToUrlEncodedConverter (сопоставитель));

Mauricio Zárate 08.04.2021 16:15

Если вы используете 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.

Другие вопросы по теме