【发布时间】:2021-09-07 19:20:16
【问题描述】:
我有一个多租户 springboot (2.4.5) 应用程序 - 我将tenantId 存储在 ThreadLocal 存储中。我为 Hibernate 过滤器启用了加载时间编织。
Link to springboot and multitenancy
当一个 HTTP 请求进来时,流,在高层次上,是 servletfilter->TenantFilterAspect(事务开始时)-> REST Api。 servletFiler 设置tenantId,由TenantFilterAspect 访问,然后在REST api 中运行查询时,hibernate 应用租户过滤器。
如果我从命令行运行应用程序,一切都会按预期运行。 但是,如果我从 intellij(最终 2021.1)运行它,则 threadlocal 变量在 Aspect 中为 null,但在 REST API 中正确。
即我在过滤器中设置它并立即打印tenantId - 它是正确的 - 当在方面打印时,它是不正确的,当在 REST API 中打印时它又是正确的。
public class JwtAuthTokenFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
/* Tenant ID hardcoded in the example */
TenantContext.setCurrentTenant(2);
SecurityContextHolder.getContext().setAuthentication(authentication);
/* Print ThreadID and value of thread local here */
/* thread local value is 2 */
}
}
filterChain.doFilter(request, response);
}
@Aspect
public class TenantFilterAspect {
@Pointcut("execution (* org.hibernate.internal.SessionFactoryImpl.SessionBuilderImpl.openSession(..))")
public void openSession() {
}
@AfterReturning(pointcut = "openSession()", returning = "session")
public void afterOpenSession(Object session) {
if (session != null && Session.class.isInstance(session)) {
Long currentTenant = TenantContext.getCurrentTenant();
/* Thread ID is same as in the filter but thread local value is null */
org.hibernate.Filter filter = ((Session) session).enableFilter("tenantFilter");
filter.setParameter("tenantId", currentTenant);
}
}
}
/* @Transactional is at the class level - transaction is started before it gets here */
@PostMapping("/invoices/getPage")
public PagingDTO getInvoices(@RequestBody PagingDTO request) {
Long currentTenant = TenantContext.getCurrentTenant();
/* Thread ID is same as in the filter & aspect and thread local value is 2 */
.....
}
public class TenantContext {
private static ThreadLocal<Long> currentTenant = new InheritableThreadLocal<>();
public static Long getCurrentTenant() {
return currentTenant.get();
}
public static void setCurrentTenant(Long tenant) {
currentTenant.set(tenant);
}
}
命令行(带有额外的换行符以提高可读性)是
java
-javaagent:/home/x/.m2/repository/org/aspectj/aspectjweaver/1.9.6/aspectjweaver-1.9.6.jar
-jar target/webacc-0.0.1-SNAPSHOT.jar
在 Intellij 中,VM 选项的设置类似
-javaagent:/home/x/.m2/repository/org/aspectj/aspectjweaver/1.9.6/aspectjweaver-1.9.6.jar
对于这种奇怪行为的任何帮助都将深表感激 - 我在这里有点无能为力。在这两种情况下,加载时间编织似乎都是正确的(在方面进行日志记录工作),除了在 IntelliJ 情况下,访问 Threadlocal 会在方面返回 NULL
**
更新 2
** 我已根据要求添加了最少的代码,以便在https://github.com/AnishJoseph/Threadlocal-Issue 重现该问题。说明在自述文件中。
更新 3
基于下面 kriegaex 的出色分析,我对与开发工具相关的 Spring 类加载进行了一些挖掘。
Link to Spring's Customizing restart loader
现在,修复相当简单 - 我将方面代码放在不同的模块中,并从重新加载中排除了该 jar。现在一切正常。
【问题讨论】:
-
我启用了 spring secutity - 我尝试访问 SecurityContextHolder.getContext() - 这也显示为 null。我试过这个,因为 SecurityContextHolder 也使用 ThreadLocal。所以看起来 threadlocal 在方面之前和之后都可用,但在方面内不可用
-
有趣的问题,但我想重现这个问题。请在 GitHub 上提供一个最小的 Maven 项目。我也使用 IntelliJ IDEA,所以我应该能够重现命令行和 IDEA 的情况。我的假设是您解释的一件或多件事情与现实有所不同。
-
在原帖中添加了maven项目的链接
-
那不是一个可点击的链接,它是一个克隆 URL。我为你修好了。请下次小心点。
标签: spring-boot intellij-idea aspectj thread-local hibernate-filters