从零开始 Spring Cloud 1:搭建项目

从零开始 Spring Cloud 1:搭建项目

从零开始 Spring Cloud 1:搭建项目_第1张图片

图源:laiketui.com

Spring Cloud 的基本宗旨是将项目进行拆分,并分别开发、部署和统一管理。

本文将搭建一个基本的 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.0modelVersion>
    <groupId>org.examplegroupId>
    <artifactId>shoppingartifactId>
    <version>1.0version>
    <properties>
        <maven.compiler.source>18maven.compiler.source>
        <maven.compiler.target>18maven.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.0modelVersion>

    <groupId>org.examplegroupId>
    <artifactId>shoppingartifactId>
    <version>1.0version>
    <modules>
        <module>shopping-usermodule>
        <module>shopping-ordermodule>
    modules>
    <properties>
        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
        <java.version>1.8java.version>
        
        <spring-cloud.version>Hoxton.SR10spring-cloud.version>
        <mysql.version>5.1.47mysql.version>
        <mybatis.version>2.1.1mybatis.version>
    properties>
    
    <packaging>pompackaging>
    
    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.3.9.RELEASEversion>
        <relativePath/>
    parent>
    <dependencyManagement>
        <dependencies>
            
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-dependenciesartifactId>
                <version>${spring-cloud.version}version>
                <type>pomtype>
                <scope>importscope>
            dependency>
            
            <dependency>
                <groupId>mysqlgroupId>
                <artifactId>mysql-connector-javaartifactId>
                <version>${mysql.version}version>
            dependency>
            
            <dependency>
                <groupId>org.mybatis.spring.bootgroupId>
                <artifactId>mybatis-spring-boot-starterartifactId>
                <version>${mybatis.version}version>
            dependency>
        dependencies>
    dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
        dependency>
    dependencies>
project>

需要注意的是,Spring Cloud 的版本与项目使用的 Spring Boot 版本有对应关系,所以这里使用的 Spring Cloud 版本是 Hoxton.SR10,其对应的 Spring Boot 版本是 2.3.x,具体这里使用了 2.3.9 版本。

  • 完整的 Spring Cloud 与 Spring Boot 版本对应列表见 Spring 官网。
  • 这里删除了 maven.compiler.sourcemaven.compiler.target 配置,否则会报错。
  • lombok 依赖要放在 dependencyManagement 节点外部,否则子模块无法使用。

删除项目中的src目录,因为根项目不需要添加任何代码。

添加子模块

在根项目上右键 New->Module 添加模块。

从零开始 Spring Cloud 1:搭建项目_第2张图片

模块同样以 Maven 项目的方式创建,过程类似根项目:

从零开始 Spring Cloud 1:搭建项目_第3张图片

这个子模块我命名为 Shopping-order,作为订单相关的微服务。

从零开始 Spring Cloud 1:搭建项目_第4张图片

用同样的方式创建子模块 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>shoppingartifactId>
        <groupId>org.examplegroupId>
        <version>1.0version>
    parent>
    <modelVersion>4.0.0modelVersion>

    <artifactId>shopping-orderartifactId>

    <properties>
        <maven.compiler.source>18maven.compiler.source>
        <maven.compiler.target>18maven.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>shoppingartifactId>
        <groupId>org.examplegroupId>
        <version>1.0version>
    parent>
    <modelVersion>4.0.0modelVersion>

    <artifactId>shopping-orderartifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
        dependency>
        
        <dependency>
            <groupId>org.mybatis.spring.bootgroupId>
            <artifactId>mybatis-spring-boot-starterartifactId>
        dependency>
    dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
            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-ordersrc/main/java 目录下添加包 org.example.shopping.order 作为子模块的根包。

在该包下添加子模块的入口类 OrderApplication

@SpringBootApplication
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}

对另一个子模块以同样的方式添加包 org.example.shopping.user 和入口类 UserApplication

添加配置文件

为子模块 shopping-order 添加配置文件 application.yml

server:
  port: 8080
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-case: true
logging:
  level:
    org.example.shopping.order: debug
  pattern:
    dateformat: MM-dd HH:mm:ss:SSS

添加两个数据库 cloud_usercloud_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_ordercloud_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.bootgroupId>
            <artifactId>spring-boot-starter-validationartifactId>
        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
}

本文的完整示例代码可以从这里获取。

参考资料

  • 2023最新国内maven仓库镜像地址
  • 使用Idea构建SpringCloud项目(图文详解+简单易懂)
  • 手把手教你搭建SpringCloud项目(一)图文详解

你可能感兴趣的:(JAVA,spring,cloud)