JSF 请求范围的 bean 不断在每个请求上重新创建新的有状态会话 bean?

Posted

技术标签:

【中文标题】JSF 请求范围的 bean 不断在每个请求上重新创建新的有状态会话 bean?【英文标题】:JSF request scoped bean keeps recreating new Stateful session beans on every request? 【发布时间】:2012-02-11 19:33:42 【问题描述】:

我正在使用 JSF、PrimeFaces、Glassfish 和 Netbeans 构建我的第一个 Java EE 应用程序。因为我是新手,所以我可能错误地处理了核心问题。

核心问题:我想安全地维护用户的信息。关于是否应该在 JSF 会话 bean 或有状态会话 EJB 中维护它似乎存在冲突的想法。我正在尝试使用有状态会话 EJB,因为这样更安全。

问题是我的应用程序似乎正在创建该 bean 的多个实例,而我希望它创建一个并重新使用它。如果我刷新页面,它将运行@PostConstruct@PostActivate 3 次,它们都具有不同的实例。然后,当我重新部署应用程序时,它们都会被销毁。

我是否误解了它应该如何工作或配置错误?

我将尝试展示一个精简的代码示例:

basic.xhtml:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:c="http://java.sun.com/jsp/jstl/core">
    <h:head>
        <title>Facelet Title</title>
    </h:head>
    <h:body>
        Hello from Facelets
        <c:if test="#loginController.authenticated">
            Authenticated
        </c:if>
        <c:if test="#loginController.authenticated">
            Authenticated
        </c:if>
        <c:if test="#loginController.authenticated">
            Authenticated
        </c:if>
    </h:body>
</html>

LoginController:

@Named(value = "loginController")
@RequestScoped
public class LoginController implements Serializable 

    @EJB
    private UserBeanLocal userBean;

    public boolean isAuthenticated() 
        return userBean.isAuthenticated();
    


UserBean(不包括UserBeanLocal接口)

@Stateful
public class UserBean implements UserBeanLocal, Serializable 

    boolean authenticated = false;

    @PostConstruct
    @PostActivate
    public void setup()
        System.out.println("##### Create user Bean: "+this.toString());
    

    @Override
    public boolean isAuthenticated() 
        System.out.println("########## Authentication test is automatically passing.");
        authenticated = true;//hard coded for simplicity.
        return authenticated;
         

    @PrePassivate
    @PreDestroy
    public void cleanup()
        System.out.println("##### Destroy user Bean");
    


最后,这是刷新 3 次后 Glassfish 的输出:

INFO: ##### Create user Bean: boundary._UserBean_Serializable@2e644784
INFO: ########## Authentication test is automatically passing.
INFO: ########## Authentication test is automatically passing.
INFO: ########## Authentication test is automatically passing.
INFO: ##### Create user Bean: boundary._UserBean_Serializable@691ae9e7
INFO: ########## Authentication test is automatically passing.
INFO: ########## Authentication test is automatically passing.
INFO: ########## Authentication test is automatically passing.
INFO: ##### Create user Bean: boundary._UserBean_Serializable@391115ac
INFO: ########## Authentication test is automatically passing.
INFO: ########## Authentication test is automatically passing.
INFO: ########## Authentication test is automatically passing.

【问题讨论】:

虽然这很旧(并且得到了很好的回答),但我认为值得一提的是,“当 [您] 重新部署应用程序时它们都会被销毁”的原因是因为当您注入有状态会话时bean 使用@EJB 由应用程序触发@PreDestroy VIA a @Remove 方法,否则它只是挂起。这就是为什么你的输出永远不会达到cleanup() 的原因。另请参阅 this demo/test app 了解一些 @EJB 与 CDI @Inject 生命周期案例。 对不起:错字test app here。 【参考方案1】:

有状态会话 bean (SFSB) 并非您所想的那样。您似乎认为它们的行为类似于会话范围的 JSF 托管 bean。这是不真实的。 EJB 中的“会话”一词的含义与您所想到的 HTTP 会话完全不同。

EJB 中的“会话”必须在事务上下文中进行解释。只要客户端存在,事务(基本上是数据库会话)就存在于 SFSB 的情况下。 SFSB 的客户端在您的特定示例中不是网络浏览器,而是 JSF 托管 bean 实例本身,正是注入 SFSB 的那个。由于您已将 JSF 托管 bean 置于请求范围内,因此 SFSB 将与 JSF 托管 bean 一起在每个 HTTP 请求上重新创建。

例如,尝试将 JSF 托管 bean 放在视图范围内。例如,视图范围对于同一页面上的多步骤表单很有用。每次当视图回发到自身时,相同的 JSF 托管 bean 实例将被重用,并且此实例使您可以访问 SFSB 的 same 实例,就像创建 bean 时一样,即 不在别处分享。只要客户端(视图范围的 JSF 托管 bean)存在,SFSB 事务就会存在。

无状态会话 bean (SLSB) 可以在其他地方共享,但这无关紧要,因为无论如何它都打算被视为无状态。这个“特性”节省了容器创建和存储它们的时间和内存。容器只能有一个池。更重要的是,注入到视图、会话或应用程序范围的 JSF 托管 bean 中的 SLSB 实例不一定需要在每个 HTTP 请求上引用与创建 JSF 托管 bean 期间完全相同的实例。它甚至可以是完全不同的实例,具体取决于容器池中的可用实例。只要 SLSB 上的单个方法调用,事务就会存在(默认情况下)。

也就是说,SFSB 不适合您“记住登录用户”的特定情况。它“更安全”真的没有意义。只需将 JSF 托管 bean 放在会话范围内,让它自己记住登录用户,并使用 SLSB 执行任何业务操作(例如与 DB 交互),并且仅在需要时使用 SFSB real 有状态会话 bean(我假设您现在了解它们到底是什么 :))。

另见:

When is it necessary or convenient to use Spring or EJB3 or all of them together? Why Stateless session beans? JSF Service Layer

【讨论】:

谢谢,这更有意义。以下是我正在阅读的关于我为什么要这样做的文章。 oio.de/public/java/…datadisk.co.uk/html_docs/ejb/ejb3_session_beans.htm 第二篇是一篇优秀的文章。我现在看到你来自“更安全”的地方。我认为您现在很好地意识到它应该在事务上下文中解释,而不是在 webapp 上下文中。第一个提出了很好的观点,但整体的解释和例子都很薄弱。我建议将this article 作为起点。 太棒了,我喜欢阅读。再次感谢您的额外反馈。 如果 SFSB IS 是托管 bean 怎么办? (用命名和状态注释)。我希望它通过请求(即像@SessionScoped)保持状态,但看起来情况并非如此。 @gpilotino:使用 CDI @SessionScoped 对其进行注释。我只是不建议这种紧耦合。【参考方案2】:

据我从调查和使用中了解到,自 JSF 以来,EJB SFSB 对 Web 应用程序没有用处,Spring 提供了有用的注释来保持每个用户的会话。但是在 web 服务和 RPC 方法调用调用所需的应用程序正在运行的情况下,EJB SFSB 需要保持每个用户的会话(传输)。

【讨论】:

以上是关于JSF 请求范围的 bean 不断在每个请求上重新创建新的有状态会话 bean?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 JSF 中正确使用组件绑定? (会话范围 bean 中的请求范围组件)

JSF 2 应用程序中的默认托管 Bean 范围是啥?

JSF 2.x @ViewScoped 托管 bean 线程安全吗?

xml 如果使用JSF作为MVC框架,则使请求和会话范围spring bean可用

在每个 ajax 请求上重建 JSF 视图

将 GET 请求参数放入 @ViewScoped bean