第2章 构建单个微服务

hanbin 2020-04-03 AM 14℃ 1条

在深入接触微服务之前,首先得知道怎么创建单个微服务工程。

2.1 使用 Spring Boot 构建第一个微服务

Spring Boot 的设计目标是用来简化新 Spring 应用的初始搭建以及开发过程。它是建立在 Spring 标准上的一个抽象层,使用特定的方式进行配置,从而使开发人员不再需要定义样板化的配置。 Spring Boot 的简单是相对于以前的 Spring Framework 入门容易,但是要完全掌握,用好 Spring Boot,我们还有很长的路要走。
下面通过一个简单的例子,我们通过官网提供的网页创建一个可以运行的 Spring Boot 基础工程。

2.1.1 工程结构

打开链接https://start.spring.io/,按照下图 2.1 填写信息,生成我们的第一个微服务工程。

图 2.1 Spring 工程包生成

点击 Generate Project 按钮,将生成的 zip 包解压并导入 Intellj Idea 中。可以看到如图 2.2 所示的结构。

图 2.2 Spring Boot 项目结构

可以看到这是一个标准的 Maven 工程。Spring 为我们自动生成了几个文件及目录:

  • CustomerApplication.java 文件是 customer 这个项目的启动类。在 Spring Boot 项目中,不需要将项目发布到 Tomcat 之类的 Web 容器中,直接以 application 的方式运行启动类即可使用内置的 Web 容器正常启动项目,并对外提供服务。
  • application.properties 文件是项目使用的配置文件,Spring Boot 中摒弃了以前 Spring 的繁琐配置,改为使用更多的约定以及更简单的配置来进行。

新建的工程中这个文件是空的,强烈建议为每个 Spring Boot 工程配置 spring.application.name 来指明当前服务的实例名。Customer 工程中设置为 spring.application.name=customer。 resources 下的 static 和 templates 是 Spring Boot 默认的放置网页及静态元素的地方,这个后面用到的时候再讨论。
打开 CustomerApplication.java 文件,我们可以看到一个简单的 main 方法。代码如下所示:

    package cn.com.hanbinit.customer;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;

    @SpringBootApplication // 使用Spring Boot构建的项目都应该添加这个注解,它可以告诉框架,这个类是项目的引导类
    public class CustomerApplication {
        public static void main(String[] args) {
            SpringApplication.run(CustomerApplication.class, args); // 启动服务
        }
    }

这里我们只需要知道这个类是 Spring Boot 的启动类即可。注解@SpringBootApplication 标识这个类是工程的启动类。 pom.xml 文件中引入了 web 服务的 starter 包,如下代码:

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

spring-boot-starter-web 包含了 spring-web 和 spring-webmvc 包,这些可以通过 IDE 查看。为 CustomerApplication 类添加注解@RestController,并添加下面的方法提供一个对外的接口/hello,代码如下所示:

    @RequestMapping(name = "/hello", method = RequestMethod.GET) // 定义一个URI是/hello的GET请求
    public String hello(){
        return "hello";
    }

打开浏览器访问http://localhost:8080/hello, 可以看到图 2.3 的结果。

图 2.3 浏览器访问 hello 接口

至此,我们第一个通过 Spring Boot 创建的对外服务就已经可以使用了。

2.1.2 Spring Boot 基础

在上一节,我们通过编写很少的代码就实现了一个对外的接口,并且测试可以访问。回忆以前使用 Spring MVC 的时候是如何实现上面这样一个接口的呢?
步骤如下:
(1)配置 web.xml。
(2)配置加载配置文件。
(3)配置日志文件。
(4)最后写完代码放在 tomcat 中启动。

我还记得前几年网上充斥了大量的“如何整合 SSH 框架”、“如何整合 SSM 框架”之类的文章,还记得当时遇到好些初学者搭建完框架后,就不想再进一步学习了。 技术架构上的“解耦”从某种程度上来说一直在推动代码和框架越来越简单化。微服务概念的提出,将“解耦”这个说法更具体化了。
我们将一个大的项目拆分成若干个微服务,单个微服务只处理某一种业务,各个微服务从根本上分开,达到“解藕”的作用。

注意:微服务的拆分并不是一定按照业务来的,但是一般都是按照业务来进行拆分的。 Spring Boot 的详细资料在官网都可以查到,本节只会以较少的篇幅讲述一些后面需要用到的基础。重新回到上节的代码,再看下完整的 CustomerApplication.java 文件内容:

    package cn.com.hanbinit.customer;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;

    @RestController
    @SpringBootApplication
    public class CustomerApplication {

    @RequestMapping(name = "/hello", method = RequestMethod.GET)
        public String hello(){
            return "hello";
        }

        public static void main(String[] args) {
            SpringApplication.run(CustomerApplication.class, args);
        }
    }

