Springboot实战11 服务发布:如何构建一个 RESTful 风格的 Web 服务?

通过前面课程的学习,我们已经掌握了构建一个 Spring Boot 应用程序的数据访问层组件实现方法。接下来的几讲,我们将讨论另一层组件,即 Web 服务层的构建方式。

服务与服务之间的交互是系统设计和发展的必然需求,其涉及 Web 服务的发布及消费,今天我们先讨论如何在 Spring Boot 应用程序中发布 Web 服务。

SpringCSS 系统中的服务交互

在具体的技术体系介绍之前,我们先来梳理 SpringCSS 案例中服务交互之间的应用场景。

对于客服系统而言,其核心业务流程是生成客服工单,而工单的生成通常需要使用用户账户信息和所关联的订单信息。

在 SpringCSS 案例中,前面几讲我们已经构建了一个用于管理订单的 order-service,接下来我们将分别构建管理用户账户的 account-service 及核心的客服服务 customer-service。

关于三个服务之间的交互方式,我们先通过一张图了解下,如下图所示:

图片6 (1).png

SpringCSS 案例系统中三个服务的交互方式图

实际上,通过上图我们已经可以梳理工单生成 generateCustomerTicket 核心方法的执行流程,这里我们先给出代码的框架,如下代码所示:

public CustomerTicket generateCustomerTicket(Long accountId, String orderNumber) {
        // 创建客服工单对象
        CustomerTicket customerTicket = new CustomerTicket();
 
        // 从远程 account-service 中获取 Account 对象
        Account account = getRemoteAccountById(accountId);
 
        // 从远程 order-service 中获取 Order 读写
        Order order = getRemoteOrderByOrderNumber(orderNumber);

        // 设置 CustomerTicket 对象并保存
        customerTicket.setAccountId(accountId);
        customerTicket.setOrderNumber(order.getOrderNumber());
        customerTicketRepository.save(customerTicket);
 
        return customerTicket;
}

因 getRemoteAccountById 与 getRemoteOrderByOrderNumber 方法都涉及远程 Web 服务的调用,因此首先我们需要创建 Web 服务。

而 Spring Boot 为我们创建 Web 服务提供了非常强大的组件化支持,简单而方便,我们一起来看一下。

创建 RESTful 服务

在当下的分布式系统及微服务架构中,RESTful 风格是一种主流的 Web 服务表现方式。

在接下来的内容中,我们将演示如何使用 Spring Boot 创建 RESTful 服务。但在此之前,我们先来理解什么是 RESTful 服务。

理解 RESTful 架构风格

你可能听说过 REST 这个名称,但并不清楚它的含义。

REST(Representational State Transfer,表述性状态转移)本质上只是一种架构风格而不是一种规范,这种架构风格把位于服务器端的访问入口看作一个资源,每个资源都使用 URI(Universal Resource Identifier,统一资源标识符) 得到一个唯一的地址,且在传输协议上使用标准的 HTTP 方法,比如最常见的 GET、PUT、POST 和 DELETE。

下表展示了 RESTful 风格的一些具体示例:

图片1 (2).png

RESTful 风格示例

另一方面,客户端与服务器端的数据交互涉及序列化问题。关于序列化完成业务对象在网络环境上的传输的实现方式有很多,常见的有文本和二进制两大类。

目前 JSON 是一种被广泛采用的序列化方式,本课程中所有的代码实例我们都将 JSON 作为默认的序列化方式。

使用基础注解

在原有 Spring Boot 应用程序的基础上,我们可以通过构建一系列的 Controller 类暴露 RESTful 风格的 HTTP 端点。这里的 Controller 与 Spring MVC 中的 Controller 概念上一致,最简单的 Controller 类如下代码所示:

@RestController
public class HelloController {
 
    @GetMapping("/")
    public String index() {
        return "Hello World!";
    }
}

从以上代码中可以看到,这里包含了 @RestController 和 @GetMapping 这两个注解。

其中,@RestController 注解继承自 Spring MVC 中的 @Controller 注解,顾名思义就是一个基于 RESTful 风格的 HTTP 端点,并且会自动使用 JSON 实现 HTTP 请求和响应的序列化/反序列化方式。

通过这个特性,在构建 RESTful 服务时,我们可以使用 @RestController 注解代替 @Controller 注解以简化开发。

另外一个 @GetMapping 注解也与 Spring MVC 中的 @RequestMapping 注解类似。我们先来看看 @RequestMapping 注解的定义,该注解所提供的属性都比较容易理解,如下代码所示:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
    String name() default "";
 
    @AliasFor("path")
    String[] value() default {};
 
    @AliasFor("value")
    String[] path() default {};
 
    RequestMethod[] method() default {};
 
    String[] params() default {};
 
    String[] headers() default {};
 
    String[] consumes() default {};
    String[] produces() default {};
}

