Spring Cloud 微服务开发

Spring Boot + Spring Cloud + Feign + Nacos 搭建微服务

搭建 Nacos 注册中心

Nacos 官网, 下载最新 zip 包, Nacos 快速开始

在服务器上解压缩, 进入 nacos/bin 目录

  • 执行 ./startup.sh -m standalone 启动服务
  • 执行 ./shutdown.sh 关闭服务

访问路径 http://localhost:8848, 初始账号/密码: nacos/nacos

创建项目

新建 Maven 项目

通过 IntelliJ IDEA 新建一个 Maven 演示项目 ut-demo-server, 其中有两个模块

  • ut-demo-service RPC 服务实现
  • ut-demo-task RPC 远程调用

创建完毕以后, 父项目的 pom.xml 内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.example.ut</groupId>
  <artifactId>ut-demo-server</artifactId>
  <packaging>pom</packaging>
  <version>1.0-SNAPSHOT</version>
  <modules>
    <module>ut-demo-service</module>
    <module>ut-demo-task</module>
  </modules>
</project>

依赖版本控制

这里核心要控制如下三个的版本号:

  • spring-boot-dependencies: 2.5.3
  • spring-cloud-dependencies: 2020.0.3
  • spring-cloud-alibaba-dependencies: 2021.1

以上版本是截止 2021-08-19 最新的版本, Spring Boot 与 Spring Cloud 的版本依赖参考这里 https://start.spring.io/actuator/info

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.5.3</version>
    <relativePath/>
  </parent>
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.example.ut</groupId>
  <artifactId>ut-demo-server</artifactId>
  <packaging>pom</packaging>
  <version>1.0-SNAPSHOT</version>
  <modules>
    <module>ut-demo-service</module>
    <module>ut-demo-task</module>
  </modules>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>2020.0.3</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-alibaba-dependencies</artifactId>
        <version>2021.1</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
</project>

服务的实现

ut-demo-service 中添加如下依赖

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  </dependency>
</dependencies>

新建 com.example.ut.Application 主入口文件, 代码如下

@RestController
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @GetMapping("/hello")
    public String hello(@RequestParam(value = "name", required = false, defaultValue = "world") String name) {
        return String.format("Hello, %s!", name);
    }
}

执行 main 函数, 即可通过 http://localhost:8080/hello 访问. 此时, 我们也可以从控制台看见报错, serviceName is blank, 服务也没有注册到 nacos 上, 这是因为我们缺少配置文件

spring:
  application:
    name: demo-service-dev
  cloud:
    nacos:
      discovery:
        server-addr: xxx.xxx.xxx.xxx:8848

再次启动服务, 没有报错, 且在 nacos 上可以看到服务注册的信息

最后, 为了生成可执行 jar 包, 增加如下插件

<build>
  <plugins>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
  </plugins>
</build>

远程调用

ut-demo-task 中添加如下依赖

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
  </dependency>
  <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
  </dependency>
</dependencies>

新建接口定义文件 com.example.ut.UtDemoService, 代码如下

@FeignClient("demo-service-dev")
public interface UtDemoService {
    @GetMapping("/hello")
    String hello(@RequestParam(value = "name", required = false, defaultValue = "world") String name);
}

新建 com.example.ut.Job 主入口文件, 代码如下

@EnableFeignClients
@SpringBootApplication
public class Job implements CommandLineRunner {
    @Resource
    private UtDemoService utDemoService;

    @Override
    public void run(String... args) throws Exception {
        System.out.println(utDemoService.hello(null));
        System.out.println(utDemoService.hello("henry"));
    }

    public static void main(String[] args) {
        new SpringApplicationBuilder().sources(Job.class).web(WebApplicationType.NONE).run(args).close();
    }
}

最后增加 application.yml 配置文件, 与 ut-demo-service 类似, 将 application name 改为 ut-demo-task. 执行 Job 可以看到如下输出

Hello, world!
Hello, henry!

拆分接口模块

前面我们已经基于 Spring Cloud 实现了微服务, 以及远程调用, 但我们也看到两个模块有重复的接口定义代码, 而且服务提供方与调用方往往不是同一个人, 我们是否能将接口定义部分抽离, 由服务开发的同学提供.

新增接口模块

创建 ut-demo-api 子模块, 将 UtDemoService 移动过去, ut-demo-task 依赖 ut-demo-api 即可. 执行 Job 可以通过测试.

接口与实现保持一致

ut-demo-service 依赖 ut-demo-api, 新建实现类 UtDemoController, 内容如下

@RestController
public class UtDemoController implements UtDemoService {
    @Override
    public String hello(String name) {
        return String.format("Hello, %s!", name);
    }
}

Application 中的实现就可以删除了

AutoConfiguration

修改 UtDemoService 的包路径为 org.enthusa, 再次运行 Job 提示如下错误

A component required a bean of type 'org.enthusa.UtDemoService' that could not be found.

修改 @EnableFeignClients 注解, 增加 basePackages = {"org.enthusa"} 参数即可. 然而, 更加优雅的方式是, 新增 org.enthusa.UtDemoServiceAutoConfiguration 配置类, 内容如下

@Configuration
@EnableFeignClients
public class UtDemoServiceAutoConfiguration {
}

新增资源文件 META-INF/spring.factories, 内容如下

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.enthusa.UtDemoServiceAutoConfiguration

这样运行不报错了, 不过由于 service 模块也依赖了 api 模块, 也会自动装配这个 bean, 不合理. 那么我们增加一个自动装配触发的条件

@ConditionalOnProperty(prefix = "rpc.ut-demo-service", name = "service-name")

在 task 模块的配置中增加如下信息, 即可通过

rpc:
  ut-demo-service:
    service-name: demo-service-dev

参考资料