从RequestMap中提取RequestScoped CDI bean

时间:2013-11-10 19:48:32

标签: model-view-controller jsf-2 cdi managed-bean

我目前正在将Ed Burns的JSF 2.0教科书中的Virtual Trainer示例应用程序从JSF Managed Beans转换为CDI。到目前为止我遇到的大多数问题都与范围和忘记正确注入有关,但现在我正在努力克服最近与从RequestMap中提取CDI Bean(实际上是实体类)有关的障碍。从目前为止我能够确定的情况来看,似乎可以通过使用Map实现提供的样板.get(String managedbeanname)方法非常简单地提取请求范围的Managed Bean。但是,使用CDI,Bean在CreationalContextImp实例中被Weld包装,我无法提取我真正追求的对象,即使它已经确认它存在于RequestMap中。我可以简单地从RequestMap访问一个代理对象,但是在调用.get(“user”)之后它会恢复为null,我怀疑它会做多少好事,因为代理中的字段都是无效的。

我发现BalusC的一篇帖子讨论了使用过滤器类来访问SessionScope中保存的CDI bean(How do I get a SessionScoped CDI bean from inside a Filter?)这似乎有点涉及 - 是否有更简单的解决方案?我也非常清楚我可能正在捣乱管理Bean与CDI策略的范围/混合,所以请随意让我直截了当...我也有点不确定直接以这种方式使用实体Bean而不是使用正面。这会导致我/可能会在以后引起我的问​​题吗?

环境:JEE7,Glassfish 4,Netbeans 7.4,Maven EE Web原型 有关使用Managed Beans的原始代码已被注释掉。

抽象支持bean类:

@RequestScoped
public abstract class AbstractBacking implements Serializable {

    //@ManagedProperty(value="#{facesContext}")
    private FacesContext facesContext;

    //@ManagedProperty(value="#{requestScope}")
    private Map<String, Object> requestMap;

    //@ManagedProperty(value="#{sessionScope}")
    private Map<String, Object> sessionMap;

    @PostConstruct
    public void init() {
        this.facesContext = FacesContext.getCurrentInstance();
        this.sessionMap = FacesContext.getCurrentInstance().getExternalContext().getSessionMap();
        this.requestMap = FacesContext.getCurrentInstance().getExternalContext().getRequestMap();
    }

注册页面支持bean:

@Named
@RequestScoped
public class RegisterBacking extends AbstractBacking implements Serializable {

    private Object password1;

    @Inject
    private User newUser;

    public String registerUser() {
        String result = null;
        User newUser = (User) getRequestMap().get("user");
        // set the password into the user, because we know the validator was
        // successful if we reached here.
        newUser.setPassword((String) getRequestMap().get("password1"));
        try {
            UserRegistry.getCurrentInstance().addUser(newUser);
            // Put the current user in the session
            setCurrentUser(newUser);
            // redirect to the main page
            result = "/user/allEvents?faces-redirect=true";
        } catch (EntityAccessorException ex) {
            getFacesContext().addMessage(null,
                    new FacesMessage("Error when adding user"
                            + ((null != newUser) ? " " + newUser.toString() : "") + "."));

        }

        return result;

    }

用户实体bean:

@Entity
@Named
@Table(name = "Users")
@RequestScoped
@NamedQueries({
    @NamedQuery(name = "user.getAll", query = "select u from User as u"), // @NamedQuery(name = "user.getTrainers", query = "select u from User as u where u.trainer = TRUE"),
// @NamedQuery(name = "user.getUsersForTrainerId", query = "select u from User as u where u.personalTrainerId = :theId")
})

public class User extends AbstractEntity implements Serializable {

    protected String firstName;
    protected String lastName;
    @Temporal(TemporalType.DATE)
    protected Date dob;
    protected String sex;
    protected String email;
    private String serviceLevel = "medium";
    @Column(name = "userid", nullable = false)
    private String userid;
    private String password;
    private boolean trainer;
    private List<Long> subscribedEventIds;
    private Long personalTrainerId;

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
    private List<TrainingSession> sessions;

    private boolean sessionsInitialized = false;

