三天学完微服务其二

Nacos注册中心

启动Nacos

配置更新步骤

是修改nacos中的配置后,微服务中无需重启即可让配置生效,也就是**配置热更新**。

方式一

在@Value注入的变量所在类上添加注解@RefreshScope

方式二

使用@ConfigurationProperties注解代替@Value注解。

Nacos快速入门

引入依赖

后端输入账号Miami访问 点击加号新建配置

点击加号新建配置

在父工程的pom文件中引入SpringCloudAlibaba依赖

然后在user-service和order-service中引入nacos-discovery依赖

复制代码
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

Feign远程调用

引入依赖

java 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

添加注解

复制代码
@EnableFeignClients

编写Feign的客户端

在order-service中新建一个接口,内容如下:

复制代码
package cn.itcast.order.client;
​
import cn.itcast.order.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
​
@FeignClient("userservice")
public interface UserClient {
    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
}

这个客户端主要是基于SpringMVC的注解来声明远程调用的信息,比如:

  • 服务名称:userservice

  • 请求方式:GET

  • 请求路径:/user/{id}

  • 请求参数:Long id

  • 返回值类型:User

总结

使用Feign的步骤:

① 引入依赖

② 添加@EnableFeignClients注解

③ 编写FeignClient接口

④ 使用FeignClient中定义的方法代替RestTemplate

Gateway服务网关

Spring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等响应式编程和事件流技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。

Gateway网关是我们服务的守门神,所有微服务的统一入口。

网关的核心功能特性:

  • 请求路由

  • 权限控制

  • 限流

权限控制

网关作为微服务入口,需要校验用户是是否有请求资格,如果没有则进行拦截。

路由和负载均衡

一切请求都必须先经过gateway,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务,这个过程叫做路由。当然路由的目标服务有多个时,还需要做负载均衡。

限流:当请求流量过高时,在网关中按照下流的微服务能够接受的速度来放行请求,避免服务压力过大。

在SpringCloud中网关的实现包括两种:

  • gateway

  • zuul

Zuul是基于Servlet的实现,属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。

断言工厂

断言工厂是一种用于创建和管理**断言(Assertion)**的设计模式或工具。断言是软件测试中的重要技术,用于验证程序在运行过程中是否符合预期的行为和结果。断言工厂主要负责动态地生成不同类型的断言,简化测试代码的编写,提高代码的复用性和可维护性。

断言工厂(Assertion Factory)概述

断言工厂是一种用于创建和管理**断言(Assertion)**的设计模式或工具。断言是软件测试中的重


1. 为什么需要断言工厂?

在测试过程中,测试用例会包含大量的断言,验证程序的输入输出、逻辑和状态是否正确。随着测试规模的增大:

  • 重复代码多:不同测试用例中的断言代码可能非常相似。
  • 灵活性不足:直接在测试代码中硬编码断言逻辑,不便于扩展和修改。
  • 代码复杂性高:断言逻辑嵌套,测试代码难以阅读和维护。

断言工厂通过将断言逻辑抽象成统一的接口或工具,按需动态生成断言,可以有效解决这些问题。


2. 断言工厂的应用场景

  • 单元测试:在测试框架中,如 JUnit、TestNG 等,断言工厂可以封装常用的断言逻辑。
  • 集成测试:动态生成复杂的断言,验证系统级的行为和数据一致性。
  • API 测试:对 API 的返回值(如 JSON)进行动态的结构化断言。
  • 自动化测试:结合断言工厂与测试工具(如 Selenium、Appium),验证页面元素的状态或行为。
  • 自定义验证规则:当系统需要复杂或定制化的断言逻辑时,断言工厂可以提供可扩展的接口。

3. 断言工厂的核心思想

断言工厂的核心是将断言逻辑进行抽象和封装,动态创建断言实例,并提供一致的调用方式。主要包括:

  1. 统一接口:定义不同类型的断言行为。
  2. 工厂类:根据断言类型或参数动态生成断言对象。
  3. 参数化支持:通过输入参数决定生成何种断言。
  4. 高可扩展性:支持用户扩展自定义断言类型。

4. 断言工厂的实现步骤

以下是一个简单的断言工厂实现思路,基于 Java 语言和常见断言逻辑。

(1) 定义断言接口
public interface Assertion {
    void assertValue(Object actual, Object expected);
}
(2) 实现具体的断言逻辑
// 判断两个对象是否相等
public class EqualsAssertion implements Assertion {
    @Override
    public void assertValue(Object actual, Object expected) {
        if (!actual.equals(expected)) {
            throw new AssertionError("Expected: " + expected + ", but got: " + actual);
        }
    }
}

