JAVA RESTful WebService实战笔记(二)

资源定位之注解相关简介

@QueryParam注解

接口描述 资源地址
分页查询列表数据 /query-resource/test?start=24&size=10
排序并分页查询列表收 /query-resource/test?limit=5&sort=program
查询单项数据 /query-resource/test?id=9

JAX-RS2定义了@QueryParam注解来定义查询参数,如下表所示

接口描述 资源地址
分页查询列表数据 /query-resource/test?start=24&size=10
排序并分页查询列表收 /query-resource/test?limit=5&sort=program
查询单项数据 /query-resource/test?id=9
1、分页查询
   /**
     *
     * @param start  起始条目  参数都使用了final进行限制
     *               符合Checkstyle风格 即输入参数只作为逻辑算法的依据使用
     *               其本身并不会再找个过程中被修改
     * @param size 查询的条目
     * @return
     */
    @Path("/page")  //page?start=0&size=10  访问
    @GET
    @Consumes(MediaType.APPLICATION_JSON)  //指定请求返回的响应体为JSON
    @Produces(MediaType.APPLICATION_JSON)
    public User getByPaging(@QueryParam("start") final int start,@QueryParam("size") final int size){

        return null;
    }
2、排序查询

    /**
     * @param limit  分页查询条目
     * @param sortName 排序规则
     * @return
     */
    @Path("/order")    //order?limit=10&sort=web 访问
    @GET
    @Consumes(MediaType.APPLICATION_JSON)  //指定请求返回的响应体为JSON
    @Produces(MediaType.APPLICATION_JSON)
    public User getByOrder(@QueryParam("limit") final int limit,@QueryParam("sort") final int sortName){

        return null;
    }
3、单项查询
    /**
     * @param segId  
     * @return
     */
    @Path("/query")    //order?id=10 访问
    @GET
    @Consumes(MediaType.APPLICATION_JSON)  //指定请求返回的响应体为JSON
    @Produces(MediaType.APPLICATION_JSON)
    public User getByQuery(@QueryParam("id") final int segId){

        return null;
    }

@PathParam注解

接口描述 资源地址
基本路径参数 /path-resource/Eric
J结合查询参数 /path-resource/Eric?hometown=Luoma
带有标点符号的资源路径 /path-resource/199-1999
/path-resource/01,2012-12,2014
子资源变长的资源路径 /path-resource/Asia/china/northeast/liaoning/shenyang/huanggu
//path-resource/q/restful;program=java;type=web
/path-resource/q2/restful;program=java;type=web

JAX-RS2定义@PathParam注解来定义路径参数----每个参数对应一个子资源,示例列表如下:

接口描述 资源地址
基本路径参数 /path-resource/Eric
J结合查询参数 /path-resource/Eric?hometown=Luoma
带有标点符号的资源路径 /path-resource/199-1999
/path-resource/01,2012-12,2014
子资源变长的资源路径 /path-resource/Asia/china/northeast/liaoning/shenyang/huanggu
//path-resource/q/restful;program=java;type=web
/path-resource/q2/restful;program=java;type=web
1、@Path注解

JAX-RS2定义@Path注解来定义资源路径,@Path接收一个value参数来解析资源路径地址,可以使用静态定义的方式外,也可以使用动态变量的方式,格式:{参数名称:正则表达式},例如资源地址:/path-resource、199-1999.参考示例如下

 @Path("{from:\\d+}-{to:\\d+}")    //order?id=10 访问
    @GET
    @Consumes(MediaType.APPLICATION_JSON)  //指定请求返回的响应体为JSON
    @Produces(MediaType.APPLICATION_JSON)
    public User getByCondition(@QueryParam("from") final Integer from, @PathParam("to") final Integer to) {

        return null;
    }

在来一个复杂的例子:/path-resource、01,2012-12,2014(引入了逗号(,))

@Path("{beginMonth:\\d+},{beginYear:\\d+}-{endMonth:\\d+},{endYear:\\d+}")
2、正则表达式
3、路径配查询

查询参数和路径参数在一个接口中配合使用,可以更便捷的完成资源定位。

    /**
     * /path-resource/Eric?hometown=Luoma 
     *
     * @param user   Eric
     * @param hometown  Luoma
     * @return
     */
    @Path("{user:[a-zA-Z][a-zA-Z_0-9]*}")    //order?id=10 访问
    @GET
    @Consumes(MediaType.APPLICATION_JSON)  //指定请求返回的响应体为JSON
    @Produces(MediaType.APPLICATION_JSON)
    public User getUserInfo(@QueryParam("user") final String user,
                            @DefaultValue("Shen Yang") @QueryParam("hometown") final String hometown) {

        return null;
    }