而 @GetMapping 的注解的定义与 @RequestMapping 非常类似,只是默认使用了 RequestMethod.GET 指定 HTTP 方法,如下代码所示:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {

Spring Boot 2 中引入的一批新注解中,除了 @GetMapping ,还有 @PutMapping、@PostMapping、@DeleteMapping 等注解,这些注解极大方便了开发人员显式指定 HTTP 的请求方法。当然,你也可以继续使用原先的 @RequestMapping 实现同样的效果。

我们再看一个更加具体的示例,以下代码展示了 account-service 中的 AccountController。

@RestController
@RequestMapping(value = "accounts")
public class AccountController {
 
    @GetMapping(value = "/{accountId}")
    public Account getAccountById(@PathVariable("accountId") Long accountId) {
        Account account = new Account();
        account.setId(1L);
        account.setAccountCode("DemoCode");
        account.setAccountName("DemoName");
        return account;
    }
}

在该 Controller 中,通过静态的业务代码我们完成了根据账号编号(accountId)获取用户账户信息的业务流程。

这里用到了两层 Mapping,第一层的 @RequestMapping 注解在服务层级定义了服务的根路径“/accounts”,第二层的 @GetMapping 注解则在操作级别定义了 HTTP 请求方法的具体路径及参数信息。

到这里,一个典型的 RESTful 服务已经开发完成了,现在我们可以通过 java –jar 命令直接运行 Spring Boot 应用程序了。

在启动日志中,我们发现了以下输出内容(为了显示效果,部分内容做了调整),可以看到自定义的这个 AccountController 已经成功启动并准备接收响应。

RequestMappingHandlerMapping : Mapped "{[/accounts/{accountId}], methods=[GET]}" onto public com.springcss.account.domain.Account com.springcss.account.controller.AccountController.getAccountById (java.lang.Long)

在本课程中,我们将引入 Postman 来演示如何通过 HTTP 协议暴露的端点进行远程服务访问。

Postman 为我们完成 HTTP 请求和响应过程提供了可视化界面,你可以尝试编写一个 AccountController,并通过 Postman 访问“http://localhost:8082/accounts/1”端点以得到响应结果。

在前面的 AccountController 中,我们还看到了一个新的注解 @PathVariable,该注解作用于输入的参数,下面我们就来看看如何通过这些注解控制请求的输入。

控制请求输入和输出

Spring Boot 提供了一系列简单有用的注解来简化对请求输入的控制过程,常用的包括 @PathVariable、@RequestParam 和 @RequestBody。

其中 @PathVariable 注解用于获取路径参数,即从类似 url/{id} 这种形式的路径中获取 {id} 参数的值。该注解的定义如下代码所示:

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PathVariable {
    @AliasFor("name")
    String value() default "";
 
    @AliasFor("value")
    String name() default "";
 
    boolean required() default true;
}

通常,使用 @PathVariable 注解时,我们只需要指定一个参数的名称即可。我们可以再看一个示例,如下代码所示:

@GetMapping(value = "/{accountName}")
public Account getAccountByAccountName(@PathVariable("accountName") String accountName) {
 
    Account account = accountService.getAccountByAccountName(accountName);
    return account;
}

@RequestParam 注解的作用与 @PathVariable 注解类似,也是用于获取请求中的参数,但是它面向类似 url?id=XXX 这种路径形式。

该注解的定义如下代码所示,相较 @PathVariable 注解,它只是多了一个设置默认值的 defaultValue 属性。

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
    @AliasFor("name")
    String value() default "";
 
    @AliasFor("value")
    String name() default "";
 
    boolean required() default true;
 
    String defaultValue() default ValueConstants.DEFAULT_NONE;
}

在 HTTP 协议中,content-type 属性用来指定所传输的内容类型,我们可以通过 @RequestMapping 注解中的 produces 属性来设置这个属性。

在设置这个属性时,我们通常会将其设置为“application/json”,如下代码所示:

