每日学习Java之一万个为什么(待完善)

发布于:2025-05-09 ⋅ 阅读:(198) ⋅ 点赞:(0)

文章目录

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 注解。
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. 实际应用中的典型场景

  1. 模块化配置:

    • 将数据库、安全、缓存等配置拆分为独立的 @Configuration 类,通过 @Import 组合。
    @Configuration
    @Import({DatabaseConfig.class, SecurityConfig.class})
    public class AppConfig {}
    
  2. 集成第三方库:

    • 通过 @Import 导入第三方库的配置类,无需修改原始代码。
    @Configuration
    @Import(ThirdPartyLibraryConfig.class)
    public class MyConfig {}
    
  3. 环境隔离:

    • 使用 ImportSelector 根据环境变量动态加载不同配置。
    @Configuration
    @Import(EnvironmentBasedImportSelector.class)
    public class AppConfig {}
    
  4. 自定义 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 的动态代理机制
  • 流程:
    1. 代理对象创建:
      • 通过 @FeignClient 注解定义的接口在 Spring 容器启动时被 FeignClientFactoryBean 扫描。
      • FeignClientFactoryBean 使用 Java 动态代理为接口生成代理对象(ReflectiveFeign),代理对象负责拦截接口方法调用。
    2. 参数收集:
      • 当调用接口方法时,动态代理会捕获方法参数(如 @PathVariable、@RequestParam 等注解标注的参数),并收集到方法上下文中。

2. 整理参数并组合 URI

  • 责任方:Contract 和 Encoder
  • 流程:
    1. 注解解析:
      • Contract(默认 SpringMvcContract)解析接口方法上的注解(如 @GetMapping、@PostMapping、@PathVariable 等),生成请求模板(RequestTemplate),包括 HTTP 方法、路径、Header 等。
    2. 参数编码:
      • Encoder(默认 SpringEncoder)将方法参数编码到请求模板中:
        • @PathVariable:替换路径中的占位符(如 /user/{id} 替换为 /user/123)。
        • @RequestParam:添加查询参数(如 ?id=123)。
        • @RequestBody:序列化对象为 JSON/表单数据并填充到请求体中。