4、路径区间

路径区间(PathSegment)是对资源地址更加灵活的支持,使资源类的一个方法可以支持更加广泛的资源地址的请求,例如下面的例子

/path-resource/Asia/china/northeast/liaoning/shenyang/huanggu
/path-resource/Asia/china/northeast/liaoning/shenyang/tiexi
/path-resource/china/liaoning/shenyang

如上所示的资源地址中含有固定子资源(shenyang),和动态子资源两部分,对于动态匹配变长的子资源地址,PathSegment类型的参数结合正则表达式将大显身手,如下:

    @Path("{region:.+}/shenyang/{district:\\w+}")              //order?id=10 访问
    @GET
    @Consumes(MediaType.APPLICATION_JSON)  //指定请求返回的响应体为JSON
    @Produces(MediaType.APPLICATION_JSON)
    public User getByAddress(@PathParam("region")final List<PathSegment> region,
                             @PathParam("district") final String district) {
        
        final StringBuilder result = new StringBuilder();
        for (PathSegment pathSegment : region) {
            result.append(pathSegment.getPath()).append("-");
        }
        result.append("shenyang-" + district);

        return null;
    }

@MatrixParam注解

通过@MatrixParam注解来逐一的定义参数,即通过声明方式来获取,示例代码如下:

///path-resource/q2/program=java;type=web
    @Path("q2/{condition}")    //order?id=10 访问
    @GET
    @Consumes(MediaType.APPLICATION_JSON)  //指定请求返回的响应体为JSON
    @Produces(MediaType.APPLICATION_JSON)
    public User getByCondition(@PathParam("condition") final PathSegment condition,
                               @MatrixParam("program") final String program,
                               @MatrixParam("type") final String type) {


        return null;
    }

@FormParam注解

JAX-RS2定义了@FormParam注解来定义表单参数,相应的REST方法用以处理请求实体媒体类型为Content-Type:application/x-www-form-urlencoded的请求,示例代码如下:

