Jersey Update Entity Property MessageBodyWriter

2019-08-04 05:46发布

问题:

I want to create a Jersey provider (MessageBodyWriter) that update a dto object property and continue the chain to Jersey-json default provider an return the json object. the problem is that looks like default provider is not called so the output of my rest service become empty as soon as I register the new Provider.

@Provider
public class TestProvider implements MessageBodyWriter<MyDTO>
{
    @Override
    public long getSize(
        MyDTO arg0, Class<?> arg1, Type arg2, Annotation[] arg3, MediaType arg4)
    {
        return 0;
    }

    @Override
    public boolean isWriteable(Class<?> clazz, Type type, Annotation[] arg2, MediaType arg3)
    {
        return type == MyDTO.class;
    }


    @Override
    public void writeTo(
        MyDTO dto,
        Class<?> paramClass,
        Type paramType, Annotation[] paramArrayOfAnnotation,
        MediaType mt,
        MultivaluedMap<String, Object> paramMultivaluedMap,
        OutputStream entityStream) //NOPMD
    throws IOException, WebApplicationException
    {
        dto.setDescription("text Description");
        // CONTINUE THE DEFAULT SERIALIZATION PROCESS
    }
}

回答1:

The MessageBodyWriter shouldn't need to perform an logic to manipulate entities. It's responsibility is simply to marshal/serialize.

What you are looking for instead is an WriterIntercptor, whose purpose is to do exactly what you are trying to do, manipulate the entity before being serialized.

It's all explained here in the Jersey Doc for Inteceptors.

Here's an example

@Provider
public class MyDTOWriterInterceptor implements WriterInterceptor {

    @Override
    public void aroundWriteTo(WriterInterceptorContext context) 
            throws IOException, WebApplicationException {
        Object entity = context.getEntity();
        if (entity instanceof MyDTO) {
            ((MyDTO)entity).setDescription("Some Description");
        }
        context.proceed();
    }  
}

You can add annotation so only certain resource methods/classes use this interceptor, e.g.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.ws.rs.NameBinding;

@NameBinding
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AddDescription {

}
...

@AddDescription
@Provider
public class MyDTOWriterInterceptor implements WriterInterceptor {
...

@Path("dto")
public class MyDTOResource {

    @GET
    @AddDescription
    @Produces(MediaType.APPLICATION_JSON)
    public Response getDto() {
        return Response.ok(new MyDTO()).build();
    }
}

If for some reason you can't alter the class (maybe that's why you need to set the description here, who's knows), then you can use Dynamic Binding, where you don't need to to use annotations. You can simple do some reflection to check the method or class. The link has an example.