当前位置: 首页 > news >正文

java每日精进 4.26【多租户之过滤器及请求处理流程】

一月没更,立誓以后断更三天我就是狗!!!!!!!!

研究多租户框架中一条请求的处理全流程

@RestController
@RequestMapping("/users")
public class UserController {@Autowiredprivate UserService userService;@PostMapping("/save")public boolean save(@RequestBody User user) {return userService.save(user);}@GetMapping("/get")public List<User> getAll() {return userService.list();}@GetMapping("/getById")public User getById(@RequestParam Long id) {return userService.getById(id);}
}

以getById接口为例

1. 请求接收

  • 客户端请求:假设客户端发送以下 GET 请求:

  • GET /users/getById?id=123 HTTP/1.1 Host: localhost:8080 tenant-id: 1 Authorization: Bearer <token>

  • Tomcat 接收:Spring Boot 嵌入的 Tomcat 服务器监听端口(默认 8080),接收请求并解析 HTTP 报文。
  • DispatcherServlet:Tomcat 将请求交给 Spring 的 DispatcherServlet,它负责路由和处理所有 HTTP 请求。

2. 执行顺序

  1. TenantContextWebFilter (Order = WebFilterOrderEnum.TENANT_CONTEXT_FILTER)

  2. Spring Security 过滤器链:

    • DisableEncodeUrlFilter

    • WebAsyncManagerIntegrationFilter

    • SecurityContextPersistenceFilter

    • HeaderWriterFilter

    • LogoutFilter

    • RequestCacheAwareFilter

    • SecurityContextHolderAwareRequestFilter

    • AnonymousAuthenticationFilter

    • SessionManagementFilter

  3. TenantSecurityWebFilter (继承自 ApiRequestFilter)

  4. ExceptionTranslationFilter

  5. FilterSecurityInterceptor

阶段一:TenantContextWebFilter 处理

public class TenantContextWebFilter extends OncePerRequestFilter {protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {System.out.println("+++++tenant-id:"+request.getHeader("tenant-id")+"+++++");// 从请求头获取租户IDLong tenantId = WebFrameworkUtils.getTenantId(request);if (tenantId != null) {TenantContextHolder.setTenantId(tenantId); // 设置到线程局部变量}try {chain.doFilter(request, response); // 继续过滤器链} finally {TenantContextHolder.clear(); // 清理线程局部变量}}
}
  • 从请求头中提取 tenant-id 并设置到 TenantContextHolder 线程局部变量中

  • 确保请求处理完成后清理线程局部变量,避免内存泄漏

阶段二:Spring Security 基础过滤器

这些过滤器主要处理安全相关的基础功能:

  • SecurityContextPersistenceFilter: 从Session中加载安全上下文

  • AnonymousAuthenticationFilter: 如果用户未认证,创建一个匿名身份

  • 其他过滤器处理会话、头信息等基础安全功能

+-------------------------------+
| DisableEncodeUrlFilter         |
| 禁用URL编码过滤器:用于禁用对URL的编码处理 |
+-------------------------------+|V
+-------------------------------+
| WebAsyncManagerIntegrationFilter |
| 异步管理器集成过滤器:将Spring的异步管理与安全上下文集成 |
+-------------------------------+|V
+-------------------------------+
| SecurityContextPersistenceFilter |
| 安全上下文持久化过滤器:在请求处理前后管理安全上下文的持久化 |
+-------------------------------+|V
+-------------------------------+
| HeaderWriterFilter             |
| 头部写入过滤器:在响应中添加安全相关的HTTP头部信息 |
+-------------------------------+|V
+-------------------------------+
| LogoutFilter                   |
| 注销过滤器:处理用户的注销请求,清除相关的安全信息 |
+-------------------------------+|V
+-------------------------------+
| RequestCacheAwareFilter        |
| 请求缓存感知过滤器:处理请求的缓存,用于重定向后恢复请求 |
+-------------------------------+|V
+-------------------------------+
| SecurityContextHolderAwareRequestFilter |
| 安全上下文持有者感知请求过滤器:包装请求对象,使其能访问安全上下文 |
+-------------------------------+|V
+-------------------------------+
| AnonymousAuthenticationFilter  |
| 匿名认证过滤器:在没有认证信息时,为请求提供匿名身份 |
+-------------------------------+|V
+-------------------------------+
| SessionManagementFilter        |
| 会话管理过滤器:管理用户会话,处理会话过期等问题 |
+-------------------------------+|V
+-------------------------------+
| ExceptionTranslationFilter     |
| 异常转换过滤器:将安全异常转换为Spring Security能处理的异常 |
+-------------------------------+|V
+-------------------------------+
| FilterSecurityInterceptor      |
| 过滤器安全拦截器:对请求进行最终的安全检查和授权控制 |
+-------------------------------+
阶段三:TenantSecurityWebFilter 处理

作用

