创建REST服务

创建REST服务

随着客户端的可选方案越来越多,我们让服务器公开API,通过这种API,各种客户端都能与后端功能进行交互,也就是我们所熟知的前后端分离。


编写REST控制器

从服务器中检索数据

@RestController
@RequestMapping(path = "/design",produces = "application/json")
@CrossOrigin(origins = "*")
public class DesignTacoController {private TacoRepository tacoRepo;@AotuwiredEntityLinks entityLinks;public DesignTacoController(TacoRepository tacoRepo) {this.tacoRepo = tacoRepo;}@GetMapping("/recent")public Iterable recentTacos() {PageRequest page = PageRequest.of(0,12,Sort.by("CreateAt").descending());return tacoRepo.findAll(page).getContent();}
}

@RestController 注解有两个目的,首先,它是一个类似于@Controller和@Service的构造性注解,能够让类被组件扫描功能发现。最重要的是,@RestController注解会告诉Spring,控制器中的所有处理器方法的返回值都要直接写入响应体中,而不是将值放到模型中并传递给一个视图以便于进行渲染。

上述注解也有一个替代方案,就是将@RestController替换成@Controller,但是我们就需要为每个处理器方法再添加一个@ResponseBody注解。另一种替代方案就是返回ResponseEntity对象。

@RequestMapping 注解还设置了一个process属性。这是指明该控制器的所有方法只会处理Accept头信息包含“application/json”的请求。它不仅会限制API只会生成JSON结果,同时还允许其他的控制器处理具有相同路径的请求,只要这些请求不要求JSON格式的输出就可以。我们还可以将process属性设置为一个String类型的数组,这样的话就允许我们设置多个内容类型。比如,为了允许生成XML格式的输出,我们可以将process属性添加“text/xml”:

@RequestMapping(path = “/design”,process = {“application/json”,“text/xml”})

@CrossOrigin 注解,在本控制器中将会允许来自任何域的客户端消费该API。

@GetMapping("/{id}")
public Taco tacoById(@PathVariable("id") Long id)

请求路径的“{id}”部分是占位符。请求中的实际值将会传递给id参数,它通过@PathVariable注解与{id}占位符进行匹配。

return new ResponseEntity<>(null,HttpStatus.NOT_FOUND);

返回一个ResponseEntity对象,包装了一个null,并且带有NOT FOUND的HTTP状态。

发送数据至服务器端

@PostMapping(consumes = "application/json")
@ResponseStatus(HttpStatus.CREATED)
public Taco postTaco(@RequestBody Taco taco) {return tacoRepo.save(taco);
}

consumes属性用于指定请求输入,表明本方法只会处理Content-type与application/json相匹配的请求

@ResponseBody注解表明请求应该被转换为一个Taco对象并绑定到该参数上。@ResponseBody注解确保请求体中的JSON会被绑定到Taco对象上。

@ResponseStatus(HttpStatus.CREATED) 告诉客户端,请求不仅成功了,还创建了一个资源。

在服务器上更新数据

PUT真正的目的是执行大规模的替换操作,而不是更新操作。HTTP PATCH的目的是对资源数据打补丁或局部更新。

删除服务器上的数据

删除之前资源是否存在并不重要,但是也可以让方法返回ResponseEntity,在资源不存在时将响应体设置为null并将HTTP状态码设置为NOT FOUND。

@ResponseStatus(code = HTTPStatus.NO_CONTENT)将响应体状态码为204(NO CONTENT)让客户端知道不要期望得到任何内容。


启用超媒体功能

超媒体作为应用状态引擎(Hypermedia as the Engine of Application State,HATEOAS)是一种创建自描述API的方式。API所返回的资源中会包含相关资源的链接,客户端只需要了解最少的API URL信息就能够导航整个API。这种方式能够掌控API所提供的资源之间的关系,客户端能够基于API的URL中所发现的关系,对他们进行遍历。

如果服务器的返回体中嵌入超链接,这种特殊风格的HATEOAS被称为HAL(超文本应用语言,Hypertext Application Language)。这是一种在JSON相应体中嵌入超链接的简单通用格式。