对上面的代码进行分析:

  • @RestController 注解相当于将@Controller@ResponseBody一起使用。它属于类级别的注解,它告诉框架这个类定义了一组基于REST的服务,并且所有服务的请求和响应都会被默认序列化和反序列化成JSON。它会让视图解析器失效,不能跳转页面视图,这个注解基本都用在纯接口类上。也是我们在使用Spring Boot编写代码时最常用的接口之一。
  • @RequestMapping 指定了接口请求的上下文路径以及请求方式等内容。它可以用在类级别和方法级别。用在类上,就表示对这个类中的所有接口设置了 URI 前缀,用在方法上,对应的就是当前接口真实的 URI。
  • @SpringBootApplication 这个注解几乎用在每个 Spring Boot 项目的启动类上。

查看@SpringBootApplication 注解的源码可以看到如下代码:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan(excludeFilters = {
    @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    public @interface SpringBootApplication {

可以看到它其实就是@SpringBootConfiguration@EnableAutoConfiguration 以及@ComponentScan 的结合体。

@SpringBootConfiguration 来源于@Configuration,和我们以前用 Spring Framework 时配置的@Configuration 注解一样,将当前类中使用@Bean 标记的方法注入到容器中管理。 @EnableAutoConfiguration 主要作用是扫描 ClassPath 下所有的 META-INF/spring.factories 配置文件,并将其中的 EnableAutoConfiguration 对应的配置项通过反射机制实例化为对应标注了 @Configuration 的形式的配置类,然后交给 Spring 管理。 @ComponentScan 是我们很熟悉的一个注解,和以前的 xml 配置项<context:component-scan>很像。用来标记需要扫描类以便交给 Spring 管理的类路径。

Spring Boot 提供了一种方便的方式去启动程序,就是我们在上面看到的 main 方法,在大多数情况下,我们都不需要去修改这个 main 方法。 在配置文件 application.properties 中,我们可以添加一些 Spring Boot 程序定义好的配置项,以此来快速实现某些功能。常用的几个配置项如下:

  • 制定项目名称:spring.application.name。
  • 日志相关配置:logger.**。
  • 端口号配置:server.port。
  • 数据库相关配置:spring.datasource.**。

上面这几类只是最基本的,但是刚才我们的程序一行配置文件都没有写,那么程序是怎么启动的呢?8080 这个端口号是在哪里配置的? 大部分的配置项都有默认值,比如 8080 这个端口号,配置项对应的是 server.port,默认值就是 8080。如果在 application.properties 文件中添加下面的配置项:
server.port=8001重新启动应用,会发现原来的 8080 端口已经不能正常访问,取而代之的 8001 端口访问正常,图 2.4 中显示的是使用 8001 端口访问时/hello 接口的返回。

图 2.4 通过端口 8001 访问接口

前面的接口并没有传输任何参数,那么在这里要怎么传参呢? 一般情况下,我们的传参分下面三种情况:

    http://localhost:8001/hello?name=xxx GET

    http://localhost:8001/hello/xxx GET

    http://localhost:8001/hello POST
    {
    “name” : “xxxx”
    }

第一种情况是我们常用的情况,我们为了说明上面三种情况,建立新的接口类,添加三个对外接口,代码如下:

    package cn.com.hanbinit.customer.controller;

    import org.springframework.http.HttpMethod;
    import org.springframework.web.bind.annotation.*;

    @RestController
    public class HelloController {

        @RequestMapping(value = "/hello", method = RequestMethod.GET)
        public String hello0(@RequestParam String name){
            return "hello " + name;
        }

        @RequestMapping(value = "/hello/{name}", method = RequestMethod.GET)
        public String hello1(@PathVariable String name){
            return "hello " + name;
        }

        @RequestMapping(value = "/hello", method = RequestMethod.POST)
        public String hello2(@RequestBody Customer customer){
            return "hello " + customer.getName();
        }
    }

    class Customer{
        private String name;
        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }

上面代码中定义的三个接口分别对应了前面提到的三种传参形式。接下来,分别看一下: 第一个方法 hello0 定义的接口访问方式如图 2.5,这种情况适合需要传递一些简单但是项目比较多的参数时使用。这种形式会指明每个参数的名称,可以比较清晰的表示出我们想要传递的参数。但是要注意的是,这种情况的参数值不能带有一些特定的符号,如&,&等。

图 2.5 hello?name=xxx 格式的访问

hello 定义的接口访问方式如图 2.6 所示,这种情况是这几年比较流行的写法,在代码中指定了 URL 中某个部分的特殊定义,在传参的时候需要按照最初的设定传递,同上面的接口一样,GET 请求都不能带有特殊符号。

图 2.6 hello/xxx 格式的访问

最后一种方式是通过非 URL 传参的方式来实现的,这种情况一般用在非 GET,DELETE 类型的请求中,他可以传递一些相对复杂的参数,并且可以按照既定的格式来传递,由上面的代码可以看到,body 中传递的其实是一个实体类。 这种情况在平时的接口定义中,使用的比较多,一些复杂的查询接口也需要通过这种参数来进行参数传递。具体的调用方式参见图 2.7 所示。

图 2.7 通过 body 传递 Json 格式参数的实例

2.1.3 Spring Boot Starters

Spring Boot 原生提供了很多开箱即用的 starter 依赖,这些依赖将以前传统的 Spring 开发过程中一些需要的 jar 进行了整合,使我们在业务开发时能够通过一个 starter 引入某种类型的配置,同时,在整合的过程中,做了一些内部的约定,可以让我们在进行业务开发的过程中,少关注一些框架的配置,多关注业务本身。 可以将这一系列的 starter 依赖理解为可插拔的插件,按需使用。每个 starter 都包含了许多的依赖项,这些依赖项可简化我们的配置,我们在一般使用的时候可以不用关注 starter 本身的依赖。比如说:我们需要使用 Spring 的 JPA 来操作数据库,我们只需要引入 spring-boot-starter-data-jpa 就可以了。 Spring 官方的 Starter 命名方式为 spring-boot-starter-*。在这里,我们只单独拿出来一个 starter 说下,所有的 Starter 大家都可以在官方提供的文档中找到: https://docs.spring.io/spring-boot/docs/2.1.1.RELEASE/reference/htmlsingle/#using-boot-starter 这里不做过多的说明,后面用到的时候会详细说明。

2.1.4 RESTful

RESTful 是一种针对 web 服务的结构设计风格,REST 独立于底层协议,是一种对外表现的设计约束。一般满足 REST 设计原则的我们就称之为 RESTful 接口。在 REST 中,每一个对象都是通过 URL 来表示的,通过 method 给出的动作构成一个完整的操作。

REST 接口很重要的一点是无状态性。客户端和服务器之间的交互是无状态的。从客户端到服务器的每个请求都必须包含请求所必需的信息。如果服务器在请求之间的任何时间出现任何意外事故,客户端不需要获得通知,客户端下次的请求仍旧应该获得同样正确的结果。 此外,无状态请求可以由任何可用服务器来接收并处理,这十分适合云计算之类的环境。客户端还可以本地缓存数据以改进性能。

Microsoft Azure 官方推出的最佳实践中有一篇讲述 API 设计的文章,其中提到了 Leonard Richardson 对 Web API 设计的四个等级。

  • Level 0: 定义一个 URI,所有操作都是对该 URI 的 POST 请求。
  • Level 1: 创建单独的个人资源的 URI。
  • Level 2: 使用 HTTP 方法来定义操作资源。
  • Level 3: 使用超媒体 (HATEOAS, described below)。

目前大部分的 API 设计还停留在 Level 2。Spring Boot 在 2.0 版本后推出了 WebFlux,这个在后面我们会有专门的介绍。

2.1.5 EndPoint

Endpoint 在国内大多数时候都被翻译为“端点”,本书也使用这个翻译来描述。 继续使用这节我们创建的项目向 pom.xml 中添加 spring-boot-starter-actuator 依赖。

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

然后重新启动服务。可以看到 Customer 这个服务中已经可以访问一些 Spring Boot 内置的端点。这些端点包括很多类型,一般可以分为控制类和监控类。端点虽然看起来都是一些很小的接口,但是在 Spring Boot 的使用过程中,端点的使用频率要比想象的高很多。
在本系列使用的版本中,只默认开启了 info 和 health 两个信息查看端点。其他端点的开启需要手工配置。

2.2 小结

本文我们使用 Spring Boot 构建了一个可以对外提供接口的工程,并且对工程的结构做了简单说明。在这个基础上,我们对开发过程中常见的接口传参过程也做了简单讨论。最后通过对 RESTful 和 endpoint 的讨论,让我们对 API 有了进一步的认识。 本文的内容是本系列最为基础的,在本文很多东西都只是提到但并未展开讲,后续的内容中会按需展开。

标签: 微服务入门

非特殊说明,本博所有文章均为博主原创。

评论啦~



唉呀 ~ 仅有一条评论


  1. 蘑菇大叔
    蘑菇大叔 博主

    测试

    回复 2020-04-24 07:05