我正在使用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在新线程中获取相同的实例?有可能实现这个写自定义范围吗?
答案 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)