// 判断对象是否为 null
public class NullAssertion implements Assertion {
    @Override
    public void assertValue(Object actual, Object expected) {
        if (actual != null) {
            throw new AssertionError("Expected null, but got: " + actual);
        }
    }
}

// 判断数值是否在范围内
public class RangeAssertion implements Assertion {
    @Override
    public void assertValue(Object actual, Object expected) {
        if (!(actual instanceof Integer) || !(expected instanceof int[])) {
            throw new IllegalArgumentException("Invalid types for range assertion");
        }
        int actualValue = (Integer) actual;
        int[] range = (int[]) expected;
        if (actualValue < range[0] || actualValue > range[1]) {
            throw new AssertionError("Value: " + actualValue + " is not in range: [" + range[0] + ", " + range[1] + "]");
        }
    }
}
(3) 创建断言工厂类
public class AssertionFactory {

    public static Assertion getAssertion(String type) {
        switch (type) {
            case "equals":
                return new EqualsAssertion();
            case "null":
                return new NullAssertion();
            case "range":
                return new RangeAssertion();
            default:
                throw new IllegalArgumentException("Unsupported assertion type: " + type);
        }
    }
}
(4) 使用断言工厂
public class AssertionTest {
    public static void main(String[] args) {
        // 获取 "equals" 断言实例
        Assertion equalsAssertion = AssertionFactory.getAssertion("equals");
        equalsAssertion.assertValue(5, 5); // 测试通过
        // equalsAssertion.assertValue(5, 10); // 测试失败

        // 获取 "null" 断言实例
        Assertion nullAssertion = AssertionFactory.getAssertion("null");
        nullAssertion.assertValue(null, null); // 测试通过
        // nullAssertion.assertValue("not null", null); // 测试失败

        // 获取 "range" 断言实例
        Assertion rangeAssertion = AssertionFactory.getAssertion("range");
        rangeAssertion.assertValue(15, new int[]{10, 20}); // 测试通过
        // rangeAssertion.assertValue(25, new int[]{10, 20}); // 测试失败
    }
}

5. 优点与扩展性

优点

  1. 复用性高:断言工厂将断言逻辑封装后,可在不同测试用例中复用。
  2. 扩展性强:可以通过新增断言实现类快速扩展断言类型。
  3. 可维护性好:断言逻辑与测试用例分离,修改断言逻辑不影响测试用例代码。

扩展方向

  • 参数化断言:支持复杂参数配置,如阈值、正则表达式等。
  • 多语言支持:将断言工厂的实现推广到其他语言(如 Python、JavaScript)。
  • 集成测试框架:将断言工厂与测试框架(如 JUnit、TestNG)结合,实现统一的测试工具链。
  • 日志记录:断言失败时自动记录详细日志,便于问题排查。

6. 总结

断言工厂是一种简化和优化测试断言的设计方法,可以将常用的断言逻辑统一封装,减少测试代码冗余,提高可读性和维护性。在实际项目中,根据测试需求,灵活扩展断言工厂的功能,可以大幅提升测试的效率和质量。

路由过滤器

路由过滤器概述

路由过滤器是网关或服务路由过程中执行的一种拦截机制,用于对进入或流经的请求和响应进行预处理、后处理、验证、修改等操作。它常用于微服务架构中,通过统一网关来集中处理服务的路由和请求。

在 Spring Cloud Gateway 中,路由过滤器主要分为:

  1. 全局过滤器(Global Filter):对所有请求生效。
  2. 路由过滤器(Route Filter):只对特定路由生效。

1. 路由过滤器的作用

  • 请求验证与认证:过滤器可以检查用户的认证信息(如 Token),对非法请求进行拦截。
  • 动态路由:根据业务规则动态地修改路由目标地址。
  • 日志记录:记录请求或响应的日志。
  • 流量控制:限制请求的流量,比如限流、熔断等。
  • 修改请求或响应:对请求头、请求体、响应头、响应体进行加工。
  • 统一处理异常:拦截异常并返回标准的错误信息。

2. 路由过滤器的分类

在 Spring Cloud Gateway 中,过滤器主要有以下两类:

(1) GatewayFilter(路由过滤器)
  • 对指定的路由生效。
  • 在配置文件或代码中为某个路由配置过滤器。
(2) GlobalFilter(全局过滤器)
  • 对所有的路由生效。
  • 通常用于全局的请求处理逻辑。

3. 路由过滤器的使用方式

(1) 配置式路由过滤器

通过 application.yml 配置文件,直接为某个路由指定过滤器。

示例:

