GatewayFilter 与 GlobalFilter 全操作

GatewayFilter 与 GlobalFilter 📖 前置阅读:本文假设读者已掌握 Route 和 Predicate 的配置方式。如果还不熟悉,建议先阅读 Spring Cloud Gateway 核心概念与快速上手 和 Predicate 与路由规则全解。 一、⚡ 路由转发了——但在转发前后你还想做很多事 Predicate 决定了请求走哪条路——但路上你还要做很多事: 请求 → 网关 → 后端 —— 转发过程中: ① 把 /api/users/1 转成 /1(去掉前缀) ② 自动加上 Header(X-Request-Id、X-Source) ③ 限制每个 IP 每秒只能调 10 次 ④ 请求失败时自动重试 3 次 ⑤ 把用户信息写入 Header(后端不用自己解析 JWT) ⑥ 后端 3 次失败后熔断——直接降级返回 这些全都靠 Filter 实现。Gateway 的 Filter 分两种——GatewayFilter(针对特定路由)和 GlobalFilter(针对所有路由)。 二、🧩 Filter 的执行模型——Pre Filter 和 Post Filter 一个 Filter 可以在两个阶段做事: public class DemoFilter implements GatewayFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // ===== Pre Filter:转发到后端之前执行 ===== System.out.println("① 请求进来了——做鉴权、限流、加 Header"); return chain.filter(exchange) // ← 转发到后端(或者说交给下一个 Filter) .then(Mono.fromRunnable(() -> { // ===== Post Filter:后端返回后执行 ===== System.out.println("⑤ 后端响应了——做日志、修改响应体"); })); } } 时间线: ① Pre Filter(鉴权) ② 下一个 Filter 的 Pre(限流) ③ 转发到后端 ④ 后端返回响应 ⑤ 当前 Filter 的 Post(修改响应) ⑥ 上一个 Filter 的 Post(记录日志) 这个链式结构是 WebFlux 的 Mono.then() 实现的——不是阻塞等待,而是注册回调。 ...

十二月 4, 2022 · 7 分钟 · 1281 字 · yaomingye

Predicate 与路由规则全解

