Подтвердить что ты не робот

Пользовательский HttpMessageConverter с @ResponseBody, чтобы делать вещи Json

Мне не нравится Джексон.

Я хочу использовать ajax, но с Google Gson.

Итак, я пытаюсь понять, как реализовать свой собственный HttpMessageConverter, чтобы использовать его с аннотацией @ResponseBody. Может кто-нибудь потратить время, чтобы показать мне, как мне идти? Какие конфигурации следует включить? Также мне интересно, могу ли я сделать это и все еще использовать < mvc: annotation-driven/ > ?

Спасибо заранее.

Я уже спросил его в Spring Community Foruns около 3 дней назад без ответа, поэтому я прошу здесь посмотреть, есть ли у меня больше шансов. Spring Форумы сообщества ссылаются на мой вопрос

Я также сделал исчерпывающий поиск в Интернете и нашел что-то интересное на эту тему, но кажется, что они думают положить его в Spring 3.1, и я все еще использую Spring 3.0.5: Jira Spring Уточнить вопрос

Ну... теперь я пытаюсь отлаживать код Spring, чтобы узнать, как это сделать, но у меня возникают некоторые проблемы, как я сказал здесь: Spring Ошибка сборки сборки

Если есть другой способ сделать это, и я пропущу его, сообщите мне.

4b9b3361

Ответ 1

Ну... было так сложно найти ответ, и мне пришлось следить за множеством подсказок до неполной информации, что, я думаю, будет хорошо опубликовать полный ответ здесь. Таким образом, это будет проще для следующего поиска этого.

Сначала мне пришлось реализовать пользовательский HttpMessageConverter:


package net.iogui.web.spring.converter;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.charset.Charset;

import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;

import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;

public class GsonHttpMessageConverter extends AbstractHttpMessageConverter<Object> {

    private Gson gson = new Gson();

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

    public GsonHttpMessageConverter(){
        super(new MediaType("application", "json", DEFAULT_CHARSET));
    }