spring:
  cloud:
    gateway:
      routes:
        - id: user_service_route
          uri: lb://user-service
          predicates:
            - Path=/user/**
          filters:
            - AddRequestHeader=X-Request-Id, 12345  # 添加请求头
            - StripPrefix=1                        # 移除路径前缀
            - Hystrix=default                      # 配置熔断

常用过滤器说明

  • AddRequestHeader:向请求中添加头部信息。
  • StripPrefix:去除请求路径的前缀。
  • Hystrix:配置熔断器,避免服务不可用时影响整个系统。
  • RewritePath:重写请求路径。
  • SetResponseHeader:向响应中添加头部信息。
  • RateLimiter:限流操作。
(2) 编程式路由过滤器

通过代码动态添加过滤器。

示例:

@Configuration
public class GatewayConfig {

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("user_service_route", r -> r.path("/user/**")
                        .filters(f -> f.addRequestHeader("X-Request-Id", "12345")
                                       .stripPrefix(1))
                        .uri("lb://user-service"))
                .build();
    }
}

4. 全局过滤器

全局过滤器对所有路由都生效,通常用来实现全局的逻辑,如日志、认证等。

(1) 实现 GlobalFilter 接口

全局过滤器需要实现 GlobalFilter 接口和 Ordered 接口(定义过滤器的优先级)。

示例:

import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("执行全局过滤器:请求路径 = " + exchange.getRequest().getPath());
        
        // 在这里可以对请求做预处理
        exchange.getRequest().mutate().header("X-Global-Filter", "Active").build();

        // 调用下一个过滤器
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            // 在这里可以对响应做后处理
            System.out.println("全局过滤器执行完成");
        }));
    }

    @Override
    public int getOrder() {
        return 0; // 数值越小,优先级越高
    }
}
(2) 注册全局过滤器

将全局过滤器标记为 @Component,Spring Boot 会自动扫描并注册。


5. 自定义路由过滤器

如果默认的过滤器不能满足需求,可以通过实现 GatewayFilter 接口来自定义路由过滤器。

示例:

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;

@Component
public class CustomFilterFactory extends AbstractGatewayFilterFactory<CustomFilterFactory.Config> {

    public CustomFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            System.out.println("执行自定义过滤器:参数 = " + config.getName());
            
            // 过滤器逻辑
            exchange.getRequest().mutate().header("X-Custom-Filter", config.getName()).build();
            
            return chain.filter(exchange);
        };
    }

    public static class Config {
        private String name;

        // Getter and Setter
        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}
配置自定义过滤器:

application.yml 中引用自定义过滤器:

spring:
  cloud:
    gateway:
      routes:
        - id: custom_filter_route
          uri: lb://order-service
          predicates:
            - Path=/order/**
          filters:
            - name=CustomFilter
              args:
                name: "TestFilter"

6. 路由过滤器的执行顺序

过滤器的执行顺序由其优先级决定:

  1. GatewayFilter:先执行局部的路由过滤器。
  2. GlobalFilter:后执行全局过滤器。

默认情况下:

  • GlobalFilter 优先级由 Ordered 接口的 getOrder() 方法控制。
  • GatewayFilter 的顺序在配置文件中按定义的顺序执行。

7. 常见问题和注意事项

  1. 异步处理 :Spring Cloud Gateway 基于 Reactor 框架,过滤器必须是非阻塞的,返回 Mono<Void>
  2. 路径冲突:多个路由可能匹配同一个请求,需注意路由优先级。
  3. 错误处理:可以通过全局异常处理器捕获和处理过滤器中的异常。
  4. 性能优化:对耗时的操作(如日志写入)需异步处理,避免阻塞网关。

8. 总结

路由过滤器是 Spring Cloud Gateway 中的核心功能,用于增强网关的灵活性和控制能力。通过路由过滤器,可以轻松实现请求预处理、响应后处理、动态路由、认证授权、日志记录等功能。在实际项目中,根据需求选择合适的过滤器类型,并结合全局过滤器与路由过滤器的特性,能够极大地提高微服务架构的可维护性和扩展性。

Spring提供了31种不同的路由过滤器工厂。例如:

名称 说明
AddRequestHeader 给当前请求添加一个请求头
RemoveRequestHeader 移除请求中的一个请求头
AddResponseHeader 给响应结果中添加一个响应头
RemoveResponseHeader 从响应结果中移除有一个响应头
RequestRateLimiter 限制请求的流量

Docker

Docker 是一个开源的容器化平台,主要用于快速构建、测试和部署应用程序。它通过容器技术将应用程序及其所有依赖打包在一起,能够确保应用程序在任何环境中都能一致运行。


1. Docker 的核心概念

1.1. 镜像(Image)
  • 定义:镜像是一个只读的模板,包含应用程序和运行时环境。

  • 作用:用来创建 Docker 容器。

  • 特点:轻量级、便携,支持分层构建。

  • 命令

    docker pull <image-name>       # 拉取镜像
    docker images                 # 查看本地镜像
    docker rmi <image-id>         # 删除镜像
    
1.2. 容器(Container)
  • 定义:容器是镜像的运行实例,它是一个轻量、独立的可执行软件环境。

  • 作用:隔离应用程序和运行环境,支持应用的跨平台部署。

  • 特点:启动快、占用资源少。

  • 命令

    docker run <image-name>                  # 启动容器
    docker ps                                # 查看运行的容器
    docker ps -a                             # 查看所有容器
    docker stop <container-id>               # 停止容器
    docker rm <container-id>                 # 删除容器
    
1.3. Dockerfile
  • 定义:Dockerfile 是用来定义镜像的文件,包含一组指令,用于描述如何构建镜像。

  • 示例

    FROM openjdk:8-jdk-alpine
    COPY app.jar /app.jar
    ENTRYPOINT ["java", "-jar", "/app.jar"]
    
  • 构建镜像命令

    docker build -t <image-name>:<tag> .
    
1.4. 仓库(Repository)
  • 定义:Docker 仓库用来存储镜像,分为本地仓库和远程仓库(如 Docker Hub)。

  • 相关命令

    docker login                   # 登录 Docker Hub
    docker tag <image> <repo-name> # 给镜像打标签
    docker push <repo-name>        # 推送镜像到远程仓库
    docker pull <repo-name>        # 从仓库拉取镜像
    

2. Docker 的主要优势

  1. 一致性:开发、测试和生产环境一致,避免"在我电脑上没问题"的现象。
  2. 轻量级:相比虚拟机更轻量,一个容器只需要几 MB 的存储空间。
  3. 快速启动:容器的启动时间通常以秒为单位。
  4. 高效资源利用:多个容器共享操作系统内核,降低资源开销。
  5. 易于扩展:通过 Docker Compose 和 Docker Swarm 快速实现多容器编排。
  6. 便携性:容器镜像可以轻松迁移到任何支持 Docker 的平台。

3. Docker 的基本操作

3.1. 镜像管理
docker pull <image-name>                 # 拉取镜像
docker images                            # 查看本地镜像列表
docker rmi <image-id>                    # 删除镜像
docker tag <image> <new-repo-name:tag>   # 为镜像打标签
3.2. 容器管理
docker run -d --name <container-name> <image-name>    # 后台运行容器
docker ps                                            # 查看运行中的容器
docker exec -it <container-id> bash                 # 进入容器
docker stop <container-id>                          # 停止容器
docker rm <container-id>                            # 删除容器
3.3. 数据管理
  • 数据卷(Volume) : 数据卷用于持久化容器中的数据,支持容器间数据共享。

    docker run -v /path/on/host:/path/in/container <image-name>
    
  • 数据卷相关命令

    docker volume create <volume-name>    # 创建数据卷
    docker volume ls                      # 查看所有数据卷
    docker volume rm <volume-name>        # 删除数据卷
    
3.4. 网络管理
  • 网络模式

    • bridge(默认):桥接网络,容器通过虚拟网卡与主机通信。
    • host:容器使用主机的网络栈。
    • none:容器没有网络。
  • 网络操作命令

    docker network create <network-name>        # 创建网络
    docker network ls                           # 查看网络
    docker network connect <network> <container> # 将容器连接到网络
    

4. Docker Compose

Docker Compose 是一个用于定义和运行多容器应用的工具。

4.1. 基本操作
  1. 创建 docker-compose.yml 文件:

    version: '3.8'
    services:
      web:
        image: nginx
        ports:
          - "8080:80"
      app:
        image: my-app
        depends_on:
          - web
    
  2. 启动服务:

    docker-compose up -d
    
  3. 停止服务:

    docker-compose down
    

5. Docker 的实际应用场景

  1. 持续集成和交付(CI/CD)

    • 构建、测试和部署在相同的 Docker 容器中运行,减少环境差异。
    • 与 Jenkins、GitLab CI 等工具集成。
  2. 微服务架构

    • 每个微服务运行在独立的容器中,独立开发、测试和部署。
    • 使用 Docker Compose 或 Kubernetes 进行服务编排和管理。
  3. 环境隔离

    • 为不同项目创建独立的运行环境。
    • 容器之间互不干扰。
  4. 云原生应用

    • 容器化应用部署在云环境中,快速扩展和迁移。
    • 与 Kubernetes 等容器编排工具结合使用。

6. Docker 常见问题

  1. 容器无法启动

    • 检查镜像是否正确。
    • 查看容器日志:docker logs <container-id>
  2. 网络连接问题

    • 确认容器是否连接到正确的网络。
    • 检查防火墙或主机端口映射。
  3. 容器数据丢失

    • 数据存储应使用数据卷或挂载主机目录。
  4. 镜像体积过大

    • 优化 Dockerfile,如减少层数、清理缓存。
  5. 端口冲突

    • 启动容器时,确保主机端口未被占用。

7.什么是数据卷

**数据卷(volume)**是一个虚拟目录,指向宿主机文件系统中的某个目录。

通过给 Nginx 容器挂载数据卷,可以方便地管理和持久化 Nginx 的配置文件、静态资源以及日志。


8. 总结

Docker 是现代软件开发的重要工具,通过容器化技术提供了快速、轻量、便携的环境隔离方案。它在开发、测试和生产部署中广泛应用,尤其适合微服务架构和云原生应用。结合 Docker Compose 和 Kubernetes,可以进一步增强服务的管理和扩展能力。

容器保护三个状态:
  • 运行:进程正常运行

  • 暂停:进程暂停,CPU不再运行,并不释放内存

  • 停止:进程终止,回收进程占用的内存、CPU等资源

其中:
  • docker run:创建并运行一个容器,处于运行状态

  • docker pause:让一个运行的容器暂停

  • docker unpause:让一个容器从暂停状态恢复运行

  • docker stop:停止一个运行的容器

  • docker start:让一个停止的容器再次运行

  • docker rm:删除一个容器

练习

需求:去DockerHub搜索并拉取一个Redis镜像

目标:

1)去DockerHub搜索Redis镜像

2)查看Redis镜像的名称和版本

3)利用docker pull命令拉取镜像

4)利用docker save命令将 redis:latest打包为一个redis.tar包

5)利用docker rmi 删除本地的redis:latest

6)利用docker load 重新加载 redis.tar文件

1. 拉取 Redis 镜像

docker pull redis:latest

2. 查看镜像

docker images

3. 导出 Redis 镜像为 redis.tar

docker save -o redis.tar redis:latest

4. 删除本地 Redis 镜像

docker rmi redis:latest

5. 重新加载 Redis 镜像

docker load -i redis.tar

6. 再次查看镜像

docker images

docker案例-进入容器,修改文件

java 复制代码
 docker run -d --name mn -p 8080:80 nginx

PS C:\Users\l\Desktop\ppp> docker run -d --name mn -p 8080:80 nginx

进入容器
java 复制代码
docker exec -it mn bash
进入nginx的HTML所在目录 /usr/share/nginx/html
html 复制代码
cd /usr/share/nginx/html

访问本地浏览器

由于本地终端输入不了中文用本地终端docker操作

点击file
根据/usr/share/nginx/html路径找到html.index
修改index.html文件

修改代码,保存,重启
html 复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>早发白帝城</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            text-align: center;
            margin: 50px;
            background-color: #f5f5f5;
        }
        h1 {
            color: #333;
            font-size: 2em;
        }
        p {
            font-size: 1.5em;
            color: #666;
            margin: 10px 0;
        }
    </style>
</head>
<body>
    <h1>早发白帝城</h1>
    <p>朝辞白帝彩云间,</p>
    <p>千里江陵一日还。</p>
    <p>两岸猿声啼不住,</p>
    <p>轻舟已过万重山。</p>
</body>
</html>

访问

html 复制代码
http://localhost:8080/
相关推荐
心灵Haven3 分钟前
1_安装JDK和Hadoop
java·开发语言·hadoop
web1368856587110 分钟前
PHP For 循环
android·java·php
loyd325 分钟前
【数据分析】5 设计不同业务分析框架
java·网络·数据分析
m0_7482451731 分钟前
Spring Boot项目开发常见问题及解决方案(上)
java·spring boot·后端
今天的接口写完了吗?31 分钟前
Spring Boot操作MaxComputer(保姆级教程)
java·spring boot·后端
金州小铁匠1 小时前
基于EasyExcel封装的Excel工具类,支持高效导出和读取操作
java·spring·excel
IIIIIIlllii1 小时前
java练习(43)
java·开发语言
xxxxxmy1 小时前
Spring MVC 程序开发(1)
java·spring·mvc
不平衡的叉叉树1 小时前
使用优化版的编辑距离算法替代ES默认的评分算法
java·算法
没什么技术1 小时前
Spock框架:让单元测试更优雅的高效武器
java·spock