Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 529
pupeno Avatar asked Oct 19 '17 17:10

pupeno


2 Answers

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();
    }
}
like image 154
SergeyB Avatar answered Oct 14 '22 14:10

SergeyB


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!

like image 4
Danylo Zatorsky Avatar answered Oct 14 '22 16:10

Danylo Zatorsky