Пользовательский HttpMessageConverter с @ResponseBody для выполнения задач Json - PullRequest
31 голосов
/ 16 февраля 2011

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

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

Поэтому я пытаюсь выяснить, как реализовать свой собственный HttpMessageConverter, чтобы использовать его с аннотацией @ResponseBody. Может кто-нибудь найти время, чтобы показать мне, как я должен идти? Какие конфигурации я должен включить? Также мне интересно, смогу ли я сделать это и по-прежнему использовать ?

Заранее спасибо.

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

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

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

Если есть другой способ сделать это, и я скучаю по нему, пожалуйста, дайте мне знать.

Ответы [ 8 ]

37 голосов
/ 24 февраля 2011

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

Сначала я должен был реализовать собственный 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's 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);
    }

}

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

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

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

16 голосов
/ 17 февраля 2011

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

5 голосов
/ 05 октября 2014

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

@Autowired
private RequestMappingHandlerAdapter adapter;

@PostConstruct
public void initStuff() {
    List<HttpMessageConverter<?>> messageConverters = adapter.getMessageConverters();
    BufferedImageHttpMessageConverter imageConverter = new BufferedImageHttpMessageConverter();;
    messageConverters.add(0,imageConverter);
}
5 голосов
/ 04 декабря 2013

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

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

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

@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();
   .
   .
}

Надеюсь, это кому-нибудь поможет, в конце концов, я использовал аннотацию, управляемую и изменяющую конвертер Android

3 голосов
/ 23 октября 2014

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

3 голосов
/ 02 мая 2011

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

3 голосов
/ 17 февраля 2011

Робби Понд в основном прав, но учтите, что его предложение использовать тег mvc: message-convertters требует использования 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>
0 голосов
/ 21 апреля 2015

Вы можете сделать это, написав файл WebConfig как файл Java. Расширьте свой файл конфигурации с помощью WebMvcConfigurerAdapter и переопределите метод extendMessageConverters, чтобы добавить свой намеренный конвертер сообщений. Этот метод сохранит конвертеры по умолчанию, добавленные 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's 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 "";
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...