如何通过编程方式注册JSF托管bean?

11

我希望能够在Servlet的init()方法中通过编程的方式向应用程序作用域注册/添加Managed Bean类。如何使用JSF 1.2实现这一功能?

5个回答

19

对于所有范围的托管 Bean,从您的应用程序以编程方式完成此操作是不太可能的。BalusC已经指出了如何为应用程序作用域托管 Bean 完成此操作。

查看了 Mojarra 2.1(JSF 2.1 实现)中托管 Bean 的注册方式后,我们发现没有太多优雅的选项可以用于编程式注册会话范围和请求范围的 Bean。简单来说,您只能调用特定于实现的类,或者自己创建和销毁即管理这些 Bean,而不是依靠 JSF 实现来管理。

将请求和会话范围填充为 Bean(非托管方式)

注意-这被称为“非托管方式”,因为您正在构建 Bean,而不是容器。注释,例如 @PostConstruct @PreDestroy 不起作用,除非您自己处理它们并调用适当的方法。甚至依赖注入也无法使用。

EL 表达式始终在运行时计算,因此它为您提供足够的机会在评估之前填充范围与 Bean(如果您有机会这样做,这会导致自找麻烦)。在 Mojarra(及其他一些 JSF 实现)中,EL 解析器将依赖 ScopeHandler(或等效类)的服务来解决 EL 表达式值。Mojarra 使用 ApplicationScopeHandlerRequestScopeHandler SessionScopeHandler 类从不同的范围获取值。

您可以在创建新会话后或 JSF 实现处理请求之前填充会话和请求范围的内容。

最好使用 HttpSessionListener 来填充会话范围,例如:

HttpSession session = request.getSession(false);
session == null ? null : session.setAttribute("<keyname>", new Bean());

keyname必须与在EL表达式中引用bean所使用的值匹配。

类似地,您可以使用以下方式(最好在过滤器中完成)填充请求范围:

ServletRequest request = ... // get the reference to the servlet request object
request.setAttribute("<keyname>", new Bean());
如果您需要了解其工作原理,可以查看类com.sun.faces.context.SessionMapcom.sun.faces.context.RequestMapcom.sun.faces.context.ApplicationMap,以查看上下文映射是如何在内部进行管理的,并由SessionScopeHandlerRequestScopeHandlerApplicationScopeHandler类使用,这些类是com.sun.faces.mgbean.BeanManager类的静态内部类。而BeanManager类则包含托管bean的注册信息,接下来的一节将讨论如何“突破”Mojarra的注册过程。
使用Mojarra类注册bean
Mojarra实现中的托管bean注册是通过com.sun.faces.mgbean.BeanManager类的public void register(ManagedBeanInfo beanInfo)方法完成的。仅使用JSF或Servlet API访问BeanManager类并不容易。但是,在Mojarra中有一个ApplicationAssociate类创建了BeanManager实例,并可以使用getCurrentInstance()方法访问。Thomas的另一个答案已经演示了如何以编程方式注册托管bean。
ApplicationAssociate.getCurrentInstance().getBeanManager().register(...)

上述方法存在一个注意事项。在Servlet的init方法中,这种方法不太可能起作用,因为getCurrentInstance方法依赖于ThreadLocal变量来检索ApplicationAssociate实例。线程本地变量是由com.sun.faces.application.WebappLifecycleListener类初始化的,因此您必须复制WebappLifecycleListener类使用的机制,即调用ApplicationAssociate getInstance(ServletContext context)方法,以访问ApplicationAssociate实例。因此,下面的代码可能会(因为我没有尝试过使用它)更好,如果你愿意使用Mojarra特定的类:

ServletContext sc = ... //get the ServletContext reference;
ApplicationAssociate.getInstance(sc).getBeanManager().register(...)

你必须仍然注意由于这种机制可能导致的一些问题,因为有可能在你的Servlet之前没有加载或初始化某些Mojarra类和实例。因此,我建议尝试使用比使用的load-on-startup值更高的值来配置你的servlet。


