如何强制应用程序范围的 bean 在应用程序启动时实例化?

Posted

技术标签:

【中文标题】如何强制应用程序范围的 bean 在应用程序启动时实例化?【英文标题】:How do I force an application-scoped bean to instantiate at application startup? 【发布时间】:2011-04-05 18:12:11 【问题描述】:

我似乎找不到在启动 Web 应用程序时强制实例化/初始化应用程序范围的托管 bean 的方法。似乎应用程序范围的 bean 在第一次访问 bean 时被延迟实例化,而不是在 web 应用程序启动时。对于我的网络应用程序,当第一个用户第一次在网络应用程序中打开一个页面时,就会发生这种情况。

我想避免这种情况的原因是因为在我的应用程序范围的 bean 的初始化过程中发生了许多耗时的数据库操作。它必须从持久存储中检索一堆数据,然后缓存其中的一些数据,这些数据将以 ListItem 元素等的形式经常显示给用户。我不希望所有这些在第一个用户连接时发生,因此造成很长的延迟。

我的第一个想法是使用旧样式的 ServletContextListener contextInitialized() 方法,然后使用 ELResolver 手动请求我的托管 bean 的实例(从而强制进行初始化)。不幸的是,我不能在这个阶段使用 ELResolver 来触发初始化,因为 ELResolver 需要 FacesContext 并且 FacesContext 仅在请求的生命周期内存在。

有没有人知道另一种方法来完成这个?

我正在使用 MyFaces 1.2 作为 JSF 实现,目前无法升级到 2.x。

【问题讨论】:

【参考方案1】:

除了BalusC's answer above,您还可以使用@Startup@Singleton (CDI),例如

//@Named    // javax.inject.Named:       only needed for UI publishing
//@Eager    // org.omnifaces.cdi.Eager:  seems non-standard like taken @Startup below
@Startup    // javax.ejb.Startup:        like Eager, but more standard
@Singleton  // javax.ejb.Singleton:      maybe not needed if Startup is there
//@Singleton( name = "myBean" )  //      useful for providing it with a defined name
@ApplicationScoped
public class Bean 
    // ...

很好地解释了here。 至少在 JPA 2.1 中工作。

【讨论】:

这是一个 EJB 而不是托管 bean,这是完全不同的。 EJB 在后端运行,在前端托管 bean。 EJB 也在事务上下文中运行。 “在 JPA 中工作”这句话很奇怪。 EJB 根本不需要 JPA 即可运行。 @BalusC 我认为您不太正确并且夸大了含义/用法,这在我看来值得商榷。 en.wikipedia.org/wiki/Enterprise_JavaBeans 。 在 JPA 中工作 应该意味着我(仅)在基于 JPA 2.1 的环境中对其进行了测试。在许多现实生活场景/设置中,我会说很难明确地说 - 从抽象/建模的角度来看 - 某个东西是 EJB 还是应用程序范围的托管 bean。所以很有趣的是,尽管从技术和建模的角度来看它对我有用,但为什么按照我的建议去做是不好的。 它本身并不坏,相反。只是从技术上讲,您根本没有以当前形式回答问题。您只是将企业 bean 与托管 bean 混淆了,我只是指出了这一点。 ? :-) 标题完全符合,问题或多或少完全准确?这是我对同样问题的回答,它解决了那里对我的要求。设置可能略有不同(对我来说是 PrimeFaces 5.3 而不是 MyFaces 1.2,这对我来说应该无关紧要) @BalusC:让我们不要脱离话题或过于基于意见,让我们跟踪那些看起来更合适的论点:***.com/a/34889633/1915920我真的很想从你的知识中获利,但不能到目前为止验证您的答案:-/【参考方案2】:

我的第一个想法是使用旧样式的 ServletContextListener contextInitialized() 方法,然后使用 ELResolver 手动请求我的托管 bean 的实例(从而强制进行初始化)。不幸的是,我不能在这个阶段使用 ELResolver 来触发初始化,因为 ELResolver 需要 FacesContext 并且 FacesContext 只存在于请求的生命周期中。

没必要那么复杂。只需实例化 bean 并将其放在应用程序范围内,并使用 same 托管 bean 名称作为键。当 bean 已经存在于作用域中时,JSF 只会重用 bean。在 Servlet API 之上使用 JSF,ServletContext 表示应用程序范围(HttpSession 表示会话范围,HttpServletRequest 表示请求范围,每个都有 setAttribute()getAttribute() 方法)。

应该这样做,

public void contextInitialized(ServletContextEvent event) 
    event.getServletContext().setAttribute("bean", new Bean());

其中"bean" 应与faces-config.xml 中应用程序作用域bean 的<managed-bean-name> 相同。