Predicate 与路由规则 📖 前置阅读:本文假设读者已理解 Route/Predicate/Filter 三要素和 Gateway 的基本用法。如果还不熟悉,建议先阅读 Spring Cloud Gateway 核心概念与快速上手。 一、⚡ Path 匹配太简单了——直到你遇到了这些需求 上一篇用 Path=/api/users/** 把请求按路径转发——基本路由够用了。但真实场景远不止于此: 需求 1:灰度发布——10% 的流量走 v2 版本,90% 走 v1 需求 2:内网用户走内网地址,外网用户走外网地址 需求 3:大促活动只在 12 月 1 日到 12 月 12 日生效——过期自动关闭 需求 4:带特定 Header(X-Tenant=alibaba)的请求路由到专门的集群 需求 5:GET 请求走缓存集群,POST/PUT/DELETE 走主集群 这些需求全都靠 Predicate 实现。Predicate 不只有 Path——Spring Cloud Gateway 内置了 12 种 Predicate Factory。 二、📖 记住 Predicate 的命名规则 Spring Cloud Gateway 的 Predicate 在配置时有一个命名转换: Java 类名: CookieRoutePredicateFactory ↓ 去掉 "RoutePredicateFactory" 后缀 配置名: Cookie=xxx 所有 Predicate 都是这个规则:HeaderRoutePredicateFactory → Header,QueryRoutePredicateFactory → Query。知道这个后——看到一个配置名就能找到对应的源码。 ...

十二月 3, 2022 · 6 分钟 · 1226 字 · yaomingye

Spring Cloud Gateway 核心概念与快速上手

搞懂网关三要素:Route、Predicate、Filter 一、⚡ 微服务上线后——前端疯了 你有 3 个微服务——用户服务在 8081、订单服务在 8082、商品服务在 8083。后端调得挺好——gRPC/Dubbo/REST 各种 RPC 全上了。 然后前端来找你:“我要调三个不同的端口?那用户登录后 Token 怎么统一校验?跨域怎么配?万一商品服务挂了——我是直接给用户看 500 错误还是给个降级提示?" 这些问题都不是前端该解决的——它们应该在一个统一的入口网关里处理: 没有网关: 浏览器 → 直接调 8081(用户服务) 浏览器 → 直接调 8082(订单服务) 浏览器 → 直接调 8083(商品服务) → 三个端口、三次鉴权、三次跨域——前端疯了 有了网关: 浏览器 → 网关 :8080 → /api/users/** 转给 8081 → /api/orders/** 转给 8082 → /api/products/** 转给 8083 → 一个端口、一次鉴权、一处跨域——前端只认识网关 API 网关就是系统的"大门”——所有外部请求都从这一个门进来,由它统一做鉴权、限流、路由、日志、降级。后端服务只关心业务逻辑——不管安全和流量控制。 二、🤔 选型:为什么是 Spring Cloud Gateway? Java 生态中做网关有一堆选择——先搞清楚 Spring Cloud Gateway 在其中的位置: 网关 底层 编程模型 适用场景 Spring Cloud Gateway WebFlux + Netty 响应式——非阻塞 I/O Spring 微服务体系——首选 Zuul 1.x Tomcat + Servlet 阻塞——一个请求一个线程 已停止维护——不推荐新项目 Zuul 2.x Netty 异步——但生态不成熟 几乎没人用 Nginx + Lua (OpenResty) Nginx C 内核 同步——Lua 脚本 性能极高——但开发门槛高 Kong OpenResty Lua + 插件 API 管理平台——适合需要 API 管理的场景 Spring Cloud Gateway 碾压 Zuul 1.x 的根本原因是"非阻塞": ...

十二月 2, 2022 · 5 分钟 · 1008 字 · yaomingye

gRPC Gateway 与生产环境部署

gRPC-Gateway:让前端也能调 gRPC 📖 前置阅读:本文假设读者已完成 gRPC 服务的开发和微服务拆分。如果还不熟悉,建议先阅读 SpringBoot gRPC 全操作指南 和 微服务拆分实战:以 proto 为契约。 一、⚡ gRPC 最大的问题:浏览器不支持 你花了两周把微服务之间的通信全换成了 gRPC——性能翻了 3 倍,Protobuf 二进制传输省了 60% 带宽。看起来很完美。 然后前端同事找来了:“你的接口怎么调?Postman 发 HTTP 请求连不上。" 这就是 gRPC 最大的现实问题——gRPC 基于 HTTP/2,浏览器不直接支持 gRPC 协议。你在浏览器里 fetch('http://localhost:9090/...') 是调不通的——浏览器不会说 gRPC。 解决方案是gRPC-Gateway——在 gRPC 服务前面放一个网关,对外提供标准的 HTTP RESTful JSON 接口,对内转成 gRPC 调用: 浏览器/移动端/curl(HTTP/1.1 JSON) ↓ [gRPC-Gateway / Envoy / grpc-web] ← 协议转换层 ↓ gRPC(HTTP/2 Protobuf) [gRPC Server] 二、🔌 方案选择:三种网关方案 方案 原理 适用场景 复杂度 gRPC-Gateway 从 proto 自动生成反向代理代码——HTTP JSON ↔ gRPC 转换 gRPC 服务需要同时支持 HTTP JSON 和 gRPC 调用方 中 Envoy gRPC-JSON Transcoder Envoy 代理层做协议转换——不需要修改代码 有服务网格——统一的入口网关 中 grpc-web + Envoy 浏览器用 grpc-web 协议(HTTP/1.1),Envoy 转成 gRPC 前端直接在浏览器中调 gRPC(不需要 REST 包装) 高 本文重点讲方案一 gRPC-Gateway——它最直接、不需要额外的代理基础设施、和 SpringBoot 整合最简单。 ...

十二月 1, 2022 · 10 分钟 · 2072 字 · yaomingye

微服务拆分实战:以 proto 为契约

微服务拆分实战 📖 前置阅读:本文假设读者已掌握 Protobuf 语法和 SpringBoot gRPC 的基本用法。如果还不熟悉,建议先阅读 Protobuf 语法精讲与 gRPC 概念 和 SpringBoot gRPC 全操作指南。 一、⚡ 微服务拆分后的第一个难题 你把这个巨大的 SpringBoot 单体应用拆成了三个微服务——订单服务、用户服务、商品服务。拆得很干净——各自有自己的数据库、各自独立部署。 然后你发现了一个问题:订单服务在创建订单时需要查用户信息、扣商品库存——这三个服务之间怎么通信? 创建订单的流程: OrderService → 查用户是否存在 → UserService OrderService → 查商品价格 + 库存 → ProductService OrderService → 创建订单 → 自己的数据库 REST 当然能做——但这里有一个更关键的问题:你怎么保证 OrderService 调 UserService 的参数格式不出错? UserService 说它接收 GET /users/{id} 返回 {"userId": 1, "userName": "张三"}。OrderService 的开发者在代码里写了 restTemplate.getForObject("/users/" + userId, UserDTO.class) —— 但 UserDTO 的字段名是 user_name 还是 userName?谁说了算? proto 文件就是"谁说了算"的答案——它就是服务之间的契约。 二、🧬 以 proto 为契约:核心思想 没有契约(REST 口头约定): OrderService 开发者问 UserService 开发者:"你的接口返回什么字段?" UserService 开发者:"userName 和 userId" OrderService 写代码 → 字段名写成了 username → 运行时炸了 有契约(proto): user.proto 里写死了 User 的字段和类型 UserService 实现时 → 必须遵守 OrderService 调用时 → 编译器帮你检查 → 字段名不一致?编译都过不了——根本跑不起来 proto 是微服务之间的"合同"——双方签字画押,编译器当法官。谁不遵守谁编译不过。 ...

十一月 30, 2022 · 11 分钟 · 2284 字 · yaomingye

SpringBoot gRPC 全操作指南

SpringBoot gRPC 实战 📖 前置阅读:本文假设读者已掌握 Protobuf 语法和 gRPC 四种调用模式的概念。如果还不熟悉,建议先阅读 Protobuf 语法精讲与 gRPC 概念。 🎯 第一步:目标说明 上一篇用 protoc 写了 .proto 文件,手动编译生成了 Java 代码。接下来把这一切接入 SpringBoot——用 @GrpcService 暴露 gRPC 服务,用 @GrpcClient 注入远程代理,四种 RPC 模式全部用代码跑通。 📋 第二步:前置条件 前置项 具体要求 验证命令 JDK 17+ java -version SpringBoot 3.x mvn dependency:tree | grep spring-boot protoc 3.25+ (Maven 插件会自动下载) — 前置知识 Protobuf 语法、gRPC 四种模式概念 — 🔧 第三步:项目结构与依赖 3.1 多模块 Maven 项目 grpc-demo ├── pom.xml # 父 POM ├── grpc-api/ # proto 文件 + 生成的 Java 代码 │ ├── pom.xml # 有 protobuf-maven-plugin │ └── src/main/proto/ │ └── order.proto ├── grpc-server/ # gRPC 服务端——实现业务逻辑 │ ├── pom.xml # 依赖 grpc-api │ └── src/main/java/... └── grpc-client/ # gRPC 客户端——调用远程服务 ├── pom.xml # 依赖 grpc-api └── src/main/java/... 为什么要把 proto 放在独立模块? 服务端和客户端都依赖 proto 生成的 Java 代码——放独立模块中双方共享编译结果,而不是各自编译一份。 ...

十一月 29, 2022 · 8 分钟 · 1495 字 · yaomingye

Protobuf 语法精讲与 gRPC 概念

Protobuf 语法与 gRPC 概念 一、⚡ Protobuf 是什么:为什么非要学一门"新语言"? 前面讲了 JSON 序列化——Jackson 和 Fastjson2 把 Java 对象和 JSON 之间互转。JSON 是人眼可读的文本——但文本格式有两个天然的劣势: JSON 消息体(108 字节): { "orderId": 10001, "userId": 2001, "productName": "iPhone 15", "amount": 6999.00 } 同样的信息——Protobuf 二进制(约 35 字节): \x08\x91N\x12\x04...(肉眼不可读的二进制) JSON Protobuf 文本格式——每个字符占 1 字节 二进制格式——用最少的字节表达同样的数据 字段名重复传输("orderId" 每次都要传) 字段名不传——用数字编号代替(orderId = 1) 解析慢——文本解析器 解析快——二进制解码器 人眼可读——curl 能调 人眼不可读——需要专用工具 Protobuf 省的不是几字节——是高并发场景下每一条消息都省 60%~80% 带宽。这就是为什么 gRPC 选择 Protobuf 作为默认序列化格式。 二、🧬 Protobuf 语法逐一拆解 Protobuf 的语法就是定义数据结构和服务接口的语言。文件后缀是 .proto。先看一个完整的例子,然后每个语法元素拆开讲: // order.proto —— 订单服务的完整 proto 定义 syntax = "proto3"; // ① 语法版本 package com.example.order; // ② 包名 option java_multiple_files = true; // ③ 编译选项 option java_package = "com.example.order.dto"; import "google/protobuf/timestamp.proto"; // ④ 导入其他 proto // ⑤ 枚举定义 enum OrderStatus { ORDER_STATUS_UNSPECIFIED = 0; // 枚举第一个值必须是 0 ORDER_STATUS_CREATED = 1; ORDER_STATUS_PAID = 2; ORDER_STATUS_CANCELLED = 3; } // ⑥ 消息定义——Protobuf 的核心 message Order { int64 order_id = 1; // 字段编号 = 1 string product_name = 2; // 字段编号 = 2 double amount = 3; // 字段编号 = 3 OrderStatus status = 4; // 使用上面定义的枚举 google.protobuf.Timestamp created_at = 5; // 使用导入的时间戳类型 repeated string tags = 6; // repeated = 数组/列表 map<string, string> metadata = 7; // map 类型 } // ⑦ 服务定义——gRPC 的方法声明 service OrderService { rpc GetOrder(GetOrderRequest) returns (Order); rpc ListOrders(ListOrdersRequest) returns (stream Order); // 服务端流 rpc CreateOrder(stream CreateOrderRequest) returns (stream Order); // 双向流 } // 消息定义可以在 service 之后——顺序不要求 message GetOrderRequest { int64 order_id = 1; } message ListOrdersRequest { int64 user_id = 1; int32 page_size = 2; } message CreateOrderRequest { string product_name = 1; double amount = 2; } 2.1 syntax —— 声明语法版本 syntax = "proto3"; // 必须在文件第一行(注释上面可以,语法上面不能有东西) 版本 特点 当前状态 proto2 老版本——有 required/optional/default 关键字 gRPC 也支持,但不推荐新项目用 proto3 新版本——去掉了 required 和 default,所有字段默认可选 当前主流——新项目统一用 proto3 2.2 package —— 包名 package com.example.order; // 防止命名冲突——和 Java 的 package 概念一样 // 生成 Java 代码时:类放在 com.example.order 包下 2.3 option —— 编译选项 // java_multiple_files: true → 每个 message 生成一个独立的 .java 文件 // false(默认) → 所有 message 生成到一个巨大的外部类中 option java_multiple_files = true; // java_package: 指定生成的 Java 文件所在的包——如果不写,用 package 的值 option java_package = "com.example.order.dto"; // java_outer_classname: java_multiple_files = false 时——指定外部类的类名 option java_outer_classname = "OrderProto"; // optimize_for: 生成代码优化方向——SPEED / CODE_SIZE / LITE_RUNTIME option optimize_for = SPEED; 选项 默认值 建议 java_multiple_files false true——每个 message 一个文件,方便 IDE 导航 java_package package 的值(但它是默认的,不是必然) 写成和 Java 项目一致的包名 optimize_for SPEED 保持默认 2.4 import —— 导入其他 proto 文件 import "google/protobuf/timestamp.proto"; // 导入 Google 内置类型 import "common/common.proto"; // 导入自己项目的公共 proto import public "common/new.proto"; // 公开导入——谁 import 你,谁也看得到 new.proto 的类型 常用内置类型(google/protobuf/ 下的类型): ...

十一月 28, 2022 · 7 分钟 · 1378 字 · yaomingye

Jackson vs Fastjson2 终极对比

Jackson vs Fastjson2 📖 前置阅读:本文假设读者已经阅读过前两篇——如果还不熟悉 Jackson 或 Fastjson2,建议先阅读 序列化本质与 Jackson 全操作指南 和 Fastjson 进化史:从 1.x 漏洞到 Fastjson2。 一、⚡ 同一个对象,两种写法 先在同一个 Order 类上对比两套注解——直观感受差异: // ===== Jackson 写法 ===== @JsonPropertyOrder({"order_id", "user_id", "product_name", "amount", "create_time"}) @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public class Order { @JsonProperty("order_id") // Jackson: 改字段名 private Long orderId; @JsonProperty("user_id") private Long userId; @JsonProperty("product_name") private String productName; @JsonFormat(shape = JsonFormat.Shape.STRING) // Jackson: 金额用字符串 private BigDecimal amount; @JsonIgnore // Jackson: 忽略字段 private String internalNote; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") // Jackson: 日期格式 private LocalDateTime createTime; } // ===== Fastjson2 写法 ===== @JSONType(orders = {"order_id", "user_id", "product_name", "amount", "create_time"}) public class Order { @JSONField(name = "order_id") // Fastjson2: 改字段名 private Long orderId; @JSONField(name = "user_id") private Long userId; @JSONField(name = "product_name") private String productName; @JSONField(serializeFeatures = JSONWriter.Feature.WriteBigDecimalAsPlain) private BigDecimal amount; // Fastjson2: 金额不用科学计数法 @JSONField(serialize = false) // Fastjson2: 忽略字段 private String internalNote; @JSONField(format = "yyyy-MM-dd HH:mm:ss") // Fastjson2: 日期格式 private LocalDateTime createTime; } Facjson2 不需要 @JsonIgnoreProperties——它默认忽略未知字段。Jackson 必须在每个类上加这个注解或全局配置。对于有 50+ 个 DTO 类的项目来说,这少写不少样板。 ...

十一月 27, 2022 · 5 分钟 · 875 字 · yaomingye

Fastjson 进化史:从 1.x 漏洞到 Fastjson2

Fastjson 进化史 📖 前置阅读:本文假设读者已理解序列化的基本概念和 JSON 序列化工具的用法。如果还不熟悉,建议先阅读 序列化本质与 Jackson 全操作指南。 一、⚡ Fastjson 曾经有多火 Fastjson 是阿里巴巴 2011 年开源的 JSON 序列化库。在 Jackson 还比较沉重、Gson 性能一般的年代,Fastjson 凭三个特点迅速占领了国内 Java 项目: 卖点 具体表现 快 号称"Java 语言最快的 JSON 处理库"——字节码生成 + ASM 动态优化,比 Jackson 快 2x API 简洁 JSON.toJSONString(obj) 一行搞定——比 Jackson 的 ObjectMapper 样板代码少 阿里出品 阿里巴巴开源——国内 Java 圈号召力最强背书 最火的那些年,几乎所有国内 Java 项目引入 Fastjson——Dubbo、RocketMQ、Nacos 内部都依赖了它。 二、💣 autoType:从"核心卖点"到"最大漏洞" 2.1 autoType 是什么 Fastjson 有一个 Jackson 没有的独特功能——autoType。它的作用是:JSON 中有一个 @type 字段,Fastjson 根据它自动反序列化为对应的 Java 类。 // Fastjson 的 autoType 机制 // 序列化时——自动写入类的全限定名 String json = JSON.toJSONString(order); // 结果:{"@type":"com.example.Order","orderId":10001,"amount":6999.00} // ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ // @type 字段记录了类的全限定名 // 反序列化时——根据 @type 字段自动还原为正确的子类型 // JSON 数据:"{\"@type\":\"com.example.Order\",\"orderId\":10001}" // 即使你只声明为 Object,Fastjson 也能还原出 Order 对象 Object obj = JSON.parse(json); // 实际返回 Order 实例 这个功能在 Jackson 中需要 @JsonTypeInfo 注解手动声明——Fastjson 默认自动开启。 ...

十一月 26, 2022 · 5 分钟 · 1055 字 · yaomingye

序列化本质与 Jackson 全操作指南

序列化本质与 Jackson 一、⚡ 序列化是什么:Java 对象和 JSON 之间的翻译官 日常写的代码里全是 Java 对象——Order、User、Product。但网络传输只能传二进制/文本,数据库只能存文本,前端浏览器只认得 JSON。怎么把 Java 对象变成 JSON、再把 JSON 变回 Java 对象?这就是序列化和反序列化: 序列化(Serialization): Java 对象 → JSON / XML / 二进制 反序列化(Deserialization): JSON / XML / 二进制 → Java 对象 如果不做序列化,你就得手动拼 JSON: // 没有序列化工具——手动拼 JSON(又臭又长) public String orderToJson(Order order) { return "{" + "\"orderId\":" + order.getOrderId() + "," + "\"userId\":" + order.getUserId() + "," + "\"productName\":\"" + escape(order.getProductName()) + "\"," + "\"amount\":" + order.getAmount() + "}"; } // 手写这段代码的时候,你就知道自己需要一个序列化工具了 序列化框架做的事就是自动完成这个转换——你要做的只是加几个注解、调一行方法。 ...

十一月 25, 2022 · 7 分钟 · 1368 字 · yaomingye
Cat Radio