java - 升级到 JAVA 8 后 Threadlocal 在 tomcat 上的不当行为

标签 java spring tomcat thread-local

我使用本地线程来存储用户请求的特定功能(例如浏览器代理)它过去在 JAVA 7 上运行良好,但现在在升级到 JAVA 8 之后在某些情况下我看到来自 android 浏览器的请求被处理得好像它的来自 iOS 浏览器,即使它被正确检测为 android 浏览器,但后来在处理请求时它被另一个线程本地值替换!我不确定这里缺少什么有人可以帮助我吗?我的环境设置(之前/之后)升级是:

  • tomcat 8 之前和之后。
  • JAVA 从 7 升级到 8。
  • Spring 从 4.1.7 升级到 4.2.5
  • Spring Security 从 3.2.3 升级到 4.03

我有一个看起来像这样的安全过滤器:

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.GenericFilterBean;

public class AuthenticationTokenProcessingFilter extends GenericFilterBean {
private final IdentityService identityService;

public AuthenticationTokenProcessingFilter(IdentityService userService) {
    this.identityService = userService;
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    SecurityManager.manager().clearManager();
    HttpServletRequest httpRequest = this.getAsHttpRequest(request);
    String agent = httpRequest.getHeader("User-Agent");
    SecurityManager.manager().setAgent(agent);

    ...

    chain.doFilter(request, response);
}

}

安全管理器看起来像这样:

import com.appseleon.platform.web.shared.CrossAppConstants;

public class SecurityManager {
private static SecurityManager manager;

private final ThreadLocal<String> agentContext = new ThreadLocal<String>();

private SecurityManager() {
    manager = this;
}

public void clearManager() {
    agentContext.set(null);
}

public static SecurityManager manager() {
    return manager;
}


public String getAgent() {
    String os = agentContext.get();
    if (os == null) {
        os = CrossAppConstants.DEFAULT_OS;
    }
    return os;
}

public void setAgent(String agent) {
    System.out.println("### os detected: " + agent);
    agentContext.set(agent);
}

}

最后,在设置代理之后,我在代码的各个区域调用了 SecurityManager 来获取当前用户代理:

SecurityManager.manager().getAgent()

谁能帮我找出这个问题的原因,或者更可靠的替代方法来实现这个问题?

提前致谢:)

最佳答案

对于初学者,您的 SecurityManager 存在缺陷,您不应获取实例,而应使用 static 直接获取/设置 ThreadLocal 上的值。当前,当在不同的类加载器中加载内容时,您可能会遇到问题,即未检测到单例。

public abstract class SecurityManager {

  private static final ThreadLocal<String> agentContext = new ThreadLocal<String>();

  private SecurityManager() { }

  public static void clearManager() {
      agentContext.set(null);
  }


  public static String getAgent() {
      String os = agentContext.get();
      if (os == null) {
          os = CrossAppConstants.DEFAULT_OS;
      }
      return os;
  }

  public static void setAgent(String agent) {
      System.out.println("### os detected: " + agent);
      agentContext.set(agent);
  }

}

然后直接在this上调用get/set方法。

在您的过滤器中,您应该将 filterChain.doFilter 包装在 try/finally block 中,在 finally 中始终清除本地线程。

try {
    chain.doFilter(request, response);
} finally {
    SecurityManager.clearManager();
}

此外,您可能不想扩展 GenericFilterBean,而是扩展 OncePerRequestFilter,这确保此功能仅被调用一次(如果您的逻辑中有一些转发,则特别有用)和它仅适用于 HttpServletRequest 类型的请求,可以为您节省一些代码。

public class AuthenticationTokenProcessingFilter extends OncePerRequestFilter {
...

    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse ress, FilterChain chain) throws IOException, ServletException {

        String agent = req.getHeader("User-Agent");
        SecurityManager.setAgent(agent);

        ...
        try {
            chain.doFilter(request, response);
        } finally {
            SecurityManager.clearManager();
        }
    }
}

这也是 Spring Security 的工作方式,例如 Springs 事务管理(使用静态方法和共享 ThreadLocal)。

关于java - 升级到 JAVA 8 后 Threadlocal 在 tomcat 上的不当行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35865305/

相关文章:

eclipse - tomcat eclipse中的http状态302错误

java - Maven 野蝇 :deploy -P profileX doesn't trigger the right profile

java - 使用RJB(Ruby Java Bridge)的OpenNLP中的java.lang.NullPointerException

java - 有谁知道 Hibernate 和 java 是否可以有效地与 Access 一起工作?

eclipse - 在 eclipse 上的 pom.xml tomcat 服务器上添加 org.apache.poi 后未启动

java - 从 Spring 应用程序上下文获取 bean 时出现 ClassCastException,但在 Autowiring 时则不会出现 ClassCastException

python - 为什么 django jython 连接到 MySQL 数据库会给出 "Communication Link Failure"?

iis - 有人可以告诉我请求是如何在以下 IIS-Tomcat isapiredirect(2.0) 配置中路由的吗?

java - 如何修复 OpenJPA 查询 SQL CACHE 错误?

java - 此类中 getter/setter 的用途