سؤال

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?

هل كانت مفيدة؟

المحلول

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.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top