    public User() {
        this.init();
    }

    public User(String firstName, String lastName,
            String sex, Date dob, String email, String serviceLevel,
            String userid, String password, boolean isTrainer) {
        this.init();
        this.setFirstName(firstName);
        this.setLastName(lastName);
        this.setSex(sex);
        this.setDob(dob);
        this.setEmail(email);
        this.setServiceLevel(serviceLevel);
        this.setUserid(userid);
        this.setPassword(password);
        this.setTrainer(isTrainer);
    }
.....
Getters/setters/etc
.....

注册页面:

<!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:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
<body>
<ui:composition template="template.xhtml">
    <ui:define name="content">
<h:form prependId="false">
    <h:panelGrid columns="3">

        <h:outputLabel for="fname" value="First Name:" />
        <h:inputText label="First Name"
                     id="fname" value="#{user.firstName}"
                     required="true"/>
        <h:message for="fname" />

        <h:outputLabel for="lname" value="Last Name:" />
        <h:inputText label="Last Name"
                     id="lname" value="#{user.lastName}"
                     required="true"/>
        <h:message for="lname" />

        <h:outputLabel for="sex" value="Sex:" />
        <h:selectOneRadio label="Sex"
                          id="sex" value="#{user.sex}" required="true">
          <f:selectItem itemLabel="Male" itemValue="male" />
          <f:selectItem itemLabel="Female" itemValue="female" />
        </h:selectOneRadio>
        <h:message for="sex" />

        <h:outputLabel for="dob" value="Date of Birth:" />
        <h:panelGroup>
            <h:inputText label="Date of Birth"
                     id="dob" value="#{user.dob}" required="true">
                <f:convertDateTime pattern="MM-dd-yy" />
            </h:inputText> (mm-dd-yy)
        </h:panelGroup>
        <h:message for="dob" />

        <h:outputLabel for="email" value="Email Address:" />
        <h:inputText label="Email Address"
                     id="email" value="#{user.email}" required="true" />
        <h:message for="email" />

        <h:outputLabel for="slevel" value="Service Level:" />
        <h:selectOneMenu label="Service Level" id="slevel"
                         value="#{user.serviceLevel}">
          <f:selectItem itemLabel="Medium" itemValue="medium" />
          <f:selectItem itemLabel="Basic" itemValue="basic" />
          <f:selectItem itemLabel="Premium" itemValue="premium" />
        </h:selectOneMenu>
        <h:message for="slevel" />

        <h:outputLabel for="userid" value="Userid:" />
        <h:inputText required="true" id="userid" value="#{user.userid}" />
        <h:message for="userid" />

        <h:outputLabel for="password" value="Password:" />
        <h:inputSecret required="true" id="password" 
                       validator="#{registerBacking.validatePassword1}"
                       value="#{requestScope.password1}" />
        <h:message for="password" />

        <h:outputLabel for="password2" value="Retype Password:" />
        <h:inputSecret required="true" id="password2" value="#{requestScope.password2}"
                       validator="#{registerBacking.validatePassword2}" />
        <h:message for="password2" />


    </h:panelGrid>

    <p><h:commandButton value="Register" 
                     action="#{registerBacking.registerUser}" /></p>
</h:form>

    </ui:define>
</ui:composition>
</body>
</html>

1 个答案:

答案 0 :(得分:4)

事实上,你似乎混合了CDI Beans和JSF Beans,说实话,你翻译的例子看起来很奇怪,我只是把它全部扔出窗外。

因为,JSF也有它自己的依赖注入,除非你有一个非常具体的用例(比如说ServletFilter),否则你不会自己从作用域地图中提取东西。

澄清CDI使用http://en.wikipedia.org/wiki/Inversion_of_control原则并自行“提取”内容是一种反模式。

此外CDI bean由CDI容器创建和管理(几乎在所有情况下都是Weld或OWB),你根本无法从externalContext检索CDI bean,因为它是JSF的上下文,而且它是一个完全独立的东西。

因此,JSF创建的实例不能在CDI Bean中注入,反之亦然。无论如何,正如我先前所说的那样自己提取东西是不好的做请参阅此列表中的#10:http://zeroturnaround.com/rebellabs/watch-out-for-these-10-common-pitfalls-of-experienced-java-developers-architects/

所以无论你想要什么,你只需使用@Inject(可能使用限定符)。

解决方法对于Inject不起作用的特殊情况:

  • CDI上下文在当前主题中不可用。
  • 当前实例未受CDI管理

第一个场景会在您拥有ThreadLocal时发生,例如QuartzJob。 EJB容器可能提供获取上下文线程的方法,但如果您使用普通的servlet容器(tomcat),或者生成的线程没有CDI上下文,那么您必须自己附加此信息。为此,请使用Deltaspike CDI Control。

http://deltaspike.apache.org/container-control.html

该示例未更新,今天您应该使用DependentProvider来获取ContextControl。

我的博客示例:http://www.kildeen.com/blog/post/2013-10-11/Batch%20Jobs%20in%20EE/

第二种情况是ServletContainer创建实例并且您在普通容器(例如Tomcat)上时,JSF创建了实例,无论如何。

然后,注射不起作用。解决方法是使用Deltaspike的BeanProvider。 http://deltaspike.apache.org/core.html它可以在当前实例中强制注入,更有用的是它可以为你提取bean。如果你这样做并且仍然遇到第一个场景,你将得到一个例外,可能是ContextNotActiveException

要正确使用JSF和CDI,我建议使用TomEE。这是一个apache项目,因此它是开源的。邮件列表和irc频道非常活跃,而且它正在崛起,即使现在也非常棒。

当然这是我的观点,其他人更喜欢其他解决方案,例如WildFly,使用Tomcat / Jetty,Glassfish等自行构建。

现在,JSF的所有常规规则都适用(必须使用getter / setter等约定)。要将bean公开给EL表达式(JSF使用),您必须将其标记为@Named。如果类名为myBean,则默认名称为MyBean

现在是范围。始终对应用程序中的每个bean使用@RequestScoped。如果您遇到麻烦,因为该范围很短,您应该考虑它应该是多长时间,如果您想要保留的数据对其他bean感兴趣,并且您希望它在所有浏览器选项卡中都可用。

如果是用户信息,那么很多豆子很有可能。因此,我们创建了一个名为WebUser的新类。用户可能希望他的信息在整个会话期间保留(您可能也需要使用该对象跟踪他)。因此,我们使用正确包中的@SessionScoped,必须导入javax.enterprise.context.SessionScoped。但突然之间,一些逻辑不应该在标签之间共享,因此您需要更精细的范围。 CDI附带@ViewScoped(CDI 1.1,JSF 2.2)和@ConversationScoped,但您很可能迟早需要来自Myfaces CODI的示波器。现在,大多数CODI已经在deltaspike但不是范围。然而,它们已被拆分,如何导入它们在这里解释:http://os890.blogspot.fr/2013/07/add-on-codi-scopes-for-deltaspike.html你迟早会在Deltaspike中看到它们。

所以现在你可以使用很多不同的范围,你可以明智地选择它。只应从数据库中读取一次的内容可以与@ApplicationScoped一起存储。

例如,您可能希望从数据库中读取系统设置并将它们存储在SettingManager中,这是一个用@ApplicationScoped注释的CDI bean。

WelcomeBean@RequestScoped@Model,负责登录和帐户创建。要确定是否可以创建新帐户,可能如下所示:

@ViewScoped
@Named
public class WelcomeBean {

    @Inject
    private SettingManager settingManager;


    private boolean allowCreateAccount;


    public boolean isAllowCreateAccount() {
        return allowCreateAccount;
    }


   // login and create account here

    @PostConstruct
    private void init() {
        allowCreateAccount = settingManager.getBooleanSetting("registrationOpen");
    }
}

Facelet创建帐户按钮如下所示:

<h:commandButton action="#{welcomeBean.createAccount}" value="login" disabled="#{welcomeBean.allowCreateAccount}"/>

现在,当用户执行登录时,您可能希望将此信号指示为事件。阅读CDI活动。真正最先进的例子与这个简单的例子有很大不同。

相关问题