  1. 检查登录用户是否有权限访问请求的租户

  2. 对于非忽略URL,必须包含有效的租户ID

  3. 校验租户状态(是否禁用、过期等)

public class TenantSecurityWebFilter extends ApiRequestFilter {protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {Long tenantId = TenantContextHolder.getTenantId();// 1. 检查登录用户租户权限User user = SecurityFrameworkUtils.getLoginUser();if (user != null) {if (tenantId == null) {// 使用登录用户的租户IDtenantId = user.getTenantId();TenantContextHolder.setTenantId(tenantId);} else if (!user.getTenantId().equals(tenantId)) {// 租户不匹配,越权访问log.error("越权访问");ServletUtils.writeJSON(response, CommonResult.error(...));return;}}// 2. 非忽略URL需校验租户if (!isIgnoreUrl(request)) {if (tenantId == null) {// 未传递租户IDlog.error("未传递租户编号");ServletUtils.writeJSON(response, CommonResult.error(...));return;}// 3. 校验租户状态try {tenantFrameworkService.validTenant(tenantId);} catch (Throwable ex) {CommonResult<?> result = globalExceptionHandler.allExceptionHandler(request, ex);ServletUtils.writeJSON(response, result);return;}} else {// 忽略URL处理if (tenantId == null) {TenantContextHolder.setIgnore(true); // 标记忽略租户}}chain.doFilter(request, response);}
}
阶段四:Controller 处理
  1. MyBatis-Plus 会调用 TenantLineInnerInterceptor

  2. 拦截器检查 TenantDatabaseInterceptor 的规则:

    • 检查表是否在忽略列表中

    • 如果没有忽略,自动添加租户条件 WHERE tenant_id = ?

1.多租户配置类YudaoTenantAutoConfiguration:
@AutoConfiguration
@ConditionalOnProperty(prefix = "moyun.tenant", value = "enable", matchIfMissing = true)
@EnableConfigurationProperties(TenantProperties.class)
public class YudaoTenantAutoConfiguration {@Beanpublic TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantProperties properties) {return new TenantLineInnerInterceptor(new TenantDatabaseInterceptor(properties));}@Beanpublic FilterRegistrationBean<TenantContextWebFilter> tenantContextWebFilter() {FilterRegistrationBean<TenantContextWebFilter> registrationBean = new FilterRegistrationBean<>();registrationBean.setFilter(new TenantContextWebFilter());registrationBean.setOrder(WebFilterOrderEnum.TENANT_CONTEXT_FILTER);return registrationBean;}
}

作用

  • 条件化装配多租户相关组件

  • 注册两个核心组件:

    • TenantLineInnerInterceptor: MyBatis Plus SQL拦截器,自动拼接租户条件或判断是否跳过租户判断拼接;

