Пользовательский ObjectMapper с Джерси 2.2 и Джексон 2.1
Я борюсь с приложением REST с Гризли, Джерси и Джексоном, потому что Джерси игнорирует мой пользовательский ObjectMapper.
Зависимости POM:
<dependencies>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-grizzly2-servlet</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.1.4</version>
</dependency>
</dependencies>
Результирующими версиями являются: Grizzly 2.3.3, Jackson 2.1.4 и Jersey 2.2.
Основной класс (я хочу, чтобы явная регистрация компонентов Джерси):
public class Main {
public static void main(String[] args) {
try {
ResourceConfig rc = new ResourceConfig();
rc.register(ExampleResource.class);
rc.register(ObjectMapperResolver.class);
HttpHandler handler = ContainerFactory.createContainer(
GrizzlyHttpContainer.class, rc);
URI uri = new URI("http://0.0.0.0:8080/");
HttpServer server = GrizzlyHttpServerFactory.createHttpServer(uri);
ServerConfiguration config = server.getServerConfiguration();
config.addHttpHandler(handler, "/");
server.start();
System.in.read();
} catch (ProcessingException | URISyntaxException | IOException e) {
throw new Error("Unable to create HTTP server.", e);
}
}
}
ContextResolver для ObjectMapper:
@Provider
@Produces(MediaType.APPLICATION_JSON)
public class ObjectMapperResolver implements ContextResolver<ObjectMapper> {
private final ObjectMapper mapper;
public ObjectMapperResolver() {
System.out.println("new ObjectMapperResolver()");
mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
}
@Override
public ObjectMapper getContext(Class<?> type) {
System.out.println("ObjectMapperResolver.getContext(...)");
return mapper;
}
}
Ни конструктор ObjectMapperResolver
, ни getContext
не вызываются. Что мне не хватает? Я бы предпочел использовать Jersey 2.2 и Jackson 2.1, потому что это зависимость для другого lib.
Полный пример можно найти на GitHub: https://github.com/svenwltr/example-grizzly-jersey-jackson/tree/stackoverflow
Ответы
Ответ 1
Я нашел решение. Я должен был создать экземпляр поставщика Jackson самостоятельно и установить свой пользовательский ObjectMapper
. Рабочий пример можно найти в GitHub: https://github.com/svenwltr/example-grizzly-jersey-jackson/tree/stackoverflow-answer
Я удалил свой ObjectMapperResolver
и изменил метод main
:
public class Main {
public static void main(String[] args) {
try {
// create custom ObjectMapper
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
// create JsonProvider to provide custom ObjectMapper
JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider();
provider.setMapper(mapper);
// configure REST service
ResourceConfig rc = new ResourceConfig();
rc.register(ExampleResource.class);
rc.register(provider);
// create Grizzly instance and add handler
HttpHandler handler = ContainerFactory.createContainer(
GrizzlyHttpContainer.class, rc);
URI uri = new URI("http://0.0.0.0:8080/");
HttpServer server = GrizzlyHttpServerFactory.createHttpServer(uri);
ServerConfiguration config = server.getServerConfiguration();
config.addHttpHandler(handler, "/");
// start
server.start();
System.in.read();
} catch (ProcessingException | URISyntaxException | IOException e) {
throw new Error("Unable to create HTTP server.", e);
}
}
}
Ответ 2
Следующее решение применяется к следующему стеку (как в... это настройка, которую я использовал для ее проверки)
Джерси 2.12, Джексон 2.4.x
Я добавляю свое сообщение с решением, которое я придумал на этом посту, поскольку это было очень актуально для многих поисковых запросов Google, которые я поставил сегодня... Это громоздкое решение для того, что я считаю быть еще более громоздкой проблемой.
1. Убедитесь, что ваша конфигурация maven СОДЕРЖИТ зависимость jackson-jaxrs-json-provider
:
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.4.1</version>
</dependency>
2. Убедитесь, что ваша конфигурация maven НЕ СОДЕРЖАЕТ зависимость jersey-media-json-jackson
:
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
</dependency>
3. Создайте компонент @Provider
, расширяющий com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider
следующим образом:
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.Provider;
@Provider
@Produces(MediaType.APPLICATION_JSON)
public class CustomJsonProvider extends JacksonJaxbJsonProvider {
private static ObjectMapper mapper = new ObjectMapper();
static {
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.enable(SerializationFeature.INDENT_OUTPUT);
}
public CustomJsonProvider() {
super();
setMapper(mapper);
}
}
Как вы можете заметить, это также определяет пользовательский экземпляр com.fasterxml.jackson.databind.ObjectMapper
4. Расширьте javax.ws.rs.core.Feature
через MarshallingFeature
следующим образом:
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
public class MarshallingFeature implements Feature {
@Override
public boolean configure(FeatureContext context) {
context.register(CustomJsonProvider.class, MessageBodyReader.class, MessageBodyWriter.class);
return true;
}
}
5. Вам необходимо зарегистрировать этот пользовательский поставщик, например, при настройке вашего приложения через org.glassfish.jersey.server.ResourceConfig
следующим образом:
import org.glassfish.jersey.server.ResourceConfig;
...
public class MyApplication extends ResourceConfig {
public MyApplication() {
...
register(MarshallingFeature.class);
...
}
}
Другие примечания и наблюдения:
- Это решение применяется независимо от того, используете ли вы
javax.ws.rs.core.Response
для переноса ответов на контроллер или нет.
- Пожалуйста, убедитесь, что вы тщательно учитываете (копируете/вставляете) следующие фрагменты кода, так как единственные "необязательные" так называемые бит - это те, которые касаются пользовательской конфигурации
com.fasterxml.jackson.databind.ObjectMapper
.
@jcreason
Извините за то, что сбросил мяч на этом @jcreason, я надеюсь, что вы по-прежнему любопытны.
Итак, я проверил код с прошлого года, и это то, что я придумал, чтобы предоставить настраиваемый картограф.
Проблема заключалась в том, что во время инициализации функций любые пользовательские сопоставления объектов отключаются некоторым кодом в
org.glassfish.jersey.jackson.JacksonFeature: 77 (Джерси-медиа-JSON-джексон-2.12.jar)
// Disable other JSON providers.
context.property(PropertiesHelper.getPropertyNameForRuntime(InternalProperties.JSON_FEATURE, config.getRuntimeType()), JSON_FEATURE);
Но эта функция регистрируется только этим компонентом
org.glassfish.jersey.jackson.internal.JacksonAutoDiscoverable
if (!context.getConfiguration().isRegistered(JacksonFeature.class)) {
context.register(JacksonFeature.class);
}
Итак, я сделал это, чтобы зарегистрировать свою собственную функцию, которая регистрирует моего собственного поставщика картографического объекта и падает при остановке проводов
org.glassfish.jersey.jackson.JacksonFeature от регистрации и переопределения моего объекта mapper...
import com.fasterxml.jackson.jaxrs.base.JsonMappingExceptionMapper;
import com.fasterxml.jackson.jaxrs.base.JsonParseExceptionMapper;
import org.glassfish.jersey.internal.InternalProperties;
import org.glassfish.jersey.internal.util.PropertiesHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.core.Configuration;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
public class MarshallingFeature implements Feature {
private final static String JSON_FEATURE = MarshallingFeature.class.getSimpleName();
@Override
public boolean configure(FeatureContext context) {
context.register(JsonParseExceptionMapper.class);
context.register(JsonMappingExceptionMapper.class);
context.register(JacksonJsonProviderAtRest.class, MessageBodyReader.class, MessageBodyWriter.class);
final Configuration config = context.getConfiguration();
// Disables discoverability of org.glassfish.jersey.jackson.JacksonFeature
context.property(
PropertiesHelper.getPropertyNameForRuntime(InternalProperties.JSON_FEATURE,
config.getRuntimeType()), JSON_FEATURE);
return true;
}
}
И вот пользовательский поставщик mapper...
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.Provider;
@Provider
@Produces(MediaType.APPLICATION_JSON)
public class JacksonJsonProviderAtRest extends JacksonJaxbJsonProvider {
private static ObjectMapper objectMapperAtRest = new ObjectMapper();
static {
objectMapperAtRest.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapperAtRest.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapperAtRest.configure(SerializationFeature.INDENT_OUTPUT, true); // Different from default so you can test it :)
objectMapperAtRest.setSerializationInclusion(JsonInclude.Include.ALWAYS);
}
public JacksonJsonProviderAtRest() {
super();
setMapper(objectMapperAtRest);
}
}
Ответ 3
Пожалуйста, сделайте следующее:
1) добавить зависимость pom.xml
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.2</version>
</dependency>
2) зарегистрируйте JacksonFeature в Main.java
public class Main {
public static void main(String[] args) {
try {
ResourceConfig rc = new ResourceConfig();
rc.register(ExampleResource.class);
rc.register(ObjectMapperResolver.class);
rc.register(JacksonFeature.class);
HttpHandler handler = ContainerFactory.createContainer(
GrizzlyHttpContainer.class, rc);
URI uri = new URI("http://0.0.0.0:8080/");
HttpServer server = GrizzlyHttpServerFactory.createHttpServer(uri);
ServerConfiguration config = server.getServerConfiguration();
config.addHttpHandler(handler, "/");
server.start();
System.in.read();
} catch (ProcessingException | URISyntaxException | IOException e) {
throw new Error("Unable to create HTTP server.", e);
}
}
}
3) Используйте org.codehaus.jackson.map.ObjectMapper в своем ресурсе
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig.Feature;
@Provider
@Produces(MediaType.APPLICATION_JSON)
public class ObjectMapperResolver implements ContextResolver<ObjectMapper> {
private final ObjectMapper mapper;
public ObjectMapperResolver() {
System.out.println("new ObjectMapperResolver()");
mapper = new ObjectMapper();
mapper.enable(Feature.INDENT_OUTPUT);
}
@Override
public ObjectMapper getContext(Class<?> type) {
System.out.println("ObjectMapperResolver.getContext(...)");
return mapper;
}
}
Ответ 4
Я понял это, основываясь на немного возиться.
Проблема, по-видимому, связана с механизмом автоопределения механизма Джерси. Если вы полагаетесь на Джерси для загрузки JacksonJaxbJsonProvider, то пользовательский поставщик контекста для вашего ObjectMapper игнорируется. Если вместо этого вы вручную зарегистрируете эту функцию, она работает. Я выдвигаю гипотезу, что это связано с тем, что автоопределенный провайдер загружается в другую область контекста, но что касается решения, вот что я получил. Обратите внимание, что я включил его в функцию, вы можете зарегистрироваться прямо с вашим приложением без проблем.
public final class RequestMappingFeature implements Feature {
@Override
public boolean configure(final FeatureContext context) {
context.register(ObjectMapperProvider.class);
// If you comment out this line, it stops working.
context.register(JacksonJaxbJsonProvider.class);
return true;
}
}
ОБНОВЛЕНИЕ Ноябрь 2017. В мире Jersey2 все изменилось. Если вышеуказанное не работает, попробуйте следующее:
Новый метод предоставления собственного объекта ObjectMapper теперь выглядит следующим образом:
public final class JacksonFeature implements Feature {
private static final ObjectMapper MAPPER;
static {
// Create the new object mapper.
MAPPER = new ObjectMapper();
// Enable/disable various configuration flags.
MAPPER.configure(
DeserializationFeature.READ_ENUMS_USING_TO_STRING, true);
// ... Add your own configurations here.
}
@Override
public boolean configure(final FeatureContext context) {
JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider(
MAPPER, DEFAULT_ANNOTATIONS);
context.register(provider);
return true;
}
}
Ответ 5
Как мне потребовалось несколько часов, чтобы это работало с Java EE7 и Glassfish4, здесь мое решение:
@javax.ws.rs.ApplicationPath("withJackson")
public class ApplicationConfig extends Application {
private static final Logger log = java.util.logging.Logger.getLogger(ApplicationConfig.class.getName());
@Override
public Set<Object> getSingletons() {
Set<Object> set = new HashSet<>();
log.log(Level.INFO, "Enabling custom Jackson JSON provider");
set.add(new JacksonJsonProvider().configure(SerializationFeature.INDENT_OUTPUT, true));
return set;
}
@Override
public Map<String, Object> getProperties() {
Map<String, Object> map = new HashMap<>();
log.log(Level.INFO, "Disabling MOXy JSON provider");
map.put("jersey.config.disableMoxyJson.server", true);
return map;
}
@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> resources = new java.util.HashSet<>();
addRestResourceClasses(resources);
return resources;
}
/**
* Do not modify addRestResourceClasses() method.
* It is automatically populated with
* all resources defined in the project.
* If required, comment out calling this method in getClasses().
*/
private void addRestResourceClasses(Set<Class<?>> resources) {
resources.add(com.fasterxml.jackson.jaxrs.base.JsonMappingExceptionMapper.class);
resources.add(com.fasterxml.jackson.jaxrs.base.JsonParseExceptionMapper.class);
resources.add(com.fasterxml.jackson.jaxrs.json.JsonMappingExceptionMapper.class);
resources.add(com.fasterxml.jackson.jaxrs.json.JsonParseExceptionMapper.class);
resources.add(de.lathspell.java_test_ee7_json.Api.class);
resources.add(de.lathspell.java_test_ee7_json.with_jackson.MyExceptionMapper.class);
}
Единственными релевантными зависимостями POM являются:
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.2.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.2.3</version>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
Ответ 6
Из документов компании Jersey 2.17: https://jersey.github.io/documentation/2.17/user-guide.html#jackson-registration
В приложении
@ApplicationPath("/")
public class MyApplication extends ResourceConfig {
public MyApplication() {
register(JacksonFeature.class);
// This is the class that you supply, Call it what you want
register(JacksonObjectMapperProvider.class);
//...
}
}
Изменить, забыли добавить JacksonObjectMapperProvider, который вы поставляете в регистре (..):
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
@Provider
public class JacksonObjectMapperProvider implements ContextResolver<ObjectMapper>{
final ObjectMapper defaultObjectMapper;
public JacksonObjectMapperProvider() {
defaultObjectMapper = createDefaultMapper();
}
@Override
public ObjectMapper getContext(Class<?> type) {return defaultObjectMapper;}
public static ObjectMapper createDefaultMapper() {
final ObjectMapper jackson = new ObjectMapper();
// any changes to the ObjectMapper is up to you. Do what you like.
// The ParameterNamesModule is optional,
// it enables you to have immutable POJOs in java8
jackson.registerModule(new ParameterNamesModule());
jackson.enable(SerializationFeature.INDENT_OUTPUT);
jackson.disable(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS);
jackson.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
return jackson;
}
}
Ответ 7
С Jackson 2.7 это не сработало:
public class MyApplication extends ResourceConfig {
public MyApplication() {
register(MyObjectMapperProvider.class);
}}
Вызывается конструктор MyObjectMapperProvider, но getContext() никогда не вызывался.
Регистрация MyObjectMapperProvider в конструкторе super() заставляет его работать:
public class MyApplication extends ResourceConfig {
public MyApplication() {
super(
// register Jackson ObjectMapper resolver
MyObjectMapperProvider.class
);
}}
Смотрите этот пример кода в Джерси.