多线程中的Guice和RequestScoped行为

时间:2013-12-10 21:27:56

标签: multithreading guice

我正在使用Guice的RequestScoped和Provider,以便在用户请求期间获取某些类的实例。目前工作正常。现在我想在后台线程中做一些工作,使用在请求期间创建的相同实例。 但是,当我调用Provider.get()时,guice会返回错误:

Error in custom provider, com.google.inject.OutOfScopeException: Cannot 
access scoped object. Either we are not currently inside an HTTP Servlet 
request, or you may have forgotten to apply     
com.google.inject.servlet.GuiceFilter as a servlet 
filter for this request.

afaik,这是因为Guice使用线程局部变量来跟踪当前请求实例,因此无法从与正在处理的线程不同的线程调用Provider.get()请求。

如何使用Provider在新线程中获取相同的实例?有可能实现这个写自定义范围吗?

3 个答案:

答案 0 :(得分:6)

我最近解决了这个问题。你可以做一些事情。首先,阅读ServletScopes.continueRequest(),它包含一个可调用的,因此它将像当前请求一样执行。但是,这不是一个完整的解决方案,因为它不会转发@RequestScoped个对象,只会转发HttpServletResponse之类的基本内容。那是因为预期@RequestScoped个对象不是线程安全的。你有一些选择:

  • 如果您的整个@RequestScoped层次结构只能通过HTTP响应进行计算,那么您就完成了!您将在另一个线程中获得这些对象的新实例。

  • 您可以使用下面的代码段明确转发所有RequestScoped个对象,但需要注意的是,这些对象都会被热切地实例化。

  • 我的一些@RequestScoped对象无法处理急切的实例化,因为它们仅适用于某些请求。我使用自己的范围@ThreadSafeRequestScoped扩展了以下解决方案,并且只转发了这些解决方案。

代码示例:

public class RequestScopePropagator {
    private final Map<Key<?>, Provider<?>> requestScopedValues = new HashMap<>();

    @Inject
    RequestScopePropagator(Injector injector) {
        for (Map.Entry<Key<?>, Binding<?>> entry : injector.getAllBindings().entrySet()) {
            Key<?> key = entry.getKey();
            Binding<?> binding = entry.getValue();
            // This is like Scopes.isSingleton() but we don't have to follow linked bindings
            if (binding.acceptScopingVisitor(IS_REQUEST_SCOPED)) {
                requestScopedValues.put(key, binding.getProvider());
            }
        }
    }

    private final BindingScopingVisitor<Boolean> IS_REQUEST_SCOPED = new BindingScopingVisitor<Boolean>() {
        @Override
        public Boolean visitScopeAnnotation(Class<? extends Annotation> scopeAnnotation) {
            return scopeAnnotation == RequestScoped.class;
        }

        @Override
        public Boolean visitScope(Scope scope) {
            return scope == ServletScopes.REQUEST;
        }

        @Override
        public Boolean visitNoScoping() {
            return false;
        }

        @Override
        public Boolean visitEagerSingleton() {
            return false;
        }
    };

    public <T> Callable<T> continueRequest(Callable<T> callable) {
        Map<Key<?>, Object> seedMap = new HashMap<>();
        for (Map.Entry<Key<?>, Provider<?>> entry : requestScopedValues.entrySet()) {
            // This instantiates objects eagerly
            seedMap.put(entry.getKey(), entry.getValue().get());
        }

        return ServletScopes.continueRequest(callable, seedMap);
    }
}

答案 1 :(得分:0)

我遇到了完全相同的问题但是以不同的方式解决了它。我在项目中使用jOOQ,并使用请求范围对象和HTTP过滤器实现了事务。

但后来我创建了一个后台任务,它在半夜由服务器生成。由于没有请求范围,注入无效。

好。解决方案很简单:手动创建请求范围。当然没有HTTP请求正在进行,但这不是重点(主要是)。它是请求范围的概念。所以我只需要一个与我的后台任务一起存在的请求范围。

Guice可以轻松创建请求范围:- (void)progressSliderValueChanged:(ASValueTrackingSlider *)slider { if (self.player.currentItem.status == AVPlayerItemStatusReadyToPlay) { NSString *style = @""; CGFloat value = slider.value - self.sliderLastValue; if (value > 0) { style = @">>"; } if (value < 0) { style = @"<<"; } if (value == 0) { return; } self.sliderLastValue = slider.value; [self pause]; CGFloat total = (CGFloat)_playerItem.duration.value / _playerItem.duration.timescale; NSInteger dragedSeconds = floorf(total * slider.value); CMTime dragedCMTime = CMTimeMake(dragedSeconds, 1); NSInteger proMin = (NSInteger)CMTimeGetSeconds(dragedCMTime) / 60; NSInteger proSec = (NSInteger)CMTimeGetSeconds(dragedCMTime) % 60; NSInteger durMin = (NSInteger)total / 60; NSInteger durSec = (NSInteger)total % 60; NSString *currentTime = [NSString stringWithFormat:@"%02zd:%02zd", proMin, proSec]; NSString *totalTime = [NSString stringWithFormat:@"%02zd:%02zd", durMin, durSec]; if (total > 0) { self.controlView.videoSlider.popUpView.hidden = !self.isFullScreen; self.controlView.currentTimeLabel.text = currentTime; if (self.isFullScreen) { [self.controlView.videoSlider setText:currentTime]; dispatch_queue_t queue = dispatch_queue_create("com.playerPic.queue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ NSError *error; CMTime actualTime; CGImageRef cgImage = [self.imageGenerator copyCGImageAtTime:dragedCMTime actualTime:&actualTime error:&error]; CMTimeShow(actualTime); UIImage *image = [UIImage imageWithCGImage:cgImage]; CGImageRelease(cgImage); dispatch_async(dispatch_get_main_queue(), ^{ [self.controlView.videoSlider setImage:image ? : ZFPlayerImage(@"ZFPlayer_loading_bgView")]; }); }); } else { self.controlView.horizontalLabel.hidden = NO; self.controlView.horizontalLabel.text = [NSString stringWithFormat:@"%@ %@ / %@",style, currentTime, totalTime]; } }else { slider.value = 0; } }else { slider.value = 0; } }

ServletScope.scopeRequest
哦,你可能需要一些注射。请务必在那里使用提供程序,您希望将其创建延迟到创建的范围内。

答案 2 :(得分:-1)

在Guice 4中更好地使用ServletScopes.transferRequest(Callable)