创建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.boot spring-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
@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添加斜线。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