    @Override
    protected Object readInternal(Class<? extends Object> clazz,
                                  HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {

        try{
            return gson.fromJson(convertStreamToString(inputMessage.getBody()), clazz);
        }catch(JsonSyntaxException e){
            throw new HttpMessageNotReadableException("Could not read JSON: " + e.getMessage(), e);
        }

    }

    @Override
    protected boolean supports(Class<?> clazz) {
        return true;
    }

    @Override
    protected void writeInternal(Object t, 
                                 HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {

        //TODO: adapt this to be able to receive a list of json objects too

        String json = gson.toJson(t);

        outputMessage.getBody().write(json.getBytes());
    }

    //TODO: move this to a more appropriated utils class
    public String convertStreamToString(InputStream is) throws IOException {
        /*
         * To convert the InputStream to String we use the Reader.read(char[]
         * buffer) method. We iterate until the Reader return -1 which means
         * there no more data to read. We use the StringWriter class to
         * produce the string.
         */
        if (is != null) {
            Writer writer = new StringWriter();

            char[] buffer = new char[1024];
            try {
                Reader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
                int n;
                while ((n = reader.read(buffer)) != -1) {
                    writer.write(buffer, 0, n);
                }
            } finally {
                is.close();
            }
            return writer.toString();
        } else {
            return "";
        }
    }

}

Затем мне пришлось отключить тег, привязанный к annnotaion, и настроить все своими руками в файле конфигурации spring -mvc:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <!-- Configures the @Controller programming model -->

    <!-- To use just with a JSR-303 provider in the classpath 
    <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
    -->

    <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean" />

    <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
        <property name="webBindingInitializer">
            <bean class="net.iogui.web.spring.util.CommonWebBindingInitializer" />
        </property>
        <property name="messageConverters">
            <list>
                <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter" />
                <bean class="org.springframework.http.converter.StringHttpMessageConverter" />
                <bean class="org.springframework.http.converter.ResourceHttpMessageConverter" />
                <bean class="net.iogui.web.spring.converter.GsonHttpMessageConverter" />
                <bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter" />
                <bean class="org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter" />
                <!-- bean class="org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter" /-->
            </list>
        </property>
    </bean>
    <bean id="handlerMapping" class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />


    <context:component-scan base-package="net.iogui.teste.web.controller"/>

    <!-- Forwards requests to the "/" resource to the "login" view -->
    <mvc:view-controller path="/" view-name="home"/>

    <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources/ directory -->
    <mvc:resources mapping="/resources/**" location="/resources/" />

    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <property name="prefix" value="/WEB-INF/view/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

</beans>

Посмотрите, чтобы заставить Formater и Validator работать, мы также должны создать собственный webBindingInitializer:


package net.iogui.web.spring.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.ConversionService;
import org.springframework.validation.Validator;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.context.request.WebRequest;

public class CommonWebBindingInitializer implements WebBindingInitializer {

    @Autowired(required=false)
    private Validator validator;

    @Autowired
    private ConversionService conversionService;

    @Override
    public void initBinder(WebDataBinder binder, WebRequest request) {
        binder.setValidator(validator);
        binder.setConversionService(conversionService);
    }

}

Интересно, что для того, чтобы сделать работу с конфигурацией без тега, связанного с аннотацией, мы должны вручную настроить AnnotationMethodHandlerAdapter и DefaultAnnotationHandlerMapping. И чтобы сделать AnnotationMethodHandlerAdapter способным обрабатывать форматирование и проверку, нам пришлось настроить валидатор, службу преобразования и создать собственный webBindingInitializer.

Я надеюсь, что все это поможет кому-то другому, кроме меня.

В моем отчаянном поиске this @Bozho был чрезвычайно полезен. Я также благодарен @GaryF couse, его ответ отвел меня на сообщение @Bozho. Для вас, которые пытаются сделать это в Spring 3.1, см. Ответ @Robby Pond.. Много проще, не так ли?

Ответ 2

Вам нужно создать GsonMessageConverter, который расширяет AbstractHttpMessageConverter и использует m vc-message-converters, чтобы зарегистрировать конвертер сообщений. Этот тег позволит вашему конвертеру иметь приоритет над Jackson.

Ответ 3

У меня была ситуация, когда использование Джексона потребовало бы, чтобы я изменил код другой группы (в той же компании). Не понравилось. Поэтому я решил использовать Gson и зарегистрировать TypeAdapters по мере необходимости.

Подключил конвертер и написал несколько интеграционных тестов с использованием spring -test (используется spring -mvc-test). Независимо от того, какую вариацию я попробовал (используя mvc: аннотирование или ручное определение bean). Ни один из них не работал. Любая комбинация из них всегда использовала Конвертер Джексона, который продолжал терпеть неудачу.

Ответ. Оказывается, что автономный метод Socket MockMvcBuilders "жестко" кодировал конвертеры сообщений по умолчанию и игнорировал все мои изменения выше. Вот что сработало:

@Autowired
private RequestMappingHandlerAdapter adapter;

public void someOperation() {
  StandaloneMockMvcBuilder smmb = MockMvcBuilders.standaloneSetup(controllerToTest);
  List<HttpMessageConverter<?>> converters = adapter.getMessageConverters();
  HttpMessageConverter<?> ary[] = new HttpMessageConverter[converters.size()];
  smmb.setMessageConverters(conveters.toArray(ary));
  mockMvc = smmb.build();
   .
   .
}

Надеюсь, что это помогает кому-то, в конце концов я использовал аннотированный и повторно назначающий андроидный конвертер

Ответ 4

Robby Pond в основном правильный, но обратите внимание, что его предложение использовать тэг mvc: message-converters требует использования 3.1. Поскольку 3.1 в настоящее время является только релизом (M1), я бы предложил зарегистрировать ваш конвертер таким образом после его создания:

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="messageConverters">
      <util:list id="beanList">
        <ref bean="someMessageConverter"/>
        <ref bean="someOtherMessageConverter"/>
      </util:list>
    </property>
</bean>

Ответ 5

Или, как указано в Jira Spring Уточнить вопрос, напишите BeanPostProcessor, который добавит ваш HttpMessageConvertor в AnnotationMethodHandlerAdapter

Ответ 6

Если вы хотите добавить конвертер сообщений без испорчения с помощью xml, вот простой пример

@Autowired
private RequestMappingHandlerAdapter adapter;

@PostConstruct
public void initStuff() {
    List<HttpMessageConverter<?>> messageConverters = adapter.getMessageConverters();
    BufferedImageHttpMessageConverter imageConverter = new BufferedImageHttpMessageConverter();;
    messageConverters.add(0,imageConverter);
}

Ответ 7

Обратите внимание, что GsonHttpMessageConverter недавно был добавлен в Spring (4.1)

Ответ 8

Вы можете сделать это, написав файл WebConfig в виде файла Java. Расширьте свой конфигурационный файл с помощью WebMvcConfigurerAdapter и переопределите метод extendMessageConverters, чтобы добавить свой намеренный Message Convertor. Этот метод сохранит конвертеры по умолчанию, добавленные Spring, и добавит ваш конвертер в конец. По-видимому, у вас есть полный контроль над списком, и вы можете добавить туда, где захотите.

@Configuration
@EnableWebMvc
@ComponentScan(basePackageClasses={WebConfig.class})
public class WebConfig extends WebMvcConfigurerAdapter {
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
      converters.add(new GsonHttpMessageConverter());
   }
}

package net.iogui.web.spring.converter;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.charset.Charset;

import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;

import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;

public class GsonHttpMessageConverter extends AbstractHttpMessageConverter<Object> {

private Gson gson = new Gson();

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

public GsonHttpMessageConverter(){
    super(new MediaType("application", "json", DEFAULT_CHARSET));
}

@Override
protected Object readInternal(Class<? extends Object> clazz,
                              HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {

    try{
        return gson.fromJson(convertStreamToString(inputMessage.getBody()), clazz);
    }catch(JsonSyntaxException e){
        throw new HttpMessageNotReadableException("Could not read JSON: " + e.getMessage(), e);
    }

}

@Override
protected boolean supports(Class<?> clazz) {
    return true;
}

@Override
protected void writeInternal(Object t, 
                             HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {

    //TODO: adapt this to be able to receive a list of json objects too

    String json = gson.toJson(t);

    outputMessage.getBody().write(json.getBytes());
}

//TODO: move this to a more appropriated utils class
public String convertStreamToString(InputStream is) throws IOException {
    /*
     * To convert the InputStream to String we use the Reader.read(char[]
     * buffer) method. We iterate until the Reader return -1 which means
     * there no more data to read. We use the StringWriter class to
     * produce the string.
     */
    if (is != null) {
        Writer writer = new StringWriter();

        char[] buffer = new char[1024];
        try {
            Reader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
            int n;
            while ((n = reader.read(buffer)) != -1) {
                writer.write(buffer, 0, n);
            }
        } finally {
            is.close();
        }
        return writer.toString();
    } else {
        return "";
    }
}