且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

Jersey,Jackson和JAX-RS POST多种JSON格式

更新时间:2023-11-18 07:56:04

好的,最后我成功了建立一个完全符合我要求的机制。但是,我不确定是否会产生诸如性能或此类事情的负面后果。

Ok, finally I succeed to put in place a mechanism that do exactly what I am looking for. But, I am not sure if there are negative consequences such the performance or such things.

首先,我定义了一个可以接受List或Single Object的类:

First, I defined a class that can accept both List or Single Object:

public class RootWrapper<T> {
    private List<T> list;

    private T object;
}

然后,我需要一个能够知道哪种T的自定义反序列化器用于反序列化和处理集合或单个对象的类型。

Then, I need a custom deserializer that is able to know which kind of T type to deserialize and to handle the collection or the single object.

public class RootWrapperDeserializer extends JsonDeserializer<CollectionWrapper<?>> {
    private Class contentType;

    public RootWrapperDeserializer(Class contentType) {
        this.contentType = contentType;
    }

    @Override
    public RootWrapper deserialize(JsonParser jp, DeserializationContext ctxt) 
        throws IOException, JsonProcessingException {

        // Retrieve the object mapper and read the tree.
        ObjectMapper mapper = (ObjectMapper) jp.getCodec();
        JsonNode root = mapper.readTree(jp);

        RootWrapper wrapper = new RootWrapper();

        // Check if the root received is an array.
        if (root.isArray()) {
            List list = new LinkedList();

            // Deserialize each node of the array using the type expected.
            Iterator<JsonNode> rootIterator = root.getElements();
            while (rootIterator.hasNext()) {
                list.add(mapper.readValue(rootIterator.next(), contentType));
            }

            wrapper.setList(list);
        }

        // Deserialize the single object.
        else {
            wrapper.setObject(mapper.readValue(root, contentType));
        }

        return wrapper;
    }
}

据我所知,我尝试只反序列化手动完成根级别然后让Jackson接下来的操作。我只需要知道我期望在Wrapper中出现哪种真实类型。

As far as I know, I try to only deserialize the root level manually and then let Jackson take the next operations in charge. I only have to know which real type I expect to be present in the Wrapper.

在这个阶段,我需要一种方法告诉Jersey / Jackson使用哪个解串器。我找到的一种方法是创建一种反序列化器注册表,其中存储了使用正确的反序列化器反序列化的类型。我扩展了Deserializers.Base类。

At this stage, I need a way to tell Jersey/Jackson which deserializer to use. One way I found for that is to create a sort of deserializer registry where are stored the type to deserialize with the right deserializer. I extended the Deserializers.Base class for that.

public class CustomDeserializers extends Deserializers.Base {
    // Deserializers caching
    private Map<Class, RootWrapperDeserializer> deserializers = new HashMap<>();

    @Override
    public JsonDeserializer<?> findBeanDeserializer(JavaType type, 
        DeserializationConfig config, DeserializerProvider provider, 
        BeanDescription beanDesc, BeanProperty property) throws JsonMappingException {

        // Check if we have to provide a deserializer
        if (type.getRawClass() == RootWrapper.class) {
            // Check the deserializer cache
            if (deserializers.containsKey(type.getRawClass())) {
                return deserializers.get(type.getRawClass());
            }
            else {
                // Create the new deserializer and cache it.
                RootWrapperDeserializer deserializer = 
                    new RootWrapperDeserializer(type.containedType(0).getRawClass());
                deserializers.put(type.getRawClass(), deserializer);
                return deserializer;
            }
        }

        return null;
    }
}

好的,然后我有我的反序列化器注册表创建新的反序列化仅在需要时保留并在创建后保留它们。我不确定这种方法是否存在任何并发问题。我知道Jackson做了很多缓存,并且一旦在特定的反序列化上下文中第一次调用它,就不会每次调用findBeanDeserializer。

Ok, then I have my deserializers registry that create new deserializer only on demand and keep them once created. What I am not sure about that approach is if there is any concurrency issue. I know that Jackson do a lot of caching and do not call every time the findBeanDeserializer once it was called a first time on a specific deserialization context.

现在我创建了我的不同的类,我需要做一些管道将所有东西组合在一起。在我创建ObjectMapper的提供程序中,我可以将deserializers注册表设置为创建的对象映射器,如下所示:

Now I have created my different classes, I need to do some plumbing to combine everything together. In a provider where I create the ObjectMapper, I can setup the deserializers registry to the created object mapper like below:

@Provider
@Produces(MediaType.APPLICATION_JSON)
public class JsonObjectMapper implements ContextResolver<ObjectMapper> {
    private ObjectMapper jacksonObjectMapper;

    public JsonObjectMapper() {
        jacksonObjectMapper = new ObjectMapper();

        // Do some custom configuration...

        // Configure a new deserializer registry
        jacksonObjectMapper.setDeserializerProvider(
            jacksonObjectMapper.getDeserializerProvider().withAdditionalDeserializers(
                new RootArrayObjectDeserializers()
            )
        );
    }

    @Override
    public ObjectMapper getContext(Class<?> arg0) {
        return jacksonObjectMapper;
    }
}

然后,我还可以定义我的@ApplicationPath我的REST应用程序如下:

Then, I can also define my @ApplicationPath that is my REST application like following:

public abstract class AbstractRestApplication extends Application {
    private Set<Class<?>> classes = new HashSet<>();

    public AbstractRestApplication() {
        classes.add(JacksonFeature.class);
        classes.add(JsonObjectMapper.class);

        addResources(classes);
    }

    @Override
    public Set<Class<?>> getClasses() {
        return classes;
    }

    @Override
    public Set<Object> getSingletons() {
        final Set<Object> singletons = new HashSet<>(1);
        singletons.add(new JacksonJsonProvider());
        return singletons;
    }

    private void addResources(Set<Class<?>> classes) {
        classes.add(SomeRestResource.class);
        // ...
    }
}

现在,一切都到位,我可以写一个这样的REST资源方法:

Now, everything is in place and I can write a REST resource method like that:

@POST
@Path("somePath")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response create(RootWrapper<SpecificClass> wrapper) {
    if (wrapper.isObject()) {
        // Do something for one single object
        SpecificClass sc = wrapper.getObject();
        // ...
        return Response.ok(resultSingleObject).build();
    }
    else {
        // Do something for list of objects
        for (SpecificClass sc = wrapper.getList()) {
            // ...
        }
        return Response.ok(resultList).build();
    }
}

这就是全部。不要犹豫,评论解决方案。反馈非常受欢迎,特别是在反序列化过程中,我真的不确定它对性能和并发性是否安全。

That's all. Do not hesitate to comment the solution. Feedbacks are really welcome especially around the way of deserialization process where I am really not sure that it is safe for performance and concurrency.