3. 根据服务名拉取实例并发送请求(第一次发起某个请求的时候,也就比较慢(懒加载开启))

  • 责任方:LoadBalancer 和 Client
  • 流程:
    1. 服务实例选择:
      • 如果接口未显式指定 URL(如 @FeignClient(name = "service-b")),LoadBalancer(默认 Ribbon 或 Spring Cloud LoadBalancer)会从注册中心(如 Nacos、Eureka)获取目标服务的所有实例。
      • LoadBalancer 根据配置的策略(如轮询、随机)选择一个实例,确定最终的请求地址(如 http://service-b:8080)。
    2. 请求发送:
      • Client(默认 HttpURLConnection,可替换为 OkHttp 或 Apache HttpClient)将 RequestTemplate 转换为实际的 HTTP 请求(如 GET http://service-b:8080/user/123)并发送。
    3. 拦截器处理:
      • 在请求发送前,RequestInterceptor(如添加认证 Token)会修改请求头或内容。
      • 在请求失败或响应返回后,Retryer(默认重试机制)决定是否重试请求。

4. 接收响应并处理结果

  • 责任方:Decoder 和 ErrorDecoder
  • 流程:
    1. 响应解码:
      • Decoder(默认 SpringDecoder)将 HTTP 响应体(如 JSON)反序列化为 Java 对象,匹配方法的返回值类型。
    2. 异常处理:
      • ErrorDecoder 处理 HTTP 错误状态码(如 404、500),抛出业务异常或触发熔断逻辑(如 Hystrix/Sentinel 的降级)。
    3. 日志记录:
      • Logger(默认 NoOpLogger)记录请求和响应的详细信息(如 URL、参数、耗时),用于调试和监控。

关键组件总结

阶段 责任方 核心功能
代理对象创建 FeignClientFactoryBean 扫描 @FeignClient 接口,生成动态代理对象。
参数收集与注解解析 Contract(如 SpringMvcContract) 解析注解生成请求模板,Encoder 编码参数到请求中。
服务实例选择 LoadBalancer(Ribbon/LoadBalancer) 根据服务名从注册中心拉取实例,并选择目标实例。
请求发送 Client(HttpURLConnection 等) 将请求模板转换为 HTTP 请求并发送。
响应处理与异常处理 Decoder、ErrorDecoder 反序列化响应体,处理 HTTP 错误,触发熔断降级。
日志与拦截器 Logger、RequestInterceptor 记录请求日志,添加认证 Token、分布式追踪 ID 等。

补充说明

  1. 动态代理的灵活性:
    Feign 通过 ReflectiveFeign 实现动态代理,开发者无需手动编写 HTTP 调用逻辑,只需关注接口定义。
  2. 负载均衡的可扩展性:
    负载均衡策略可通过 IRule(Ribbon)或 LoadBalancer(Spring Cloud LoadBalancer)自定义,支持权重、区域隔离等策略。
  3. 性能优化:
    默认的 HttpURLConnection 性能较差,建议替换为连接池实现(如 OkHttp)以提高吞吐量。
  4. 熔断与降级:
    集成 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 获取所有路由配置,逐个测试断言是否满足。

流程:

  1. 通过 RouteLocator 获取所有路由规则(如 YAML 配置或代码定义的 RouteDefinition)。
  2. 使用断言(如 Path=/api/**)判断请求是否符合路由条件。
  3. 返回第一个匹配的 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(后置) 阶段。

流程:

  1. Pre 过滤器:在请求转发前执行(如鉴权、日志记录)。
    • 示例:AddRequestHeader 添加请求头,RequestRateLimiter 限流。
  2. 路由转发:将请求转发到目标服务(见下一阶段)。
  3. Post 过滤器:在响应返回前执行(如修改响应头、记录日志)。
    • 示例:AddResponseHeader 添加响应头,NettyWriteResponseFilter 写入响应。

代码示例:

public Mono<Void> filter(ServerWebExchange exchange) {
    return this.filterChain.filter(exchange)
            .then(Mono.fromRunnable(() -> {
                // 后置处理逻辑
            }));
}

4. 服务发现与负载均衡

负责人:DiscoveryClient 和 LoadBalancer(如 ReactorLoadBalancer)
作用:

  • 服务发现:通过 DiscoveryClient(如 Nacos、Eureka)获取目标服务的可用实例列表。
  • 负载均衡:根据配置的策略(如轮询、随机)选择一个实例。

流程:

  1. DiscoveryClient 从注册中心拉取目标服务的实例列表。
  2. LoadBalancer 根据策略选择一个实例(如 RoundRobinRule)。
  3. 构建目标 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 模型)。

流程:

  1. 构建 ClientRequest,复制原始请求的 headers、body 等信息。
  2. 使用 WebClient 发送请求到目标 URI。
  3. 将目标服务的响应返回给客户端。

代码示例:

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 阶段处理响应数据(如添加自定义头、记录日志)。

流程:

  1. 目标服务返回响应体后,NettyWriteResponseFilter 将响应写入客户端。
  2. 执行其他 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 规则的 持久化 和 动态更新。

  • 技术原理:

    1. 规则持久化:
      • Sentinel 默认将规则存储在内存中,应用重启后规则会丢失。
      • 通过 Nacos 将规则存储到 sentinel-spzx-gateway 的 dataId 文件中,实现规则持久化。
    2. 动态更新:
      • 当 Nacos 中的规则文件内容变更时,Sentinel 会自动拉取新规则并生效。
    3. 规则类型 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
        }
      ]
      

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. 整体配置的协同作用

  1. Sentinel 与 Nacos 集成:
    • 应用通过 sentinel.datasource 配置从 Nacos 拉取网关流量控制规则。
    • 规则变更后无需重启应用,Sentinel 会自动生效新规则。
  2. Sentinel 与控制台联动:
    • 应用将监控数据上报到 101.42.185.156:8858 的 Sentinel 控制台。
    • 控制台可实时查看流量、QPS、线程池等指标,并通过规则管理界面动态调整规则。
  3. 响应式网关架构:
    • 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. 关键注意事项

  1. 线程安全问题:

    • ThreadLocal 在异步调用(如使用 @Async)或线程池中可能失效。需使用 TransmittableThreadLocal 或在异步任务中手动传递上下文。
    • 示例(使用 TransmittableThreadLocal):
      import com.alibaba.ttl.TransmittableThreadLocal;
      private static final TransmittableThreadLocal<UserInfo> currentUser = new TransmittableThreadLocal<>();
      
  2. Token 过期处理:

    • 网关需校验 Token 有效性,若 Token 过期或非法,需返回 401 错误。
  3. 性能优化:

    • 用户信息尽量精简(如只传递用户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 通过以下机制实现用户信息传递:

  1. 网关层:解析 Token 并注入自定义 Header。
  2. 业务服务:拦截器将 Header 信息存入 ThreadLocal。
  3. 服务间调用:Feign 拦截器将 ThreadLocal 数据附加到下游请求中。
  4. 工具类:通过 UserContext 简化用户信息的获取和管理。

这种方式确保了用户信息在微服务架构中透明传递,同时保持了代码的简洁性和可维护性。

RBAC

代码生成

数据权限

定时任务


微信公众号

今日签到

点亮在社区的每一天
去签到