    • TenantContextWebFilter: 租户上下文过滤器,将租户id设置到上下文;

2. TenantContextHolder (租户上下文持有者)
/*** 多租户上下文 Holder** @author 芋道源码*/
public class TenantContextHolder {/*** 当前租户编号*/private static final ThreadLocal<Long> TENANT_ID = new TransmittableThreadLocal<>();/*** 是否忽略租户*/private static final ThreadLocal<Boolean> IGNORE = new TransmittableThreadLocal<>();/*** 获得租户编号** @return 租户编号*/public static Long getTenantId() {return TENANT_ID.get();}/*** 获得租户编号。如果不存在,则抛出 NullPointerException 异常** @return 租户编号*/public static Long getRequiredTenantId() {Long tenantId = getTenantId();if (tenantId == null) {throw new NullPointerException("TenantContextHolder 不存在租户编号!可参考文档:"+ DocumentEnum.TENANT.getUrl());}return tenantId;}public static void setTenantId(Long tenantId) {TENANT_ID.set(tenantId);}public static void setIgnore(Boolean ignore) {IGNORE.set(ignore);}/*** 当前是否忽略租户** @return 是否忽略*/public static boolean isIgnore() {return Boolean.TRUE.equals(IGNORE.get());}public static void clear() {TENANT_ID.remove();IGNORE.remove();}}

TenantContextHolder 是一个用于管理多租户系统中租户信息的工具类,它使用 ThreadLocal 来存储当前线程的租户上下文信息。

主要作用

  1. 存储当前租户ID:在同一个线程中共享租户ID

  2. 控制租户过滤:可以临时忽略租户过滤条件

  3. 线程安全:使用 TransmittableThreadLocal 保证线程安全且支持线程池场景

核心功能说明

  • getTenantId(): 获取当前租户ID(可能为null)

  • getRequiredTenantId(): 获取当前租户ID(如果不存在则抛出异常)

  • setTenantId(Long): 设置当前租户ID

  • isIgnore(): 检查是否忽略租户过滤

  • setIgnore(Boolean): 设置是否忽略租户过滤

  • clear(): 清理当前线程的租户上下文

为什么用 TransmittableThreadLocal?

与普通ThreadLocal的区别:

特性ThreadLocalTransmittableThreadLocal
线程隔离
线程池支持❌(会丢失上下文)✅(自动传递)
异步任务支持
性能开销略高(需注册TTL修饰的线程池)

实际工作流程示例

// 主线程设置值
TenantContextHolder.setTenantId(1L);  // TENANT_ID线程变量=1
TenantContextHolder.setIgnore(false); // IGNORE线程变量=false// 当新线程/线程池任务执行时:
// 普通ThreadLocal会丢失这两个值
// TransmittableThreadLocal会自动传递这两个值// 子线程中仍然可以获取:
Long tenantId = TenantContextHolder.getTenantId(); // 得到1
boolean ignore = TenantContextHolder.isIgnore();  // 得到false

为什么要这样设计?

  1. 线程安全:避免使用全局变量导致的多线程冲突

  2. 调用链透明:在方法调用链中无需显式传递tenantId参数

  3. 灵活控制:可以通过IGNORE临时绕过租户隔离

  4. 兼容异步:支持线程池、异步任务等复杂场景

注意事项

  1. 必须成对使用:set后必须clear/remove,否则会导致:

    • 内存泄漏(特别是线程池场景)

    • 租户信息错乱(后续请求可能读到错误的tenantId)

  2. 默认值规则

    • TENANT_ID.get() 未设置时返回null

    • IGNORE.get() 未设置时返回null,isIgnore()做了null安全处理(返回false)

  3. 性能影响

    • TransmittableThreadLocal比普通ThreadLocal稍慢

    • 在极高并发场景需要考虑性能损耗

3. TenantDatabaseInterceptor (数据库拦截器)
  • 实现MyBatis Plus的多租户SQL改写

  • 根据配置决定哪些表需要忽略租户条件

public class TenantDatabaseInterceptor implements TenantLineHandler {private final Set<String> ignoreTables = new HashSet<>();@Overridepublic Expression getTenantId() {return new LongValue(TenantContextHolder.getRequiredTenantId());}@Overridepublic boolean ignoreTable(String tableName) {return TenantContextHolder.isIgnore() || ignoreTables.contains(SqlParserUtils.removeWrapperSymbol(tableName));}
}
阶段五:响应返回
  • 响应通过过滤器链反向返回

