I'm having some issues migrating Jersey from 1.x to 2.x. My application uses Jersey to provide REST web services, with data served in JSON via Jackson and Spring 4 to handle the dependency injection.
In Jersey 1.x i used to write JsonDeserializer as components managed by Spring, so i could access my services to load from the persistance layer my domain object during the deserialization, but in 2.x i'm having issues getting the injection of services in the deserializers to work. The approach i follewd was inspired by this blog post: http://www.runningasroot.com/blog/2012/05/02/autowiring-jackson-deserializers-in-spring/
This is dependency section of my pom.xml:
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Jersey -->
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.ext</groupId>
<artifactId>jersey-spring3</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-multipart</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>${jersey.version}</version>
</dependency>
<!-- Commons Codec -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>${commons-codec.version}</version>
</dependency>
<!-- cut -->
<dependencies>
Jersey version is 2.7, Spring 4.0.2.RELEASE.
This is my web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<module-name>myApp/module-name>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>it.mgt.myApp.config.ApplicationConfig</param-value>
</context-param>
<servlet>
<servlet-name>jersey-serlvet</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>it.mgt.myApp.config.JerseyConfig</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>jersey-serlvet</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
</web-app>
This is my Spring configuration class:
@Configuration
@ComponentScan({"it.mgt.myApp"})
@PropertySource("classpath:myApp.properties")
public class ApplicationConfig {
// Cut
}
This is my Jersey Resource config class:
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
packages("it.mgt.myApp");
register(MultiPartFeature.class);
register(RequestContextFilter.class);
register(ObjectMapperContextResolver.class);
register(JacksonFeature.class);
register(CorsRequestFilter.class);
register(SignatureProcessingFilter.class);
register(AuthorizationFeature.class);
register(CorsResponseFilter.class);
register(new UserBinder());
}
}
This is my ObjectMapperContextResolver class:
@Component
@Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
@Autowired
private SpringObjectMapper objectMapper;
public ObjectMapperContextResolver() {
super();
}
@Override
public ObjectMapper getContext(Class<?> type) {
return objectMapper;
}
}
I think @Provider annotation is redundant with the registrationg in the resource config class.
This is my SpringObjectMapper class:
@Component
public class SpringObjectMapper extends ObjectMapper {
private static final long serialVersionUID = 1413033425692174337L;
@Autowired
ApplicationContext applicationContext;
public SpringObjectMapper() {
this.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
this.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, true);
}
@Override
@Autowired
public void setHandlerInstantiator(HandlerInstantiator hi) {
super.setHandlerInstantiator(hi);
}
}
This is my SpringBeanHandlerInstantiator class:
@Component
public class SpringBeanHandlerInstantiator extends HandlerInstantiator {
private ApplicationContext applicationContext;
@Autowired
public SpringBeanHandlerInstantiator(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Override
public JsonDeserializer<?> deserializerInstance(DeserializationConfig dc, Annotated antd, Class<? extends JsonDeserializer<?>> type) {
try {
return (JsonDeserializer<?>) applicationContext.getBean(type);
} catch (Exception e) {
}
return null;
}
@Override
public KeyDeserializer keyDeserializerInstance(DeserializationConfig dc, Annotated antd, Class<? extends KeyDeserializer> type) {
try {
return (KeyDeserializer) applicationContext.getBean(type);
} catch (Exception e) {
}
return null;
}
@Override
public JsonSerializer<?> serializerInstance(SerializationConfig sc, Annotated antd, Class<? extends JsonSerializer<?>> type) {
try {
return (JsonSerializer<?>) applicationContext.getBean(type);
} catch (Exception e) {
}
return null;
}
@Override
public TypeResolverBuilder<?> typeResolverBuilderInstance(MapperConfig<?> mc, Annotated antd, Class<? extends TypeResolverBuilder<?>> type) {
try {
return (TypeResolverBuilder<?>) applicationContext.getBean(type);
} catch (Exception e) {
}
return null;
}
@Override
public TypeIdResolver typeIdResolverInstance(MapperConfig<?> mc, Annotated antd, Class<? extends TypeIdResolver> type) {
try {
return (TypeIdResolver) applicationContext.getBean(type);
} catch (Exception e) {
}
return null;
}
}
This is my domain entity class, serializers and deserializers are static inner classes:
@JsonSerialize(using = User.Serializer.class)
@JsonDeserialize(using = User.Deserializer.class)
public class User {
@Component
public static class Serializer extends JsonSerializer<User> {
@Override
public void serialize(User obj, JsonGenerator jg, SerializerProvider sp) throws IOException, JsonProcessingException {
// Cut
}
}
@Component
public static class Deserializer extends JsonDeserializer<User> {
@Autowired
SomeService someService;
@Override
public User deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
User user = new User();
// Cut
// Use someService here
}
}
// Cut
}
I tried to put a brakpoint in ObjectMapperContextResolver.getContext(Class type) but it never get hit, i suspect that's the root of the problem, but after two days of attempts and studying jersey docs i'm running out of ideas.
Anybody can point me out on how to achive this properly?
Since I launched this project, one of the most wanted feature was to add support for customizing the underlying Jackson ObjectMapper, and since version 2.1.1, this can be done either declaratively or programmatically. In this article, you are going to see how do customize the ObjectMapper when using the hibernate-types project.
According to the Jackson2ObjectMapperBuilder documentation, it will also register some modules if they're present on the classpath: The advantage of this approach is that the Jackson2ObjectMapperBuilder offers a simple and intuitive way to build an ObjectMapper.
The methods writeValueAsString and writeValueAsBytes of ObjectMapper class generate a JSON from a Java object and return the generated JSON as a string or as a byte array: String carAsString = objectMapper.writeValueAsString(car);
As already explained, the hibernate-types open-source project allows you to map JSON, ARRAY when using JPA and Hibernate. Since I launched this project, one of the most wanted feature was to add support for customizing the underlying Jackson ObjectMapper, and since version 2.1.1, this can be done either declaratively or programmatically.
After further attempts, it turned out the @Component on the ObjectMapperContextResolver was causing Jersey 2.x to not use the provider even if it was explicitly registered in Jersey configuration class. This is the opposite of Jersey 1.x behaviour, where the @Component was needed.
Removing it did the trick, as odd it may seems. The @Autowired SpringObjectMapper in ObjectMapperContextResolver was still injected by Jersey.
From jersey docs i wasn't able to tell if this is by design or if this is a bug.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With