如何在 Spring 应用程序中拥有每线程但可重用的对象 (PubNub)?

How to have per-thread but reusable objects (PubNub) in a Spring app?

我正在 Spring 引导应用程序中连接到 PubNub。 From the documentation, it's ok to re-use PubNub objects 但最好每个线程有一个。 Spring Boot 中每个线程存储和检索一个对象的合适方法是什么?

这就是使用 ThreadLocal 在 Spring 中每个线程存储和检索对象的方式,此示例基于 Spring 自己的 ThreadLocalSecurityContextHolderStrategy用于每个线程存储SecurityContext

此外,请查看 InheritableThreadLocal,尤其是当您的代码启动新线程时,例如Spring 的 @Async 注释,它具有在创建子线程时传播现有或创建新线程局部值的机制。

import org.springframework.util.Assert;

final class ThreadLocalPubNubHolder {

    private static final ThreadLocal<PubNub> contextHolder = new ThreadLocal<PubNub>();

    public void clearContext() {
        contextHolder.remove();
    }

    public PubNub getContext() {
        PubNub ctx = contextHolder.get();

        if (ctx == null) {
            ctx = createEmptyContext();
            contextHolder.set(ctx);
        }

        return ctx;
    }

    public void setContext(PubNub context) {
        Assert.notNull(context, "Only non-null PubNub instances are permitted");
        contextHolder.set(context);
    }

    public PubNub createEmptyContext() {
        // TODO - insert code for creating a new PubNub object here
        return new PubNubImpl();
    }
}

您可以使用@SergeyB 上面提到的 Java ThreadLocal 支持。另一种方法是为您的 bean 使用 Thread Scope:

@Configuration
public class AppConfig {
    //Register thread scope for your application
    @Bean
    public BeanFactoryPostProcessor beanFactoryPostProcessor() {
        return beanFactory -> beanFactory.registerScope("thread", new SimpleThreadScope());
    }
}

然后就可以创建线程作用域的bean了(下面会解释代理模式):

@Scope(value = "thread", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
public class PubSubContext {

    private PubSub pubSub;

    public PubSub getPubSub() {
        return pubSub;
    }

    public void setPubSub(PubSub pubSub) {
        this.pubSub = pubSub;
    }

    @PostConstruct
    private void init() {
        // TODO: your code for initializing PubSub object
        log.info("RequiredMessageHeaders started in thread " + Thread.currentThread().getId());
    }

    @PreDestroy
    private void destroy() {
        // TODO: your code for cleaning resources if needed
        log.info("RequiredMessageHeaders destroyed in thread " + Thread.currentThread().getId());
    }
}

最后一步是在你需要的地方注入PubSubContext:

@Controller
public class YourController {

    // Spring will inject here different objects specific for each thread. 
    // Note that because we marked PubSubContext with proxyMode = ScopedProxyMode.TARGET_CLASS we do not need to use applicationContext.get(PubSubContext.class) to obtain a new bean for each thread - it will be handled by Spring automatically.
    @Autowired
    private PubSubContext pubSubContext;

    @GetMapping
    public String yourMethod(){
        ...
        PubSub pubSub = pubSubContext.getPubSub();
        ...
    }

}

使用这种方法,您可以更进一步,将您的 PubSubContext 标记为 @Lazy,这样在 yourMethod 内部请求之前不会创建它:

@Controller
public class YourController {

    @Lazy
    @Autowired
    private PubSubContext pubSubContext;

    ...
}

如您所见,PubSubContext 基本上完成了 ThreadLocal 所做的工作,但利用了 Spring 功能。

希望对您有所帮助!

首先,

因为在多个线程中使用单个 PubNub 对象是安全的,

仅当您需要提高性能时才需要多个 PubNub 对象

如果是这种情况 - 我的建议是组织 pool 个 PubNub 对象(用例非常接近数据库连接用例)。