Java RESTful Web Service实战(第2版) 2.2 资源定位

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介:

2.2 资源定位

REST使用URI实现资源定位,从这个角度上讲,对外提供REST式的Web服务的接口就是公布一系列的URI及其参数,这使得REST的实践过程简单到了极致。但是URI形式上的简单并不意味着我们可以将URI的定义信手拈来,正所谓“没有规矩,不成方圆”。

在设计REST式的Web服务过程中,资源地址的设计是非常严谨的,如果设计不得体,不仅REST接口的风格无法统一,使系统的扩展性和易用性降低,也很难实现资源准确地被定位。

资源地址的设计过程是面向资源的,资源名称应是准确描述该资源的名词,资源地址应具有直观的描述性。比如一个班级的资源地址可以是:学校/学院/学级/班级。值得注意的是一个URI资源地址唯一对应一个资源,但是一个资源可以拥有多个URI资源地址。比如Jersey最新版本的文档地址和Jersey2.7版本的文档地址指向同一个资源(本书写作时)。

阅读指南

本节示例源代码地址:https://github.com/feuyeux/jax-rs2-guide-II/tree/master/2.simple-service-3。

相关包:com.example.annotation.param。

2.2.1 资源地址设计

资源地址的设计对整个REST式的Web服务至关重要,涉及系统的可用性、可维护性和可扩展性等诸多方面的表现。因此,本节关注如何对资源地址进行设计。

1. 资源路径概览

资源地址的路径变量是用来表达逻辑上的层次结构的,资源和子资源的形式是自左至右、斜杠分割的名词。它们的关系可以是从整体到局部,比如学校到班级,城市到乡镇;也可以是从一般到具体,比如一个生物的“门、纲、目、科、属、种”的资源路径。资源地址具体可以分为5个部分,以scheme://host:port/path?queryString为例,如表2-1所示。

表2-1 资源地址路径分解

元素         描  述

scheme    协议名称,通常是HTTP和HTTPS

host (DNS)主机名称或者IP地址

port 服务端口

path 资源地址,使用“/”符号来分隔逻辑上的层次结构

?       用来分隔资源地址和查询字符串符号

queryString      查询字符串,方法作用域信息

使用“&”符号来分隔查询条件

使用逗号分隔有次序的作用域信息

使用分号分隔无次序的作用域信息

 

一个典型的URI如表2-1所示,包括协议名称、主机名称、服务端口、资源地址和查询字符串等5个部分。其中资源地址部分,根据具体部署的不同或有差别,如图2-2所示。

 

图2-2 资源地址示例

图2-2中,通常使用ContextPath、ServletPath和PathInfo来细分资源地址。ContextPath是上下文名称,通常和部署服务器的配置或者REST服务的web.xml配置有关。ServletPath是Servlet的名称,与REST服务中定义的@ApplicationPath注解或者web.xml的配置有关。JAX-RS2定义了@Path注解来定义资源地址。PathInfo是资源路径信息,与资源类、子类以及类中的方法定义的@Path注解有关。

现在我们对资源地址的层次结构有了认识,此时需要思考一个问题:资源地址是否可以唯一定位一个资源?

答案是否定的。资源地址相同,但HTTP方法不同的两个方法是两个不同的REST接口。HTTP方法和资源地址结合在一起才可以完成对资源的定位。细心的读者也许已经从3.1节的示例中看出端倪。示例中,GET方法用于读取/检索、查询/过滤一个资源,PUT方法用于修改/更新资源、创建客户端维护主键信息的资源,DELETE方法用于删除资源,POST方法用于创建资源。但这些方法的资源地址是相同的,都是"book"。

当上述的标准HTTP方法无法满足业务需求时,比如对于图书资源,除了基本的CRUD之外,若需要公布像借阅、折旧、电子版下载等实际生活中的更新操作的接口时,单单公布一个PUT方法就不够用了。这些操作是动词性的,无法简单地使用一个book名词定位。在路径变量难以准确描述的情况下,一种方案是可以考虑使用动词作为查询参数;另一种方案是可以在REST设计过程中引入RPC风格的POST方法,辅助完成复杂业务的接口设计,这就是REST和RPC混合型的Web服务了。

