Spring Cloud 环境和工程基本搭建
Spring Cloud 微服务架构基于 Spring Boot 3.X 和 JDK 17 进行开发。本文介绍了电商场景下的服务拆分原则,包括单一职责、服务自治和单向依赖。内容涵盖数据库初始化脚本、Maven 父子工程配置及依赖管理、订单与商品服务的代码实现。通过 RestTemplate 演示了服务间 HTTP 远程调用,并分析了硬编码 URL 的局限性,引出后续服务发现与负载均衡的学习方向。

Spring Cloud 微服务架构基于 Spring Boot 3.X 和 JDK 17 进行开发。本文介绍了电商场景下的服务拆分原则,包括单一职责、服务自治和单向依赖。内容涵盖数据库初始化脚本、Maven 父子工程配置及依赖管理、订单与商品服务的代码实现。通过 RestTemplate 演示了服务间 HTTP 远程调用,并分析了硬编码 URL 的局限性,引出后续服务发现与负载均衡的学习方向。

Oracle 从 JDK9 开始每半年发布一个新版本,新版本发布后,老版本就不再进行维护。但是会有几个长期维护的版本。 目前长期维护的版本有:JDK8, JDK11, JDK17, JDK21。在 JDK 版本的选择上,尽量选择长期维护的版本。 为什么选择 JDK17? Spring Cloud 是基于 SpringBoot 进行开发的,SpringBoot 3.X 以下的版本,Spring 官方已不再进行维护(还可以继续使用),SpringBoot 3.X 的版本,使用的 JDK 版本基线为 JDK17。所以本文选择使用 JDK17。
实现一个电商平台(不真实实现,仅为演示)。一个电商平台包含的内容非常多,以京东为例,仅从首页上就可以看到巨多的功能。 我们该如何实现呢?如果把这些功能全部写在一个服务里,这个服务将是巨大的。巨多的会员,巨大的流量,微服务架构是最好的选择。 微服务应用开发的第一步,就是服务拆分。拆分后才能进行各自开发。
服务拆分原则: 微服务到底多小才算'微',这个在业界并没有明确的标准。微服务并不是越小越好,服务越小,微服务架构的优点和缺点都会越来越明显。 服务越小,微服务的独立性就会越来越高,但同时,微服务的数量也会越多,管理这些微服务的难度也会提高。所以服务拆分也要考虑场景。
还是以企业管理为例 企业中一个员工的工作内容与企业规模、项目规模等都有关系。 在小公司,一个员工可能需要负责很多部门的事情,大公司的话,一个部门的工作可能需要多个员工来处理。
拆分微服务一般遵循如下原则:
微服务架构并无标准架构,合适的就是最好的,不然架构师大会也不会各个系统架构百花齐放了。 在架构设计的过程中,坚持'合适优于业界领先',避免'过度设计'(为了设计而设计)。很多业界领先方案并不是一群天才在某个时期一下子做出来的,而是经过数年的发展逐步完善。业界领先的方案大多是'逼'出来的,随着业务的发展,量变导致质变,新的问题出现了,当前的方案无法满足需求,需要用新的方案来解决。通过不断的创新和尝试,业界领先的方案才得以形成。
服务拆分示例: 一个完整的电商系统是庞大的,咱们课程中重点关注如何使用 SpringCloud 解决微服务架构中遇到的问题。 以订单列表为例:订单列表需要包含的主要信息有:
根据服务的单一职责原则,我们把服务进行拆分为:订单服务,商品服务。 订单服务:提供订单 ID,获取订单详细信息。 商品服务:根据商品 ID,返回商品详细信息。
根据服务自治原则,每个服务都应有自己独立的数据库。
订单服务:
-- 建库
create database if not exists cloud_order charset utf8mb4;
-- 订单表
DROP TABLE IF EXISTS order_detail;
CREATE TABLE order_detail (
`id` INT NOT NULL AUTO_INCREMENT COMMENT '订单 id',
`user_id` BIGINT(20) NOT NULL COMMENT '用户 ID',
`product_id` BIGINT(20) NULL COMMENT '产品 id',
`num` INT(10) NULL DEFAULT 0 COMMENT '下单数量',
`price` BIGINT(20) NOT NULL COMMENT '实付款',
`delete_flag` TINYINT(4) NULL DEFAULT 0,
`create_time` DATETIME DEFAULT now(),
`update_time` DATETIME DEFAULT now(),
PRIMARY KEY (id)
) ENGINE = INNODB DEFAULT CHARACTER SET = utf8mb4 COMMENT ='订单表';
-- 数据初始化
insert into order_detail (user_id, product_id, num, price)
values (2001, 1001, 1, 99), (2002, 1002, 1, 30), (2001, 1003, 1, 40), (2003, 1004, 3, 58), (2004, 1005, 7, 85), (2005, 1006, 7, 94);
商品服务:
create database if not exists cloud_product charset utf8mb4;
-- 产品表
DROP TABLE IF EXISTS product_detail;
CREATE TABLE product_detail (
`id` INT NOT NULL AUTO_INCREMENT COMMENT '产品 id',
`product_name` varchar(128) NULL COMMENT '产品名称',
`product_price` BIGINT(20) NOT NULL COMMENT '产品价格',
`state` TINYINT(4) NULL DEFAULT 0 COMMENT '产品状态 0-有效 1-下架',
`create_time` DATETIME DEFAULT now(),
`update_time` DATETIME DEFAULT now(),
PRIMARY KEY (id)
) ENGINE = INNODB DEFAULT CHARACTER SET = utf8mb4 COMMENT ='产品表';
-- 数据初始化
insert into product_detail (id, product_name, product_price, state)
values (1001, "T 恤", 101, 0), (1002, "短袖", 30, 0), (1003, "短裤", 44, 0), (1004, "卫衣", 58, 0), (1005, "马甲", 98, 0), (1006, "羽绒服", 101, 0), (1007, "冲锋衣", 30, 0), (1008, "袜子", 44, 0), (1009, "鞋子", 58, 0), (10010, "毛衣", 98, 0);
<?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>org.example</groupId>
<artifactId>spring-cloud-demo</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.6</version>
<relativePath/>
</parent>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<java.version>17</java.version>
<mybatis.version>3.0.3</mybatis.version>
<mysql.version>8.0.33</mysql.version>
<spring-cloud.version>2022.0.3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter-test</artifactId>
<version>${mybatis.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
DependencyManagement 和 Dependencies:
做如下操作:创建模块并声明项目依赖和项目构建插件。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
创建模块部分同上,下面声明项目依赖和项目构建插件。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
启动类:
@SpringBootApplication
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
配置文件(application.yml):
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/cloud_order?characterEncoding=utf8&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
configuration:
# 配置打印 MyBatis 日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true # 配置驼峰自动转换
@Data
public class OrderInfo {
private Integer id;
private Integer userId;
private Integer productId;
private Integer num;
private Integer price;
private Integer deleteFlag;
private Date createTime;
private Date updateTime;
}
@RequestMapping("/order")
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@RequestMapping("/{orderId}")
public OrderInfo getOrderById(@PathVariable("orderId") Integer orderId) {
return orderService.selectOrderById(orderId);
}
}
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
public OrderInfo selectOrderById(Integer orderId) {
OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
return orderInfo;
}
}
@Mapper
public interface OrderMapper {
@Select("select * from order_detail where id=#{orderId}")
OrderInfo selectOrderById(Integer orderId);
}
完善商品服务类似,故代码不展示。
根据订单查询订单信息时,根据订单里产品 ID,获取产品的详细信息。
实现思路: order-service 服务向 product-service 服务发送一个 HTTP 请求,把得到的返回结果,和订单结果融合在一起,返回给调用方。 实现方式:采用 Spring 提供的 RestTemplate。
@Configuration
public class BeanConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RestTemplate restTemplate;
public OrderInfo selectOrderById(Integer orderId) {
OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
String url = "http://127.0.0.1:9090/product/" + orderInfo.getProductId();
ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
orderInfo.setProductInfo(productInfo);
return orderInfo;
}
}
RestTemplate 是从 Spring3.0 开始支持的一个 HTTP 请求工具,它是一个同步的 REST API 客户端,提供了常见的 REST 请求方案的模版。
什么是 REST? REST (Representational State Transfer),表现层资源状态转移。 REST 是由 HTTP 的主要设计者提出来的软件架构风格。这里面主要有三个概念:
REST 是一种设计风格,指资源在网络中以某种表现形式进行状态转移。 简单来说:REST 描述的是在网络中 Client 和 Server 的一种交互形式,REST 本身不实用,实用的是如何设计 RESTful API(REST 风格的网络接口)。
什么是 RESTful? REST 是一种设计风格,并没有一个明确的标准。满足这种设计风格的程序或接口我们称之为 RESTful(从单词字面来看就是一个形容词)。所以 RESTful API 就是满足 REST 架构风格的接口。
RESTful 风格大致有以下几个主要特征:
RESTful 实践 RESTful 风格的 API 固然很好很规范,但大多数互联网公司并没有按照其规则来设计,因为 REST 是一种风格,而不是一种约束或规则,过于理想的 RESTful API 会付出太多的成本。 RESTful API 缺点:
http://127.0.0.1:9090/product/),如果更换 IP,需要修改代码。
除此之外,微服务架构还面临很多问题,后续文章将为大家介绍学习如何使用 Spring Cloud 来解决这些问题。
本文搭建了 Spring Cloud 后续组件使用的基本工程环境,介绍了服务拆分原则及基于 RestTemplate 的远程调用实现,并指出了当前方案存在的局限性,为后续深入学习服务发现与负载均衡打下基础。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online