简体   繁体   English

访问CDI SessionScoped bean在Java 8并行流中不起作用

[英]Accessing CDI SessionScoped bean doesn't work in Java 8 parallel stream

I'm having trouble understanding why this code doesn't work. 我无法理解为什么这段代码不起作用。 Basically I want to access a CDI SessionScoped bean from a CDI ViewScoped bean during a parallelStream() function, I get this exception: 基本上我想在parallelStream()函数期间从CDI ViewScoped bean访问CDI SessionScoped bean,我得到以下异常:

WELD-001303: No active contexts for scope type javax.enterprise.context.SessionScoped

This is running in Wildfly 10.1. 这是在Wildfly 10.1中运行的。

The ViewScoped bean: ViewScoped bean:

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import javax.faces.view.ViewScoped;
import javax.inject.Inject;
import javax.inject.Named;

@ViewScoped
@Named
public class TestController implements Serializable {
    private static final long serialVersionUID = 1L;

    @Inject SessionController sessionController;

    public void works() {
        List<Function<String, String>> functions = new ArrayList<>();
        functions.add((String input) -> {
            return sessionController.getSomething();
        });
        functions.add((String input) -> {
            return sessionController.getSomethingElse();
        });
        functions.stream().forEach(f -> f.apply("input"));
    }

    public void doesNotWork() {
        List<Function<String, String>> functions = new ArrayList<>();
        functions.add((String input) -> {
            return sessionController.getSomething();
        });
        functions.add((String input) -> {
            return sessionController.getSomethingElse();
        });
        functions.parallelStream().forEach(f -> f.apply("input"));
    }
}

The SessionScoped bean: SessionScoped bean:

import java.io.Serializable;
import javax.enterprise.context.SessionScoped;
import javax.inject.Named;

@Named
@SessionScoped
public class SessionController implements Serializable {
    private static final long serialVersionUID = 1L;

    public String getSomething() {
        return "something";
    }
    public String getSomethingElse() {
        return "else";
    }
}

The XHTML: XHTML:

<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:p="http://primefaces.org/ui">

    <h:head />

    <h:body>
        <h:form>
            <p:commandButton value="Works" action="#{testController.works}" />
            <br />
            <p:commandButton value="Does Not Work" action="#{testController.doesNotWork}" />
        </h:form>
    </h:body>
</html>

Stacktrace: 堆栈跟踪:

Caused by: org.jboss.weld.context.ContextNotActiveException: WELD-001303: No active contexts for scope type javax.enterprise.context.SessionScoped
    at org.jboss.weld.manager.BeanManagerImpl.getContext(BeanManagerImpl.java:689)
    at org.jboss.weld.bean.ContextualInstanceStrategy$DefaultContextualInstanceStrategy.getIfExists(ContextualInstanceStrategy.java:90)
    at org.jboss.weld.bean.ContextualInstanceStrategy$CachingContextualInstanceStrategy.getIfExists(ContextualInstanceStrategy.java:165)
    at org.jboss.weld.bean.ContextualInstance.getIfExists(ContextualInstance.java:63)
    at org.jboss.weld.bean.proxy.ContextBeanInstance.getInstance(ContextBeanInstance.java:83)
    at org.jboss.weld.bean.proxy.ProxyMethodHandler.getInstance(ProxyMethodHandler.java:125)
    at com.SessionController$Proxy$_$$_WeldClientProxy.getSomething(Unknown Source)
    at com.TestController.lambda$3(TestController.java:33)
    at com.TestController.lambda$5(TestController.java:38)
    at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
    at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:291)
    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:1056)
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)

We have theories that the session may be thread specific, but no hard proof. 我们有理论认为会话可能是特定于线程的,但没有硬性证明。

Also curious if there's a work-around. 如果有一个解决办法,也很好奇。 The real code is much more complex than this so we can't preload the SessionController results beforehand without losing the benefits of the parallel stream. 真正的代码比这复杂得多,所以我们不能事先预加载SessionController结果而不会失去并行流的好处。

What I think you are bumping into is that using parallel streams means you will run in multiple threads. 我认为你要碰到的是使用并行流意味着你将在多个线程中运行。 Now, that is a problem with CDI and contexts, because you would need context propagation - eg in the main thread you currently have (for instance) session context active, but when you create another thread, it is not active there. 现在,这是CDI和上下文的一个问题,因为您需要上下文传播 - 例如,您当前拥有的主线程(例如)会话上下文处于活动状态,但是当您创建另一个线程时,它在那里不活动。

Chapter 6.3 in spec describes it more in-depth but to give you the short story - context propagation to other threads does not work by default . 规范中的第6.3章更深入地描述了它,但是为了给你提供简短的故事 - 默认情况下,向其他线程的上下文传播不起作用 And there are good reasons for that - it would be very costly (synchronization) and you would need to solve loads of very weird situations such as having one of the threads invalidation session, hence de-activating session context while other threads operate on it. 并且有充分的理由 - 它将是非常昂贵的(同步)并且您将需要解决许多非常奇怪的情况,例如具有一个线程无效会话,因此在其他线程对其进行操作时取消激活会话上下文。 And many more such situations. 还有更多这样的情况。

Also, there is no built-in workaround for this. 此外,没有内置的解决方法。 What you could do though, is implementing your own scope, or enhancing existing session scope, but that would be very complicated I guess. 你可以做的是,实现你自己的范围,或者增强现有的会话范围,但我想这会非常复杂。

If you want to parallelize queries in Java EE, I suggest that you use ManagedExecutorService for that. 如果要在Java EE中并行化查询,我建议您使用ManagedExecutorService。 Fork join pool, which parallel streams use, is meant for parallelizing computations, not IO. 并行流使用的fork连接池用于并行计算,而不是IO。

When it comes to CDI context propagation, you may find BoundSessionContext useful, assuming you are using WELD as your CDI implementation. 当谈到CDI上下文传播时,您可能会发现BoundSessionContext很有用,假设您使用WELD作为CDI实现。 Read more at http://john-ament.blogspot.fi/2014/01/bridging-netty-resteasy-and-weld.html 更多信息,请访问http://john-ament.blogspot.fi/2014/01/bridging-netty-resteasy-and-weld.html

Probably you can manage without a bound session context. 您可以在没有绑定会话上下文的情况下进行管理。 Just gather all necessary data from session context and pass it to the worker threads. 只需从会话上下文中收集所有必要的数据并将其传递给工作线程。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM