当前位置: 首页 > 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

相关文章:

  • Eigen库入门
  • Day13(前缀和)——LeetCode2845.统计趣味子数组的数目
  • Python Cookbook-6.10 保留对被绑定方法的引用且支持垃圾回收
  • Eigen稀疏矩阵类 (SparseMatrix)
  • Centos7系统防火墙使用教程
  • 某东h5st_5.1(补环境)
  • qt/c++云对象浏览器
  • 文章记单词 | 第46篇(六级)
  • java函数式接口与方法引用
  • 八猴渲染器三维场景实时预览软件 Marmoset Toolbag 5.01 安装包免费下载
  • 山东大学离散数学第九章习题解析
  • C++ 为什么建议类模板定义在头文件中,而不定义在源文件中
  • Nacos详解
  • Python 第 12、13 节课 - 元组和列表
  • Linux基本指令(保姆级教学)
  • 【新技术】Testfy.js v3.0 深度解析与使用指南
  • 关于循环缓冲区
  • MUC基本知识
  • 基于javaweb的SpringBoot小说阅读系统设计与实现(源码+文档+部署讲解)
  • Threejs中顶视图截图
  • 同款瑞幸咖啡竟差了6元,开了会员仍比别人贵!客服回应
  • 理想汽车副总裁刘杰:不要被竞争牵着鼻子走,也不迷信护城河
  • 伊朗阿巴斯港港口爆炸已致47人受伤
  • 比亚迪一季度日赚亿元,净利润同比翻倍至91.55亿元
  • 贵州通报9起群众身边不正之风和腐败问题典型案例
  • 云南舞蹈大家跳暨2025年牟定“三月会”昨天开幕