一、rest服务
Spring MVC HTTP 请求处理注解
注解 | HTTP 方法 | 用法 |
---|---|---|
@GetMapping | HTTP GET 请求 | 读取资源 |
@PostMapping | HTTP POST 请求 | 创建资源 |
@PutMapping | HTTP PUT 请求 | 更新资源 |
@PatchMapping | HTTP PATCH 请求 | 更新资源 |
@DeleteMapping | HTTP DELETE 请求 | 删除资源 |
@RequestMapping | 通用请求处理 |
1、获取数据从服务器
// @RestController 注解告诉 Spring,控制器中的所有处理程序方法都应该将它们的返回值直接写入响应体,
// 而不是在模型中被带到视图中进行呈现。
@RestController
// 只在请求的 Accept 头包含application/json 时才处理请求。
@RequestMapping(path="/user", produces="application/json")
// 为了允许 XML 输出,可以向 produces 属性添加 “text/html”,
// @RequestMapping(path="/user", produces={"application/json", "text/xml"})
// CORS(跨源资源共享)
@CrossOrigin(origins="*")
public class UserController {
private UserRepository userRepo;
public UserController(UserRepository userRepo) {
this.userRepo = userRepo;
}
// 处理 /user/recent 接口的 GET 请求。
@GetMapping("/recent")
public Iterable<User> recentUsers() {
PageRequest page = PageRequest.of(
0, 10, Sort.by("createdAt").descending());
return userRepo.findAll(page).getContent();
}
// 通过其 ID 获取单个User。方法的路径中使用占位符变量并接受 path 变量的方法。
// 处理 /user/{id} 的 GET 请求。
@GetMapping("/{id}")
public ResponseEntity<User> userById(@PathVariable("id") Long id) {
Optional<User> optUser = userRepo.findById(id);
if (optUser.isPresent()) {
return ResponseEntity<>(userRepo.get(), HttpStatus.OK);
}
return new ResponseEntity<>(null, HttpStatus.NOT_FOUND);
}
}
2、发送数据给服务器
@RestController
@RequestMapping(path="/user", produces="application/json")
@CrossOrigin(origins="*")
public class UserController {
private UserRepository userRepo;
public UserController(UserRepository userRepo) {
this.userRepo = userRepo;
}
// ...
// consumer 属性用于处理输入,表示该方法只处理 Content-type 与 application/json 匹配的请求。
@PostMapping(consumes="application/json")
// 在正常情况下(当没有抛出异常时),所有响应的 HTTP 状态码为 200(OK),表示请求成功.
@ResponseStatus(HttpStatus.CREATED)
// @RequestBody 表示请求体应该转换为 User 对象并绑定到参数。
// 如果没有 @RequestBody,Spring MVC 会假设将请求参数(查询参数或表单参数)绑定到 User 对象。
// @RequestBody 注解确保将请求体中的 JSON 绑定到 User 对象
public User postUser(@RequestBody Uesr user) {
return userRepo.save(user);
}
}
3、 更新服务器上的资源
@PutMapping、@PatchMapping。
UserController
:
@RestController
@RequestMapping(path="/user", produces="application/json")
@CrossOrigin(origins="*")
public class UserController {
private UserRepository userRepo;
public UserController(UserRepository userRepo) {
this.userRepo = userRepo;
}
// ...
// 处理 HTTP PATCH, @PutMapping 的意思是“把这个数据放到这个 URL 上”,本质上是替换任何已经存在的数据。PUT 完全替换了资源数据。
// 如果User的任何属性被省略,该属性的值将被 null 覆盖。
@PutMapping("/{userId}")
public User putUser(@RequestBody User user) {
return userRepo.save(user);
}
// @PatchMapping进行部分更新的请求
@PatchMapping(path="/{userId}", consumes="application/json")
public User patchUser(@PathVariable("userId") Long userId, @RequestBody User patch) {
User user = userRepo.findById(uesrId).get();
// ...
if (patch.getUserName() != null) {
order.setUserName(patch.getUserName());
}
return userRepo.save(user);
}
}
PATCH 方式应用于 patchUser() 方法时,有两个限制:
如果传递的是 null 值,意味着没有变化。
没有办法从一个集合中移除或添加一个子集。如果客户端想要从集合中添加或删除一条数据,它必须发送完整的修改后的集合。
4、服务器删除数据
UserController
:
@RestController
@RequestMapping(path="/user", produces="application/json")
@CrossOrigin(origins="*")
public class UserController {
private UserRepository userRepo;
public UserController(UserRepository userRepo) {
this.userRepo = userRepo;
}
// ...
// 处理 /user/{orderId} 的删除请求
@DeleteMapping("/{userId}")
// HTTP 状态是 204
@ResponseStatus(code=HttpStatus.NO_CONTENT)
public void deleteUser(@PathVariable("userId") Long userId) {
try {
userRepo.deleteById(userId);
} catch (EmptyResultDataAccessException e) {
}
}
}
二、超媒体
0、要启用超媒体,需添加 Spring HATEOAS starter 依赖
Spring HATEOAS 项目为 Spring 提供了超链接支持。
在pom.xml引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
1、添加超链接
@RestController
@RequestMapping(path="/user", produces="application/json")
@CrossOrigin(origins="*")
public class UserController {
private UserRepository userRepo;
public UserController(UserRepository userRepo) {
this.userRepo = userRepo;
}
//...
// 为资源添加超链接
@GetMapping("/recent")
public Resources<Resource<User>> recentUsers() {
PageRequest page = PageRequest.of(
0, 12, Sort.by("createdAt").descending());
List<User> users = userRepo.findAll(page).getContent();
// Resources<Resource<User>> recentResources = Resources.wrap(users);
// 使用 UesrResourceAssembler。
List<UserResource> userResources = new UserResourceAssembler().toResources(user);
Resources<UserResource> recentResources = new Resources<UserResource>(userResources);
recentResources.add(
// new Link("http://localhost:8080/user/recent", "recents")); // 该链接的关系名称为 recents。这种是在URL中硬编码localhost:8080。
// 下面有2种方法解决,选其一即可。这里选择方法2。整个 URL 都是从控制器的映射中派生出来的。
// 方法1:
// 不需要硬编码主机名, 还不必指定 /user 路径。
// 需要一个指向 UserController 的链接,它的基本路径是 /user
// ControllerLinkBuilder 使用控制器的基本路径作为正在创建的链接对象的基础
// ControllerLinkBuilder.linkTo(UserController.class)
// .slash("recent") // 在URL后面附加了一个斜杠 / 和给定的值。这里,URL的路径是/user/recent。
// .withRel("recents")); // 为链接指定一个关系名。
// 方法2:
// linkTo() 方法,让 ControllerLinkBuilder 从 控制器基础路径和方法 映射路径中派生出基础 URL。
// methodOn() 方法获取控制器类。允许调用 recentUsers() 方法
linkTo(methodOn(UserController.class).recentUsers())
.withRel("recents"));
return recentResources;
}
}
http://localhost:8080/user/recent
在 API 请求返回的资源中,会包含以下 JSON 片段:
"_links": {
"recents": {
"href": "http://localhost:8080/user/recent"
}
}
2、创建资源装配器
定义一个工具类,解决重复编写问题(按上面的方式,每一个api都要wrap一遍。)。
将user对象转化成新的UserResource对象。而不是让 Resources.wrap() 为列表中的每个 user 创建一个资源对象。
package com.example.springbootlearn.web.api;
import java.util.Date;
import java.util.List;
import org.springframework.hateoas.ResourceSupport;
import lombok.Getter;
import com.example.User;
// UserResource 扩展了 ResourceSupport 以继承链接对象列表和管理链接列表的方法。
public class UserResource extends ResourceSupport {
// UserResource 不包含 User 的 id 属性。这是因为不需要在 API 中公开任何特定于数据库的 id。从 API 客户端的角度来看,资源的自链接将作为资源的标识符。
@Getter
private final String name;
@Getter
private final Date createdAt;
@Getter
private final List<Address> addresses;
public UserResource(User user) {
this.name = user.getUserName();
this.createdAt = user.getCreatedAt();
// this.addresses = user.getAddresses();
this.addresses = ingredientAssembler.toResources(user.getAddresses());
}
}
仅通过上面这样,仍然需要循环才能将 User 对象列表转换为 Resources。
将 User 对象转换为 UserResource 对象,创建一个资源装配器:
package com.example.springbootlearn.web.api;
import org.springframework.hateoas.mvc.ResourceAssemblerSupport;
import com.example.User;
public class UserResourceAssembler extends ResourceAssemblerSupport<User, UserResource> {
public UserResourceAssembler() {
// 使用 UserController 来确定它创建的链接中的任何 url 的基本路径
super(UserController.class, UserResource.class);
}
// 重写 instantiateResource() 方法来实例化给定 User 的 UserResource。
// 如果 UserResource 有一个默认的构造函数,那么这个方法是可选的。
// 但在这UserResource,因为上面UserResource代码中自定义了构造方法。
// 所以需要使用 User 进行构造。
@Override
protected UserResource instantiateResource(User user) {
return new UserResource(user);
}
// 从 User 创建一个 UserResource 对象,并自动给它一个自链接。
// 该链接的 URL 来自 User 对象的 id 属性
// toResource() 将会调用 instantiateResource()。
@Override
public UserResource toResource(User user) {
return createResourceWithId(user.getId(), user);
}
}
为收货地址创建一个新的资源装配器:
package com.example.springbootlearn.web.api;
import org.springframework.hateoas.mvc.ResourceAssemblerSupport;
import com.example.Address;
class AddressResourceAssembler extends
ResourceAssemblerSupport<Address, AddressResource> {
public AddressResourceAssembler() {
super(AddressController.class, AddressResource.class);
}
@Override
public AddressResource toResource(Address address) {
return createResourceWithId(address.getId(), address);
}
@Override
protected AddresstResource addressResource(Address address) {
return new AddressResource(address);
}
}
package com.example.springbootlearn.web.api;
import org.springframework.hateoas.ResourceSupport;
import lombok.Getter;
import com.example.Address;
public class AddressResource extends ResourceSupport {
@Getter
private String province;
@Getter
private String city;
@Getter
private String area;
@Getter
private String street;
public AddressResource(Address address) {
this.province = address.getProvince();
this.city = address.getCity();
this.area = address.getArea();
this.street = address.getStreet();
}
}
这样 /user/recent 接口的 GET 请求将生成一个 user 列表。完全嵌套了超链接,其中每个 user 都有一个自链接和一个 recents 链接,而且为它所有的 user 数据和那些user 的 address 数据。
3、嵌套命名关系
userResourceList 这个名称,它来自 List 中创建的 Resources 对象实例。
{
"_embedded": {
"userResourceList": [
// ...
]
}
}
如果将 UserResource 类名称重构为其他名称,那么 JSON 中的字段名将会需要更改以与之匹配。这可能会影响任何依赖该名称的客户端。
@Relation
注解:可以解决 JSON 字段名
与 Java 中定义的资源类型类名
耦合。
UserResource 上使用 @Relationip 注解,如:
package com.example.springbootlearn.web.api;
import java.util.Date;
import java.util.List;
import org.springframework.hateoas.ResourceSupport;
import lombok.Getter;
import com.example.User;
@Relation(value="user", collectionRelation="users")
public class UserResource extends ResourcesSupport {
// ...
}
这样,从 /user/recent 返回的 JSON 结果会是下面这种:
{
"_embedded": {
"users": [
// ...
]
}
}
三、以数据为中心的服务
使用Spring Data REST之前,先在
pom.xml引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
设置 spring.data.rest 基本路径属性:
spring:
data:
rest:
base-path: /api
调整关系名称和路径:
@Data
@Entity
@RestResource(rel="users", path="users")
public class User {
...
}