目 录
- 一. 开发环境安装
- 二. 案例介绍
- 2.1 需求
- 2.2 服务拆分
- 三. 数据准备
- 四. 工程搭建
- 4.1 构建父子工程
- 4.1.1 创建父工程
- 4.1.2 创建子项目-订单服务
- 4.1.3 创建子项目-商品服务
- 4.2 完善订单服务
- 4.2.1 完善启动类, 配置文件
- 4.2.2 业务代码
- 4.2.3 测试
- 4.3 完善商品服务
- 4.3.1 完善启动类, 配置文件
- 4.3.2 业务代码
- 4.3.3 测试
- 4.4 远程调用
- 4.4.1 需求
- 4.4.2 实现
- 4.4.3 测试
- 五. RestTemplate
一. 开发环境安装
- JDK17安装
- MySQL安装
二. 案例介绍
2.1 需求
实现⼀个电商平台,⼀个电商平台包含的内容⾮常多, 以京东为例, 仅从首页上就可以看到巨多的功能
我们该如何实现呢? 如果把这些功能全部写在⼀个服务里, 这个服务将是巨大的.
巨多的会员, 巨大的流量, 微服务架构是最好的选择.
微服务应用开发的第⼀步, 就是服务拆分. 拆分后才能进行"各自开发"
2.2 服务拆分
服务拆分原则
微服务并不是越小越好, 服务越小, 微服务架构的优点和缺点都会越来越明显.
服务越小, 微服务的独立性就会越来越⾼, 但同时, 微服务的数量也会越多, 管理这些微服务的难度也会提高. 所以服务拆分也要考虑场景.
拆分微服务—般遵循如下原则:
1. 单⼀职责原则
单⼀职责原则原本是面向对象设计中的⼀个基本原则, 它指的是⼀个类应该专注于单⼀功能. 不要存在多于一个导致类变更的原因.
2. 服务自治
服务自治是指每个微服务都应该具备高度自治的能力, 即每个服务要能做到独立开发, 独立测试, 独立构建, 独立部署, 独立运行
3. 单向依赖
微服务之间需要做到单向依赖, 严禁循环依赖, 双向依赖
循环依赖: A -> B -> C ->A
双向依赖: A -> B, B -> A
代码演示前提:
根据服务的单⼀职责原则, 我们把服务进行拆分为: 订单服务, 商品服务
- 订单服务: 提供订单ID, 获取订单详细信息
- 商品服务: 根据商品ID, 返回商品详细信息.
三. 数据准备
根据服务自治原则, 每个服务都应有自己独立的数据库
订单服务:
1 -- 建库
2 create database if not exists cloud_order charset utf8mb4;
3
4 -- 订单表
5 DROP TABLE IF EXISTS order_detail;
6 CREATE TABLE order_detail (
7 `id` INT NOT NULL AUTO_INCREMENT COMMENT '订单id',
8 `user_id` BIGINT ( 20 ) NOT NULL COMMENT '⽤⼾ID',
9 `product_id` BIGINT ( 20 ) NULL COMMENT '产品id',
10 `num` INT ( 10 ) NULL DEFAULT 0 COMMENT '下单数量',
11 `price` BIGINT ( 20 ) NOT NULL COMMENT '实付款',
12 `delete_flag` TINYINT ( 4 ) NULL DEFAULT 0,
13 `create_time` DATETIME DEFAULT now(),
14 `update_time` DATETIME DEFAULT now(),
15 PRIMARY KEY ( id )) ENGINE = INNODB DEFAULT CHARACTER
16 SET = utf8mb4 COMMENT = '订单表';
17
18 -- 数据初始化
19 insert into order_detail (user_id,product_id,num,price)
20 values
21 (2001, 1001,1,99), (2002, 1002,1,30), (2001, 1003,1,40),
22 (2003, 1004,3,58), (2004, 1005,7,85), (2005, 1006,7,94);
23 create database if not exists cloud_product charset utf8mb4;
-- 产品表
24 DROP TABLE IF EXISTS product_detail;
25 CREATE TABLE product_detail (
26 `id` INT NOT NULL AUTO_INCREMENT COMMENT '产品id',
27 `product_name` varchar ( 128 ) NULL COMMENT '产品名称',
28 `product_price` BIGINT ( 20 ) NOT NULL COMMENT '产品价格',
29 `state` TINYINT ( 4 ) NULL DEFAULT 0 COMMENT '产品状态 0-有效 1-下架',
30 `create_time` DATETIME DEFAULT now(),
31 `update_time` DATETIME DEFAULT now(),
32 PRIMARY KEY ( id )) ENGINE = INNODB DEFAULT CHARACTER
33 SET = utf8mb4 COMMENT = '产品表';
34
35 -- 数据初始化
36 insert into product_detail (id, product_name,product_price,state)
37 values
38 (1001,"T恤", 101, 0), (1002, "短袖",30, 0), (1003, "短裤",44, 0),
39 (1004, "卫⾐",58, 0), (1005, "⻢甲",98, 0),(1006,"⽻绒服", 101, 0),
40 (1007, "冲锋⾐",30, 0), (1008, "袜⼦",44, 0), (1009, "鞋⼦",58, 0),
41 (10010, "⽑⾐",98, 0)
四. 工程搭建
4.1 构建父子工程
4.1.1 创建父工程
- 创建⼀个空的 Maven 项目, 删除所有代码, 只保留 pom.xml
目录结构:
- 完善 pom 文件
使用 properties 来进行版本号的统⼀管理, 使用 dependencyManagement 来管理依赖, 声明父工程的打包方式为 pom
1 <?xml version="1.0" encoding="UTF-8"?>
2 <project xmlns="http://maven.apache.org/POM/4.0.0"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
5 <modelVersion>4.0.0</modelVersion>
6
7 <groupId>org.example</groupId>
8 <artifactId>spring-cloud-demo</artifactId>
9 <packaging>pom</packaging>
10 <version>1.0-SNAPSHOT</version>
11
12 <parent>
13 <groupId>org.springframework.boot</groupId>
14 <artifactId>spring-boot-starter-parent</artifactId>
15 <version>3.1.6</version>
16 <relativePath/> <!-- lookup parent from repository -->
17 </parent>
18 <properties>
19 <maven.compiler.source>17</maven.compiler.source>
20 <maven.compiler.target>17</maven.compiler.target>
21 <java.version>17</java.version>
22 <mybatis.version>3.0.3</mybatis.version>
23 <mysql.version>8.0.33</mysql.version>
24 <spring-cloud.version>2022.0.3</spring-cloud.version>
25 </properties>
26
27 <dependencies>
28 <dependency>
29 <groupId>org.projectlombok</groupId>
30 <artifactId>lombok</artifactId>
31 <optional>true</optional>
32 </dependency>
33 </dependencies>
34
35 <dependencyManagement>
36 <dependencies>
37 <dependency>
38 <groupId>org.springframework.cloud</groupId>
39 <artifactId>spring-cloud-dependencies</artifactId>
40 <version>${spring-cloud.version}</version>
41 <type>pom</type>
42 <scope>import</scope>
43 </dependency>
44 <dependency>
45 <groupId>org.mybatis.spring.boot</groupId>
46 <artifactId>mybatis-spring-boot-starter</artifactId>
47 <version>${mybatis.version}</version>
48 </dependency>
49 <dependency>
50 <groupId>com.mysql</groupId>
51 <artifactId>mysql-connector-j</artifactId>
52 <version>${mysql.version}</version>
53 </dependency>
54 <dependency>
55 <groupId>org.mybatis.spring.boot</groupId>
56 <artifactId>mybatis-spring-boot-starter-test</artifactId>
57 <version>${mybatis.version}</version>
58 <scope>test</scope>
59 </dependency>
60 </dependencies>
61 </dependencyManagement>
62 </project>
DependencyManagement 和 Dependencies
- dependencies :将所依赖的 jar 直接加到项目中. 子项目也会继承该依赖.
- dependencyManagement :只是声明依赖, 并不实现Jar包引入. 如果子项目需要用到相关依赖,需要显式声明. 如果子项目没有指定具体版本, 会从父项目中读取 version. 如果子项目中指定了版本号,就会使用子项目中指定的 jar 版本. 此外父工程的打包方式应该是 pom,不是 jar, 这里需要手动使用 packaging 来声明.
SpringBoot 实现依赖jar包版本的管理, 也是这种方式
依赖 Jar 的版本判断
1 <dependencies>
2 <dependency>
3 <groupId>org.projectlombok</groupId>
4 <artifactId>lombok</artifactId>
5 <optional>true</optional>
6 </dependency>
7 </dependencies>
8
9 <dependencyManagement>
10 <dependencies>
11 <dependency>
12 <groupId>com.mysql</groupId>
13 <artifactId>mysql-connector-j</artifactId>
14 <version>${mysql.version}</version>
15 </dependency>
16 </dependencies>
17 </dependencyManagement>
上述代码中, lombok 会被直接引入到当前项目以及子项目中, mysql-connector-j 不会实际引入jar,子项目继承时必须显式声明.
Spring Cloud版本
Spring Cloud 是基于 SpringBoot 搭建的, 所以Spring Cloud 版本与SpringBoot 版本有关
4.1.2 创建子项目-订单服务
声明项目依赖 和 项目构建插件
1 <dependencies>
2 <dependency>
3 <groupId>org.springframework.boot</groupId>
4 <artifactId>spring-boot-starter-web</artifactId>
5 </dependency>
6 <dependency>
7 <groupId>com.mysql</groupId>
8 <artifactId>mysql-connector-j</artifactId>
9 </dependency>
10 <!--mybatis-->
11 <dependency>
12 <groupId>org.mybatis.spring.boot</groupId>
13 <artifactId>mybatis-spring-boot-starter</artifactId>
14 </dependency>
15 </dependencies>
16
17 <build>
18 <plugins>
19 <plugin>
20 <groupId>org.springframework.boot</groupId>
21 <artifactId>spring-boot-maven-plugin</artifactId>
22 </plugin>
23 </plugins>
24 </build>
4.1.3 创建子项目-商品服务
声明项目依赖 和 项目构建插件
1 <dependencies>
2 <dependency>
3 <groupId>org.springframework.boot</groupId>
4 <artifactId>spring-boot-starter-web</artifactId>
5 </dependency>
6 <dependency>
7 <groupId>com.mysql</groupId>
8 <artifactId>mysql-connector-j</artifactId>
9 </dependency>
10 <!--mybatis-->
11 <dependency>
12 <groupId>org.mybatis.spring.boot</groupId>
13 <artifactId>mybatis-spring-boot-starter</artifactId>
14 </dependency>
15 </dependencies>
16
17 <build>
18 <plugins>
19 <plugin>
20 <groupId>org.springframework.boot</groupId>
21 <artifactId>spring-boot-maven-plugin</artifactId>
22 </plugin>
23 </plugins>
24 </build>
4.2 完善订单服务
4.2.1 完善启动类, 配置文件
启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
配置文件
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 #配置驼峰⾃动转换
4.2.2 业务代码
- 实体类
import lombok.Data;
import java.util.Date;
@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;
}
- Controller
@RequestMapping("/order")
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@RequestMapping("/{orderId}")
public OrderInfo getOrderById(@PathVariable("orderId") Integer orderId){
return orderService.selectOrderById(orderId);
}
}
- Service
import com.bite.order.mapper.OrderMapper;
import com.bite.order.model.OrderInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
public OrderInfo selectOrderById(Integer orderId) {
OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
return orderInfo;
}
}
- Mapper
import com.bite.order.model.OrderInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface OrderMapper {
@Select("select * from order_detail where id=#{orderId}")
OrderInfo selectOrderById(Integer orderId);
}
4.2.3 测试
访问 url: http://127.0.0.1:8080/order/1
页面正常返回结果:
4.3 完善商品服务
4.3.1 完善启动类, 配置文件
启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ProductServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ProductServiceApplication.class, args);
}
}
配置文件
server:
port: 9090
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/cloud_product?
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 #配置驼峰⾃动转换
后⾯需要多个服务⼀起启动, 所以设置为不同的端口号
4.3.2 业务代码
- 实体类
import lombok.Data;
import java.util.Date;
@Data
public class ProductInfo {
private Integer id;
private String productName;
private Integer productPrice;
private Integer state;
private Date createTime;
private Date updateTime;
}
- Controller
@RequestMapping("/product")
@RestController
public class ProductController {
@Autowired
private ProductService productService;
@RequestMapping("/{productId}")
public ProductInfo getProductById(@PathVariable("productId") Integer productId){
System.out.println("收到请求,Id:"+productId);
return productService.selectProductById(productId);
}
}
- Service
@Service
public class ProductService {
@Autowired
private ProductMapper productMapper;
public ProductInfo selectProductById(Integer id){
return productMapper.selectProductById(id);
}
}
- Mapper
@Mapper
public interface ProductMapper {
@Select("select * from product_detail where id=#{id}")
ProductInfo selectProductById(Integer id);
}
4.3.3 测试
访问 url: http://127.0.0.1:9090/product/1001
页面正常返回结果:
4.4 远程调用
4.4.1 需求
根据订单查询订单信息时, 根据订单里产品 ID, 获取产品的详细信息.
4.4.2 实现
实现思路: order-service 服务向 product-service 服务发送⼀个 http 请求, 把得到的返回结果, 和订单结果融合在⼀起, 返回给调用方
实现方式: 采用 Spring 提供的 RestTemplate
实现http请求的⽅式, 有很多, 可参考: https://zhuanlan.zhihu.com/p/670101467 点击跳转
- 定义 RestTemplate
@Configuration
public class BeanConfig {
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
- 修改 order-service 中的 OrderService
@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 详细使用可参考: https://www.cnblogs.com/54chensongxia/p/11414923.html 点击跳转
4.4.3 测试
访问 url: http://127.0.0.1:8080/order/1
页面返回结果:
五. RestTemplate
RestTemplate 是从 Spring3.0 开始支持的⼀个 HTTP 请求工具, 它是⼀个同步的 REST API 客户端, 提供了常见的REST请求方案的模版.
什么是REST?
REST(Representational State Transfer), 表现层资源状态转移.
REST 是由 HTTP 的主要设计者Roy Fielding 博士在2000年他的博士论文中提出来的⼀种软件架构风格