到底什么样的风格才是RESTful风格呢?
1,约束请求命令如下:
GET,获取资源。例如:/employees表示获取列表资源,/employees/{id}表示获取单个对象资源。
POST,新增。例如:/employees,body为json对象,表示新增。
PUT,更新。例如:/employees/{id},body为json对象,表示更新。
DELETE,删除。例如: /employees/{id},表示删除。
2,约束返回结果: 返回数据为列表,则每个对象资源附加自己的资源链接、列表资源链接以及可操作性链接。
参考链接:
官网demo地址:https://spring.io/guides/tutorials/bookmarks/
官网demo的git地址:https://github.com/spring-guides/tut-rest/
1 get 获取所有员工
curl -G http://localhost:8080/employees
{
"_embedded": {
"employeeList": [{
"id": 1,
"firstName": "Bilbo",
"lastName": "Baggins",
"role": "burglar",
"name": "Bilbo Baggins",
"_links": {
"self": {
"href": "http://localhost:8080/employees/1"
},
"employees": {
"href": "http://localhost:8080/employees"
}
}
}, {
"id": 2,
"firstName": "Frodo",
"lastName": "Baggins",
"role": "thief",
"name": "Frodo Baggins",
"_links": {
"self": {
"href": "http://localhost:8080/employees/2"
},
"employees": {
"href": "http://localhost:8080/employees"
}
}
}]
},
"_links": {
"self": {
"href": "http://localhost:8080/employees"
}
}
}
返回所有员工,以及每个员工对应的可调用的接口。每次都是动态生成。相比传统http模式,更加灵活。
从上面接口返回的_links–>self,获取指定员工信息
curl -G http://localhost:8080/employees/1
{
"id": 1,
"firstName": "Bilbo",
"lastName": "Baggins",
"role": "burglar",
"name": "Bilbo Baggins",
"_links": {
"self": {
"href": "http://localhost:8080/employees/1"
},
"employees": {
"href": "http://localhost:8080/employees"
}
}
}
post 请求新增用户
curl localhost:8080/employees -X POST -d "{\"firstName\":\"nigulasi\",\"lastName\":\"zhaosi\",\"role\":\"yingdi\"}" --header "Content-Type: application/json"
{
"id": 3,
"firstName": "nigulasi",
"lastName": "zhaosi",
"role": "yingdi",
"name": "nigulasi zhaosi",
"_links": {
"self": {
"href": "http://localhost:8080/employees/3"
},
"employees": {
"href": "http://localhost:8080/employees"
}
}
}
put 请求 更新员工信息,3代表指定员工id,具体信息通过data传递。
curl localhost:8080/employees/3 -X PUT -d "{\"firstName\":\"xie\",\"lastName\":\"guangkun\",\"role\":\"zhemowang\"}" --header "Content-Type: application/json"
{
"id": 3,
"firstName": "xie",
"lastName": "guangkun",
"role": "zhemowang",
"name": "xie guangkun",
"_links": {
"self": {
"href": "http://localhost:8080/employees/3"
},
"employees": {
"href": "http://localhost:8080/employees"
}
}
}
delete 请求 删除指定员工 3代表指定员工id
curl -X DELETE http://localhost:8080/employees/3
如果删除成功,此接口没有返回结果。
使用H2作为内存数据库,创建员工
@Configuration
class LoadDatabase {
private static final Logger log = LoggerFactory.getLogger(LoadDatabase.class);
@Bean
CommandLineRunner initDatabase(EmployeeRepository repository) {
return args -> {
// tag::new_constructor[]
log.info("Preloading " + repository.save(new Employee("Bilbo", "Baggins", "burglar")));
log.info("Preloading " + repository.save(new Employee("Frodo", "Baggins", "thief")));
// end::new_constructor[]
};
}
}
使用的jar包
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
原理解释:
从Spring3.0,@Configuration用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。
@Configuration标注在类上,相当于把该类作为spring的xml配置文件中的,作用为:配置spring容器(应用上下文)
@Bean标注在方法上(返回某个实例的方法),等价于spring的xml配置文件中的,作用为:注册bean对象
(1)@Bean注解在返回实例的方法上,如果未通过@Bean指定bean的名称,则默认与标注的方法名相同;
(2)@Bean注解默认作用域为单例singleton作用域,可通过@Scope(“prototype”)设置为原型作用域;
(3)既然@Bean的作用是注册bean对象,那么完全可以使用@Component、@Controller、@Service、@Ripository等注解注册bean,当然需要配置@ComponentScan注解进行自动扫描。
使用JPA访问数据库
interface EmployeeRepository extends JpaRepository<Employee, Long> {
}
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
JPA创建实体类
@Entity
class Employee {
private @Id @GeneratedValue Long id;
private String firstName;
private String lastName;
private String role;
Employee() {}
Employee(String firstName, String lastName, String role) {
this.firstName = firstName;
this.lastName = lastName;
this.role = role;
}
public String getName() {
return this.firstName + " " + this.lastName;
}
public void setName(String name) {
String[] parts = name.split(" ");
this.firstName = parts[0];
this.lastName = parts[1];
}
public Long getId() {
return this.id;
}
public String getFirstName() {
return this.firstName;
}
public String getLastName() {
return this.lastName;
}
public String getRole() {
return this.role;
}
public void setId(Long id) {
this.id = id;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public void setRole(String role) {
this.role = role;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof Employee))
return false;
Employee employee = (Employee) o;
return Objects.equals(this.id, employee.id) && Objects.equals(this.firstName, employee.firstName)
&& Objects.equals(this.lastName, employee.lastName) && Objects.equals(this.role, employee.role);
}
@Override
public int hashCode() {
return Objects.hash(this.id, this.firstName, this.lastName, this.role);
}
@Override
public String toString() {
return "Employee{" + "id=" + this.id + ", firstName='" + this.firstName + '\'' + ", lastName='" + this.lastName
+ '\'' + ", role='" + this.role + '\'' + '}';
}
增加@ControllerAdvice注解,实现异常处理器:
@ControllerAdvice
class EmployeeNotFoundAdvice {
@ResponseBody
@ExceptionHandler(EmployeeNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
String employeeNotFoundHandler(EmployeeNotFoundException ex) {
return ex.getMessage();
}
}
class EmployeeNotFoundException extends RuntimeException {
EmployeeNotFoundException(Long id) {
super("Could not find employee " + id);
}
}
二、restful的http服务
pom.xml增加hateoas依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
控制器更改,在原来的返回json基础之上附加操作链接
@GetMapping("/employees")
CollectionModel<EntityModel<Employee>> all() {
List<EntityModel<Employee>> employees = repository.findAll().stream()
.map(employee -> EntityModel.of(employee,
linkTo(methodOn(EmployeeController.class).one(employee.getId())).withSelfRel(), // 附加自身链接
linkTo(methodOn(EmployeeController.class).all()).withRel("employees"))) // 附加all操作链接
.collect(Collectors.toList());
return CollectionModel.of(employees, linkTo(methodOn(EmployeeController.class).all()).withSelfRel()); // 附加自身链接
}
@GetMapping("/employees/{id}")
EntityModel<Employee> one(@PathVariable Long id) {
Employee employee = repository.findById(id) //
.orElseThrow(() -> new EmployeeNotFoundException(id));
return EntityModel.of(employee, linkTo(methodOn(EmployeeController.class).one(id)).withSelfRel(), // 附加自身链接
linkTo(methodOn(EmployeeController.class).all()).withRel("employees")); // 附加all操作链接
}
三、附加可操作链接的restful服务
订单实体转换器,如果订单状态为可执行的订单则附加取消和完成链接 :
@Component
public class OrderModelAssembler implements RepresentationModelAssembler<Order, EntityModel<Order>> {
@Override
public EntityModel<Order> toModel(Order order) {
// Unconditional links to single-item resource and aggregate root
EntityModel<Order> orderModel = EntityModel.of(order,
linkTo(methodOn(OrderController.class).one(order.getId())).withSelfRel(),
linkTo(methodOn(OrderController.class).all()).withRel("orders"));
// Conditional links based on state of the order
if (order.getStatus() == Status.IN_PROGRESS) {
orderModel.add(linkTo(methodOn(OrderController.class).cancel(order.getId())).withRel("cancel")); // 附加cancel链接
orderModel.add(linkTo(methodOn(OrderController.class).complete(order.getId())).withRel("complete")); // 附加complete链接
}
return orderModel;
}
}