SpringCloud 起步学习简单项目

IDEA 2022
jdk 1.8
maven 4.0
springCloud:Hoxton.SR3
学习的教程:springCloud — 初级篇(1)

SpringCloud 版本问题

写 SpringCloud 项目一定要注意版本问题,一定要和 SpringBoot 的版本对应,否则 SpringCloud 启动失败

进入 https://spring.io/projects/spring-cloud/ 向下滚动查看对应 SpringCloud 版本支持的 SpringBoot 版本。

如下图 2021.0.x aka Jubilee 系列版本仅支持 SpringBoot 2.6.x 版本

学习方式

在这个教程下面的内容,除了在创建模块、application.yml 文件(配置)和 Application 启动类(注解)的部分需要多多注意以外,其他部分和正常的 SpringBoot 开发都没什么两样,其他的复制粘贴进行查看效果即可。

开始

需要先创建一个数据库 cloudtest,后面会用到

create database cloudtest;
use cloudtest;

create table payment(
    id bigint(20) primary key auto_increment,
    serial varchar(200)
)character set utf8;

创建 SpringCloudDemo 项目

创建一个 Maven 项目,选中 Maven 直接点击下一步

项目名 SpringCloudDemo

点击 finish 进行创建

点击 pom.xml 文件,追加以下依赖包(这个是添加的内容,不要把原本的删除了)

    <!-- 表示该工程是一个父工程 -->
    <packaging>pom</packaging>

    <!--统一管理jar包版本 -->
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <spring-boot.version>2.2.2.RELEASE</spring-boot.version>
        <spring-cloud.version>Hoxton.SR3</spring-cloud.version>
        <spring-cloud.alibaba.version>2.2.5.RELEASE</spring-cloud.alibaba.version>

        <junit.version>4.12</junit.version>
        <lombok.version>1.18.10</lombok.version>
        <log4j.version>1.2.17</log4j.version>
        <mysql.version>8.0.22</mysql.version>

        <mybatis.spring.boot.version>1.3.2</mybatis.spring.boot.version>
    </properties>

    <!--子模块继承之后,提供作用:锁定版本+子module不用写groupId和version -->
    <!-- 只是声明依赖,不会引入具体依赖 -->
    <dependencyManagement><!--定义规范,但不导入 -->
        <dependencies>

            <!--spring boot -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>{spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!--spring cloud  -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>{spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!--spring cloud 阿里巴巴 -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>{spring-cloud.alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- mysql -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>{mysql.version}</version>
                <scope>runtime</scope>
            </dependency>

            <!--mybatis -->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>{mybatis.spring.boot.version}</version>
            </dependency>

            <!--junit -->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>{junit.version}</version>
            </dependency>

        </dependencies>
    </dependencyManagement>

    <!--热启动插件 -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <fork>true</fork>
                    <addResources>true</addResources>
                </configuration>
            </plugin>
        </plugins>
    </build>

有时上方的依赖有个别不能下载,这时候就要到 https://mvnrepository.com/ 去搜索对应的 artifactId 名字去看看是否还能下载了,可能这个依赖有漏洞不能下载…比如阿里巴巴的 Druid 包(如下图)。那就得再找找看看能不能用,或改其他方法了,多百度看看…

默认创建的 src 文件夹可以直接删除了,当前示例用不到

添加一个模块,右键选中项目文件夹 – New – Module…,选中 Maven,点击下一步,

创建cloud-api-commons模块

存放一些公共功能的类

pom 文件中添加依赖

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>

        <!-- DevTools -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- 糊涂工具包 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.3.9</version>
        </dependency>
    </dependencies>

创建实体类 Payment

package com.zcq.springcloud.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * @author z
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Payment implements Serializable {

    private Long id;
    private String serial;

}

创建一个 JSON 工具类,用于返回响应的数据

package com.zcq.springcloud.util;

import lombok.Data;

@Data
public class JsonResult<T> {
    private Integer code;
    private String message;
    private T data;

    public JsonResult(Integer code, String message) {
        this(code, message, null);
    }

    public JsonResult(Integer code, String message, T data) {
        super();
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public JsonResult() {
        super();
    }

}

目录结构如下

创建 cloud-provider-payment8001 模块

创建模块步骤和上面创建 cloud-api-commons 模块的步骤一样,这个模块名设为 cloud-provider-payment8001,这个模块充当支付服务

pom 依赖

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>


    <dependencies>

        <!--另一个模块-->
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator </artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>


        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>


        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

    </dependencies>

Dao 层的 Mapper

package com.zcq.springcloud.mapper;

import com.zcq.springcloud.entity.Payment;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface PaymentDao {

    int create(Payment payment);

    Payment getPaymentById(@Param(("id")) Long id);

}

Service 层

PaymentService

package com.zcq.springcloud.service;

import com.zcq.springcloud.entity.Payment;
import org.apache.ibatis.annotations.Param;

public interface PaymentService {

    int create(Payment payment);

    Payment getPaymentById(@Param(("id")) Long id);

}

PaymentServiceImpl

package com.zcq.springcloud.service.impl;

import com.zcq.springcloud.entity.Payment;
import com.zcq.springcloud.mapper.PaymentDao;
import com.zcq.springcloud.service.PaymentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class PaymentServiceImpl implements PaymentService {

    @Autowired
    private PaymentDao paymentDao;

    @Override
    public int create(Payment payment) {
        return paymentDao.create(payment);
    }

    @Override
    public Payment getPaymentById(Long id) {
        return paymentDao.getPaymentById(id);
    }
}

Controller 层

package com.zcq.springcloud.controller;

import com.zcq.springcloud.entity.Payment;
import com.zcq.springcloud.service.PaymentService;
import com.zcq.springcloud.util.JsonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/payment")
@Slf4j
public class PaymentController {

    @Autowired
    private PaymentService paymentService;

    @PostMapping("/create")
    public JsonResult<Payment> create(@RequestBody Payment payment) {
        final int result = paymentService.create(payment);
        log.info("-- 插入结果:" + result);
        if (result > 0) {
            return new JsonResult<>(200, "success", null);
        } else {
            return new JsonResult<>(400, "fail", null);
        }
    }

    @GetMapping("/{id}")
    public JsonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
        final Payment result = paymentService.getPaymentById(id);
        log.info("-- 执行操作:");
        if (result != null) {
            return new JsonResult<>(200, "success", result);
        } else {
            return new JsonResult<>(400, "fail", null);
        }
    }

}

添加对应的 mapper xml 动态SQL文件,resources 文件夹下创建 mapper 文件夹,创建 PaymentDao.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zcq.springcloud.mapper.PaymentDao">

    <resultMap type="com.zcq.springcloud.entity.Payment" id="PaymentMap">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="serial" property="serial" jdbcType="VARCHAR"/>
    </resultMap>

    <insert id="create" parameterType="com.zcq.springcloud.entity.Payment" useGeneratedKeys="true" keyProperty="id">
        insert into payment(serial)
        values (#{serial});
    </insert>

    <select id="getPaymentById" parameterType="Long" resultMap="PaymentMap">
        select *
        from payment
        where id = #{id};
    </select>

</mapper>

resources 文件夹下创建 application.yml 配置文件

server:
  port: 8001

spring:
  application:
    name: cloud-payment-service #提交到注册中心的微服务名称
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/cloudtest?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    username: root
    password: 123456

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.zcq.springcloud.entity

创建 SpringBoot 启动类 PaymentMain8001

package com.zcq.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

创建 cloud-consumer-order8002 模块

创建模块步骤和上面创建 cloud-api-commons 模块的步骤一样,这个模块名设为 cloud-consumer-order8002,这个模块充当订单服务

pom 依赖

<properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator </artifactId>
        </dependency>

        <!--DevTools-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <!--Lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>

    </dependencies>

resources 文件夹下创建 application.yml 配置文件

server:
  port: 8002

spring:
  application:
    name: cloud-order-service #提交到注册中心的微服务名称

创建配置类

用于配置 RestTemplate

package com.zcq.springcloud.common;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;


/**
 * RestTemplate 是从 Spring3.0 开始支持的一个 HTTP 请求工具,它提供了常见的REST请求方案的模版,
 * 例如 GET 请求、POST 请求、PUT 请求、DELETE 请求以及一些通用的请求执行方法 exchange 以及 execute
 *
 * @author z
 * @link https://blog.csdn.net/jinjiniao1/article/details/100849237
 */
@Configuration
public class ApplicationConfig {

    @Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }

}

创建 controller

package com.zcq.springcloud.controller;

import com.zcq.springcloud.entity.Payment;
import com.zcq.springcloud.util.JsonResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;


/**
 * 这里直接写payment服务所在的服务器ip和端口,然后通过Spring的restTemplate直接发送post或者get请求,
 * 从而达到服务相互调用的目的。有人说,这样就已经达到目的了,服务在不同的服务器,也能相互调用,
 * 为什么还要学其他的呢?因为,这样调用不好做服务的协调与管理。需要一个中间商,来维护管理这些服务,做服务的注册与发现。
 * springCloud支持的中间商有很多,如开篇说的eureka、zookeeper和nacos。
 *
 * @author z
 */
@RestController
@RequestMapping("/consumer")
public class OrderController {

    // 上面那个服务的 URL 地址
    private static final String PAYMENT_URL = "http://localhost:8001";

    @Autowired
    private RestTemplate restTemplate;

    @SuppressWarnings("unchecked")
    @GetMapping("/payment/create")
    public JsonResult<Payment> create(Payment payment) {
        return restTemplate.postForObject(PAYMENT_URL + "/payment/create", payment, JsonResult.class);
    }

    @SuppressWarnings("unchecked")
    @GetMapping("/payment/get/{id}")
    public JsonResult<Payment> getPayment(@PathVariable("id") Long id) {
        return restTemplate.getForObject(PAYMENT_URL + "/payment/" + id, JsonResult.class);
    }

}

创建 SpringBoot 启动类 OrderMain8002

package com.zcq.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;


@SpringBootApplication
public class OrderMain8002 {

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

}

OK,两个测试的模块创建完成!下面开始进行单元测试

测试

我们可以直接在 cloud-consumer-order8002 模块的 test/java 文件夹下,创建一个 SpringBoot 测试类

package com.zcq.springcloud;


import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

@SpringBootTest
public class OrderMain8002Test {

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mvc;
    private MockHttpSession session;

    // 需要是 @BeforeEach 才生效,junit 5.x中,@Before主键被@BeforeEach所替代
    // > 引用自:https://blog.csdn.net/weixin_45612794/article/details/105450942
    @BeforeEach
    public void setupMockMvc() {
        System.out.println("-- Before > setupMockMvc()");
        //初始化MockMvc对象
        mvc = MockMvcBuilders.webAppContextSetup(wac).build();
        session = new MockHttpSession();
    }

    @Test
    public void test01() {
        // 创建一个订单
        String url = "/consumer/payment/create?id=1&serial=zhangsan";
        try {
            mvc.perform(MockMvcRequestBuilders.get(url)
                    .session(session))
                    .andExpect(MockMvcResultMatchers.status().isOk())
                    .andDo(MockMvcResultHandlers.print())
            ;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

运行我们刚刚创建的两个模块 PaymentMain8001、OrderMain8002,然后点击运行 OrderMain8002Test 中的 test01() 方法进行测试。点击之后输出执行结果


Java 项目中不存在跨域问题,因为只有跟浏览器有关的内容的时候才会有跨域问题,所以不要在意这个问题


Eureka 服务

创建 cloud-eureka-server7001 模块

pom 依赖

<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!-- eureka server -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

        <dependency>
            <groupId>org.example</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>

创建 SpringBoot 启动类 EurekaMain7001

package com.zcq.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaMain7001 {

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

}

创建 application.yml 配置

server:
  port: 7001

eureka:
  instance:
    hostname: localhost #eureka服务端的实例名称
  client:
    register-with-eureka: false # false表示不向服务中心注册自己
    fetch-registry: false # false表示自己就是注册中心,维护服务实例,而不去检索服务
    service-url:
      # 向eureka查询和注册服务都通过这个地址
      defaultZone: https://{eureka.instance.hostname}:{server.port}/eureka/

然后启动这个项目,访问localhost:7001,可以看到如下画面:

这仅仅是将注册中心eureka的server搭建好了,但是我们并没有把服务注册进去,接下来我们把payment8001注册进eureka中。

将payment8001注册进eureka中:

修改 cloud-provider-payment8001 模块的 pom.xml,添加eureka client依赖

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>2.2.4.RELEASE</version>
        </dependency>

并添加这个模块的 application.yml 的 eureka 配置

eureka:
  client:
    register-with-eureka: true # 将自己注册进eureka
    fetch-registry: true # 从eureka抓取自己的注册信息
    service-url:
      defaultZone: http://localhost:7001/eureka   #eureka server所在的主机和端口

对 PaymentMain8001 类添加 @EnableEurekaClient 注解

这样就行了,启动payment8001,再刷新localhost:7001,就会发现eureka中有 payment8001 这个服务了,如下图:

将 cloud-consumer-order8002 注册进 eureka:

修改 cloud-consumer-order8002 模块的 pom.xml 依赖,添加 Eureka 的依赖

<!-- Eureka -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>2.2.4.RELEASE</version>
        </dependency>

修改这个模块的 application.yml 文件,添加 eureka 配置

eureka:
  client:
    register-with-eureka: true # 将自己注册进eureka,不注册就false
    fetch-registry: true # 从eureka抓取自己的注册信息
    service-url:
      defaultZone: http://localhost:7001/eureka/ #eureka server所在的主机和端口

OrderMain8002 启动类添加 @EnableEurekaClient 注解

把 OrderController 类中的 PAYMENT_URL 变量中的链接修改为 http://CLOUD-PAYMENT-SERVICE

然后启动 OrderMain8002,刷新一下 http://localhost:7001/ 即可看到已经添加了服务

给 ApplicationConfig 类中的 getRestTemplate() 方法上添加 @LoadBalanced 注解,不加会报类似如下的错误

I/O error on GET request for “http://CLOUD-PAYMENT-SERVICE/payment/get/31”: CLOUD-PAYMENT-SERVICE; nested exception is java.net.UnknownHostException: CLOUD-PAYMENT-SERVICE] with root cause

然后访问 http://localhost:8002/consumer/payment/get/1 看有没有获取到数据

eureka集群

eureka集群原理就是互相注册,对外表现为一个整体。上面新建了一个cloud-eureka-server7001,端口是7001,现在再建一个名为cloud-eureka-server7002的module,端口为7002。

发表评论