Spring HATEOAS项目为Spring提供了超链接的支持。

org.springframework.bootspring-boot-starter-hateoas

只要我们重构控制器,使其返回资源类型,而不是领域类型。

添加超链接

@GetMappingpublic EntityModel> test() {Optional employee = ingredientRepository.findById(1);return EntityModel.of(employee,linkTo(methodOn(TestController.class).test()).withSelfRel());}

上述代码运行后你可以看到这样的结果

{"id": 1,"name": "热狗","_links": {"self": {"href": "http://localhost:8080/test"}}
}

与之作为对比的有:

@GetMappingpublic Optional get() {return ingredientRepository.findById(1);}

以及结果

{"id": 1,"name": "热狗"
}

不难发现,元素包含了一个名为"_links"的属性,为客户端提供导航的超链接。self链接用来引用该资源。
这样做的一个好处就是如果客户端需要对该资源进行HTTP请求,那么在开发的时候就不需要关心该资源的URL长什么样子,相反,只需要请求“self”链接就可以了。

Spring HATEOAS提供了两个主要的类型来表示超链接资源:EntityModel和CollectionModel,EntityModel代表一个资源,而CollectionModel代表一个资源的集合。这两种类型都能携带到其他资源的链接,当从Spring MVC REST控制器返回时,他们所携带的链接将会包含到客户端接收到的JSON中。

linkTo与methodOn()方法来自WebMvcLinkBuilder,如果使用Spring WebFlux则必须改用WebFluxLinkBuilder。

@PostMappingpublic CollectionModel> test2() {List ingredients = (List) ingredientRepository.findAll();CollectionModel> resources = CollectionModel.wrap(ingredients);return CollectionModel.of(resources,linkTo(methodOn(TestController.class).test2()).withSelfRel());/*resources.add(linkTo(methodOn(TestController.class).test2()).withSelfRel());*//*return resources;*/}

上述为CollectionModel的用法结果为

{"_embedded": {"ingredientList": [{"id": 1,"name": "热狗"}]},"_links": {"self": {"href": "http://localhost:8080/test"}}
}

创建装配器

我们需要为我们的领域数据添加链接,如此我们就可以创造一个领域数据资源,创建一个单独的资源类,从而将id属性排除出去,以及不必要为每个领域都添加链接。

public class TacoResource extends RepresentationModel {@Getterprivate String name;@Getterprivate Date createdAt;@Getterprivate List ingredients;public TacoResource(Taco taco) {this.name = taco.getName();this.createdAt = taco.getCreatedAt();this.ingredients = taco.getIngredients();}
}

为了将领域对象转换成领域资源对象,我们需要创建一个资源适配器

public class IngredientResourceAssembler extends RepresentationModelAssemblerSupport {public IngredientResourceAssembler(Class controllerClass, Class resourceType) {super(controllerClass, resourceType);}@Overrideprotected IngredientResource instantiateModel(Ingredient entity) {return new IngredientResource(entity);}@Overridepublic IngredientResource toModel(Ingredient entity) {return createModelWithId(entity.getId(),entity);}
}

IngredientResourceAssemble有一个默认的构造器,会告诉超类在创建IngredientResource中的链接时,将使用该控制器来确定所有URL的基础路径。

instantiateModel方法进行了重写,以便于给定的Ingredient实例化IngredientResource。如果IngredientResource有默认构造器,那么这个方法是可选的。

ToModel方法是用于告诉它通过Ingredient来创建IngredientResource,并设置一个self链接,并且这个链接的URL是根据Ingredient对象的id属性衍生出来的

在内部,ToModel将会调用instantiateModel。

控制器的调整