@Path("form-resource)
public class FormResource{
    @Post
    public String newPassword(
        @DefaultValue("ruolan") @FormParam(FromResource.USER) final String user,
        @Encoded @FormParam(FormParam.PW) final String password,
        @Encoded @FormParam(FormParam.NPW) final String newPassword,
        @FormParam(FormParam.VNPW) final String verification{
        }
    )
}

上述代码中,newPassword()方法是@FormParam注解定义了user等4个参数,这些参数是容器请求中获取并且匹配的,测试代码示例如下:

    @Test
    public void testPost(){
        final Form form = new Form();

        form.param(FormResource.USER,"ruolan");
        form.param(FormResource.PW,"北京");
        form.param(FormResource.NPW,"上海");
        form.param(FormResource.VNPW,"上海");

        final String result = target.("form-resource").request()
                .post(Entity.entity(form,MediaType.APPLICATION_FORM_URLENCODED_TYPE),String.class);
        FormTest.LOGGER.debug(result);
        Assert.assertEquals("encoded should let id to disable decoding",
                "ruolan:%E5%8C%97%E4%BA%AC:%E4%B8%8A%E6%B5%B7:上海",result);

    }

注意:

  • @Encoded注解用以标识禁用自动解码,示例中的测试结果中%E4%B8%8A%E6%B5%B7是newPassword()方法中的参数值"上海"的编码值,当对newPassword使用@Encoded注解,REST方法得到的参数值就不会被编码
  • @DefaultValue注解,用以为客户端没有为其提供值的参数 提供默认的参数

@BeanParam注解

JAX-RS2定义了@BeanParam注解用于自定义参数组合,使REST方法可以使用简洁的参数形式完成复杂的接口设计

public String getByAddress(@BeanParam Jaxrs2GuideParam param) {
//关注点2:参数组合 
public class Jaxrs2GuideParam {
    @HeaderParam("accept")
    private String acceptParam;
    @PathParam("region")
    private String regionParam;
    @PathParam("district")
    private String districtParam;
    @QueryParam("station")
    private String stationParam;
    @QueryParam("vehicle")
    private String vehicleParam;
 
public void testBeanParam() {
...
    final WebTarget queryTarget = target(path).path("China").path("northeast")
    .path("shenyang").path("tiexi")
.queryParam("station", "Workers Village").queryParam("vehicle", "bus");
    result = queryTarget.request().get().readEntity(String.class);
    //关注点3:查询结果断言 
    Assert.assertEquals("China/northeast:tiexi:Workers Village:bus", result);
}
 

//关注点4:复杂的查询请求

http://localhost:9998/ctx-resource/China/shenyang/tiexi?station=Workers+Village&vehicle=bus 

在这段代码中,getByAddress()方法只用了一个使用@BeanParam注解定义的Jaxrs2GuideParam类型的参数,见关注点1;Jaxrs2GuideParam类定义了一系列REST方法会用到的参数类型,包括示例中使用的查询参数"station"和路径参数"region"等,从而使得getByAddress()方法可以匹配更为复杂的资源路径,见关注点2;在变长子资源的例子基础上,增加了查询条件,但测试方法testBeanParam()发起的请求的资源地址见关注点4;可以看出这是一个较为复杂的查询请求。其中路径部分包括China/shenyang/tiexi,查询条件包括station=Workers+Village和vehicle=bus。这些条件均在Jaxrs2GuideParam类中可以匹配,因此从关注点3的测试断言中可以看出,该请求响应的预期结果是

"China/northeast:tiexi:Workers Village:bus"。

@CookieParam注解

JAX-RS2定义了@CookieParam注解用以匹配Cookie中的键值对信息,示例如下。

@GET
public String getHeaderParams(@CookieParam("longitude") final String longitude,
    @CookieParam("latitude") final String latitude,
    @CookieParam("population") final double population,
    @CookieParam("area") final int area) {//关注点1:资源方法入参 
    return longitude + "," + latitude + " population=" + population + ",area=" + area;
@Test
public void testContexts() {
    final Builder request = target(path).request();
    request.cookie("longitude", "123.38");
    request.cookie("latitude", "41.8");
    request.cookie("population", "822.8");
    request.cookie("area", "12948");
    result = request.get().readEntity(String.class);
    //关注点2:测试结果断言 
    Assert.assertEquals("123.38,41.8 population=822.8,area=12948", result);
}

在这段代码中,getHeaderParams()方法包含4个使用@CookieParam注解定义的参数,用于匹配Cookie的字段,见关注点1;在测试方法testContexts中,客户端Builder实例填充了相应的cookie键值对信息,其断言是对cookie字段值的验证,见关注点2。

@Context注解

JAX-RS2定义了@Context注解来解析上下文参数,JAX-RS2中有多种元素可以通过@Context注解作为上下文参数使用,示例代码如下。

public String getByAddress(
    @Context final Application application,
    @Context final Request request,
    @Context final javax.ws.rs.ext.Providers provider,
    @Context final UriInfo uriInfo,
    @Context final HttpHeaders headers){
在这段代码中,分别定义了Application、Request、Providers、UriInfo和HttpHeaders等5种类型的上下文实例。从这些实例中可以获取请求过程中的重要参数信息,示例代码如下。
final MultivaluedMap<String, String> pathMap = uriInfo.getPathParameters();
final MultivaluedMap<String, String> queryMap = uriInfo.getQueryParameters();
final List<PathSegment> segmentList = uriInfo.getPathSegments();
final MultivaluedMap<String, String> headerMap = headers.getRequestHeaders();

在这段代码中,UriInfo类是路径信息的上下文,从中可以获取路径参数集合getPath-Parameters()和查询参数集合getQueryParameters()。类似地,我们可以从HttpHeaders类中获取头信息集合getRequestHeaders()。这些业务逻辑处理中常用的辅助信息的获取,要通过@Context注解定义方法的参数或者类的字段来实现。


处理响应

REST的响应处理结果应包括响应头中HTTP状态码,响应实体中媒体参数类型和返回值类型,以及异常情况处理。JAX-RS2支持4中返回值类型的响应,分别是无返回值、返回Response类实例、返回GenericEntity类实例和返回自定义类的实例.

VOID(无返回值类型)

在返回值类型中是void的响应,其响应实体为空,HTTP状态码是204。例子代码如下:

    @DELETE
    @Path("{s}")
    public void deleteTest(@PathParam("s") final String s){
        
        
    }

因为delete操作不需要返回值关于资源标书的信息,因此该方法没有返回值.(疑问点,如果需要通知客户端是否删除成功了没有呢??????)

RESPONSE(Response返回值类型)

在Response的响应中,响应实体为Response类的entity()方法定义的实体实例。如果该内容为空,则HTTP状态码是204,否则HTTP状态码为200 OK,示例代码如下:

    @POST
    @Path("c")
    public Response getResponse(final String s){

        return Response.ok().entity("char[] : " + s ).build();
    }

在上述的代码中,Response首先定义了HTTP的状态码为OK,然后填充实体信息,最后调用build()方法构建Response实例

GenericEntity(GenericEntity返回值类型)

通用实体类型作为返回值的情况并不是很常用。其形式是构造一个统一的实体实例并将其返回,实体实例作为第一个参数,该实体类型作为第二个参数,示例代码如下:

    @POST
    @Path("b")
    public String getGenericEntity(final  byte[] bytes) {
       
        return "byte[] : "  + new String(bytes);
    }
    
    public GenericEntity<String> getGenericTest(final  byte[] bytes){
        
        //构建GenericEntity实例
        return new GenericEntity<>("byte[] : " + new String(bytes),String.class );
    }
自定义类型

JDK中的类(例如File,String等)都可以作为返回值类型,更常用的是返回自定义的POJO类型。示例代码如下:

    @POST
    @Path("f")
    public File getFile(final File f) throws FileNotFoundException, IOException {
        BufferedReader br = new BufferedReader(new FileReader(f));
        String s;
        do {
            s = br.readLine();
        } while (s != null);
        return f;
    }
    
    @POST
    @Consumes({MediaType.APPLICATION_XML,MediaType.APPLICATION_JSON})
    @Produces(MediaType.APPLICATION_XML)
    public User getEntity(User user){
        return user;
    }

    @POST
    @Consumes({MediaType.APPLICATION_XML,MediaType.APPLICATION_JSON})
    @Produces(MediaType.APPLICATION_XML)
    public User getEntity(JAXBElement<User> user){
        User user1 = user.getValue();
        return user1;
    }
    

处理异常

处理状态码
状态码 含义
200 OK 服务器正常响应
201 Created 创建新实体,响应头Location指定访问该实体的URL
202 Accepted 服务器接受请求,处理尚未完成。可用于异步处理机制
204 No Content 服务器正常响应,但是响应实体为空
301 Moved Permanently 请求资源的地址发生永久变动,响应头Location指定新的URL
302 Found 请求资源的额地址发生临时变动
304 Not Modified 客户端缓存资源依然有效
400 Bad Request 请求信息出现语法错误
401 Unauthorized 请求资源无法授权给未验证错误
403 Frobidden 请求资源未授权当前用户
404 Not Found 请求资源不存在
405 Method Not Allowed 请求方法不匹配
406 Not Acceptable 请求资源的媒体类型不匹配
500 Internale Server Error 服务器内部错误,意外终止响应
501 Not Implemented 服务器不支持当前请求

内容协商

@Produces注解

@Produces注解用于定义方法的响应实体的数据类型,可以定义一个或者多个,同事可以为每种类型定义质量因素(qualityfactor)。质量因素是取值范围从0到1的小数值。如果不定义质量因素,那么该类型的质量因素默认为1

    @GET
    @Path("{id}")
    @Produces(MediaType.APPLICATION_XML)
    public User getJaxUser(@PathParam("id") final int userId) {
        return new User(userId);
    }

   
    @GET
    @Path("{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public User getJsonUser(@PathParam("id") final int userId) {
        return new User(userId);
    }
    
    /**
    * 以下的代码中 定义了xml和json两种表述数据类型,xml的质量因素是0.5 json的质量因素是0.9
    **/
    @GET
    @Path("book/{id}")
    @Produces({"application/json;qs=0.9","application/xml;qs=0.6"})
    public User getUser(@PathParam("id") final int userId) {
        return new User(userId);
    }

如果客户端的请求中,明确接收的数据类型是两者之一,响应实体使用指定类型.如果没有定义或者两者都定义且JSON的质量因素大雨或者等于XML,则返回JSON,还有一种用例就是,两者都定义但是json的质量因素小于XML,内容协商的结果按照客户端的喜好选择相应实体的数据类型(xml格式)

@Consumes注解

@Consumes注解用于定义方法的请求实体的数据类型,和@Produces不同的是,@Consumes注解的数据类型的定义只用于JAX-RS2匹配请求处理的方法,不做内容协商使用,如果匹配不到,服务器会返回HTTP状态码415(Unsupported Meia Type),示例代码如下:

    @POST
    @Consumes({MediaType.APPLICATION_XML,MediaType.APPLICATION_JSON})
    @Produces(MediaType.APPLICATION_XML)
    public User getEntity(User user){
        final Builder request = Target(path).request();

        final User result = request.post(Entity.entity(user,MediaType.APPLICATION_XML), User.class);
        
        return user;
    }
    

@Consumes媒体类型为XML格式和JSON格式,那么在客户端的请求中,如果请求实体的数据类型定义是两者之一,该方法会被选择为处理请求的方法,否则查找是否有定义为相应数据类型的方法,如果没有抛出javax.ws.rs.NotSupportedException异常,则使用该方法处理请求。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容