当前位置: 首页 > 知识库问答 >
问题:

lambda中的Spring请求作用域bean

穆城
2023-03-14

我有一个Spring应用程序,它根据请求上下文注入某些bean。在这个例子中,它是Facebook bean。

@RestController
@RequestMapping("facebook")
public class FacebookInjectionController {

    @Autowired
    private Facebook facebook;

    @Autowired
    private UserRepository userRepository;

    @RequestMapping(method = RequestMethod.GET)
    public List<String> blah() {
        String firstName = facebook.userOperations().getUserProfile().getFirstName();
        return Arrays.asList(firstName);
    }

    @RequestMapping(method = RequestMethod.GET, value = "complex")
    public List<String> blah2() {
        UserJwt principal = (UserJwt) SecurityContextHolder.getContext().getAuthentication().getPrincipal();

        Stream<User> stream = StreamSupport.stream(userRepository.findAll().spliterator(), true);

        return stream.filter(u -> u.getUid().equals(principal.getUid()))
                .map(u ->
                        facebook.userOperations().getUserProfile().getFirstName()
                ).collect(Collectors.toList());
    }

}

此代码将正常运行,但偶尔会出现以下错误:

2017-02-09 01:39:59.133错误40802-[o-auto-1-exec-2]o.a.c.c.c.[/]。[dispatcherServlet]:Servlet。路径为[]的上下文中servlet[dispatcherServlet]的service()引发异常[请求处理失败;嵌套异常为org.springframework.beans.factory.BeanCreationException:创建名为“scopedTarget.facebook”的bean时出错:当前线程的作用域“Request”未处于活动状态;如果要从单例引用此bean,请考虑为其定义作用域代理;嵌套异常为java.lang.IllegalStateException:未找到线程绑定请求:是否在实际web请求之外引用请求属性,还是在最初接收线程之外处理请求?如果您实际上在web请求中操作,并且仍然收到此消息,那么您的代码可能在DispatcherServlet/DispatcherPortlet之外运行:在这种情况下,请使用RequestContextListener或RequestContextFilter公开当前请求。]有根本原因

java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
    at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131)
    at org.springframework.web.context.request.AbstractRequestAttributesScope.get(AbstractRequestAttributesScope.java:41)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:340)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
    at org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:35)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:187)
    at com.sun.proxy.$Proxy137.userOperations(Unknown Source)
    at com.roomsync.FacebookInjectionController.lambda$blah2$5(FacebookInjectionController.java:43)
    at com.roomsync.FacebookInjectionController$$Lambda$10/2024009478.apply(Unknown Source)
    at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
    at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
    at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502)
    at java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:747)
    at java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:721)
    at java.util.stream.AbstractTask.compute(AbstractTask.java:316)
    at java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:731)
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:902)
    at java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1689)
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1644)
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)

我尝试了多种解决方案(包括Spring MVC:如何在生成的线程中使用请求范围的bean?),但没有一个有效。

有没有办法将请求范围的bean传递给lambda或其他线程?

去什么地方https://stackoverflow.com/users/1262865/john16384说我已将配置更改为:

   @Bean
@Scope(value = "inheritableThreadScope", proxyMode = ScopedProxyMode.INTERFACES)
public ConnectionRepository connectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    if (authentication == null) {
        throw new IllegalStateException("Unable to get a ConnectionRepository: no user signed in");
    }
    return getUsersConnectionRepository(connectionFactoryLocator).createConnectionRepository(authentication.getName());
}

@Bean
@Scope(value="inheritableThreadScope", proxyMode=ScopedProxyMode.INTERFACES)
public Facebook facebook(ConnectionFactoryLocator connectionFactoryLocator) {
    Connection<Facebook> connection = connectionRepository(connectionFactoryLocator).findPrimaryConnection(Facebook.class);

    return connection != null ? connection.getApi() : null;
}

@Bean
@Scope(value = "inheritableThreadScope", proxyMode = ScopedProxyMode.INTERFACES)
public ExecutorService fbExecutor () {
    return Executors.newSingleThreadExecutor();
}

控制器现在看起来像:

@RestController
@RequestMapping("facebook")
public class FacebookInjectionController {

    @Autowired
    private Facebook facebook;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private ExecutorService fbExecutor;

    @RequestMapping(method = RequestMethod.GET)
    public List<String> blah() {
        String firstName = facebook.userOperations().getUserProfile().getFirstName();
        return Arrays.asList(firstName);
    }

    @RequestMapping(method = RequestMethod.GET, value = "complex")
    public List<String> blah2() throws ExecutionException, InterruptedException {
        UserJwt principal = (UserJwt) SecurityContextHolder.getContext().getAuthentication().getPrincipal();

        Stream<User> stream = StreamSupport.stream(userRepository.findAll().spliterator(), true);

        Future<List<String>> submit = fbExecutor.submit(() -> stream.filter(u -> u.getUid().equals(principal.getUid()))
                .map(u ->
                        facebook.userOperations().getUserProfile().getFirstName()
                )
                .collect(Collectors.toList()));

        return submit.get();
    }

}

我还有以下配置:

