文章目录
- SpringMVC复习
- Redis 与 MySQL数据不一致
- 微服务组件复习
-
- SpringCloud-Alibaba-Nacos
- OpenFeign
- SpringCloud-Gateway
-
- 网关处理请求的流程
- **1. 请求分发(DispatcherHandler)**
- **2. 路由匹配(RoutePredicateHandlerMapping)**
- **3. 过滤器链处理(FilteringWebHandler + GatewayFilterChain)**
- **4. 服务发现与负载均衡**
- **5. 请求转发(WebClient)**
- **6. 响应返回**
- **总结:完整流程图**
- **关键组件职责对照表**
- 网关配置
- **1. `sentinel` 配置解析**
- **2. `main.web-application-type: reactive`**
- **3. 整体配置的协同作用**
- **4. 验证配置是否生效**
- **5. 总结**
- Rou-Yi 框架学习
SpringMVC复习
SpringCloud 带动了 SpringBoot,SpringBoot成就了SpringCloud。从微软热词的反馈中可以直观感受到它们两的热度是一致的。
微服务本身是一种架构风格。相比于单体架构,扩展性更高,上限更高。单体架构稳定,成本低。
@Import
在 Spring 框架中,@Import
注解是一个核心机制,用于将外部类、配置类或动态注册的 Bean 导入到 Spring 应用上下文中。如果一个注解本身带有 @Import
注解(即作为元注解使用),它的作用通常是封装配置逻辑,简化其他注解的使用。以下从多个角度解析 @Import
的作用和导入的类模板的角色:
1.1 导入配置类
- 功能:将其他
@Configuration
类导入到当前配置类中,实现模块化配置。 - 示例:
@Configuration @Import({DataConfig.class, SecurityConfig.class}) public class AppConfig { // AppConfig 中会包含 DataConfig 和 SecurityConfig 中定义的 Bean }
- 技术原理:
@Import
会将指定的类注册为 Spring 容器中的 Bean。- 如果目标类是
@Configuration
,则其@Bean
方法也会被处理。
1.2 导入普通类
- 功能:将普通类(未标注
@Component
等注解的类)注册为 Spring 容器中的 Bean。 - 示例:
@Configuration @Import(MyService.class) public class AppConfig { // MyService 会被注册为 Bean,Bean 名称为全限定类名 }
- 注意事项:
- 默认情况下,Bean 名称为类的全限定名(如
com.example.MyService
)。 - 如果需要自定义 Bean 名称,需通过
@Bean
或@Component
注解。
- 默认情况下,Bean 名称为类的全限定名(如
1.3 动态导入类(ImportSelector
)
- 功能:通过实现
ImportSelector
接口,在运行时动态决定导入哪些类。 - 示例:
public class MyImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { return new String[] { "com.example.MyDynamicBean" }; } } @Configuration @Import(MyImportSelector.class) public class AppConfig { // AppConfig 会动态导入 MyDynamicBean }
- 技术原理:
selectImports
方法返回的类名数组会被注册为 Spring 容器中的 Bean。- 适用于根据条件(如环境变量)动态加载不同配置。
1.4 自定义 Bean 注册(ImportBeanDefinitionRegistrar
)
- 功能:通过实现
ImportBeanDefinitionRegistrar
接口,手动注册 Bean。 - 示例:
public class MyRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(MyCustomBean.class); registry.registerBeanDefinition("myCustomBean", beanDefinition); } } @Configuration @Import(MyRegistrar.class) public class AppConfig { // AppConfig 会手动注册 MyCustomBean }
- 技术原理:
- 直接操作
BeanDefinitionRegistry
,灵活控制 Bean 的注册逻辑。 - 适用于需要深度定制 Bean 创建场景(如动态代理、AOP)。
- 直接操作
2. @Import
作为元注解的作用
如果一个注解本身带有 @Import
,则它可以通过封装配置逻辑,简化其他注解的使用。例如:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MyImportSelector.class) // 将 @MyAnnotation 标注的类自动导入 MyImportSelector
public @interface MyAnnotation {
}
作用:
- 使用
@MyAnnotation
注解的类会自动触发MyImportSelector
的执行。 - 将复杂的配置逻辑封装到元注解中,提升代码复用性。
- 使用
示例:
@MyAnnotation @Configuration public class MyConfig { // 自动导入 MyImportSelector 指定的类 }
3. 导入的类模板角色
导入的类模板在 Spring 容器中扮演不同的角色,具体取决于其类型:
3.1 配置类(@Configuration
)
- 角色:定义 Bean 的创建逻辑(通过
@Bean
方法)。 - 示例:
@Configuration public class DataConfig { @Bean public DataSource dataSource() { return new HikariDataSource(); // 注册 DataSource Bean } }
3.2 普通类
- 角色:直接注册为 Spring 容器中的 Bean。
- 示例:
public class MyService { public void doSomething() { // ... } }
3.3 ImportSelector
实现类
- 角色:动态决定导入哪些类。
- 示例:
public class EnvironmentBasedImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata metadata) { if (Environment.isProduction()) { return new String[] { "com.example.ProductionConfig" }; } else { return new String[] { "com.example.DevConfig" }; } } }
3.4 ImportBeanDefinitionRegistrar
实现类
- 角色:手动注册 Bean,支持复杂逻辑。
- 示例:
public class CustomBeanRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // 动态创建 BeanDefinition 并注册 registry.registerBeanDefinition("customBean", new RootBeanDefinition(CustomBean.class)); } }
4. @Import
与 @ComponentScan
的区别
功能 | @ComponentScan |
@Import |
---|---|---|
作用 | 扫描并注册包路径下的组件(如 @Component ) |
显式导入特定类或动态注册类 |
适用场景 | 自动发现组件(推荐用于通用组件) | 精确控制配置(推荐用于模块化或第三方集成) |
灵活性 | 依赖包路径,灵活性较低 | 完全灵活,支持动态逻辑 |
5. 实际应用中的典型场景
模块化配置:
- 将数据库、安全、缓存等配置拆分为独立的
@Configuration
类,通过@Import
组合。
@Configuration @Import({DatabaseConfig.class, SecurityConfig.class}) public class AppConfig {}
- 将数据库、安全、缓存等配置拆分为独立的
集成第三方库:
- 通过
@Import
导入第三方库的配置类,无需修改原始代码。
@Configuration @Import(ThirdPartyLibraryConfig.class) public class MyConfig {}
- 通过
环境隔离:
- 使用
ImportSelector
根据环境变量动态加载不同配置。
@Configuration @Import(EnvironmentBasedImportSelector.class) public class AppConfig {}
- 使用
自定义 Bean 注册:
- 通过
ImportBeanDefinitionRegistrar
注册动态生成的 Bean(如代理对象)。
@Configuration @Import(MyBeanRegistrar.class) public class AppConfig {}
- 通过
6. 总结
@Import
的核心作用:显式导入类或动态注册 Bean,实现模块化配置和灵活扩展。- 导入的类模板角色:
- 配置类:定义 Bean 的创建逻辑。
- 普通类:直接注册为容器管理的 Bean。
- 动态选择类:通过
ImportSelector
动态决定导入哪些类。 - 手动注册类:通过
ImportBeanDefinitionRegistrar
精确控制 Bean 的注册。
- 最佳实践:
- 对通用组件使用
@ComponentScan
。 - 对模块化配置或第三方集成使用
@Import
。 - 复杂场景下结合
ImportSelector
和ImportBeanDefinitionRegistrar
实现动态配置。
- 对通用组件使用
通过合理使用 @Import
,可以显著提升 Spring 应用的可维护性和扩展性。
自定义SpringMVC
WebMvcAutoConfigurer
WebMvcConfigurer
Servlet容器
Redis 与 MySQL数据不一致
写操作不一致
读写不一致
Redis中的布隆过滤器
微服务组件复习
SpringCloud-Alibaba-Nacos
服务注册
导入依赖后自动注册,需要添加应用名称
服务发现
需要拉取注册中心的服务实例
配置中心
将公共配置放置到nacos中,由项目的bootstrap自己拉取
OpenFeign
Feign客户端发请求的大致流程
Feign客户端发送请求的完整流程可以分为以下几个阶段,每个阶段由不同的组件或模块负责完成:
1. 创建代理对象并收集参数
- 责任方:
FeignClientFactoryBean
和Feign
的动态代理机制 - 流程:
- 代理对象创建:
- 通过
@FeignClient
注解定义的接口在 Spring 容器启动时被FeignClientFactoryBean
扫描。 FeignClientFactoryBean
使用 Java 动态代理为接口生成代理对象(ReflectiveFeign
),代理对象负责拦截接口方法调用。
- 通过
- 参数收集:
- 当调用接口方法时,动态代理会捕获方法参数(如
@PathVariable
、@RequestParam
等注解标注的参数),并收集到方法上下文中。
- 当调用接口方法时,动态代理会捕获方法参数(如
- 代理对象创建:
2. 整理参数并组合 URI
- 责任方:
Contract
和Encoder
- 流程:
- 注解解析:
Contract
(默认SpringMvcContract
)解析接口方法上的注解(如@GetMapping
、@PostMapping
、@PathVariable
等),生成请求模板(RequestTemplate
),包括 HTTP 方法、路径、Header 等。
- 参数编码:
Encoder
(默认SpringEncoder
)将方法参数编码到请求模板中:@PathVariable
:替换路径中的占位符(如/user/{id}
替换为/user/123
)。@RequestParam
:添加查询参数(如?id=123
)。@RequestBody
:序列化对象为 JSON/表单数据并填充到请求体中。
- 注解解析:
3. 根据服务名拉取实例并发送请求(第一次发起某个请求的时候,也就比较慢(懒加载开启))
- 责任方:
LoadBalancer
和Client
- 流程:
- 服务实例选择:
- 如果接口未显式指定 URL(如
@FeignClient(name = "service-b")
),LoadBalancer
(默认Ribbon
或Spring Cloud LoadBalancer
)会从注册中心(如 Nacos、Eureka)获取目标服务的所有实例。 LoadBalancer
根据配置的策略(如轮询、随机)选择一个实例,确定最终的请求地址(如http://service-b:8080
)。
- 如果接口未显式指定 URL(如
- 请求发送:
Client
(默认HttpURLConnection
,可替换为OkHttp
或Apache HttpClient
)将RequestTemplate
转换为实际的 HTTP 请求(如GET http://service-b:8080/user/123
)并发送。
- 拦截器处理:
- 在请求发送前,
RequestInterceptor
(如添加认证 Token)会修改请求头或内容。 - 在请求失败或响应返回后,
Retryer
(默认重试机制)决定是否重试请求。
- 在请求发送前,
- 服务实例选择:
4. 接收响应并处理结果
- 责任方:
Decoder
和ErrorDecoder
- 流程:
- 响应解码:
Decoder
(默认SpringDecoder
)将 HTTP 响应体(如 JSON)反序列化为 Java 对象,匹配方法的返回值类型。
- 异常处理:
ErrorDecoder
处理 HTTP 错误状态码(如 404、500),抛出业务异常或触发熔断逻辑(如 Hystrix/Sentinel 的降级)。
- 日志记录:
Logger
(默认NoOpLogger
)记录请求和响应的详细信息(如 URL、参数、耗时),用于调试和监控。
- 响应解码:
关键组件总结
阶段 | 责任方 | 核心功能 |
---|---|---|
代理对象创建 | FeignClientFactoryBean |
扫描 @FeignClient 接口,生成动态代理对象。 |
参数收集与注解解析 | Contract (如 SpringMvcContract ) |
解析注解生成请求模板,Encoder 编码参数到请求中。 |
服务实例选择 | LoadBalancer (Ribbon/LoadBalancer) |
根据服务名从注册中心拉取实例,并选择目标实例。 |
请求发送 | Client (HttpURLConnection 等) |
将请求模板转换为 HTTP 请求并发送。 |
响应处理与异常处理 | Decoder 、ErrorDecoder |
反序列化响应体,处理 HTTP 错误,触发熔断降级。 |
日志与拦截器 | Logger 、RequestInterceptor |
记录请求日志,添加认证 Token、分布式追踪 ID 等。 |
补充说明
- 动态代理的灵活性:
Feign 通过ReflectiveFeign
实现动态代理,开发者无需手动编写 HTTP 调用逻辑,只需关注接口定义。 - 负载均衡的可扩展性:
负载均衡策略可通过IRule
(Ribbon)或LoadBalancer
(Spring Cloud LoadBalancer)自定义,支持权重、区域隔离等策略。 - 性能优化:
默认的HttpURLConnection
性能较差,建议替换为连接池实现(如OkHttp
)以提高吞吐量。 - 熔断与降级:
集成 Hystrix 或 Sentinel 后,Feign 可在服务不可用时自动触发降级逻辑(通过fallback
配置)。
注解
@EnableFeignClients
@FeignClient
SpringCloud-Gateway
网关处理请求的流程
Spring Cloud Gateway 处理请求的流程可以分为以下几个阶段,每个阶段由特定的组件负责:
1. 请求分发(DispatcherHandler)
负责人:DispatcherHandler
作用:
- 作为网关的入口,负责接收所有 HTTP 请求。
- 通过
HandlerMapping
(如RoutePredicateHandlerMapping
)将请求分发到对应的处理器。
流程: DispatcherHandler
调用handle(ServerWebExchange exchange)
方法,依次遍历所有HandlerMapping
实现类,找到能处理当前请求的Handler
。- 如果未找到匹配的处理器,返回错误响应(如 404)。
代码示例:
public class DispatcherHandler implements WebHandler {
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
return Flux.fromIterable(this.handlerMappings)
.concatMap(mapping -> mapping.getHandler(exchange))
.next()
.switchIfEmpty(Mono.error(HANDLER_NOT_FOUND_EXCEPTION))
.flatMap(handler -> invokeHandler(exchange, handler))
.flatMap(result -> handleResult(exchange, result));
}
}
2. 路由匹配(RoutePredicateHandlerMapping)
负责人:RoutePredicateHandlerMapping
作用:
- 根据
Predicates
(断言)匹配请求到对应的路由规则(Route
)。 - 从
RouteLocator
获取所有路由配置,逐个测试断言是否满足。
流程:
- 通过
RouteLocator
获取所有路由规则(如 YAML 配置或代码定义的RouteDefinition
)。 - 使用断言(如
Path=/api/**
)判断请求是否符合路由条件。 - 返回第一个匹配的
Route
。
代码示例:
public Route route(ServerWebExchange exchange) {
return this.routeLocator.getRoutes()
.filter(route -> route.getPredicate().test(exchange))
.next()
.block();
}
3. 过滤器链处理(FilteringWebHandler + GatewayFilterChain)
负责人:FilteringWebHandler
和 GatewayFilterChain
作用:
- 执行过滤器链(
GatewayFilter
),对请求和响应进行预处理或后处理。 - 过滤器分为 Pre(前置) 和 Post(后置) 阶段。
流程:
- Pre 过滤器:在请求转发前执行(如鉴权、日志记录)。
- 示例:
AddRequestHeader
添加请求头,RequestRateLimiter
限流。
- 示例:
- 路由转发:将请求转发到目标服务(见下一阶段)。
- Post 过滤器:在响应返回前执行(如修改响应头、记录日志)。
- 示例:
AddResponseHeader
添加响应头,NettyWriteResponseFilter
写入响应。
- 示例:
代码示例:
public Mono<Void> filter(ServerWebExchange exchange) {
return this.filterChain.filter(exchange)
.then(Mono.fromRunnable(() -> {
// 后置处理逻辑
}));
}
4. 服务发现与负载均衡
负责人:DiscoveryClient
和 LoadBalancer
(如 ReactorLoadBalancer
)
作用:
- 服务发现:通过
DiscoveryClient
(如 Nacos、Eureka)获取目标服务的可用实例列表。 - 负载均衡:根据配置的策略(如轮询、随机)选择一个实例。
流程:
DiscoveryClient
从注册中心拉取目标服务的实例列表。LoadBalancer
根据策略选择一个实例(如RoundRobinRule
)。- 构建目标 URI(如
http://192.168.1.10:8080
)。
代码示例:
public URI selectInstance(String serviceId) {
List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
ServiceInstance instance = loadBalancer.choose(instances);
return instance.getUri();
}
5. 请求转发(WebClient)
负责人:WebClient
(基于 Netty 的非阻塞 HTTP 客户端)
作用:
- 将请求转发到目标服务实例,并返回响应。
- 支持异步、非阻塞的请求处理(基于 Reactor 模型)。
流程:
- 构建
ClientRequest
,复制原始请求的 headers、body 等信息。 - 使用
WebClient
发送请求到目标 URI。 - 将目标服务的响应返回给客户端。
代码示例:
public Mono<Void> forward(ServerWebExchange exchange, URI url) {
ClientRequest clientRequest = ClientRequest.create(
exchange.getRequest().getMethod(), url)
.headers(headers -> headers.addAll(exchange.getRequest().getHeaders()))
.build();
return webClient.exchange(clientRequest);
}
6. 响应返回
负责人:NettyWriteResponseFilter
(默认的 Post 过滤器)
作用:
- 将目标服务的响应写回客户端。
- 在 Post 阶段处理响应数据(如添加自定义头、记录日志)。
流程:
- 目标服务返回响应体后,
NettyWriteResponseFilter
将响应写入客户端。 - 执行其他 Post 过滤器(如日志记录)。
总结:完整流程图
Client 请求
↓
DispatcherHandler(分发请求)
↓
RoutePredicateHandlerMapping(路由匹配)
↓
FilteringWebHandler + GatewayFilterChain(过滤器链处理)
↓
DiscoveryClient(服务发现) + LoadBalancer(负载均衡)
↓
WebClient(请求转发)
↓
NettyWriteResponseFilter(响应返回)
↓
Client 响应
关键组件职责对照表
阶段 | 负责人 | 核心功能 |
---|---|---|
请求分发 | DispatcherHandler |
接收请求并匹配处理器。 |
路由匹配 | RoutePredicateHandlerMapping |
根据断言规则匹配路由。 |
过滤器链处理 | GatewayFilterChain |
执行 Pre/Post 过滤器(如鉴权、限流、日志)。 |
服务发现 | DiscoveryClient |
从注册中心获取目标服务的实例列表。 |
负载均衡 | LoadBalancer |
根据策略选择一个服务实例。 |
请求转发 | WebClient |
非阻塞式转发请求到目标服务实例。 |
响应返回 | NettyWriteResponseFilter |
将目标服务的响应写回客户端,并执行 Post 过滤器逻辑。 |
网关配置
以下是对 Gateway 配置 的详细解析,涵盖 sentinel
和 main
配置项的功能、作用及技术原理:
1. sentinel
配置解析
1.1 eager: true
- 作用:取消 Sentinel 控制台(Dashboard)的懒加载。
- 默认行为:Sentinel 控制台默认在首次请求时初始化(懒加载),
eager: true
会强制在应用启动时主动连接控制台。 - 适用场景:
- 确保应用启动后能立即与 Sentinel 控制台通信。
- 避免因懒加载导致的首次请求延迟或连接失败问题。
1.2 transport.dashboard
transport:
dashboard: 101.42.185.156:8858
- 作用:指定 Sentinel 控制台的地址和端口。
- 技术原理:
- 应用启动后,会通过
101.42.185.156:8858
向 Sentinel 控制台发送心跳、监控数据(如流量、QPS)。 - 控制台可基于这些数据进行实时监控和规则推送。
- 应用启动后,会通过
- 注意事项:
- 确保 IP 和端口可访问(需检查防火墙、网络策略)。
- 控制台服务需已启动(如使用 Docker 或独立部署)。
1.3 datasource.ds1
配置
datasource:
ds1:
nacos:
server-addr: 101.42.185.156:8848
dataId: sentinel-spzx-gateway
groupId: DEFAULT_GROUP
data-type: json
rule-type: gw-flow
作用:通过 Nacos 实现 Sentinel 规则的 持久化 和 动态更新。
技术原理:
- 规则持久化:
- Sentinel 默认将规则存储在内存中,应用重启后规则会丢失。
- 通过 Nacos 将规则存储到
sentinel-spzx-gateway
的dataId
文件中,实现规则持久化。
- 动态更新:
- 当 Nacos 中的规则文件内容变更时,Sentinel 会自动拉取新规则并生效。
- 规则类型
gw-flow
:- 表示当前规则类型是 网关流量控制规则(Gateway Flow Rule)。
- 适用于 Spring Cloud Gateway 或 Dubbo 网关场景。
- 规则持久化:
配置参数说明:
参数名 说明 server-addr
Nacos 服务地址(IP + 端口)。 dataId
Nacos 中存储规则的配置文件 ID(需与 Nacos 中实际配置匹配)。 groupId
Nacos 中的分组(默认为 DEFAULT_GROUP
)。data-type
规则文件格式( json
表示 JSON 格式的规则文件)。rule-type
规则类型( gw-flow
表示网关流量控制规则,其他类型如flow
是普通流量控制规则)。注意事项:
- 需确保 Nacos 服务已启动,且
sentinel-spzx-gateway
对应的 JSON 文件内容符合 Sentinel 规则格式。 - 示例 JSON 规则文件(网关流量控制):
[ { "resource": "/api/**", "resourceType": "gateway-api", "count": 100, "grade": 1, "strategy": 0, "controlBehavior": 0, "clusterMode": false } ]
- 需确保 Nacos 服务已启动,且
2. main.web-application-type: reactive
- 作用:指定 Spring Boot 应用的 Web 类型为 响应式(Reactive)。
- 技术原理:
- 使用
Spring WebFlux
替代传统的Spring MVC
。 - 基于非阻塞式 I/O(Netty、Undertow 等),支持异步、背压(Backpressure)和高并发。
- 使用
- 适用场景:
- 高并发、低延迟的网关或微服务场景。
- 需要处理大量长连接(如 WebSocket)或异步请求的场景。
- 注意事项:
- 需确保依赖库兼容响应式编程(如
Spring Cloud Gateway
本身基于 WebFlux)。 - 避免在响应式应用中混用阻塞式代码(如直接调用
Thread.sleep()
或ResultSet.next()
)。
- 需确保依赖库兼容响应式编程(如
3. 整体配置的协同作用
- Sentinel 与 Nacos 集成:
- 应用通过
sentinel.datasource
配置从 Nacos 拉取网关流量控制规则。 - 规则变更后无需重启应用,Sentinel 会自动生效新规则。
- 应用通过
- Sentinel 与控制台联动:
- 应用将监控数据上报到
101.42.185.156:8858
的 Sentinel 控制台。 - 控制台可实时查看流量、QPS、线程池等指标,并通过规则管理界面动态调整规则。
- 应用将监控数据上报到
- 响应式网关架构:
web-application-type: reactive
确保网关基于响应式编程模型,适配高并发场景。- 与 Sentinel 的非阻塞限流机制兼容,避免因限流导致线程阻塞。
4. 验证配置是否生效
4.1 检查 Sentinel 控制台连接
- 访问 Sentinel 控制台(
http://101.42.185.156:8858
)。 - 登录后查看是否出现当前应用的节点(名称由
spring.application.name
决定)。
4.2 检查 Nacos 规则加载
- 在 Nacos 控制台(
http://101.42.185.156:8848
)中查找sentinel-spzx-gateway
的dataId
。 - 确保规则内容正确,并观察 Sentinel 是否成功加载规则(可通过日志或控制台验证)。
4.3 测试限流效果
- 发送请求到网关的
/api/**
路径。 - 当 QPS 超过规则中的
count
(如 100)时,应触发限流(返回 HTTP 429 或自定义错误响应)。
5. 总结
配置项 | 作用 |
---|---|
sentinel.eager |
取消 Sentinel 控制台懒加载,确保启动时连接。 |
sentinel.transport.dashboard |
指定 Sentinel 控制台地址,用于监控和规则推送。 |
sentinel.datasource.ds1.nacos |
通过 Nacos 实现规则持久化和动态更新。 |
main.web-application-type |
启用响应式编程模型,适配高并发网关场景。 |
此配置适用于需要 动态限流、高可用监控 和 响应式架构 的 Spring Cloud Gateway 应用,结合 Sentinel 和 Nacos 可实现灵活的流量治理能力。
Rou-Yi 框架学习
注解
@EnableCustomConfig
@EnableRyFeignClients
Ry如何传递用户信息
在 RuoYi-Cloud 微服务框架中,用户信息的传递主要通过 网关(Gateway)、OpenFeign 拦截器 和 ThreadLocal 三部分协作完成。以下是详细的实现流程和关键代码示例:
1. 网关层传递用户信息
目标:在请求进入业务微服务前,网关将用户信息写入请求头中。
实现方式:
- 解析 Token:网关(如 Spring Cloud Gateway)从请求头中提取 JWT Token,解析出用户信息(如用户ID、用户名等)。
- 写入请求头:将用户信息以自定义 Header 的形式(如
X-User-Info
)附加到请求中,传递给下游服务。
代码示例(网关层的 AuthFilter
):
@Component
public class AuthFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1. 从请求头中提取 JWT Token
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
// 2. 验证 Token 并解析用户信息(假设解析后的用户信息为 userInfo)
UserInfo userInfo = parseUserInfoFromToken(token);
// 3. 将用户信息写入请求头(自定义 Header)
ServerHttpRequest request = exchange.getRequest().mutate()
.header("X-User-Info", JSON.toJSONString(userInfo))
.build();
return chain.filter(exchange.mutate().request(request).build());
}
}
2. 业务微服务接收用户信息
目标:业务微服务接收到请求后,从请求头中提取用户信息并存储到当前线程的 ThreadLocal
中。
实现方式:
- 拦截器处理:通过自定义拦截器(如
HeaderInterceptor
)拦截请求,解析请求头中的用户信息并存入ThreadLocal
。
代码示例(业务微服务的拦截器):
@Component
public class HeaderInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 1. 从请求头中提取用户信息
String userInfoStr = request.getHeader("X-User-Info");
UserInfo userInfo = JSON.parseObject(userInfoStr, UserInfo.class);
// 2. 存入 ThreadLocal
UserContext.setUser(userInfo);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// 3. 请求结束后清除 ThreadLocal,避免内存泄漏
UserContext.removeUser();
}
}
3. 微服务间调用传递用户信息
目标:当业务微服务之间通过 OpenFeign 调用时,将当前线程的用户信息传递到下游服务。
实现方式:
- Feign 拦截器:通过自定义
RequestInterceptor
,在 Feign 请求中自动附加用户信息到请求头。
代码示例(Feign 拦截器):
@Component
public class FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// 1. 从 ThreadLocal 中获取当前用户信息
UserInfo userInfo = UserContext.getUser();
// 2. 将用户信息写入 Feign 请求头
if (userInfo != null) {
template.header("X-User-Info", JSON.toJSONString(userInfo));
}
}
}
4. 用户信息在业务代码中的使用
目标:通过工具类(如 UserContext
)在业务代码中直接获取用户信息。
实现方式:
- ThreadLocal 存取:
UserContext
使用ThreadLocal
存储和获取用户信息,确保线程安全。
代码示例(工具类 UserContext
):
public class UserContext {
private static final ThreadLocal<UserInfo> currentUser = new ThreadLocal<>();
public static void setUser(UserInfo user) {
currentUser.set(user);
}
public static UserInfo getUser() {
return currentUser.get();
}
public static void removeUser() {
currentUser.remove();
}
}
业务代码中使用:
// 直接获取当前用户信息
UserInfo currentUser = UserContext.getUser();
System.out.println("当前用户ID:" + currentUser.getUserId());
5. 关键注意事项
线程安全问题:
ThreadLocal
在异步调用(如使用@Async
)或线程池中可能失效。需使用TransmittableThreadLocal
或在异步任务中手动传递上下文。- 示例(使用
TransmittableThreadLocal
):import com.alibaba.ttl.TransmittableThreadLocal; private static final TransmittableThreadLocal<UserInfo> currentUser = new TransmittableThreadLocal<>();
Token 过期处理:
- 网关需校验 Token 有效性,若 Token 过期或非法,需返回 401 错误。
性能优化:
- 用户信息尽量精简(如只传递用户ID),避免频繁序列化/反序列化。
6. 整体流程图
客户端请求 → 网关(解析 Token 并写入 X-User-Info) →
业务服务A(拦截器读取 X-User-Info 存入 ThreadLocal) →
服务A调用服务B(Feign 拦截器附加 X-User-Info) →
服务B(拦截器读取 X-User-Info 存入 ThreadLocal) →
业务代码通过 UserContext 获取用户信息
7. 总结
RuoYi-Cloud 通过以下机制实现用户信息传递:
- 网关层:解析 Token 并注入自定义 Header。
- 业务服务:拦截器将 Header 信息存入
ThreadLocal
。 - 服务间调用:Feign 拦截器将
ThreadLocal
数据附加到下游请求中。 - 工具类:通过
UserContext
简化用户信息的获取和管理。
这种方式确保了用户信息在微服务架构中透明传递,同时保持了代码的简洁性和可维护性。