2. 资源地址和作用域

在路径变量里可以使用标点符号以辅助增强逻辑清晰性。这些辅助符号用在表2-2中的查询字符串,作为资源地址的查询变量,用来表达算法的输入,实现对方法的作用域的约束。下面来逐一讲述这些对资源地址设计至关重要的符号。

(1)问号(?)是用来分隔资源地址和查询字符串的,与符号(&)是用来分隔查询条件的参数的。示例代码如下。

GET /books?start=0&size=10

这行代码中的作用是查询图书列表,开始行参数为0,条目参数为10,即从第0行开始取10条并返回该图书列表。

(2)逗号(,)是用来分隔有次序的作用域信息。需要注意的是逗号分隔的逻辑上的顺序信息,这种顺序可以是约定俗成的,比如先写经度后写纬度;也可以是系统约定的,比如月、日、年的顺序等。举例来说,按时间区间查询图书,日期信息在资源地址中是采用月、年顺序,示例如下。

GET /books/01,2002-12,2014

这行代码中的作用是查询2002年1月到2014年12月这个时间段(出版)的图书。这个例子中还使用了连字符(-),有时候也可以使用下横线(_)来做逻辑上的辅助分隔。

(3)分号(;)是用来分隔无次序的作用域信息。通常这些信息是逻辑上并列存在的,比如并列的查询条件,示例如下所示。

GET /books/restful;program=java;type=web

这行代码中的作用是查询满足图书内容为restful的、使用的编程语言是Java的、讲述的类型是Web的图书列表。这样的逻辑没有顺序,互换顺序的查询条件不会影响资源的表述。

基于上述理论,这里抛砖引玉,列出常用的资源地址设计示例如表2-2所示。

表2-2 资源地址设计

功能         资源地址

添加/创建        POST /books

PUT /books/{id}

删除         DELETE /books/{id}

修改/更新        PUT /books/{id}

查询全部         GET /books HTTP1.1

主键查询         GET /books/{id} HTTP1.1

GET /books?id=12345678

分页作用域查询     GET /books?start=0&size=10

GET /books/01,2002-12,2014

GET /books/restful;program=java;type=web

GET /books?limit=100&sort=bookname

 

如果读者可以轻松领会表2-2列出的这些典型的REST接口和资源定位的设计,就可以放手实现了,否则建议回顾本节内容。接下来,我们完成从设计到实现的跨越,看看JAX-RS2标准是如何通过注解来支持资源定位的,并使用Jersey完成上述设计的实践。

2.2.2 @QueryParam注解

查询条件决定了方法的作用域,查询参数组成了查询条件。JAX-RS2定义了@QueryParam注解来定义查询参数,本节使用@QueryParam演示3个REST查询接口的实现示例如表2-3所示。

表2-3 @QueryParam示例列表

接口描述         资源地址

分页查询列表数据         /query-resource/yijings?start=24&size=10

排序并分页查询列表数据     /query-resource/sorted-yijings?limit=5&sort=pronounce

查询单项数据         /query-resource/yijing?id=8

 

1. 分页查询

分页查询是使用@QueryParam解析参数的基本示例,实现代码如下所示。

public Yijings getByPaging(@QueryParam("start")final int start,

@QueryParam("size")final int size){//关注点1:资源方法入参

...

    int listSize = globalList.size();

    final int max = size > listSize ? listSize : size;

//关注点2:分页迭代逻辑

    for(int i = 0, index = start; i < max; i++) {

        final Yijing yijing = globalList.get(index + i);

//关注点3:添加Link以保证REST的连通性

        final URI location = ub.clone().queryParam("id", yijing.getSequence()).build();

        final Link link =

new Link("detail", location.toASCIIString(), MediaType.APPLICATION_XML);

        links.add(link);

        yijings.add(yijing);

    }

    result.setLinks(links);

    result.setGuas(yijings);

    return result;

}

