美文网首页Java技术升华jerseyRESTful webservice
Jersey 开发RESTful(十一)Entity Provi

Jersey 开发RESTful(十一)Entity Provi

作者: 叩丁狼教育 | 来源:发表于2018-01-25 22:45 被阅读249次

    【原创文章,转载请注明原文章地址,谢谢!】

    本节开始,进入Jersey的Provider介绍。本节介绍的是Jersey提供的最简单的两个Provider:MessageBodyWriter和MessageBodyReader。

    每一个涉及到输入输出的框架都会存在输入输出转化的问题。比如接受到一个请求,应该以什么样的方式去处理请求?应该由谁来把请求里面的内容按照什么样的方式转化成应用需要的数据。一个资源方法返回一个对象,又应该以什么样的方式输出给客户端?
    在SpringMVC中,是由HttpMessageConverter类来完成这个转化的,在Jersey中,就是依赖MessageBodyWriter和MessageBodyReader两个类型来完成的。

    为什么要学习这两个相对来说比较内部的类型呢?
    1,对于我们了解Jersey内部请求转化机制和响应输出机制有更深刻的理解;
    2,对于我们选择合适的Entity Provider有合理的依据;
    3,对于我们扩展自定义的Entity Provider提供可能;

    那本节的流程为,我们先来完成自己的MessageBodyWriter和MessageBodyReader,然后理解MessageBodyReader/MessageBodyWriter的选择流程,最后来看看Jersey提供的内置的MessageBodyWriter/MessageBodyReader。

    认识MessageBodyWriter/MessageBodyReader

    简单理解,MessageBodyWriter作用就是把资源方法的响应按照某种方式输出给客户端;MessageBodyReader就是把请求资源方法的请求内容按照某种方式转化成资源方法的参数;
    举个例子:

    @Path("/emps/")
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Employee get(@QueryParam("no") Long no){
        return new Employee(no);
    }
    

    那么,当我们在请求GET /emps?no=100的时候,就需要通过一个指定的MessageBodyReader来把请求中的no=100设置到Long no参数中。同理,当方法返回一个Employee对象,就需要一个指定的MessageBodyWriter把Employee对象使用某个json框架序列化为JSON字符串返回客户端。

    自定义MessageBodyWriter/MessageBodyReader

    需求:在Jersey中,默认使用Jackson(老外都喜欢用Jackson)来完成Request Json->Object和Object-> Json的转化。我们尝试来使用Fastjson实现该功能。但是为了简化开发难度,我们创建一个注解,例如FastJson,来标注哪些类使用Fastjson来完成序列化和反序列化。那么标注了FastJson的类才是我们的目标类型。

    那么首先,创建一个FastJson注解:

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface FastJson {
    
    }
    

    其次,修改pom.xml,增加Fastjson的依赖:

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.7</version>
    </dependency>
    

    实现MessageBodyWriter

    要实现MessageBodyWriter,只需要实现MessageBodyWriter接口即可。先来看看具体的实现:

    @Provider//8
    public class FastJsonBodyWriter implements MessageBodyWriter<Object> {//1
    
        @Override
        @Produces(MediaType.APPLICATION_JSON)//3
        public boolean isWriteable(Class<?> type, 
                          Type genericType, 
                          Annotation[] annotations, MediaType mediaType) {//2
            return type.isAnnotationPresent(FastJson.class);//4
        }
    
        @Override
        public long getSize(Object t, Class<?> type, 
                          Type genericType, 
                          Annotation[] annotations, 
                          MediaType mediaType) {//5
            return -1;
        }
    
        @Override
        public void writeTo(Object t, Class<?> type, 
                          Type genericType, 
                          Annotation[] annotations, 
                          MediaType mediaType,
                          MultivaluedMap<String, Object> httpHeaders, 
                          OutputStream entityStream)//6
                throws IOException, WebApplicationException {
            entityStream.write(JSONObject.toJSONString(t).getBytes());//7
        }
    
    }
    

    我们按照标记的顺序来介绍:
    1,让我们的FastJsonBodyWriter实现MessageBodyWriter接口。因为我们要处理的目标类型(就是把哪种类型的对象使用fastjson写入到response中,因为我们针对的是添加了@FastJson标签的任何对象,所以MessageBodyWriter接口的泛型类型填写的是Object;
    2,isWriteable方法:该方法用于判断针对一个结果,是否能够使用该MessageBodyWriter来处理。需要注意的是,isWriteable方法仅仅只是判断针对某种情况,是否能够用该MessageBodyWriter,但并不表示一定会使用该MessageBodyWriter处理。因为在Jersey中还存在很多其他的MessageBodyWriter,最终选择使用哪个,是有遵循一定策略的,这个策略后面介绍。isWriteable方法有四个参数:

    • Class<?> type:代表当前资源方法返回的类型;
    • Type genericType:代表当前资源方法返回的类型,这个类型可以获取泛型类型;
    • Annotation[] annotations:代表当前资源方法返回的类型上面的注解实例;
    • MediaType mediaType:代表当前HTTP方法的生产MIME类型;

    3,因为处理的是响应结果到response的内容转化,所以,为了方便我们开发,可以通过在isWriteable方法上添加@Produces标签来辅助限定该方法的响应类型。比如示例代码中,我们标记@Produces为MediaType.APPLICATION_JSON,那么就是要求该FastJsonBodyWriter至少作用于标记了@Produces标签中,包含MediaType.APPLICATION_JSON类型的方法;
    4,在我们的FastJsonBodyWriter中,我们只需要判断对应的资源方法返回的对象类型上是否有@FastJson标签即可。
    5,getSize方法,该方法本是用于标记响应的Content-Length属性的,在JAX-RS2.0中已经作废。平时我们只需要返回-1即可;
    6,writeTo:该方法就是真正在实施写入操作的方法了;有以下一些参数:

    • object t:即资源方法返回的对象;
    • Class<?> type,Type genericType,Annotation[] annotations, MediaType mediaType:同isWriteable方法中的对应参数;
    • MultivaluedMap<String, Object> httpHeaders:本次HTTP请求的请求头列表;
    • OutputStream entityStream:响应实体内容的输出流,即我们要写的内容的目标流;

    7,在本方法中,我们只需要非常简单的使用Fastjson提供的JSONObject.toJSONString方法,把我们的目标对象转化为json字符串输出到响应实体即可;
    8,@Provider标签,标记这是一个Provider类(可以理解为提供特殊服务的类);标记为@Provider的类可以被Jersey自动发现并注册(当然也可以在ResourceConfig中手动注册);

    完成之后,我们来写一个测试(因为还没有介绍Jersey Client API,所以还是用Postman):

    创建一个Project类,并完成一个资源类:

    @Setter
    @Getter
    @NoArgsConstructor
    @AllArgsConstructor
    @FastJson
    public class Project {
        private Long id;
        private String name;
    }
    

    注意Project类上面的@FastJson标签;

    @GET
    @Path("{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Project get(@PathParam("id") Long id) {
        return new Project(id, "proj");
    }
    

    注意,@Produces(MediaType.APPLICATION_JSON),这个和我们的FastJsonBodyWriter的isWriteable方法上的@Produces保持一致;
    这时候,我们请求GET /product/10,就可以得到Project的JSON格式的响应,并且通过Logger我们可以看到确实是由我们的FastJsonBodyWriter完成;

    实现MessageBodyReader

    要实现MessageBodyReader,只需要实现MessageBodyReader接口即可。还是先来看看具体的实现:

    @Provider//1
    public class FastJsonBodyReader implements MessageBodyReader<Object> {//2
        @Override
        @Consumes(MediaType.APPLICATION_JSON)//4
        public boolean isReadable(Class<?> type, 
                          Type genericType, 
                          Annotation[] annotations, MediaType mediaType) {//3
            return type.isAnnotationPresent(FastJson.class);//5
        }
    
        @Override
        public Object readFrom(Class<Object> type, 
                          Type genericType, Annotation[] annotations, 
                          MediaType mediaType,
                          MultivaluedMap<String, String> httpHeaders, 
                          InputStream entityStream)
                throws IOException, WebApplicationException {//6
            BufferedInputStream bis = new BufferedInputStream(entityStream);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            
            byte[] buffer = new byte[1024];
            int bytesRead = -1;
            while ((bytesRead = bis.read(buffer)) != -1) {
                baos.write(buffer, 0, bytesRead);
            }
            baos.flush();//7
    
            Object o = JSON.parseObject(baos.toByteArray(), type);//8
            return o;//9
        }
    }
    

    这次,在解释具体代码之前,先来看看我们的测试代码:

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Project save(Project p) {
        p.setId(100L);
        return p;
    }
    

    当请求POST /products的时候,我们设置请求MIME类型为application/json,在请求实体内容中添加完整的标准JSON内容:


    image.png

    在我们的资源方法中,能正确的获取到Project对象。查看logger,确实是由我们的FastJsonBodyReader完成的JSON->Project对象的转化。

    当我们明确了FastJsonBodyReader存在的价值,我们再来来看看我们的FastJsonReader类,就会更加明确了:
    1,@Provider:和FastJsonBodyWriter一样,代表这是一个Provider类,Jersey会自动发现并注册;
    2,FastJsonBodyReader实现MessageBodyReader接口;
    3,isReadable方法:这个类似于isWriteable方法,就是判断给定的目标资源方法是否能够使用我们的MessageBodyReader接口实施转化;其中的参数和isWriteable方法完全相同;
    4,@Consumes(MediaType.APPLICATION_JSON):该标签也是辅助匹配方法,要求对应资源方法的@Consumes,必须包含MediaType.APPLICATION_JSON类型,才能够实施我们的MessageBodyReader;
    5,在这里同样很简单,只有返回的目标对象类型上面有我们的@FastJson标签,才实施转化;
    6,Object readFrom:改方法就是具体实施从请求到资源方法参数的转化,在FastJsonBodyReader中,即是把请求体中的JSON字符串转化成目标的对象;这个方法也有多个参数:

    • 前面的type,genericType,annotations,mediaType,httpHeaders这几个参数和MessageBodyWriter接口中的writeTo方法的对应参数完全一致;
    • InputStream:输入流,代表的就是本次请求实体的输入流;我们就是从这个inputStream中读入提交的json字符;

    7,这段代码就是一个普通的IO操作,把inputStream中的内容读取到一个byte[]中;
    8,通过fastjson提供的JSON.parseObject方法,把读入的byte[]转变成对应的对象类型,那我们的目标类型就是传入的type参数;
    9,最后把转化的对象返回,那么Jersey就把这个返回的对象真实的设置给我们的资源方法对应的参数;

    总体来说,MessageBodyWriter和MessageBodyReader的操作还是比较简单的;

    选择的流程?

    介绍完基本的代码,我们得重新考虑一个问题。刚才我们已经提到,当我们完成了自己的MessageBodyWriter和MessageBodyReader,在对应的isWriteable方法和isReadable方法中,设置的判定的条件,是不是只要符合条件的资源方法,就会使用我们的BodyWriter和BodyReader呢?
    其实并不是这样的,Jersey内部也有很多内置的MessageBodyWriter和MessageBodyReader,怎么执行选择,有一套策略,下面简单来描述这个策略(我们使用MessageBodyWriter来描述这一策略);

    前提:假如目前有5个MessageBodyWriter,各自的匹配条件为(其中的对应类型就是对应MessageBodyWriter中的泛型类型):
    1,A: @Produces("application/*") ,对应类型为Object;
    2,B: @Produces("*/*") ,对应类型为Project;
    3,C: @Produces("text/plain") ,对应类型为Project;
    4,D: @Produces("application/json") ,对应类型为Object;
    5,E: @Produces("application/json") ,对应类型为Project;

    假如资源方法为:

    @Produce(MediaType.APPLICATION_JSON)
    @GET
    public Project list(){}
    

    那么,Jersey的选择策略为:
    1,获取资源方法Content-type和方法返回类型:application/json和Project,如果没有找到Content-type,则默认为:application/octet-stream。
    2,从待选MessageBodyWriter中选择匹配的,则A,B,D,E四个选中;
    3,排序,按照GenericType>MediaType的顺序排序,则第一遍排序:(Project匹配)E、B,D、A排序完成;在进行第二遍排序:E>B>D>A;(注意,这个时候只是排序,还未选定)
    4,按照排序,依次调用E,B,D,A的isWriteable方法进行测试,假如E测试返回true,则E真正选中,如果E测试返回false,则调用B的isWriteable方法进行测试,直到找到一个匹配的,如果一个都没有匹配上,返回状态为500,抛出一个InternalServerErrorException异常。

    Jersey默认的Entity Provider

    Jersey中提供了很多内置的Entity Provider,我们简单列举一下。以下Entity Provider我们都按照类似:
    byte[] (/) : org.glassfish.jersey.message.internal.ByteArrayProvider 这种方式来列举。第一个是代表GenericType,第二个代表匹配的MediaType,后面的是对应的Provider类型。

    基础类型 (text/plain):BasicTypesMessageProvider
    byte[] (*/*) : org.glassfish.jersey.message.internal.ByteArrayProvider
    String (*/*) : org.glassfish.jersey.message.internal.StringMessageProvider
    InputStream (*/*) : org.glassfish.jersey.message.internal.InputStreamProvider
    Reader (*/*) : org.glassfish.jersey.message.internal.ReaderProvider
    File (*/*) : org.glassfish.jersey.message.internal.FileProvider
    DataSource (*/*) : org.glassfish.jersey.message.internal.DataSourceProvider
    JAXBElement (text/xml, application/xml,application/*+xml) : XmlJaxbElementProvider
    MultivaluedMap<K,V> (application/x-www-form-urlencoded):
    FormMultivaluedMapProvider
    Form (application/x-www-form-urlencoded):org.glassfish.jersey.message.internal.FormProvider
    StreamingOutput ((/)):org.glassfish.jersey.message.internal.StreamingOutputProvider

    有了本章的基础,要看这些Entity Provider相对来说都是比较简单的了。搞清楚对应处理目标即可。

    小结

    在本章中,我们简单的了解了Jersey的Entity Provider,算是了解的第一种Provider。下一节,我们会介绍过滤器和拦截器。

    WechatIMG7.jpeg

    相关文章

      网友评论

        本文标题:Jersey 开发RESTful(十一)Entity Provi

        本文链接:https://www.haomeiwen.com/subject/eyipaxtx.html