@RestController
@RequestMapping(value = "accounts", produces="application/json")
public class AccountController {

@RequestBody 注解用来处理 content-type 为 application/json 类型时的编码内容,通过 @RequestBody 注解可以将请求体中的 JSON 字符串绑定到相应的 JavaBean 上。

如下代码所示就是一个使用 @RequestBody 注解来控制输入的场景。

@PutMapping(value = "/")
public void updateAccount(@RequestBody Account account) {

如果使用 @RequestBody 注解,我们可以在 Postman 中输入一个 JSON 字符串来构建输入对象,如下代码所示:

Drawing 1.png

使用 Postman 输入 JSON 字符串发起 HTTP 请求示例图

通过以上内容的讲解,我们发现使用注解的操作很简单,接下来我们有必要探讨下控制请求输入的规则。

关于控制请求输入的规则,关键在于按照 RESTful 风格的设计原则设计 HTTP 端点,对于这点业界也存在一些约定。

  • 以 Account 这个领域实体为例,如果我们把它视为一种资源,那么 HTTP 端点的根节点命名上通常采用复数形式,即“/accounts”,正如前面的示例代码所示。

  • 在设计 RESTful API 时,我们需要基于 HTTP 语义设计对外暴露的端点的详细路径。针对常见的 CRUD 操作,我们展示了 RESTful API 与非 RESTful API 的一些区别。

图片3 (1).png

RESTful 风格对比示例

基于以上介绍的控制请求输入的实现方法,我们可以给出 account-service 中 AccountController 类的完整实现过程,如下代码所示:

@RestController
@RequestMapping(value = "accounts", produces="application/json")
public class AccountController {
 
    @Autowired
    private AccountService accountService;
 
    @GetMapping(value = "/{accountId}")
    public Account getAccountById(@PathVariable("accountId") Long accountId) {
        Account account = accountService.getAccountById(accountId);
        return account;
    }
 
    @GetMapping(value = "accountname/{accountName}")
    public Account getAccountByAccountName(@PathVariable("accountName") String accountName) {
 
        Account account = accountService.getAccountByAccountName(accountName);
        return account;
    }
 
    @PostMapping(value = "/")
    public void addAccount(@RequestBody Account account) {

        accountService.addAccount(account);
    }
 
    @PutMapping(value = “/”)
    public void updateAccount(@RequestBody Account account) {

        accountService.updateAccount(account);
    }

    @DeleteMapping(value = “/”)
    public void deleteAccount(@RequestBody Account account) {

        accountService.deleteAccount(account);
    }
}

介绍完对请求输入的控制,我们再来讨论如何控制请求的输出。

相较输入控制,输出控制就要简单很多,因为 Spring Boot 所提供的 @RestController 注解已经屏蔽了底层实现的复杂性,我们只需要返回一个普通的业务对象即可。@RestController 注解相当于是 Spring MVC 中 @Controller 和 @ResponseBody 这两个注解的组合,它们会自动返回 JSON 数据。

这里我们也给出了 order-service 中的 OrderController 实现过程,如下代码所示:

@RestController
@RequestMapping(value="orders/jpa")
public class JpaOrderController {

    @Autowired
    JpaOrderService jpaOrderService;

    @GetMapping(value = “/{orderId}”)
    public JpaOrder getOrderById(@PathVariable Long orderId) {

        JpaOrder order = jpaOrderService.getOrderById(orderId);
    return order;
    }

    @GetMapping(value = “orderNumber/{orderNumber}”)
    public JpaOrder getOrderByOrderNumber(@PathVariable String orderNumber) {

        JpaOrder order = jpaOrderService.getOrderByOrderNumber(orderNumber);
//      JpaOrder order = jpaOrderService.getOrderByOrderNumberByExample(orderNumber);
//      JpaOrder order = jpaOrderService.getOrderByOrderNumberBySpecification(orderNumber);
    return order;
    }

    @PostMapping(value = “”)
    public JpaOrder addOrder(@RequestBody JpaOrder order) {

        JpaOrder result = jpaOrderService.addOrder(order);
    return result;
    }
}

从上面示例可以看到,我们使用了 09 讲中介绍的 Spring Data JPA 完成实体对象及数据访问功能。

小结与预告

构建 Web 服务是开发 Web 应用程序的基本需求,而设计并实现 RESTful 风格的 Web 服务是开发人员必须具备的开发技能。

基于 Spring Boot 框架,开发人员只需要使用几个注解就能实现复杂的 HTTP 端点,并暴露给其他服务进行使用,工作都变得非常简单。

这里给你留一道思考题:在使用 Spring Boot 构建 Web 服务时,可以使用哪些注解实现对输入参数的有效控制?

现在,我们已经构建了一个具有 RESTful 风格的 Web 服务。在 12 讲中,我们将介绍 Spring Boot 所提供的另一个模板工具类 RestTemplate 完成对 HTTP 端点的消费。


精选评论

*旺:

produces是生成的格式吧,consumes是接收的格式?

    讲师回复:

    对,指定生产者所提供的内容格式。

**飞:

老师,请问为什么注释掉SpringCssUser相关几个类就不报错呢?

    讲师回复:

    这几个类只是示例代码,是不完整的,除了这几个类之外都是完整的,所以能运行。

**飞:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘springCssUserDetailsService’: Unsatisfied dependency expressed through field ‘repository’; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘springCssUserRepository’: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Not a managed type: class com.springcss.account.security.SpringCssUser老师好,请问运行account-service项目报错是什么问题?

    讲师回复:

    SpringCssUser相关几个类都是为了演示用的Demo类,没有实际的应用,直接注释掉不用应该就可以了,你看一下代码整个执行流程应该就明白了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

源码头

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值