I work in a project with an old version of Jersey (1.19) along with old Jackson (1.19.13). I would like to switch, to new Jackson (2.x) deserialization, but only for new endpoints (and old ones gradually) because migration in one step to Jackson 2 and/or Jersey 2 would be really difficult (oh, the monolith!).
I've seen some topics about how to provide custom-configured ObjectMapper with Jackson providers in Jersey, or how to install new Jackson in 1.x Jersey but that's not I'm looking for, at least not all.
What I imagine as a solution, is (preferably) annotating my new JAX-RS endpoint with something like @UseJackson2 , or having some base class with some magic retrieving ObjectMapper from the correct package, for this particular endpoint and extending it later - in other words forcing given endpoint to use other (de)serialization provider than normally.
I've seen examples with providers for differently configured ObjectMappers (like here Using Jackson in Jersey with multiple configured ObjectMappers), but in my case, the ObjectMappers would come from completely different packages/maven artifacts.
What you can do is create a MessageBodyReader/Writer
that handles the 2.x version of Jackson. The isReadable
and isWritable
methods determine which entities it can handle. What you can do to check is inject Jersey's ExtendedUriInfo
into the provider and check the resource class for your @Jackson2
annotation. If the annotation is not present than your provider ignores the entity and the runtime moves on to the next provider and checks if it can handle it; in this case the 1.x provider.
Instead of completely creating your own, you would just extend the provider that Jackson already provides in it artifact
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.9.2</version>
</dependency>
You would extend the JacksonJaxbJsonProvider
.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Jackson2 {
}
@Provider
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class MyProvider extends JacksonJaxbJsonProvider {
@Context
private ExtendedUriInfo info;
@Override
public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
if (!info.getMatchedMethod().getDeclaringResource().isAnnotationPresent(Jackson2.class)) {
return false;
}
return super.isReadable(type, genericType, annotations, mediaType);
}
@Override
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
if (!info.getMatchedMethod().getDeclaringResource().isAnnotationPresent(Jackson2.class)) {
return false;
}
return super.isWriteable(type, genericType, annotations, mediaType);
}
}
Then just annotate your resource classes with @Jackson2
.
I had problems earlier while trying to get this to work, because I was using Jackson 2.8.4. It seams the whole 2.8 line has a problem. Not sure what it is, but with 2.8, my provider would not register at all. I tested with minor versions 2.2-2.9 (excluding 2.8) and they all work.
As far as priority, I'm not sure about how Jersey determines precedence. If the 1.x provider were to be called first, then this solution would all fall apart. One way around this would be to use composition instead of inheritance, where you would Just determine which reader/writer (1.x or 2.x) to use inside your readFrom
and writeTo
methods. The 1.x version is also JacksonJaxbJsonProvider
but it used the codehaus
packaging. From what I tested though, my provider always gets called first, so this may not be needed. Unfortunately, I cannot confirm that this is by design.
Based on the solution of Paul Samsotha, creaing 2 providers worked fine for me.
public class Main {
public static void main(final String[] args) {
ResourceConfig config = new ResourceConfig();
config.register(Service1.class)
.register(Service2.class)
.register(JacksonV1Provider.class)
.register(JacksonV2Provider.class);
GrizzlyHttpServerFactory.createHttpServer(URI.create("http://0.0.0.0:8123"), config);
}
}
@Provider
public class JacksonV1Provider extends org.codehaus.jackson.jaxrs.JacksonJsonProvider {
@Context
private ExtendedUriInfo uriInfo;
@Override
public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return uriInfo.getPath().contains("/v1/");
}
@Override
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return uriInfo.getPath().contains("/v1/");
}
}
@Provider
public class JacksonV2Provider extends com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider {
@Context
private ExtendedUriInfo uriInfo;
@Override
public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return uriInfo.getPath().contains("/v2/");
}
@Override
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return uriInfo.getPath().contains("/v2/");
}
}
dependencies {
compile group: 'org.glassfish.jersey.containers', name: 'jersey-container-grizzly2-http', version: '2.17'
compile 'org.glassfish.jersey.media:jersey-media-json-jackson:2.17'
compile group: "org.codehaus.jackson", name: "jackson-mapper-asl", version: "$jackson1_version"
compile group: "org.codehaus.jackson", name: "jackson-jaxrs", version: "$jackson1_version"
compile group: "org.codehaus.jackson", name: "jackson-xc", version: "$jackson1_version"
compile "com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:$jackson2_vervion"
compile "com.fasterxml.jackson.core:jackson-core:$jackson2_vervion"
compile "com.fasterxml.jackson.core:jackson-annotations:$jackson2_vervion"
}
来源:https://stackoverflow.com/questions/47077481/how-to-use-two-versions-of-jackson-1-x-and-2-x-in-the-same-jersey-jax-rs-appli