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. 断言工厂的核心思想
断言工厂的核心是将断言逻辑进行抽象和封装,动态创建断言实例,并提供一致的调用方式。主要包括:
- 统一接口:定义不同类型的断言行为。
- 工厂类:根据断言类型或参数动态生成断言对象。
- 参数化支持:通过输入参数决定生成何种断言。
- 高可扩展性:支持用户扩展自定义断言类型。
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. 优点与扩展性
优点
- 复用性高:断言工厂将断言逻辑封装后,可在不同测试用例中复用。
- 扩展性强:可以通过新增断言实现类快速扩展断言类型。
- 可维护性好:断言逻辑与测试用例分离,修改断言逻辑不影响测试用例代码。
扩展方向
- 参数化断言:支持复杂参数配置,如阈值、正则表达式等。
- 多语言支持:将断言工厂的实现推广到其他语言(如 Python、JavaScript)。
- 集成测试框架:将断言工厂与测试框架(如 JUnit、TestNG)结合,实现统一的测试工具链。
- 日志记录:断言失败时自动记录详细日志,便于问题排查。
6. 总结
断言工厂是一种简化和优化测试断言的设计方法,可以将常用的断言逻辑统一封装,减少测试代码冗余,提高可读性和维护性。在实际项目中,根据测试需求,灵活扩展断言工厂的功能,可以大幅提升测试的效率和质量。
路由过滤器
路由过滤器概述
路由过滤器是网关或服务路由过程中执行的一种拦截机制,用于对进入或流经的请求和响应进行预处理、后处理、验证、修改等操作。它常用于微服务架构中,通过统一网关来集中处理服务的路由和请求。
在 Spring Cloud Gateway 中,路由过滤器主要分为:
- 全局过滤器(Global Filter):对所有请求生效。
- 路由过滤器(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. 路由过滤器的执行顺序
过滤器的执行顺序由其优先级决定:
- GatewayFilter:先执行局部的路由过滤器。
- GlobalFilter:后执行全局过滤器。
默认情况下:
- GlobalFilter 优先级由
Ordered
接口的getOrder()
方法控制。 - GatewayFilter 的顺序在配置文件中按定义的顺序执行。
7. 常见问题和注意事项
- 异步处理 :Spring Cloud Gateway 基于 Reactor 框架,过滤器必须是非阻塞的,返回
Mono<Void>
。 - 路径冲突:多个路由可能匹配同一个请求,需注意路由优先级。
- 错误处理:可以通过全局异常处理器捕获和处理过滤器中的异常。
- 性能优化:对耗时的操作(如日志写入)需异步处理,避免阻塞网关。
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 的主要优势
- 一致性:开发、测试和生产环境一致,避免"在我电脑上没问题"的现象。
- 轻量级:相比虚拟机更轻量,一个容器只需要几 MB 的存储空间。
- 快速启动:容器的启动时间通常以秒为单位。
- 高效资源利用:多个容器共享操作系统内核,降低资源开销。
- 易于扩展:通过 Docker Compose 和 Docker Swarm 快速实现多容器编排。
- 便携性:容器镜像可以轻松迁移到任何支持 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. 基本操作
-
创建
docker-compose.yml
文件:version: '3.8' services: web: image: nginx ports: - "8080:80" app: image: my-app depends_on: - web
-
启动服务:
docker-compose up -d
-
停止服务:
docker-compose down
5. Docker 的实际应用场景
-
持续集成和交付(CI/CD):
- 构建、测试和部署在相同的 Docker 容器中运行,减少环境差异。
- 与 Jenkins、GitLab CI 等工具集成。
-
微服务架构:
- 每个微服务运行在独立的容器中,独立开发、测试和部署。
- 使用 Docker Compose 或 Kubernetes 进行服务编排和管理。
-
环境隔离:
- 为不同项目创建独立的运行环境。
- 容器之间互不干扰。
-
云原生应用:
- 容器化应用部署在云环境中,快速扩展和迁移。
- 与 Kubernetes 等容器编排工具结合使用。
6. Docker 常见问题
-
容器无法启动
- 检查镜像是否正确。
- 查看容器日志:
docker logs <container-id>
。
-
网络连接问题
- 确认容器是否连接到正确的网络。
- 检查防火墙或主机端口映射。
-
容器数据丢失
- 数据存储应使用数据卷或挂载主机目录。
-
镜像体积过大
- 优化 Dockerfile,如减少层数、清理缓存。
-
端口冲突
- 启动容器时,确保主机端口未被占用。
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>
访问
htmlhttp://localhost:8080/