图源:
Spring Cloud 的基本宗旨是将项目进行拆分,并分别开发、部署和统一管理。
这个架构会在之后进行一步步完善。
创建根项目
创建一个 Maven 项目,默认生成的 POM 文件如下:
<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>shopping</artifactId>
<version>1.0</version>
<properties>
<maven.compiler.source>18</maven.compiler.source>
<maven.compiler.target>18</maven.compiler.target>
</properties>
</project>
添加根项目依赖
修改 POM 文件:
<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>shopping</artifactId>
<version>1.0</version>
<modules>
<module>shopping-user</module>
<module>shopping-order</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<!-- spring cloud 版本 -->
<spring-cloud.version>Hoxton.SR10</spring-cloud.version>
<mysql.version>5.1.47</mysql.version>
<mybatis.version>2.1.1</mybatis.version>
</properties>
<!-- 打包方式 -->
<packaging>pom</packaging>
<!-- spring boot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.9.RELEASE</version>
<relativePath/>
</parent>
<dependencyManagement>
<dependencies>
<!-- springCloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
需要注意的是,Spring Cloud 的版本与项目使用的 Spring Boot 版本有对应关系,所以这里使用的 Spring Cloud 版本是 Hoxton.SR10,其对应的 Spring Boot 版本是 2.3.x,具体这里使用了 2.3.9 版本。
完整的 Spring Cloud 与 Spring Boot 版本对应列表见 。
这里删除了 maven.compiler.source 和 maven.compiler.target 配置,否则会报错。
lombok 依赖要放在 dependencyManagement 节点外部,否则子模块无法使用。
删除项目中的src
目录,因为根项目不需要添加任何代码。
添加子模块
在根项目上右键 New->Module 添加模块。
模块同样以 Maven 项目的方式创建,过程类似根项目:
这个子模块我命名为 Shopping-order,作为订单相关的微服务。
用同样的方式创建子模块 shopping-user。
自动生成的子模块 pom.xml 文件内容如下:
<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>
<artifactId>shopping</artifactId>
<groupId>org.example</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>shopping-order</artifactId>
<properties>
<maven.compiler.source>18</maven.compiler.source>
<maven.compiler.target>18</maven.compiler.target>
</properties>
</project>
添加子模块项目依赖
为其添加必要的依赖和插件:
<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>
<artifactId>shopping</artifactId>
<groupId>org.example</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>shopping-order</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</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>
</project>
子模块只需要添加3个必须依赖:
-
spring-boot-starter-web,Spring MVC 相关功能
-
mysql-connector-java,MySQL 驱动
-
mybatis-spring-boot-starter,MyBatis
子模块的依赖不需要指定版本,因为这些都在父项目中定义好了。
如果 Maven 下载依赖出错,可能是国内最常用的阿里云镜像的问题(可能该镜像站不保存老旧版本的依赖),本人通过注释阿里云镜像,改从其它镜像站(比如 repo1.maven.org)正常下载。更多的 Maven 镜像站地址见。
另一个子模块以同样的方式添加依赖。
添加入口类
在子模块 shopping-order 的 src/main/java 目录下添加包 org.example.shopping.order 作为子模块的根包。
在该包下添加子模块的入口类 OrderApplication:
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
对另一个子模块以同样的方式添加包 org.example.shopping.user 和入口类 UserApplication。
添加配置文件
为子模块 shopping-order 添加配置文件 application.yml:
server
port8080
spring
datasource
url jdbc mysql //localhost 3306/cloud_order?useSSL=false
username root
password mysql
driver-class-name com.mysql.jdbc.Driver
mybatis
type-aliases-package org.example.shopping.order.entity
configuration
map-underscore-to-camel-casetrue
logging
level
org.example.shopping.order debug
pattern
dateformat MM-dd HH mm ss SSS
添加两个数据库 cloud_user 和 cloud_order,分别对应两个子模块。
为另一个子模块添加类似的配置文件。
注意,要将两个子模块启动端口区分开,因为它们需要同时运行。
现在启动一下两个子模块,应该都可以正常启动了。
下面给两个子模块分别添加两个作为示例的接口,一个用于查询订单数据,另一个用于查询用户信息。
添加数据库表
在这之前先创建两张表:
CREATE TABLE `tb_order` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单id',
`user_id` bigint NOT NULL COMMENT '用户id',
`name` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT '商品名称',
`price` bigint NOT NULL COMMENT '商品价格',
`num` int DEFAULT '0' COMMENT '商品数量',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `username` (`name`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=109 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=COMPACT;
CREATE TABLE `tb_user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT '收件人',
`address` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT '地址',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `username` (`username`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=109 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=COMPACT;
两张表分别保存在 cloud_order 和 cloud_user 数据库中。
测试数据:
mysql> select * from tb_user;
+----+----------+--------------------+
| id | username | address |
+----+----------+--------------------+
| 1 | 柳岩 | 湖南省衡阳市 |
| 2 | 文二狗 | 陕西省西安市 |
| 3 | 华沉鱼 | 湖北省十堰市 |
| 4 | 张必沉 | 天津市 |
| 5 | 郑爽爽 | 辽宁省沈阳市大东区 |
| 6 | 范兵兵 | 山东省青岛市 |
+----+----------+--------------------+
mysql> select * from tb_order;
+-----+---------+----------------------------------+--------+------+
| id | user_id | name | price | num |
+-----+---------+----------------------------------+--------+------+
| 101 | 1 | Apple 苹果 iPhone 12 | 699900 | 1 |
| 102 | 2 | 雅迪 yadea 新国标电动车 | 209900 | 1 |
| 103 | 3 | 骆驼(CAMEL)休闲运动鞋女 | 43900 | 1 |
| 104 | 4 | 小米10 双模5G 骁龙865 | 359900 | 1 |
| 105 | 5 | OPPO Reno3 Pro 双模5G 视频双防抖 | 299900 | 1 |
| 106 | 6 | 美的(Midea) 新能效 冷静星II | 544900 | 1 |
| 107 | 2 | 西昊/SIHOO 人体工学电脑椅子 | 79900 | 1 |
| 108 | 3 | 梵班(FAMDBANN)休闲男鞋 | 31900 | 1 |
+-----+---------+----------------------------------+--------+------+
添加业务代码
下面为子模块 shopping-order 添加使用 MyBatis 的持久层代码:
package org.example.shopping.order.entity;
// ...
@Data
public class Order {
private Long id;
private Long userId;
private String name;
private Long price;
private Integer num;
}
package org.example.shopping.order.mapper;
// ...
public interface OrderMapper {
@Select("select * from tb_order where id = #{id}")
Order findById(Long id);
}
为了能让 MyBatis 检索到 Mapper 目录,需要添加:
@MapperScan("org.example.shopping.order.mapper")
@SpringBootApplication
public class OrderApplication {
// ...
}
编写 Service :
package org.example.shopping.order.service;
// ...
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
public Order findOrderById(Long orderId) {
Order order = orderMapper.findById(orderId);
return order;
}
}
编写 Controller :
package org.example.shopping.order.controller;
// ...
@RestController
@RequestMapping("/order")
@Validated
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("/{id}")
public Result<Order> getOrderInfo(@Min(1) @NotNull @PathVariable Long id) {
return Result.success(orderService.findOrderById(id));
}
}
Controller 中使用了 Hibernate Validation 对入参进行校验,所以需要在根项目的 POM 文件中添加相关依赖:
<project>
<!-- ... -->
<dependencies>
<!-- ... -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
</dependencies>
</project>
为了让接口返回统一格式,还使用了一个辅助类 Result :
@Data
@NoArgsConstructor
public class Result<T> {
T data;
@NonNull
String errorMsg;
@NonNull
String errorCode;
boolean success;
private static final String DEFAULT_ERROR_CODE = "error.default";
private Result(T data, @NonNull String errorMsg, @NonNull String errorCode, boolean success) {
this.data = data;
this.errorMsg = errorMsg;
this.errorCode = errorCode;
this.success = success;
}
public static <D> Result<D> success(D data) {
return new Result<>(data, "", "", true);
}
public static Result<Object> fail(String errorMsg, String errorCode) {
return new Result<>(null, errorMsg, errorCode, false);
}
public static Result<Object> fail(String errorMsg) {
return fail(errorMsg, DEFAULT_ERROR_CODE);
}
}
测试
现在重新启动子模块 shopping-order,在浏览器访问 http://localhost:8080/order/101,没有意外的话会看到类似下面的输出:
{
"data": {
"id": 101,
"userId": 1,
"name": "Apple 苹果 iPhone 12 ",
"price": 699900,
"num": 1
},
"errorMsg": "",
"errorCode": "",
"success": true
}
用类似的方式为 shopping-user 编写一个接口,用于查询用户信息。
子模块交互
现在编写两个子模块之间的交互,我们希望 shopping-order 的订单信息接口返回的数据中包含用户信息。
RestTemplate
为了能让 shopping-order 调用 shopping-user 的接口,添加一个RestTemplate
:
package org.example.shopping.order;
// ...
@Configuration
public class WebConfig {
@Bean
RestTemplate restTemplate(){
return new RestTemplate();
}
}
修改 Service :
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RestTemplate restTemplate;
public Order findOrderById(Long orderId) {
Order order = orderMapper.findById(orderId);
String url = String.format("http://localhost:8081/user/%d", order.getUserId());
Result<?> result = restTemplate.getForObject(url, Result.class);
User user = Result.parseData(result, User.class);
order.setUser(user);
return order;
}
}
在获取到订单信息后,利用订单中的用户 ID 查询 shopping-user 的用户信息接口以获取用户信息,并组装到返回数据中。
虽然RestTemplate.getForObject
可以将接口返回的 JSON 串反序列化为指定对象,但因为Result
是一个泛型,所以并不能正确解析出其中的data
属性类型,所以这里使用一个辅助方法Result.parseData
完成二次转换:
package org.example.shopping.order;
// ...
@Data
@NoArgsConstructor
public class Result<T> {
// ...
@SneakyThrows
public static <D> D parseData(Result<?> result, Class<D> dataCls) {
if (result == null || result.getData() == null) {
return null;
}
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(result.getData());
return objectMapper.readValue(json, dataCls);
}
}
测试
重启子模块 shopping-order,并访问 http://localhost:8080/order/101,应该可以看到以下输出:
{
"data": {
"id": 101,
"userId": 1,
"name": "Apple 苹果 iPhone 12 ",
"price": 699900,
"num": 1,
"user": {
"id": 1,
"userName": "柳岩",
"address": "湖南省衡阳市"
}
},
"errorMsg": "",
"errorCode": "",
"success": true
}
本文的完整示例代码可以从获取。
文章评论