@GetMapping("/test")public CollectionModel ingredients () {List ingredients = (List) ingredientRepository.findAll();CollectionModel ingredientResources =new IngredientResourceAssembler(TestController.class,IngredientResource.class).toCollectionModel(ingredients);ingredientResources.add(linkTo(methodOn(TestController.class).ingredients()).withSelfRel());return ingredientResources;

结果:

{"_embedded": {"ingredientResourceList": [{"name": "热狗","_links": {"self": {"href": "http://localhost:8080/test/1"}}},{"name": "生菜","_links": {"self": {"href": "http://localhost:8080/test/2"}}}]},"_links": {"self": {"href": "http://localhost:8080/test/test"}}
}

如果是资源中嵌套资源的情况,如Taco与Ingredients,则需要这样定义上层资源:

public class TacoResource extends RepresentationModel {private static final IngredientResourceAssembleringredientAssembler = new IngredientResourceAssembler(IngredientController.class, IngredientResource.class);@Getterprivate String name;@Getterprivate Date createdAt;@Getterprivate CollectionModel ingredients;public TacoResource(Taco taco) {this.name = taco.getName();this.createdAt = taco.getCreatedAt();this.ingredients = ingredientAssembler.toCollectionModel(taco.getIngredients());}
}

@Relation注解能够帮助我们消除JSON字段名,和Java代码中定义的资源名的耦合

@Relation(value = "taco",collectionRelation = "tacos")
public class TacoResource extand ···

启用后端数据服务

为了在资源中添加超链接,也可以借助Spring Data REST自动创建API。需要用Spring Data来实现repository。

为了使用Spring Data REST,添加依赖:

org.springframework.bootspring-boot-starter-data-rest

为了避免Spring Data REST 创建的API与自己创建的控制器发生冲突,我们可以为其API设置基础路径。设置spring.data.rest.base-path属性。

spring:data:rest:base-path:  /api

这项配置会将Spring Data REST端点的基础路径设置为“/api”

请求资源时,可能遇到因为资源其名的复数形式而出现问题。如taco->tacoes
我们可以使用@RestResource为我们提供任何想要的关系名以及路径。只要将其注解在entity类名前。
@RestResource(rel = "tacos",path = "tacos")

分页及排序

Spring Data REST提供分页支持,要使用它,repository需要继承PagingAndSortingRepository<>,而不是CurdRepository。我们可以在URL出设置分页大小如L:

http://localhost:8080/api/ingredients?page=0&size=1

展现结果就有趣得多

{"_embedded": {"ingredients": [{"name": "热狗","_links": {"self": {"href": "http://localhost:8080/api/ingredients/1"},"ingredient": {"href": "http://localhost:8080/api/ingredients/1"}}}]},"_links": {"first": {"href": "http://localhost:8080/api/ingredients?page=0&size=1"},"self": {"href": "http://localhost:8080/api/ingredients?page=0&size=1"},"next": {"href": "http://localhost:8080/api/ingredients?page=1&size=1"},"last": {"href": "http://localhost:8080/api/ingredients?page=1&size=1"},"profile": {"href": "http://localhost:8080/api/profile/ingredients"}},"page": {"size": 1,"totalElements": 2,"totalPages": 2,"number": 0}
}

page参数是从0开始的。

不仅可以分页,还可以将分页与排序混合起来。如按照降序排序:

http://localhost:8080/api/ingredients?sort=id,desc&page=0&size=1

创建自定义端点

Spring Data REST为我们提供了一个新的注解@RepositoryRestController,这个注解可以用到控制器类上,这样控制器类所有映射得基础路径就会与Spring Data REST端点配置的基础路径相同。

@RepositoryRestController并不能保证处理器方法的值会自动写入响应体中,所以我们要么为方法添加@ResponseBody注解,要么返回包装响应数据的@ResponseEntity。

为Spring Data端点添加自定义的超链接

通过声明资源处理器bean,我们可以为Spring Data REST自动包含的链接列表继续添加链接。Spring Data HATEOAS提供了一个RepresentationModelProcessor接口,能够在资源通过API返回自前对其进行操作。

如“api/tacos”端点所返回的类型为PagedModel添加链接。Spring HATEOAS会自动发现这个bean并将其应用到对应资源上。如果控制器返回PagedModel,就会包含一个最新创建的taco链接。

@Beanpublic RepresentationModelProcessor>> tacoProcessor(EntityLinks links) {return new RepresentationModelProcessor>>() {@Overridepublic PagedModel> process(PagedModel> resource) {resource.add(links.linkFor(Taco.class).slash("recent").withRel("recents"));return resource;}};}

.slash会为URL添加斜线。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部