【问题标题】:Accessing CDI SessionScoped bean doesn't work in Java 8 parallel stream访问 CDI SessionScoped bean 在 Java 8 并行流中不起作用
【发布时间】:2017-09-02 01:33:40
【问题描述】:

我无法理解为什么此代码不起作用。基本上我想在 parallelStream() 函数期间从 CDI ViewScoped bean 访问 CDI SessionScoped bean,我得到了这个异常:

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

这是在 Wildfly 10.1 中运行的。

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"));
    }
}

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";
    }
}

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>

堆栈跟踪:

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)

我们有理论认为会话可能是特定于线程的,但没有确凿的证据。

也很好奇是否有解决方法。实际代码比这要复杂得多,因此我们无法预先加载 SessionController 结果而不失去并行流的好处。

【问题讨论】:

  • 我还没有看到一个真实的案例,即并行流对性能至关重要
  • 在这种情况下,我将在每个函数中触发不同的 SQL 查询,因此我只需要等待最长的查询完成我们的 SQL Server。
  • 并行流并不意味着用于 IO 操作。相反,它们旨在用于执行并行计算,例如通过将列表划分为 N 个部分并并行执行 N 个部分列表的总和,最后对子结果求和来对非常长的列表的元素求和

标签: java jsf jsf-2 java-8 cdi


【解决方案1】:

我认为您遇到的是使用并行流意味着您将在多个线程中运行。现在,这是 CDI 和上下文的问题,因为您需要上下文传播 - 例如在您当前(例如)会话上下文处于活动状态的主线程中,但是当您创建另一个线程时,它在那里不活动。

Chapter 6.3 in spec 对其进行了更深入的描述,但为您提供一个简短的故事 - 默认情况下无法将上下文传播到其他线程。并且有充分的理由 - 这将非常昂贵(同步)并且您需要解决非常奇怪的情况,例如让一个线程失效会话,因此在其他线程对其进行操作时停用会话上下文。还有更多这样的情况。

此外,没有内置的解决方法。不过,您可以做的是实现自己的范围,或增强现有的会话范围,但我猜这会非常复杂。

【讨论】:

  • 谢谢,我没有意识到范围与线程的联系如此紧密。感谢您为这个答案付出的时间和精力。
【解决方案2】:

如果您想在 Java EE 中并行化查询,我建议您使用 ManagedExecutorService。并行流使用的分叉连接池用于并行计算,而不是 IO。

当涉及到 CDI 上下文传播时,假设您使用 WELD 作为 CDI 实现,您可能会发现 BoundSessionContext 很有用。阅读更多http://john-ament.blogspot.fi/2014/01/bridging-netty-resteasy-and-weld.html

也许您可以在没有绑定会话上下文的情况下进行管理。只需从会话上下文中收集所有必要的数据并将其传递给工作线程。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-04-16
    • 1970-01-01
    • 2019-08-04
    • 2013-09-25
    • 2013-06-30
    • 2016-08-02
    相关资源
    最近更新 更多