@Configuration
public class BeanFactoryConfig implements BeanFactoryAware {
    private static final Logger LOGGER = Logger.getLogger(BeanFactoryConfig.class);

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        if (beanFactory instanceof ConfigurableBeanFactory) {

//            logger.info("MainConfig is backed by a ConfigurableBeanFactory");
            ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;

            /*Notice:
             *org.springframework.beans.factory.config.Scope
             * !=
             *org.springframework.context.annotation.Scope
             */
            org.springframework.beans.factory.config.Scope simpleThreadScope = new SimpleThreadScope() {
                @Override
                public void registerDestructionCallback(String name, Runnable callback) {
                                        RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
                    attributes.registerDestructionCallback(name, callback, 3);
                }
            };
            cbf.registerScope("inheritableThreadScope", simpleThreadScope);

            /*why the following? Because "Spring Social" gets the HTTP request's username from
             *SecurityContextHolder.getContext().getAuthentication() ... and this
             *by default only has a ThreadLocal strategy...
             *also see https://stackoverflow.com/a/3468965/923560
             */
            SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);

        }
        else {
//            logger.info("MainConfig is not backed by a ConfigurableBeanFactory");
        }
    }
}

即使这样,有时也会出现错误:

{
    "timestamp": 1486686875535,
    "status": 500,
    "error": "Internal Server Error",
    "exception": "java.util.concurrent.ExecutionException",
    "message": "org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.facebook' defined in class path resource [com/roomsync/config/SocialConfig.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.social.facebook.api.Facebook]: Factory method 'facebook' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.connectionRepository': Scope 'inheritableThreadScope' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.",
    "path": "/facebook/complex"
}

因此,我似乎仍然缺少激活作用域并将线程本地上下文复制到它的部分

共有3个答案

洪安顺
2023-03-14

这就是我在fork连接的线程中传输请求bean的方法。该示例仅用于说明。

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
// org.slf4j:slf4j-api:1.7.30
import org.slf4j.MDC;
// org.springframework:spring-web:5.2.12.RELEASE
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

class Scratch {

    public static void main(String[] args) {
        RequestAttributes context = RequestContextHolder.currentRequestAttributes();
        Map<String, String> contextMap = MDC.getCopyOfContextMap();
        List<String> list = new ArrayList<>();
        list.parallelStream().map(id -> {
            try {
                // copy all required for spring beans
                RequestContextHolder.setRequestAttributes(context);
                MDC.setContextMap(contextMap);

                // ************************************
                // Spring request beans usage goes here
                // ************************************
                return 1;
            } finally {
                // clean all from thread local
                MDC.clear();
                RequestContextHolder.resetRequestAttributes();
            }
        })
                .collect(Collectors.toList());

    }
} 
和丰羽
2023-03-14

是否需要并行处理流?这会导致lambda可能在另一个线程中执行。

Stream stream=StreamSupport.stream(userRepository.findAll(). spliterator(), false);

张宝
2023-03-14

发生了两件事:

1) Java流使用公共的Fork/Join池来并行执行事情。这些线程不是由Spring框架(或您)创建的。

2) 使用ThreadLocal支持请求范围的bean。

这意味着如果不是由Spring创建的线程尝试访问请求范围的bean,则不会找到它,因为线程不知道它(它不在ThreadLocal中)。

为了解决这个问题,您需要控制哪些线程用于流。一旦实现了这一点,就可以制作请求范围bean的副本以用于子线程。在线程完成其任务后,您还需要再次清理它们,否则您可能会留下bean,在该线程上执行的下一个任务可能会看到这些bean。

要更改并行流使用的线程,请参阅:Java 8并行流中的自定义线程池

我认为,如何正确配置Spring以将请求范围的bean传播到您已经找到的子线程。

 类似资料:
  • 目前正在用Spring Boot 2.0.0.M4、Spring 5.0.0.RC4和Reactor 3.1.0.RC1进行反应性编程。 如果没有,这是计划好的吗? 谢谢你抽出时间。

  • 在我的Spring Boot应用程序中,我有一个调度器任务,每隔一小时执行一次。在调度器方法中尝试访问请求作用域bean。总是获取异常org.springframework.beans.factory.BeanCreationException。 下面是代码示例。 会有帮助的..谢了。

  • 本文向大家介绍请问Spring中Bean的作用域有哪些?相关面试题,主要包含被问及请问Spring中Bean的作用域有哪些?时的应答技巧和注意事项,需要的朋友参考一下 考察点:框架 参考回答: 在Spring的早期版本中,仅有两个作用域:singleton和prototype,前者表示Bean以单例的方式存在;后者表示每次从容器中调用Bean时,都会返回一个新的实例,prototype通常翻译为原

  • 我试图弄清楚spring是如何将线程安全的请求/会话范围的bean注入控制器组件(即通过方法访问这些bean的单线程和多线程)的 作为例子,考虑<代码> HttpServletRequest 字段,该控件标记为“代码> @ AutoWordEng/代码>注释(我知道将控制器与servlet API配对是不好的,但在学习目的上可以)。我了解到这样的bean是使用CGLib代理的,但仍然无法弄清楚代理

  • 问题内容: 我正在通过jQuery的$ .ajax函数使用的第三方API上调用POST。但是,当我拨打电话时,出现以下错误: 我从这篇文章中看到这可能是Webkit的错误,所以我在Firefox中尝试了此操作(我正在使用Chrome开发),并且得到了相同的结果。 在这篇文章中,我还尝试通过将$.ajax函数的属性设置为并将设置为来使用jsonp。但是,这导致了500个内部服务器错误。 当我使用–d

  • 我们有一个应用程序正在使用。我试图将其更改为较新的版本。如果我没有指定或者如果我指定了一个作用域,那么应用程序可以正常工作。我在请求多个作用域(如)时遇到一个问题,该作用域在以前的版本中适用。 我请求的客户端具有所有权限。 当我使用时,为了获得一个令牌,我经常执行get调用,如 如果我用或来尝试它,它工作得很好,因为或是客户端作用域的一部分。 如果我想在中请求多个作用域,我该怎么做?