Spring, Джексон и настройка (например, CustomDeserializer)

Будучи еще немного незнакомым с Spring, я столкнулся с проблемой , что делает необходимым внедрение моего пользовательского десеризатора для Jackson. Процедура описана в небольшом учебнике, однако я застрял с Spring. Я не понимаю, где

 ObjectMapper mapper = new ObjectMapper();

in Spring MVC выполняется, когда json десериализуется с помощью метода класса контроллера. Поэтому я не знаю, что делать, чтобы заменить десериализатор по умолчанию на пользовательский десериализатор.

Любые предложения приветствуются.

Ответ 1

Вы не говорите, как вы используете Джексона в Spring, поэтому я предполагаю, что вы используете его через <mvc:annotation-driven/> и аннотации @RequestBody и/или @ResponseBody.

Одна из вещей, которую выполняет <mvc:annotation-driven/>, - это зарегистрировать AnnotationMethodHandlerAdapter bean, который поставляется с рядом предварительно настроенных HttpMessageConverter beans, включая MappingJacksonHttpMessageConverter, который обрабатывает маршаллинг в и из Джексона -номинированные классы моделей.

Теперь MappingJacksonHttpMessageConverter имеет метод setObjectMapper(), который позволяет переопределить значение по умолчанию ObjectMapper. Но поскольку MappingJacksonHttpMessageConverter создается за кулисами <mvc:annotation-driven/>, вы не можете добраться до него.

Однако <mvc:annotation-driven/> является просто удобным сокращением. Это справедливо для объявления вашего собственного AnnotationMethodHandlerAdapter bean, введя в него свой собственный MappingJacksonHttpMessageConverter bean (через свойство messageConverters) и введя в него свой собственный ObjectMapper.

>

У вас возникла проблема создания пользовательского ObjectMapper, так как это не очень класс Spring. Я предлагаю написать собственную простую реализацию FactoryBean.

Итак, у вас получится что-то вроде этого:

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
   <property name="messageConverters">
      <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
         <property name="objectMapper">
            <bean class="com.x.MyObjectMapperFactoryBean"/>
         </property>
      </bean>
   </property>
</bean>

Ответ 2

Новый способ сделать это в Spring 3.1:
http://magicmonster.com/kb/prg/java/spring/webmvc/mvc_spring_config_namespace.html

http://blog.springsource.org/2011/02/21/spring-3-1-m1-mvc-namespace-enhancements-and-configuration/

Позволяет сделать что-то вроде этого:

<mvc:annotation-driven>
      <mvc:message-converters>
          <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
              <property name="objectMapper" ref="customObjectMapper"/>
          </bean>
      </mvc:message-converters>
  </mvc:annotation-driven>

Ответ 3

Решение, на которое ссылается Rakesh, вероятно, работает с Spring MVC 3.0, но с 3.1 некоторыми из инфраструктуры MVC изменилось. В результате у вас может не быть AnnotationMethodHandlerAdapter bean, зарегистрированного в вашем контексте приложения, и вы закончите с BeanCreationException во время инициализации.

Для Spring MVC 3.1 элемент mvc:annotation-driven создаст для вас RequestMappingHandlerAdapter, поэтому вы должны использовать этот тип вместо этого. Он по-прежнему будет предоставлять доступ к списку зарегистрированных HttpMessageConverters и позволит вам установить свойство ObjectMapper в MappingJacksonHttpMessageConverter. Это также требует небольшого изменения в пределах init. метод к типу ссылки HttpMessageConverters.

Обновленный класс выглядит следующим образом:

@Component
public class JacksonFix {
    private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
    private CustomObjectMapper objectMapper;

    @PostConstruct
    public void init() {
        List<HttpMessageConverter<?>> messageConverters = requestMappingHandlerAdapter.getMessageConverters();
        for (HttpMessageConverter<?> messageConverter : messageConverters) {
            if (messageConverter instanceof MappingJacksonHttpMessageConverter) {
                MappingJacksonHttpMessageConverter m = (MappingJacksonHttpMessageConverter) messageConverter;
                m.setObjectMapper(objectMapper);
            }
        }
    }

    // this will exist due to the <mvc:annotation-driven/> bean
    @Autowired
    public void setRequestMappingHandlerAdapter(RequestMappingHandlerAdapter requestMappingHandlerAdapter) {
        this.requestMappingHandlerAdapter = requestMappingHandlerAdapter;
    }

    @Autowired
    public void setObjectMapper(CustomObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }
}

ОБНОВЛЕНИЕ. Оказывается, самая простая вещь, связанная с Spring 3.1, заключается в добавлении дополнительной конфигурации в конфигурацию MVC:

<mvc:annotation-driven conversion-service="applicationConversionService">
    <mvc:message-converters register-defaults="true">
        <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
            <property name="objectMapper" ref="customObjectMapper" />
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

Это добавит новый экземпляр MappingJacksonHttpMessageConverter с пользовательским ObjectMapper до любого из HttpMessageConverters по умолчанию (которые все еще присутствуют из-за register-defaults="true").

Ответ 4

В моем случае (Spring 3.2.4 и Jackson 2.3.1), конфигурация XML для пользовательского сериализатора:

<mvc:annotation-driven>
    <mvc:message-converters register-defaults="false">
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <property name="objectMapper">
                <bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
                    <property name="serializers">
                        <array>
                            <bean class="com.example.business.serializer.json.CustomObjectSerializer"/>
                        </array>
                    </property>
                </bean>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

был необъяснимым образом переписанным по умолчанию чем-то.

Это сработало для меня:

CustomObject.java

@JsonSerialize(using = CustomObjectSerializer.class)
public class CustomObject {

    private Long value;

    public Long getValue() {
        return value;
    }

    public void setValue(Long value) {
        this.value = value;
    }
}

CustomObjectSerializer.java

public class CustomObjectSerializer extends JsonSerializer<CustomObject> {

    @Override
    public void serialize(CustomObject value, JsonGenerator jgen,
        SerializerProvider provider) throws IOException,JsonProcessingException {
        jgen.writeStartObject();
        jgen.writeNumberField("y", value.getValue());
        jgen.writeEndObject();
    }

    @Override
    public Class<CustomObject> handledType() {
        return CustomObject.class;
    }
}

В моем решении не требуется конфигурация XML (<mvc:message-converters>(...)</mvc:message-converters>).

Ответ 5

Я бы хотел, чтобы я знал Spring MVC лучше, но с реализациями Jax-RS, такими как Jersey и RESTeasy, один регистрирует поставщиков. Может быть, Spring делает что-то подобное?

Ответ 6

Документы spring для состояния MappingJacksonHttpMessageConverter:

2.4.5 MappingJacksonHttpMessageConverter

Реализация HttpMessageConverter, которая может считывать и записывать JSON с использованием ObjectMapper процессора Jackson JSON. Отображение JSON можно настроить по мере необходимости с помощью аннотаций, предоставленных Джексоном. Когда требуется дополнительный контроль, пользовательский ObjectMapper может быть введен через свойство ObjectMapper для случаев, когда для определенных типов должны быть предусмотрены специализированные сериализаторы/десериализаторы JSON. По умолчанию этот конвертер поддерживает (application/json).

Не удалось ли вам просто открыть доступ к ObjectMapper для изменения его поведения?