  • TenantContextWebFilter 的 finally 块清理租户上下文

完整请求处理流程(以/users/getById为例)

阶段一:过滤器初始化

  1. YudaoTenantAutoConfiguration 被Spring加载

    • 创建 TenantLineInnerInterceptor 并注册到MyBatis

    • 注册 TenantContextWebFilter 到Servlet容器

阶段二:请求处理

  1. TenantContextWebFilter

    • 从请求头提取 tenant-id

    • 设置到 TenantContextHolder

    • System.out.println("+++++tenant-id:"+request.getHeader("tenant-id")+"+++++");

  2. Spring Security 过滤器链

    • 执行基础安全处理(认证、会话管理等)

  3. TenantSecurityWebFilter

    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {// 1. 检查登录用户权限// 2. 校验租户ID是否存在(非忽略URL)// 3. 通过tenantFrameworkService校验租户状态
    }
  4. Controller处理

    @GetMapping("/getById")
    public User getById(@RequestParam Long id) {return userService.getById(id);
    }
  5. MyBatis Plus拦截器

    • TenantLineInnerInterceptor 拦截SQL执行

    • 调用 TenantDatabaseInterceptor:

      • 检查 ignoreTable() 决定是否添加租户条件

      • 如需添加,调用 getTenantId() 获取当前租户ID

  6. 响应返回

    • 过滤器链反向执行

    • TenantContextWebFilter 清理线程局部变量

关键顺序解析

  1. 租户上下文设置必须最先执行

    • TenantContextWebFilter 设置了最高优先级(最早执行)

    • 确保后续过滤器能获取正确的租户信息

  2. 安全校验在上下文设置之后

    • TenantSecurityWebFilter 依赖已设置的租户上下文

    • 在Spring Security基础过滤器之后执行

  3. SQL拦截最后执行

    • MyBatis拦截器不是Servlet Filter

    • 在Service层执行SQL时触发

HTTP Request│▼
TenantContextWebFilter (设置租户上下文)│▼
Spring Security Filters│▼
TenantSecurityWebFilter (租户权限校验)│▼
Controller│▼
Service ───┐│      │▼      ▼
Mapper → TenantLineInnerInterceptor (SQL改写)│▼
DB


http://www.mrgr.cn/news/100319.html

相关文章:

  • 零基础上手Python数据分析 (24):Scikit-learn 机器学习初步 - 让数据预测未来!
  • Goland终端PowerShell命令失效
  • 【Linux网络】构建HTTP响应与请求处理系统 - HttpResponse从理解到实现
  • Kafka 面试,java实战贴
  • JAVA多线程(8.0)
  • 多系统安装经验,移动硬盘,ubuntu grub修改/etc/fstab 移动硬盘需要改成nfts格式才能放steam游戏
  • 【Linux网络】打造初级网络计算器 - 从协议设计到服务实现
  • Deep Reinforcement learning for real autonomous mobile robot navigation
  • Linux下编译并打包MNN项目迁移至其他设备
  • Qt ModbusSlave多线程实践总结
  • AAAI2016论文 UCO: A Unified Cybersecurity Ontology
  • 刚体运动 (位置向量 - 旋转矩阵) 笔记 1.1~1.3 (台大机器人学-林沛群)
  • 云原生--核心组件-容器篇-3-Docker核心之-镜像
  • C++ 同步原语
  • Swift与iOS内存管理机制深度剖析
  • 前端职业发展:如何规划前端工程师的成长路径?
  • 泰迪杯实战案例学习资料:生产线的故障自动识别和人员配置优化
  • 深度学习驱动下的字符识别:挑战与创新
  • 【股票系统】使用docker本地构建ai-hedge-fund项目,模拟大师炒股进行分析。人工智能的对冲基金的开源项目
  • 来自 Bisheng 关于微调的内容总结