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

3

8

I'm connecting to PubNub in a Spring Boot application. From the documentation, it's ok to re-use PubNub objects but it's better to have one per thread. What's the appropriate method to store and retrieve one object per thread in Spring Boot?

Collective answered 19/10, 2017 at 17:6 Comment(4)
What is your use case here, from a PubNub perspective. Using multiple instances is typically the exception so you want to be sure it is what you want to do.Gild
@CraigConover: the PubNub documentation recommends one instance per thread. It's linked from the question.Collective
Yes, I know, I am just asking the use case for this. Often, there is no need for multiple instances.Gild
@CraigConover: I'm not sure exactly what information would answer your request other than show you what I'm building: dashman.tech. PubNub is used to send messages from the servers to the clients about screenshots being required or being ready, about new clients, etc.Collective
A
8

This is how you'd store and retrieve an object per thread in Spring using ThreadLocal, this example is based on Spring's own ThreadLocalSecurityContextHolderStrategy which is used to store SecurityContext per thread.

Also, take a look at InheritableThreadLocal especially if your code spins up new thread, e.g. Spring's @Async annotation, it has mechanisms to propagate existing or create new thread local values when creating child threads.

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();
    }
}
Alphonsa answered 22/10, 2017 at 22:33 Comment(0)
P
4

You can use Java ThreadLocal support as mentioned above by @SergeyB. Another way to do it is to use Thread Scope for your beans:

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

Then you can create a bean with a thread scope (proxy mode will be explained below):

@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());
    }
}

The last step is to inject PubSubContext where you need it:

@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();
        ...
    }

}

With this approach, you could go even further and mark your PubSubContext as @Lazy, so it won't be created until it's requested inside yourMethod :

@Controller
public class YourController {

    @Lazy
    @Autowired
    private PubSubContext pubSubContext;

    ...
}

As you see PubSubContext does basically what ThreadLocal does but leveraged by Spring capabilities.

Hope it helps!

Potboiler answered 29/10, 2017 at 8:55 Comment(1)
Is that a typo pubsub instead of pubnubPatter
C
0

First of all,

As it is safe to use single PubNub object in multiple threads,

You need multiple PubNub objects ONLY if you need performance increase

If that is your case - my suggestion will be to organize pool of PubNub objects (the use case is quite close to DB connection use case).

Comus answered 29/10, 2017 at 13:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.