Spring Cloud开发实践(六): 基于Consul和Spring Cloud 2021.0的演示项目
目录
- Spring Cloud开发实践(一): 简介和根模块
- Spring Cloud开发实践(二): Eureka服务和接口定义
- Spring Cloud开发实践(三): 接口实现和下游调用
- Spring Cloud开发实践(四): Docker部署
- Spring Cloud开发实践(五): Consul - 服务注册的另一个选择
- Spring Cloud开发实践(六): 基于Consul和Spring Cloud 2021.0的演示项目
Consul 服务
启动Consul服务, 在Win10下可以执行以下命令, 或者存成bat文件运行, 保持窗口打开
consul agent -dev -client=0.0.0.0 -data-dir .\ -advertise 127.0.0.1 -ui -config-dir .\
浏览器访问 http://127.0.0.1:8500 , 用于观察后面注册的Node和Health情况
Spring Cloud 项目
这个演示项目使用的 Spring Boot 和 Spring Cloud 都不是最新版本, 因为最新版本最低要求 JDK17. 这里选择的是对应 JDK11 可用的最高版本, 各组件版本明细为
- Consul 1.15
- JDK 11
- Spring Boot 2.7.11
- Spring Cloud 2021.0.6
整体结构
这个用于演示的项目名称为 Dummy, 包含3个子模块, 分别是 dummy-common-api, dummy-common-impl 和 dummy-admin, 其中
- dummy-common-api 和 dummy-common-impl 逻辑上属于同一个模块 dummy-common. api 是对外输出的接口, impl是对应的实现
- dummy-admin 依赖 dummy-common-api , 使用其提供的接口
打包后, 需要部署的是两个jar: dummy-common.jar 和 dummy-admin.jar, 前者提供服务接口, 后者消费前者提供的接口, 并对外(例如前端, 小程序, APP)提供接口
项目的整体结构如下
│ pom.xml
├───dummy-admin
│ │ pom.xml
│ ├───src
│ │ ├───main
│ │ │ ├───java
│ │ │ └───resources
│ │ │ application.yml
│ │ └───test
│ └───target
├───dummy-common-api
│ │ pom.xml
│ ├───src
│ │ ├───main
│ │ │ ├───java
│ │ │ └───resources
│ │ └───test
│ └───target
└───dummy-common-impl
│ pom.xml
├───src
│ ├───main
│ │ ├───java
│ │ └───resources
│ │ application.yml
│ └───test
└───target
根模块 Dummy
根模块的 pom.xml 中,
- 定义了子模块, module标签中的内容, 要和子模块目录名一致.
- 设置JDK版本 11
- 引入全局 Spring Boot Dependencies, 版本 2.7.11
- 引入全局 Spring Cloud Dependencies, 版本 2021.0.6
- 还有一些是Plugin相关的版本, 略
<?xml version="1.0" encoding="UTF-8"?>
...
<name>Dummy: Root</name>
<modules>
<module>dummy-common-api</module>
<module>dummy-common-impl</module>
<module>dummy-admin</module>
</modules>
<properties>
<!-- Global encoding -->
<project.jdk.version>11</project.jdk.version>
<project.source.encoding>UTF-8</project.source.encoding>
<!-- Global dependency versions -->
<spring-boot.version>2.7.11</spring-boot.version>
<spring-cloud.version>2021.0.6</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- Spring Boot Dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud Dependencies -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
...
</build>
</project>
Dummy Common API 模块
这个模块用于生成依赖的jar包, 作用非常重要. 以下详细说明
pom.xml 中除了定义和父模块的关系, 需要引入 openfeign
<?xml version="1.0" encoding="UTF-8"?>
...
<parent>
<groupId>com.rockbb.test</groupId>
<artifactId>dummy</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>dummy-common-api</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>Dummy: Commons API</name>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
...
</dependencies>
<build>
...
</build>
</project>
定义一个 UserDTO, 这个是用于传输的数据对象
@Data
public class UserDTO implements Serializable {
private Long id;
private String name;
}
对应的服务接口. 这里用到了 @FeignClient 注解
- @FeignClient 是给 dummy-admin 模块用的
- name= CommonConstant.SERVICE_NAME 就是 "dummy-common", 因为这个API模块中所有Service接口都使用同样的名称, 这边做成常量
- contextId = "userDTOService" 如果不加这个参数, 多个 FeignClient 使用同样的 name 时, 就会冲突. 这个一般直接定义为这个 service 的bean名称
- path = "/userDTOService" 用于指定当前类中所有接口的请求前缀. 在更早的版本中, 可以将 @RequestMapping 和 @FeignClient 联用, 这个是定义在 @RequestMapping 中的, 后来不允许了, 因为有安全风险.
- @GetMapping 和 @PostMapping 同时用于 dummy-admin 和 dummy-common
- 对于 dummy-admin, 这就是 FeignClient 的请求路径
- 对于 dummy-common, 这就是 Contoller 方法的服务路径
- 需要注意 @GetMapping 请求的接口形式, 必须显式添加 @RequestParam("id") 这类 GET 模式的参数注解, 否则使用 @GetMapping 的 Feign 请求也会被转为 POST 而导致请求错误.
@FeignClient(name = CommonConstant.SERVICE_NAME, contextId = "userDTOService", path = "/userDTOService")
public interface UserDTOService {
@GetMapping("/get")
UserDTO get(@RequestParam("id") long id);
@PostMapping("/add")
int add(@RequestBody UserDTO dto);
}
在 dummy-admin 中, 这个接口会被实例化为 feign 代理, 在模块中可以像普通 service 一样调用, 而在 dummy-common 中, 不引入 feign 依赖, 或者在 @EnableFeignClients 的 basePackages 中避开本包路径, 就会忽略这个注解, 从而实现模块间接口的关联.
与现在很多 Spring Cloud 项目中单独拆出一个 Service 模块的做法, 这种实现有很多的优点
- 开发过程友好. 与单机开发几乎一样的代码量, 唯一区别是要注意 Get 和 Post 对请求参数的格式和个数的约束
- 易重构易扩展. 可以借助 IDE 的代码分析能力, 改动自动标红, 避免人为错误和遗漏
- 性能开销小, 如果 DTO 直接映射到数据库字段, 可以全程使用一个类.
Dummy Common Impl 模块
模块的 pom.xml
- 引入 spring-boot-starter-web, 因为要提供 RestController 的能力
- 引入 spring-cloud-starter-consul-discovery 或 spring-cloud-starter-consul-all, 因为要接 Consul
- 引入 dummy-common-api 依赖, 因为 Controller 请求定义在 API 中
- 打包使用 spring-boot-maven-plugin 的 repackage, 因为要打 fat jar, 在服务器上实现单包部署
<?xml version="1.0" encoding="UTF-8"?>
...
<name>Dummy: Common Implementation</name>
<dependencies>
<!-- Spring Boot Dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Cloud Dependencies consul-discovery 和 consul-all 二选一 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-all</artifactId>
</dependency>
...
<dependency>
<groupId>com.rockbb.test</groupId>
<artifactId>dummy-common-api</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<finalName>dummy-common</finalName>
<resources>
...
</resources>
<plugins>
...
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
配置部分 application.yml
- 定义服务端口 8762
- 定义 servlet 路径, 必须定义, 否则不会配置 Controller 请求
- spring.application.name: dummy-common 定义了本服务的名称, 这个名称就是在 FeignClient 中引用的服务名称, 需要与 FeignClient 中的值一致
- spring.config.import 如果使用这个设置, 依赖要使用 consul-all, 因为 consul-discovery 中不带 consul-config. 使用这个设置后, 会自动使用默认的 Consul 地址和端口
- cloud.consul.host 和 port 如果使用了config.import, 在这里可以修改默认的值, 如果不使用config.import, 则必须配置 host 和 port, 依赖可以换成 consul-discovery
- cloud.consul.discovery.health-check-path 用于更改默认的 health 检查请求路径, 默认的是 /actuator/health, 这里改为 /health
- cloud.consul.discovery.instance-id 用于定义当前实例在 Consul 里的实例ID. 默认使用 application.name-port, 如果正好这个服务在两个服务器上分别跑了一个实例, 且实例端口一样, 就会产生冲突, 可以改为 application.name-[随机串] 的形式避免冲突
server:
port: 8762
tomcat:
uri-encoding: UTF-8
servlet:
context-path: /
spring:
application:
name: dummy-common
config:
import: 'optional:consul:' #This will connect to the Consul Agent at the default location of "http://localhost:8500"
# cloud:
# consul:
# host: 127.0.0.1
# port: 8500
# discovery:
# health-check-path: /health # replace the default /actuator/health
# instance-id: ${spring.application.name}:${random.value}
代码部分, 首先是实现 health 检查的处理方法, 这部分是普通的 RestController 方法. 返回字符串可以任意指定, 只要返回的 code 是 200 就可以
@RestController
public class HealthCheckServiceImpl {
@GetMapping("/health")
public String get() {
return "SUCCESS";
}
}
服务接口的实现类, 这里实现了两个接口方法 get 和 add
- 使用 @RestController 注解, 与 API Service 中方法上的 @GetMapping 和 @PostMapping 配合, 将 Service 方法映射为 Controller 方法
- 在类上的 @RequestMapping("userDTOService") 方法是必须的, 因为在 API Service 中与 @FeignClient 冲突无法定义, 只能在这里定义
- 方法和参数上除了 @Override 不需要任何注解, 因为都在 API Service 上定义过了. 这里加上注解也没问题, 但是要手工保持一致.
@RestController
@RequestMapping("userDTOService")
public class UserDTOServiceImpl implements UserDTOService {
@Autowired
private UserRepo userRepo;
@Override
public UserDTO get(long id) {
log.debug("Get user: {}", id);
UserDTO user = new UserDTO();
user.setId(id);
user.setName("dummy");
return user;
}
@Override
public int add(UserDTO dto) {
log.debug("Add user: {}", dto.getName());
return 0;
}
}
dummy-common 模块运行后会将接口注册到 Consul, 启动后注意观察两部分:
- Consul 的日志输出和控制面板显示, 在-dev模式下, 节点注册后 Consul 日志会显示模块的名称和心跳检测记录, 面板上会显示新的 Node
- Consul 控制面板中显示的 Health Checks 是否正常, 如果不正常, 需要检查 /health 路径为什么访问失败
Dummy Admin 模块
dummy-admin 是调用接口, 并对外提供服务的模块
pom.xml 和 dummy-common 基本一样, 因为都要连接 Consul, 都要提供 Controller 方法
<?xml version="1.0" encoding="UTF-8"?>
...
<name>Dummy: Admin API</name>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.rockbb.test</groupId>
<artifactId>dummy-common-api</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<finalName>dummy-admin</finalName>
<resources>
...
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
...
</plugins>
</build>
</project>
在主应用入口, 除了 @SpringBootApplication 以外, 还需要增加两个注解
- @EnableDiscoveryClient(autoRegister=false) 连接到 Consul 并使用服务发现, 默认会将当前节点也注册到 Consul 作为服务. 对于纯消费节点, 不对其它节点提供接口的, 使用 autoRegister=false 可以避免将自己注册到 Consul
- @EnableFeignClients(basePackages = {"com.rockbb.test.dummy.common.api"}) 扫描对应的包, 对 @FeignClient 注解实例化接口代理
/* Attach to discovery service without registering itself */
@EnableDiscoveryClient(autoRegister=false)
@EnableFeignClients(basePackages = {"com.rockbb.test.dummy.common.api"})
@SpringBootApplication
public class AdminApp {
public static void main(String[] args) {
SpringApplication.run(AdminApp.class, args);
}
}
在调用方法的地方, 按普通 Service 注入和调用
@Slf4j
@RestController
public class IndexController {
@Autowired
private UserDTOService userDTOService;
@GetMapping(value = "/user_get")
public String doGetUser() {
UserDTO user = userDTOService.get(100L);
return user.getId() + ":" + user.getName();
}
@GetMapping(value = "/user_add")
public String doAddUser() {
UserDTO user = new UserDTO();
user.setName("foobar");
int result = userDTOService.add(user);
return String.valueOf(result);
}
可以通过注入的 DiscoveryClient 对象, 查看对应服务的服务地址(一般不需要)
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/services")
public Optional<URI> serviceURL() {
return discoveryClient.getInstances(CommonConstant.SERVICE_NAME)
.stream()
.map(ServiceInstance::getUri)
.findFirst();
}
参考
- Consul和Spring Boot非常详细的配置教程 https://medium.com/javarevisited/hands-on-consul-with-spring-boot-1ebf2918165c
Spring Cloud开发实践(六): 基于Consul和Spring Cloud 2021.0的演示项目的更多相关文章
- Spring Cloud开发实践 - 01 - 简介和根模块
简介 使用Spring Boot的提升主要在于jar的打包形式给运维带来了很大的便利, 而Spring Cloud本身的优点不是那么明显, 相对于Dubbo而言, 可能体现在跨语言的交互性上(例如可以 ...
- 吴裕雄--天生自然JAVA SPRING框架开发学习笔记:SSM(Spring+Spring MVC+MyBatis)框架整合搭建详细步骤
因为 Spring MVC 是 Spring 框架中的一个子模块,所以 Spring 与 SpringMVC 之间不存在整合的问题.实际上,SSM 框架的整合只涉及 Spring 与 MyBatis ...
- 基于centos7+nginx+uwsgi+python3+django2.0部署Django项目
0.序言 本文讲解如何基于centos7+nginx+uwsgi+python3+django2.0把windows上的本地项目部署到云服务器上. 本文服务器上的django项目和虚拟环境的路径将建立 ...
- Spring Cloud开发实践 - 04 - Docker部署
Docker的安装和命令可以参考 https://www.cnblogs.com/milton/p/9866963.html . 资源规划 这一步要区分传统资源和Docker资源, 为后面的细节定好基 ...
- Spring Cloud开发实践 - 03 - 接口实现和下游调用
接口实现 Scot Commons Impl 接口实现模块 scot-commons-impl, 一方面实现了 scot-commons-api 的接口, 一方面将自己暴露为 REST 服务. 有4个 ...
- Spring Cloud开发实践 - 02 - Eureka服务和接口定义
服务注册 EurekaServer Eureka服务模块只有三个文件, 分别是pom.xml, application.yml 和 EurekaServerApplication.java, 内容如下 ...
- Spring 3.x 实践 第一个例子(Spring 3.x 企业应用开发实战读书笔记第二章)
前言:工作之后一直在搞android,现在需要更多和后台的人员交涉,技术栈不一样,难免鸡同鸭讲,所以稍稍学习下. 这个例子取自于<Spring 3.x 企业应用开发实战>一书中的第二章,I ...
- Spring学习笔记之二----基于XML的Spring AOP配置
在Spring配置文件中,通常使用<aop:config>元素来设置AOP,其中应包括: <aop:aspect>指定aspect,aspect是一个POJO类,包含了很多的a ...
- spring+cxf 开发webService(主要是记录遇到spring bean注入不进来的解决方法)
这里不介绍原理,只是记录自己spring+cxf的开发过程和遇到的问题 场景:第三方公司需要调用我们的业务系统,以xml报文的形式传递数据,之后我们解析报文存储到我们数据库生成业务单据: WebSer ...
- 吴裕雄--天生自然JAVA SPRING框架开发学习笔记:第一个Spring程序
1. 创建项目 在 MyEclipse 中创建 Web 项目 springDemo01,将 Spring 框架所需的 JAR 包复制到项目的 lib 目录中,并将添加到类路径下,添加后的项目如图 2. ...
随机推荐
- K8S 性能优化 - 大型集群 CIDR 配置
前言 K8S 性能优化系列文章,本文为第三篇:Kubernetes 大型集群 CIDR 配置最佳实践. 系列文章: <K8S 性能优化 - OS sysctl 调优> <K8S 性能 ...
- 基于Quartz.Net通过反射进行任务调度
通过反射加载任务调度 需求: 因为有些任务需要进行各种定时操作,因此将 Quartz.Net 简单封装了一下使用: 希望通过上传 dll 来进行每个任务的调度,所以写了个反射调度示例: Program ...
- 一篇博客带你学会MyBatis
概念 MyBatis是一款持久层框架,用于简化JDBC操作(JavaEE三层架构:表现层(用来做页面的代码),业务层(业务逻辑代码),持久层(对数据库操作的代码))(框架就是一个半成品软件,是一套可重 ...
- Echarts的安装和使用
安装步骤 下载相关文件 可以在该网站下载Echarts.js文件,网址在此:https://www.echartsjs.com/zh/builder.html 然后选择号自己需要用到的图形模块,点击下 ...
- RBAC学习(一)
0.前提 :用户只有一个直属部门,但角色可以关联多个部门 有一种情况就不太适用:比如说地区经理是一个角色,张三是北京市地区经理,他在组织架构中的直属部门是华北大区,然后一个黑龙江的销售李四提一个折扣申 ...
- Centos 7安装ansible自动化运维工具
1.介绍: ansible是新出现的自动化运维工具,基于Python开发,集合了众多运维工具(puppet.SaltStack.chef.func)的优点,实现了批量系统配置.批量程序部署.批 ...
- FutureTask 类
更多内容,前往IT-BLOG 一.什么是 Future和 FutureTask FutureTask 的 Future就源自于它的异步工作机制,如果我们在主线程中直接写一个函数来执行任务,这是同步的任 ...
- nginx重启和操作
在linux操作系统中,重启nginx 1.当不知道nginx所在目录时,需要先查找到nginx的位置 查看ngnix位置(master process 后面的就是 nginx的目录): ps -e ...
- (数据科学学习手札151)速通pandas2.0新版本干货内容
本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 大家好我是费老师,前两天pandas正式发布了其 ...
- pysimplegui之元素简单介绍(元素值得获取修改,key的规范及特殊用法)
重点 1获取元素的值 Input(key='mykey') values['mykey'] 2通过key查找元素 对象window['key'] 3更新元素的值 window['key'](要更新的值 ...