SpringCloud 环境&工程搭建
文章目录
- SpringCloud 环境&工程搭建
- 1. SpringCloud介绍
- 2. 服务拆分原则
- 2.1 单一职责原则
- 2.2 服务自治
- 2.3 单向依赖
- 2.4 服务拆分示例
- 3. 数据准备
- 4. 工程搭建
- 4.1 创建父工程
- 4.2 创建子工程
- 4.2.1 子项目-订单服务
- 4.2.2 子项目-商品服务
- 4.3 完善子工程
- 4.3.1 完善订单服务
- 4.3.2 完善商品服务
- 5. 远程调用
1. SpringCloud介绍
SpringCloud 提供了一些可以让开发人员快速搭建分布式服务的工具,比如配置管理、服务发现、熔断、智能路由等,它们可以在任何分布式环境中很好的工作:
更直接的讲,SpringCloud 介绍分布式微服务架构的一站式解决方案,是微服务架构落地的多种技术的集合,比如:
- Distributed/versioned configuration 分布式版本配置
- Service registration and discovery 服务注册和发现
- Load balancing 负载均衡
- Service-to-service calls 服务调用
同时,SrpingCloud的所有子项目都依赖于SpringBoot,所以SpringBoot和SpringCloud的版本之间也存在一定的对应关系,在使用时需要注意彼此间的对应匹配:
2. 服务拆分原则
拆分微服务一般遵循如下原则:
2.1 单一职责原则
在微服务架构中,一个微服务应该只负责一个功能或业务领域,每个服务应该有清晰的定义和边界,只关注自己的特定业务领域。
比较类似于,一个人专注的做一件事的效率远高于同时关注多件事情,而业务中如电商系统也是由多个服务共同构成的:
2.2 服务自治
服务自治是指每个微服务都应该具备高度自治的能力,即每个服务要能做到独立开发,独立测试,独立构建,独立部署,独立运行。
以上面的电商系统为例,每一个微服务应该有自己的存储、配置,在进行开发、构建、部署、运行和测试时,并不需要过多关注其它微服务的状态和数据:
2.3 单向依赖
微服务之间需要做到单向依赖,严禁导致循环依赖、双向依赖:
在特定场景下如果无法避免循环依赖或双向依赖,可以考虑使用消息队列等其它方式来实现。
注:微服务并无标准架构,合适的就是最好的,在架构设计的过程中,坚持”合适由于业界领先“,避免为了设计而设计
2.4 服务拆分示例
我们以电商系统中的一个管理服务为例,即订单管理:
概括的讲,这个页面提供了以下信息:
- 订单列表
- 商品信息
根据服务的单一职责原则,我们把服务拆分为:
- 订单服务:提供订单ID,获取订单详细信息
- 商品服务:根据商品ID,返回商品详细信息
3. 数据准备
对于上述案例,我们使用JDK-17版本和MySQL8版本进行构造,这里需要提前配置好。
根据服务自治原则,每个服务都应有自己独立的数据库。
订单服务:
-- 订单服务
-- 建库
create database if not exists cloud_order charset utf8mb4;
use cloud_order;
-- 订单表
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);
商品服务:
-- 数据初始化
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;
-- 产品表
use cloud_product;
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);
4. 工程搭建
在这个案例中,我们使用父子工程的方式来创建项目
4.1 创建父工程
-
先创建一个空的Maven项目,删除所有代码,只保留pom.xml
-
完善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>org.example</groupId> <artifactId>spring-cloud-demo</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.1.6</version> <relativePath/> <!-- lookup parent from repository --> </parent> <packaging>pom</packaging> <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>
注:我们这里使用到了
dependencyMangement
来进行声明依赖,并不实现jar包的引入。如果子项目需要用到相关依赖,需要显式声明(也需要引入);如果子项目没有指定具体版本,会从父项目中读取version。如果子项目中指定了版本号,就会使用子项目中指定的jar版本。此外父工程的打包方式应该是pom,不是jar,这里需要手动使用packagin
来声明.SpringCloud版本需要于Springboot版本对应,我们这里Springboot使用的是
3.1.6
版本,对应的SpringCloud版本则需使用2022.0.x中的任意版本即可:
4.2 创建子工程
4.2.1 子项目-订单服务
构建order-service子工程:
声明 项目依赖 和 项目构建插件:
<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>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/**</include>
</includes>
</resource>
</resources>
</build>
4.2.2 子项目-商品服务
构建 product-service 子工程
声明 项目依赖 和 项目构建插件:
<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>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/**</include>
</includes>
</resource>
</resources>
</build>
4.3 完善子工程
4.3.1 完善订单服务
实现以下目录结构:
-
完善启动类
package com.order; 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); } }
-
配置文件 application.yml
server: port: 8080 spring: datasource: url: jdbc:mysql://127.0.0.1/cloud_order?characterEncoding=utf8&useSSL=false username: root password: 11111 driver-class-name: com.mysql.cj.jdbc.Driver # 设置 Mybatis 的 xml 保存路径 mybatis: mapper-locations: classpath:mapper/*Mapper.xml configuration: # 配置打印 MyBatis 执行的 SQL log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true #自动驼峰转换
-
构建实体类
package com.order.model; 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层
package com.order.controller; import com.order.model.OrderInfo; import com.order.service.OrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RequestMapping("/order") @RestController public class OrderController { @Autowired private OrderService orderService; @RequestMapping("/{orderId}") public OrderInfo getOrderById(@PathVariable("orderId") Integer orderId) { return orderService.selectOrderById(orderId); } }
-
Service层
// OrderService package com.order.service; import com.order.model.OrderInfo; public interface OrderService { OrderInfo selectOrderById(Integer orderId); } // OrderServiceImpl package com.order.service.Impl; import com.order.mapper.OrderMapper; import com.order.model.OrderInfo; import com.order.service.OrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; @Service public class OrderServiceImpl implements OrderService { @Autowired private OrderMapper orderMapper; @Autowired private RestTemplate restTemplate; @Override public OrderInfo selectOrderById(Integer orderId) { OrderInfo orderInfo = orderMapper.selectOrderById(orderId); return orderInfo; } }
-
Mapper层
package com.order.mapper; import com.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.3.2 完善商品服务
实现以下目录:
-
完善启动类
package com.product; 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); } }
-
配置文件 application.yml
server: port: 9090 spring: datasource: url: jdbc:mysql://127.0.0.1/cloud_product?characterEncoding=utf8&useSSL=false username: root password: 11111 driver-class-name: com.mysql.cj.jdbc.Driver # 设置 Mybatis 的 xml 保存路径 mybatis: mapper-locations: classpath:mapper/*Mapper.xml configuration: # 配置打印 MyBatis 执行的 SQL log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true #自动驼峰转换
-
构建实体类
package com.product.model; 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层
package com.product.controller; import com.product.model.ProductInfo; import com.product.service.ProductService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RequestMapping("/product") @RestController public class ProductController { @Autowired private ProductService productService; @RequestMapping("/{productId}") public ProductInfo getProductById(@PathVariable("productId") Integer productId) { return productService.selectProductById(productId); } }
-
Service层
// ProductService package com.product.service; import com.product.model.ProductInfo; public interface ProductService { ProductInfo selectProductById(Integer productId); } // ProductServiceImpl package com.product.service.Impl; import com.product.mapper.ProductMapper; import com.product.model.ProductInfo; import com.product.service.ProductService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class ProductServiceImpl implements ProductService { @Autowired private ProductMapper productMapper; @Override public ProductInfo selectProductById(Integer productId) { return productMapper.selectProductById(productId); } }
-
Mapper层
package com.product.mapper; import com.product.model.ProductInfo; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; @Mapper public interface ProductMapper { @Select("select * from product_detail where id = #{productId}") ProductInfo selectProductById(Integer productId); }
完善好上述代码后,我们来启动测试一下:
运行成功!
5. 远程调用
此时我们有一个需求,即根据订单查询订单信息时,根据订单里的产品ID来获取产品的详细信息:
我们可以通过远程调用的方式来实现:
实现思路:order-service
服务向product-service
服务发送一个http请求,把得到的返回结果和订单结果融合在一起,返回给调用方;
实现方式:采用Spring提供的 RestTemplate 实现请求发送
-
定义RestTemplate
package com.order.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class BeanConfig { @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
-
引入并在OrderInfo中添加ProductInfo
package com.order.model; 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; private ProductInfo productInfo; }
-
修改OrderServiceImpl
package com.order.service.Impl; import com.order.mapper.OrderMapper; import com.order.model.OrderInfo; import com.order.model.ProductInfo; import com.order.service.OrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; @Service public class OrderServiceImpl implements OrderService { @Autowired private OrderMapper orderMapper; @Autowired private RestTemplate restTemplate; @Override 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; } }
此时访问
127.0.0.1:8080/order/1
测试结果:
测试成功!!
我们也可以看到上面在进行远程调用时URL的IP和端口号是写死的(http://127.0.0.1:9090/product/
),如果这个时候我们要更换IP了,原本部署完毕的程序就需要重新修改再部署,这样就会导致很多的问题,对此也能使用SrpingCloud
来这个解决问题,敬请期待!!