在这段代码中,getByPaging()方法的输入参数包含了2个使用@QueryParam注解定义的查询参数,分别是起始条目参数"start"和条目数量参数"size",参数的类型是整型,见关注点1。在查询的迭代中使用这两个参数获取图书列表,见关注点2。在迭代中,每个图书资源条目的URI都存储在返回值中,以保证资源的联通性,见关注点3。该URI被封装到Link实例中,在单项查询时使用。

另外,参数的定义使用了final,符合Checkstyle的编程风格,即输入参数只作为逻辑算法的依据使用,其本身不会在这过程中被修改。也许这种不变的变量对提高执行效率并没有多少影响,但跬步积千里、蚁穴溃长堤。推荐Java开发者在REST开发中引入SonarQube平台或者单纯使用Checkstyle工具对静态代码进行质量检测,以帮助我们改进代码的质量。

2. 排序查询

排序查询是在解析参数的基础上,额外处理结果集顺序的示例,代码如下。

public Yijings getByOrder(@QueryParam("limit") final int limit,

@QueryParam("sort") final String sortName) {//关注点1:资源方法入参

...

Collections.sort(list, new Comparator<Yijing>() {

    @Override

//关注点2:排序中的比较算法

    public int compare(final Yijing o1, final Yijing o2) {

        switch (sortName) {

            case "sequence":

                return o1.getSequence().compareTo(o2.getSequence());

            case "name":

                return o1.getName().compareTo(o2.getName());

            case "pronounce":

                return o1.getPronounce().compareTo(o2.getPronounce());

        }

        return 0;

    }

});

在这段代码中,limit参数的用途同分页查询示例,而sortName参数则用于排序,见关注点1;排序接口需要额外解析sortName传递的排序字段,并将其作为数据库查询语句中的排序参数使用。这里实现了Comparator接口的compare()方法来完成根据不同字段对集合的排序,见关注点2。

3. 单项查询

客户端在获得结果集的基础上,根据表述中链接信息,向服务器发起单项查询的示例,代码示例如下所示。

public Yijing getByQuery(@QueryParam("id") final int seqId) {

    return ParamCache.find("" + seqId);

}

在这段代码中,使用@QueryParam定义了"id"参数,该参数来自分页查询中返回的URI信息。

注解QueryParam可以和注解DefaultValue一起使用。注解DefaultValue的作用是预置一个默认值,当请求中不包含此参数时使用,示例如下。

@DefaultValue("100") @QueryParam("size") final Integer pageSize

这句话的意思是当请求中不包含分页参数pageSize时,分页参数pageSize的默认值为100。

2.2.3 @PathParam注解

JAX-RS2定义了@PathParam注解来定义路径参数—每个参数对应一个子资源,本节使用@PathParam完成如表2-4所示的REST查询接口。

表2-4 @PathParam示例列表

接口描述         资源地址

基本路径参数         /path-resource/Eric

结合查询参数         /path-resource/Eric?hometown=Buenos Aires

带有标点符号的资源路径     /path-resource/199-1999

/path-resource/01,2012-12,2014

子资源变长的资源路径         /path-resource/Asia/China/northeast/liaoning/shenyang/huangu

/path-resource/q/restful;program=java;type=web

/path-resource/q2/restful;program=java;type=web

 

1. @Path注解

JAX-RS2定义了@Path注解来定义资源路径,@Path接收一个value参数来解析资源路径地址。该参数除了前面示例中的books这种静态定义的方式外,也可以使用动态变量的方式,其格式为:{参数名称:正则表达式}。这个接口的功能和查询参数实现的/query-resource/yijings?start=24&size=10相似,也是用于分页查询,其资源地址形如:/path-resource/199-1999,参考示例如下。

@GET

@Path("{from:\\d+}-{to:\\d+}")

public String getByCondition(@PathParam("from") final Integer from,

@PathParam("to") final Integer to) {

...

在这段代码中,使用@PathParam注解定义的两个参数from和to用以定义查询区间,正则表达式部分是\d+,表示数字。两个参数中间的连接符(-)是路径的格式信息。稍显复杂的例子是:/path-resource/01,2012-12,2014,引入了逗号(,)作为有顺序的日期分隔符号,那么对应的正则表达式为:@Path("{beginMonth:\\d+},{beginYear:\\d+}-{endMonth:\\d+},{endYear:\\d+}")

2. 正则表达式

正则表达式的讲述超出了本书范围,这里只简述示例中用到的正则表达式。刚刚的例子中的\\d+,代表参数应为数字并且至少出现一次。第一个反斜杠是Java中的转义字符,第二个反斜杠是正则表达式的起始,加号(+)是至少出现一次的意思,星号(*)则代表出现至少零次,句号(.)是匹配任何字符,d是匹配数字,w是匹配数字和字母。我们有的放矢,示例中使用的正则表达式如表2-5所示,读者掌握所列的路径含义即可,我们的目的是学习REST API设计,而非正则本身。

表2-5 正则表达式示例

正则表达式     含  义

[a-zA-Z][a-zA-Z_0-9]*       以字母开头,后面是零到多个“字母_数字”格式的字符组合

{region:.+}/{district:\w+} region变量至少包含一个任意字符。

district变量至少包含一个为数字或者字母的字符

 

3. 路径配查询

查询参数和路径参数在一个接口中配合使用,可以更便捷地完成资源定位,这很像战场上的多兵种协同作战。前述的图书资源的复杂设计就需要两者结合来完成,示例代码如下。

@Path("{user: [a-zA-Z][a-zA-Z_0-9]*}")

@Produces(MediaType.TEXT_PLAIN)

public String getUserInfo(@PathParam("user") final String user,

@DefaultValue("Shen Yang")@QueryParam("hometown") final String hometown) {

    return user + ":" + hometown;

}

在这段代码中,路径参数user中使用了通配符,方法参数中同时使用@PathParam注解和@QueryParam,定义了user和hometown两个参数。以资源地址:/path-resource/Eric?hometown=Buenos Aires为例,REST容器会将该请求匹配到getUserInfo()方法,其中Eric是路径变量user的值,Buenos Aires作为查询变量hometown的值。

4. 路径区间

路径区间(PathSegment)是对资源地址更灵活的支持,使资源类的一个方法可以支持更广泛的资源地址的请求。我们从下面定义的资源地址列表来走近PathSegment。

/path-resource/Asia/China/northeast/liaoning/shenyang/huangu

/path-resource/China/northeast/liaoning/shenyang/tiexi

/path-resource/China/shenyang/huangu

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

@GET

@Path("{region:.+}/shenyang/{district:\\w+}")

public String getByAddress(@PathParam("region") final List<PathSegment> region,

@PathParam("district") final String district) {

    final StringBuilder result = new StringBuilder();

    for (final PathSegment pathSegment : region) {

        result.append(pathSegment.getPath()).append("-");

    }

    result.append("shenyang-" + district);

...

}

在这段代码中,getByAddress()方法用来匹配表的这些资源地址。该方法的region变量是PathSegment类型的数组,以匹配至少出现一个字符的正则表达式(+)。PathSegment如其名字所示,是路径的片段,是子资源的集合。遍历PathSegment集合,对于每一个PathSegment实例,可以通过调用其getPath()方法获取子资源名称。

对于查询参数动态给定的场景,可以定义PathSegment作为参数类型,通过getMatrix-Parameters()方法获取MultivaluedMap类型的查询参数信息,即可将参数条件作为一个整体解析,示例代码如下。

@Path("q/{condition}")

public String getByCondition3(@PathParam("condition") final PathSegment condition) {

...

  final MultivaluedMap<String, String> matrixParameters = condition.getMatrixParameters();

   final Iterator<Entry<String, List<String>>>

iterator = matrixParameters.entrySet().iterator();

    while (iterator.hasNext()) {

        final Entry<String, List<String>> entry = iterator.next();

        conds.append(entry.getKey()).append("=");

        conds.append(entry.getValue()).append(" ");

    }

    return conds.toString();

}

在这段代码中,getByCondition3()方法只有一个PathSegment类型的参数condition,该参数包含了查询条件中携带的全部参数列表。举例来说,资源地址为path-resource/q/restful;program=java;type=web的请求可以匹配到getByCondition3()方法,其中,MultivaluedMap类型的实例matrixParameters的值为[program=[java], type=[web]]。

5. @MatrixParam注解

上例中,通过编程方式,调用PathSegment类的getMatrixParameters()方法来获取查询参数信息。还有一种方式是通过@MatrixParam注解来逐一定义参数,即通过声明方式来获取,示例代码如下。

@Path("q2/{condition}")

public String getByCondition4(@PathParam("condition")

final PathSegment condition, @MatrixParam("program") final String program,

    @MatrixParam("type") final String type) {

    return condition.getPath() + " program=[" + program + "] type=[" + type + "]";

}

在这段代码中,使用@MatrixParam注解分别定义了"program"和"type"两个参数。与上例相比,这段代码更能清晰地表达可接收的参数名称和类型,缺点是缺乏对请求资源地址更灵活的支持。

2.2.4 @FormParam注解

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

@Path("form-resource")

public class FormResource {

@POST

public String newPassword(

    @DefaultValue("feuyeux") @FormParam(FormResource.USER) final String user,

    @Encoded @FormParam(FormResource.PW) final String password,

    @Encoded @FormParam(FormResource.NPW) final String newPassword,

    @FormParam(FormResource.VNPW) final String verification) {

在这段代码中,newPassword()方法是@FormParam注解定义了user等4个参数,这些参数是容器从请求中获取并匹配的。相关的客户端测试如图2-3所示。

 

 

图2-3 表单示例

图2-3所示的客户端工具是POSTMAN(详见2.6节),使用POSTMAN定义的基本表单信息与newPassword()方法一致。

newPassword()方法的测试代码片段,示例代码如下。

@Test

public void testPost2() {

    final Form form = new Form();

    form.param(FormResource.USER, "feuyeux");

    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 it to disable decoding",

"feuyeux:%E5%8C%97%E4%BA%AC:%E4%B8%8A%E6%B5%B7:上海", result);

}

在这段代码中,Form类实例是请求实体,请求实体的类型为MediaType.APPLI-CATION_FORM_URLENCODED_TYPE,即application/x-www-form-urlencoded。这里还需要注意的是@Encoded注解和@DefaultValue注解的使用。

JAX-RS2定义了@Encoded注解用以标识禁用自动解码。示例的测试结果中“%E4%B8%8A%E6%B5%B7”是newPassword()方法的参数值“上海”的编码值,当对newPassword使用@Encoded注解,REST方法得到的参数值就不会被解码,如果将其直接返回,那么客户端得到的值就会是处于编码状态的字符串。

JAX-RS2定义了@DefaultValue注解,用以为客户端没有为其提供值的参数提供默认值。本例的user参数的默认值为feuyeux。

2.2.5 @BeanParam注解

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

@GET

@Path("{region:.+}/shenyang/{district:\\w+}")

//关注点1:资源方法入参

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"。

2.2.6 @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。

2.2.7 @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接口还需要在此基础上,掌握请求实体和响应实体的传输格式。接下来让我们看看Jersey都支持哪些类型的传输格式。

相关文章
|
15天前
【Azure 应用服务】Web App Service 中的 应用程序配置(Application Setting) 怎么获取key vault中的值
【Azure 应用服务】Web App Service 中的 应用程序配置(Application Setting) 怎么获取key vault中的值
|
12天前
|
小程序 JavaScript Java
【Java】服务CPU占用率100%,教你用jstack排查定位
本文详细讲解如何使用jstack排查定位CPU高占用问题。首先介绍jstack的基本概念:它是诊断Java应用程序线程问题的工具,能生成线程堆栈快照,帮助找出程序中的瓶颈。接着,文章通过具体步骤演示如何使用`top`命令找到高CPU占用的Java进程及线程,再结合`jstack`命令获取堆栈信息并进行分析,最终定位问题代码。
94 1
【Java】服务CPU占用率100%,教你用jstack排查定位
|
13天前
|
监控 安全 Java
Java多线程调试技巧:如何定位和解决线程安全问题
Java多线程调试技巧:如何定位和解决线程安全问题
65 2
|
15天前
|
关系型数据库 MySQL Linux
【Azure 应用服务】在创建Web App Service的时候,选Linux系统后无法使用Mysql in App
【Azure 应用服务】在创建Web App Service的时候,选Linux系统后无法使用Mysql in App
【Azure 应用服务】在创建Web App Service的时候,选Linux系统后无法使用Mysql in App
|
9天前
|
API C# 开发框架
WPF与Web服务集成大揭秘:手把手教你调用RESTful API,客户端与服务器端优劣对比全解析!
【8月更文挑战第31天】在现代软件开发中,WPF 和 Web 服务各具特色。WPF 以其出色的界面展示能力受到欢迎,而 Web 服务则凭借跨平台和易维护性在互联网应用中占有一席之地。本文探讨了 WPF 如何通过 HttpClient 类调用 RESTful API,并展示了基于 ASP.NET Core 的 Web 服务如何实现同样的功能。通过对比分析,揭示了两者各自的优缺点:WPF 客户端直接处理数据,减轻服务器负担,但需处理网络异常;Web 服务则能利用服务器端功能如缓存和权限验证,但可能增加服务器负载。希望本文能帮助开发者根据具体需求选择合适的技术方案。
31 0
|
9天前
|
Java 网络架构 数据格式
Struts 2 携手 RESTful:颠覆传统,重塑Web服务新纪元的史诗级组合!
【8月更文挑战第31天】《Struts 2 与 RESTful 设计:构建现代 Web 服务》介绍如何结合 Struts 2 框架与 RESTful 设计理念,构建高效、可扩展的 Web 服务。Struts 2 的 REST 插件提供简洁的 API 和约定,使开发者能快速创建符合 REST 规范的服务接口。通过在 `struts.xml` 中配置 `&lt;rest&gt;` 命名空间并使用注解如 `@Action`、`@GET` 等,可轻松定义服务路径及 HTTP 方法。
24 0
|
9天前
|
前端开发 API 开发者
JSF与RESTful服务的完美邂逅:如何打造符合现代Web潮流的数据交互新体验
【8月更文挑战第31天】随着互联网技术的发展,RESTful架构风格因其实现简便与无状态特性而在Web服务构建中日益流行。本文探讨如何结合JavaServer Faces (JSF) 和 JAX-RS 构建RESTful API,展示从前端到后端分离的完整解决方案。通过定义资源类、配置 `web.xml` 文件以及使用依赖注入等步骤,演示了在JSF项目中实现RESTful服务的具体过程,为Java开发者提供了实用指南。
19 0
|
9天前
|
API 网络安全 数据库
Web2py框架如何颠覆传统的RESTful API开发?掌握这些技巧,让你的开发效率飞跃!
【8月更文挑战第31天】Web2py是一款全栈Python Web框架,适用于快速开发复杂交互的Web应用。本文将介绍如何使用Web2py创建RESTful API,包括设置新控制器、定义RESTful路由、处理数据库交互、确保API安全性、编写文档与使用Swagger、测试API以及部署时的注意事项。Web2py的高度抽象和易用性使其成为实现RESTful API的理想选择,帮助开发者专注于业务逻辑而非技术细节。
14 0
|
15天前
|
缓存 网络协议 API
【APIM】Azure APIM抛出 java.lang.RuntimeException 错误定位
【APIM】Azure APIM抛出 java.lang.RuntimeException 错误定位
|
15天前
|
Shell PHP Windows
【Azure App Service】Web Job 报错 UNC paths are not supported. Defaulting to Windows directory.
【Azure App Service】Web Job 报错 UNC paths are not supported. Defaulting to Windows directory.