在传统设置中,我的应用程序执行简单的操作:接受请求,加载一些与发送该请求的特定用户相关的元数据(来自沉重的休息调用),并将此元数据存储在threadlocal bean上下文中(即一个简单的hashMap支持上下文的类)这样我就可以轻松地从代码路径中的任何其他类访问这些细节,只要我注入bean。这与在我的API生命周期中注入的所有其他非threadlocal bean协调工作,因此没有任何问题。
然而,一旦我尝试做一些聪明的事情(在一个单独的线程中启动一些任务来模仿“火灾和忘记”类型的调用)我注意到代码路径(在这个新线程中)依赖于threadlocal bean我被烧了,因为之前持有的hashMap(加入上下文的类)的细节现在已经消失了。对我来说,为什么它们已经蒸发是完全合理的,因为毕竟,一旦我开始一个新线程,它就没有自动“克隆”父线程的上下文到新子线程的上下文中。
我的问题是,是否存在任何现有的Spring框架机制(或简称Java),我可以利用它来确保为我发起的新子线程保留此信息?将原始父线程的上下文“deepClone”到新孩子中是多么的微不足道?如果这是一种天真的方式,有人可以推荐一些其他设置,我可以在开始线程之前参与吗?鉴于我正在寻求仅针对请求的范围维护此上下文,我不确定是否可以使用something like Spring's object pooling(毕竟,我不希望出现新请求和回收不同的情况/旧用户对新用户的偏好。)
答案 0 :(得分:0)
我认为你可以采用三种方式。
使用'InheritableThreadLocal`
这是ThreadLocal
的子类和默认的JDK功能。
可继承的线程本地文件将自动从其父级的子线程中复制。
看起来像个好人。
编辑:如果您的代码看起来像这样,那么它就是替代品。
public class Test {
// I guess this, in your actual code, is an injected Reference
InheritableThreadLocal<String> expensiveData = new InheritableThreadLocal<String>();
public void work(String userName){
expensiveData.set(computeExpensiveData(userName)); // whatever that is
Worker workUnit = new Worker();
workUnit.userData = expensiveData; // Spring does some variant of this (I guess)
Thread child = new Thread(workUnit); // This is the key line
child.start(); // As long as the parent thread has a clean "expensiveData"
// The child thread will have too.
}
protected static class Worker implements Runnable {
// This is injected too...
protected InheritableThreadLocal<String> userData = null;
@Override
public void run() {
userData.get(); // Returns spwaning thread's version
// Work ...!
return;
}
}
}
如果Worker
个实例(此处为userData
变量)未创建,则Thread
的{{1}}存在实际风险。从这里开始,但在游泳池中重复使用。
使用请求/会话范围的bean
如果您访问数据的方式是通过某种bean,也许您可以将此bean的生命周期与您的webapp会话或请求联系起来。你每次都会自动获得一个新的bean,但你会在你的范围内保持同一个bean。
重构为缓存
Spring有@Cachable注释,允许对给定方法调用进行相当容易的缓存。再一次,您可以将数据访问权限设置为@Cachable,并将缓存配置调整为“按用户” 编辑:Spring缓存使用代理,因此它是一个正在运行的metod-leve拦截。 我称这是一个因素,因为它需要你做一些工作,但在我看来 是一种更干净,更像春天的事情。从上面的相同示例开始,关键的区别在于您必须创建一个新的Bean,它将实现:
child
实现只是一个细节(您可能会从当前代码中复制/粘贴它)。 这个bean会被注入(它是Spring的术语中的协作者)。
这看起来像这样。请注意,您不必操纵ThreadLocal或生成线程或其他任何东西。让每个协作者完成它的工作,将它们与Spring连接在一起,然后使用@Cacheable进行优化(或者你认为合适的其他缓存方式)。然后缓存是工作流程的“实现细节”。
public static interface ExpensiveUserDataCalculator {
@Cacheable // With the proper configuration tuning (eviction ? TTL ? size ?)
public String computeExpensiveData(String userName);
}
最后两个approches在Spring Reference Guide中有自己的部分,所以如果需要,请查看它。