1
Vineet,你完成了这个主题,真是太有价值了。只有一个绿色的勾勾,有点可惜啊。 :) - Zeemee
@Mulmoth,我不介意。不过总有点赞按钮 :) - Vineet Reynolds

14

在 Servlet 的 init() 方法中

因此,它涉及到一个非 JSF 请求。在这里,FacesContext#getCurrentInstance() 将返回 null,因此对您没有用处。

了解到 JSF 应用程序范围的管理 bean 基本上是作为 ServletContext 的属性存储的。在 init() 方法中,您可以通过继承的getServletContext()方法来获取ServletContext。所以,以下代码应该可以完成:

@Override
public void init() {
    getServletContext().setAttribute("managedBeanName", new BackingBean());
}

就是这样。它将在JSF中可用,通过#{managedBeanName}


1
你知道自2011年以来在编程方面的bean注册是否有任何变化吗?或者他们至少是否考虑在Java 8中将此功能添加到标准中?我找不到任何可以确认或否认这一点的信息。 - abc

3
尝试使用FacesContext.currentInstance().getExternalContext().getApplicationMap().put(name, bean);,将托管bean实例放入地图中,使用您想在表达式中使用的名称。
编辑:
要注册bean,请尝试调用:ApplicationAssociate.getCurrentInstance().getBeanManager().register(...)并传递您填写的ManagedBeanInfo

这只会将Bean放入应用程序范围,它不会被动态注册为托管Bean [除非已经标记]。 - jmj
4
请注意,在普通的基础servlet中是没有FacesContext对象的,因此使用getCurrentInstance()方法会返回null。我不确定ApplicationAssociate对象是否可用,但这样做实际上是一种不好的编程习惯,因为它将代码与特定的JSF实现(在本例中为Mojarra)紧密耦合。您应该尽量避免导入/使用特定于实现的代码,并优先使用抽象API进行编程(就像您为JDBC、Servlet等所做的一样)。 - BalusC
@BalusC 是的,使用抽象API进行编程更可取。只要我能使用表达式等访问它,我也不会在意应用程序关联是否知道该bean。只是 @Mulmoth 要求 register,而 @Joshi 表示注册意味着更多地将其放入应用程序范围内,这点我并不太在意。 - Thomas

1
下面的代码使用FacesContext正确注册托管bean,但需要servlet请求和响应。您可以使用该代码并在servlet中懒惰地初始化,而不是在init期间进行初始化。
用法:
UserBean ub = (UserBean) 
    Example.getBean(servletRequest, servletResponse, "user", UserBean.class);

来源:

import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
import javax.faces.FactoryFinder;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.context.FacesContextFactory;
import javax.faces.lifecycle.LifecycleFactory;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

class Example {

    public static Object getBean(HttpServletRequest request, HttpServletResponse response, String beanName, Class expectedType){
        FacesContext ctx = getFacesContext(request, response);
        ValueExpression vex = ctx.getApplication().getExpressionFactory().createValueExpression(ctx.getELContext(), "#{"+beanName+"}", expectedType);
        return vex.getValue(ctx.getELContext());
    }

    private static FacesContext getFacesContext(HttpServletRequest request, HttpServletResponse response) {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        if (facesContext == null) {
            facesContext = ((FacesContextFactory) FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY)).
                getFacesContext(request.getSession().getServletContext(), request, response, 
                ((LifecycleFactory) FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY))
                .getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE));

            InnerFacesContext.setFacesContextAsCurrentInstance(facesContext);
            facesContext.setViewRoot(facesContext.getApplication().getViewHandler().createView(facesContext, ""));
        }
        return facesContext;
    }

    private abstract static class InnerFacesContext extends FacesContext {
        protected static void setFacesContextAsCurrentInstance(FacesContext facesContext) {
            FacesContext.setCurrentInstance(facesContext);
        }
    }
}

-1

假设我已经使用注册了我的Bean:

ApplicationAssociate.getInstance(sc).getBeanManager().register(...)

现在它运行得很好,但是当服务器重新启动时,我的bean被销毁了,我该如何在启动时注册相同的bean。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接