仅作记录,在 JSF 2.x 上,您只需在 @ApplicationScoped bean 上将 eager=true 添加到 @ManagedBean

@ManagedBean(eager=true)
@ApplicationScoped
public class Bean 
    // ...

它将在应用程序启动时自动实例化。

或者,当您通过 CDI @Named 管理支持 bean 时,请使用 OmniFaces @Eager

@Named
@Eager
@ApplicationScoped
public class Bean 
    // ...

【讨论】:

+1 以获得有效的解决方案。一个小问题:按照规范这样做是否正式可行,还是依赖于一些 JSF 实现细节?我的意思是,JSF 实现可以决定跟踪应用程序 bean 是否以完全不明显的方式实例化,然后重新创建 bean,例如。 @BalusC 这很简单,而且很有效。我避免在 ServletContext 中使用 setAttribute() 方法,因为我认为它会干扰 JSF,但显然不会。 PS:喜欢你在 blogspot.com 上的页面——你关于使用 DataTables 的旧文章很有帮助。 @Jim:不客气。 @ewernli:规范没有明确允许,但也没有明确禁止。然而,规范描述了一个托管 bean 必须在范围内不存在时创建。 @BalusC,如果应用程序范围的 bean 具有 JSF 托管 bean 或 Spring bean 的属性,如何使用完全标记的依赖项对其进行初始化? @org:你需要自己照顾它们。作为一个 hacky 替代方案,您还可以创建一个附加了应用程序范围 bean 的简单视图,并在 contextInitialized() 期间在视图的本地 URL 上调用 URL#openStream()【参考方案3】:

Romain Manni-Bucau 在他的 blog 上发布了一个使用 CDI 1.1 的巧妙解决方案。

诀窍是让 bean 观察内置生命周期范围的初始化,即在本例中为 ApplicationScoped。这也可以用于关机清理。所以一个例子看起来像这样:

@ApplicationScoped
public class ApplicationScopedStartupInitializedBean 
    public void init( @Observes @Initialized( ApplicationScoped.class ) Object init ) 
        // perform some initialization logic
    

    public void destroy( @Observes @Destroyed( ApplicationScoped.class ) Object init ) 
        // perform some shutdown logic
    

【讨论】:

从 GlassFish 4.1 迁移到 Payara 4.1.1.164 时,我遇到了一些奇怪的错误,其中 @ApplicationScoped bean 中的 @PersistenceContext 字段未正确注入使用此模式进行急切初始化的 @ApplicationScoped 字段。出现这样的错误:“没有有效的 EE 环境用于注入 ApplicationScopedStartupInitializedBean”。原来参数必须是ServletContext 类型才能解决这个问题,即public void init( @Observes @Initialized( ApplicationScoped.class ) ServletContext init ) 我将把它作为一个错误提交。你有没有找到关于这个的错误报告或者你自己提交过? github.com/payara/Payara/issues/299 要么是关于其他事情,要么是不够的。 @KarlRichter 不,我没有进一步调查。我认为参数类型的更改可能是通过更严格地应用某些规范或类似的东西来强制执行的。 我担心@Observes @Initialized 在其他地方使用@ApplicationScoped bean 之前仍有可能不完整。我建议不要将任何初始化逻辑放在@Observes 中,并仍然将其留给@PostCreate@Observes @Destroyed 也一样。【参考方案4】:

据我所知,您不能强制在应用程序启动时实例化托管 bean。

也许您可以使用 ServletContextListener,而不是实例化您的托管 bean,而是自己执行所有数据库操作?


另一种解决方案可能是在应用程序启动时手动实例化 bean,然后将 bean 设置为 ServletContext 的属性。

这是一个代码示例:

public class MyServletListener extends ServletContextListener 

    public void contextInitialized(ServletContextEvent sce) 
        ServletContext ctx = sce.getServletContext();
        MyManagedBean myBean = new MyManagedBean();
        ctx.setAttribute("myManagedBean", myManagedBean);
    


在我看来,这远非干净的代码,但它似乎可以解决问题。

【讨论】:

以上是关于如何强制应用程序范围的 bean 在应用程序启动时实例化?的主要内容,如果未能解决你的问题,请参考以下文章

如何(以及何时?)在 JSF 2.0 中删除 Session 范围的 bean

如何在启动程序且 UAC 被禁用时强制提示输入凭据?

如何强制 iCloud 在全新安装/首次启动时同步应用程序的数据?

Ionic / Cordova:如何强制应用程序在启动时刷新,即使它在后台?

应用程序强制关闭时如何重新启动应用程序

spring默认的bean范围是单例,但它是如何在实际应用程序中处理的?