第2章 构建单个微服务

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

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填写信息,生成我们的第一个微服务工程。

85b00d110b2d550a26ac5aa131f3e27f.png

图2.1 Spring工程包生成

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

a10dbf04dd3a85361742fbcb2a02506a.png

图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的结果。

97cc94917776a4dea15581fcb6b71b49.png

图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接口的返回。

dd0bd838d1c4af02b78e4d73bd80f126.png

图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,这种情况适合需要传递一些简单但是项目比较多的参数时使用。这种形式会指明每个参数的名称,可以比较清晰的表示出我们想要传递的参数。但是要注意的是,这种情况的参数值不能带有一些特定的符号,如&,&等。

60475a60149ad2086be6fe2aa11bb39b

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

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

bf225c1019ab86c2b260b9820b108472.png

图2.6 hello/xxx格式的访问

最后一种方式是通过非URL传参的方式来实现的,这种情况一般用在非GET,DELETE类型的请求中,他可以传递一些相对复杂的参数,并且可以按照既定的格式来传递,由上面的代码可以看到,body中传递的其实是一个实体类。

这种情况在平时的接口定义中,使用的比较多,一些复杂的查询接口也需要通过这种参数来进行参数传递。具体的调用方式参见图2.7所示。

e8986120c8fbdf81709043c1c22415b3.png

图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有了进一步的认识。

本节的内容是本书最为基础的,在本节很多东西都只是提到但并未展开讲,后续的内容中会按需展开。

第2章 构建单个微服务》有1条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注