1

我目前正在将 Ed Burns 的 JSF 2.0 教科书中的 Virtual Trainer 示例应用程序从 JSF Managed Beans 转换为 CDI。到目前为止,我遇到的大多数问题都与范围界定和忘记正确注入有关,但现在我正在努力克服与从 RequestMap 中提取 CDI Bean(实际上是实体类)相关的最新障碍。从我目前能够确定的情况来看,似乎可以通过使用 Map 实现提供的样板 .get(String managedbeanname) 方法非常简单地提取请求范围的托管 Bean。但是,对于 CDI,bean 由 Weld 包装在 CreationalContextImp 实例中,即使我已确认它存在于 RequestMap 中,我也无法提取我真正想要的对象。

我发现 BalusC 的一篇文章讨论了使用过滤器类访问 SessionScope 中保存的 CDI bean(如何从过滤器中获取 SessionScoped CDI bean?)似乎有点涉及 - 有没有更简单的解决方案?我也很清楚,我可能会混淆 Managed Bean vs CDI 策略的范围/混合,所以请随时让我直截了当......我也有点不确定以这种方式直接使用实体 Bean 而不是使用正面。这是否会导致我/以后可能会导致我出现问题?

环境:JEE7、Glassfish 4、Netbeans 7.4、Maven EE Web Archetype 与使用 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>
4

1 回答 1

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 感兴趣,以及您是否希望它在所有浏览器选项卡中都可用。

如果它是用户信息,那么它很可能对许多 bean 来说都很有趣。因此,我们创建了一个名为 的新类WebUser。用户可能希望他的信息在他的整个会话期间被保留(并且您可能也需要使用该对象跟踪他)。所以我们使用@SessionScopedfrom 正确的包,必须是 import 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.

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 事件。实际上,最高级的示例与这个简单的示例几乎没有什么不同。

于 2013-11-11T09:59:15.290 回答