Как иметь в потоке, но многоразовые объекты (PubNub) в приложении Spring?

я подключаюсь к PubNub в приложении Spring Boot. из документации можно повторно использовать объекты PubNub но лучше иметь по одному на поток. Каков подходящий метод для хранения и извлечения одного объекта на поток в Spring Boot?

4 ответов


это, как вы будете хранить и извлекать объект на поток весной, используя ThreadLocal, этот пример основан на собственном ThreadLocalSecurityContextHolderstrategy для хранения SecurityContext в каждом потоке.

кроме того, взгляните на InheritableThreadLocal особенно, если ваш код запускает новый поток, например Spring's @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();
    }
}

вы можете использовать поддержку Java ThreadLocal, как упоминалось выше @SergeyB. Другой способ сделать это-использовать область потока для ваших бобов:

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

затем вы можете создать боб с областью потока (режим прокси будет объяснен ниже):

@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, только если вам нужно увеличить производительность

Если это ваш случай - мое предложение будет организовать бассейн объектов PubNub (случай использования довольно близок к случаю использования соединения с БД).


Я предполагаю, что мы имеем дело с подключением к PubNub для входного запроса к серверу tomcat, когда вы говорите о новом потоке.

Я считаю, что хороший способ справиться с такой ситуацией-создать новый объект PubNub для каждого запроса и позволить spring обрабатывать жизненный цикл объекта.

наличие приведенного ниже определения компонента с областью прототипа позволяет создавать новый объект PubNub для каждого поиска в приложении контекст.

@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
PubNub getPubNub() {
    PNConfiguration pnConfiguration = new PNConfiguration();
    pnConfiguration.setSubscribeKey("SubscribeKey");
    pnConfiguration.setPublishKey("PublishKey");
    pnConfiguration.setSecure(false);

    PubNub pubnub = new PubNub(pnConfiguration);
    return pubnub;
}

класс обслуживания, в котором вы хотите использовать созданный выше объект PubNub, будет выглядеть так:

class PubNubService{
@Autowired
ApplicationContext context; 
...
public void process()
{

PubNub pubNub = context.getBean(PubNub.class);

pubNub.publish()....
//your code for publish goes here
}
} 

Я надеюсь, что мое предположение о сценарии / usecase, который вы хотите решить, правильно. Напишите нам больше информации, если предположение неверно / нуждается в коррекции. Держи нас в курсе.