创建REST服务

本文介绍了如何使用Spring创建REST服务,包括编写REST控制器、启用超媒体功能、启用后端数据服务等。详细讲解了各个关键注解的作用,如@RestController、@RequestMapping、@CrossOrigin、@PathVariable等,以及如何处理数据的增删查改、超链接的添加和资源的分页排序。
摘要由CSDN通过智能技术生成

创建REST服务

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


编写REST控制器

从服务器中检索数据

@RestController
@RequestMapping(path = "/design",produces = "application/json")
@CrossOrigin(origins = "*")
public class DesignTacoController {
    private TacoRepository tacoRepo;

    @Aotuwired
    EntityLinks entityLinks;

    public DesignTacoController(TacoRepository tacoRepo) {
        this.tacoRepo = tacoRepo;
    }

    @GetMapping("/recent")
    public Iterable<Taco> 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提供了超链接的支持。

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>

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

添加超链接

@GetMapping
    public EntityModel<Optional<Ingredient>> test() {
        Optional<Ingredient> employee = ingredientRepository.findById(1);

        return EntityModel.of(employee,
                linkTo(methodOn(TestController.class).test()).withSelfRel());
    }

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

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

与之作为对比的有:

@GetMapping
    public Optional<Ingredient> 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。

@PostMapping
    public CollectionModel<EntityModel<Ingredient>> test2() {
       List<Ingredient> ingredients = (List<Ingredient>) ingredientRepository.findAll();
        CollectionModel<EntityModel<Ingredient>> 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<TacoResource> {

    @Getter
    private String name;

    @Getter
    private Date createdAt;

    @Getter
    private List<Ingredient> ingredients;

    public TacoResource(Taco taco) {
        this.name = taco.getName();
        this.createdAt = taco.getCreatedAt();
        this.ingredients = taco.getIngredients();
    }
}

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

public class IngredientResourceAssembler extends RepresentationModelAssemblerSupport<Ingredient, IngredientResource> {
    public IngredientResourceAssembler(Class<?> controllerClass, Class<IngredientResource> resourceType) {
        super(controllerClass, resourceType);
    }

    @Override
    protected IngredientResource instantiateModel(Ingredient entity) {
        return new IngredientResource(entity);
    }

    @Override
    public 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<IngredientResource> ingredients () {
        List<Ingredient> ingredients = (List<Ingredient>) ingredientRepository.findAll();
        CollectionModel<IngredientResource> 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<TacoResource> {
    private static final IngredientResourceAssembler
            ingredientAssembler = new IngredientResourceAssembler(IngredientController.class, IngredientResource.class);

    @Getter
    private String name;

    @Getter
    private Date createdAt;

    @Getter
    private CollectionModel<IngredientResource> 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,添加依赖:

<dependency>
    <groupId>org.springframework.boot</goupId>
    <artifactId>spring-boot-starter-data-rest</artifactId>
</denpendency>

为了避免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<EntityModel>添加链接。Spring HATEOAS会自动发现这个bean并将其应用到对应资源上。如果控制器返回PagedModel<EntityModel>,就会包含一个最新创建的taco链接。

@Bean
    public RepresentationModelProcessor<PagedModel<EntityModel<Taco>>> tacoProcessor(EntityLinks links) {

        return new RepresentationModelProcessor<PagedModel<EntityModel<Taco>>>() {
            @Override
            public PagedModel<EntityModel<Taco>> process(PagedModel<EntityModel<Taco>> resource) {
                resource.add(
                        links.linkFor(Taco.class)
                                .slash("recent")
                                .withRel("recents"));
                return resource;
            }
        };
    }

.slash会为URL添加斜线。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值