1

私は現在、Ed Burns の JSF 2.0 に関する教科書の Virtual Trainer サンプル アプリを JSF Managed Beans から CDI に変換中です。これまでに遭遇した問題のほとんどは、スコーピングと正しく注入するのを忘れることに関連していましたが、現在、RequestMap から CDI Bean (実際にはエンティティ クラス) を抽出することに関連する最新のハードルを克服するのに苦労しています。これまでに確認できたことから、リクエスト スコープのマネージド Bean は、Map 実装によって提供されるボイラープレート .get(String managedbeanname) メソッドを使用して非常に簡単に抽出できるようです。ただし、CDI を使用すると、Bean は CreationalContextImp インスタンスで Weld によってラップされ、RequestMap に存在することを確認したにもかかわらず、実際に求めているオブジェクトを抽出できません。

フィルター クラスを使用して、SessionScope に保持されている CDI Bean にアクセスする方法について説明している BalusC からの投稿を見つけました (フィルター内から SessionScoped CDI Bean を取得するにはどうすればよいですか? )。また、Managed Bean と CDI 戦略のスコーピング/ミキシングを台無しにしている可能性があることも十分承知しているので、自由に説明してください。ファサード。これにより、後で問題が発生する可能性がありますか?

環境: JEE7、Glassfish 4、Netbeans 7.4、Maven EE Web Archetype マネージド Bean の使用に関連する元のコードはコメント アウトされています。

抽象バッキング 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 Bean と JSF Bean を混在させているようですが、正直なところ、翻訳している例はかなり奇妙に思えます。

JSFにも独自の依存性注入があり、非常に具体的なユースケース(ServletFilterなど)がない限り、スコープマップから自分で何かを抽出することはないからです。

明確にするために、CDI はhttp://en.wikipedia.org/wiki/Inversion_of_control原則を使用し、自分で「抽出」することはアンチ パターンです。

さらに、CDI Bean は CDI コンテナー (ほとんどの場合、Weld または OWB) によって作成および管理されます。また、JSF のコンテキストであり、完全に別のものであるため、externalContext から CDI Bean を取得することはできません。

したがって、JSF によって作成されたインスタンスは CDI Bean に注入できず、その逆も同様です。とにかく、前に言ったように、自分で何かを抽出するのは悪い習慣です。このリストの 10 番を参照してください

したがって、必要なものは何でも使用するだけです@Inject(おそらく修飾子を使用して)。

回避策 Inject が機能しない特殊なケースの場合:

  • 現在のスレッドでは CDI コンテキストを使用できません。
  • 現在のインスタンスは CDI で管理されていません

最初のシナリオは、QuartzJob などの ThreadLocal がある場合に発生します。EJB コンテナーはコンテキスト スレッドを取得する方法を提供する場合がありますが、プレーン サーブレット コンテナー (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/

2 番目のシナリオは、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 を (JSF が使用する) EL 式に公開するには、Bean を としてマークする必要があります@NamedmyBeanクラスに名前が付けられている場合、デフォルト名は になりますMyBean

今度はスコープです。アプリケーションにあるすべての Bean に対して常に @RequestScoped を使用してください。その範囲が短すぎるために問題が発生した場合は、保持したいデータが他の Bean にとって興味深いかどうか、およびすべてのブラウザー タブで利用できるようにするかどうかを検討する必要があります。

それがユーザー情報である場合、多くの Bean にとって興味深い可能性があります。したがって、 という名前の新しいクラスを作成しますWebUser。ユーザーはおそらく、自分の情報がセッションの全期間保持されることを期待しています (また、そのオブジェクトでユーザーを追跡する必要がある場合もあります)。したがって@SessionScoped、正しいパッケージから使用するため、インポートする必要がありますjavax.enterprise.context.SessionScoped。しかし突然、一部のロジックはタブ間で共有されるべきではないため、よりきめ細かいスコープが必要になります。CDI には@ViewScoped(CDI 1.1、JSF 2.2)が付属し@ConversationScopedていますが、遅かれ早かれ Myfaces CODI のスコープが必要になる可能性があります。現在、ほとんどの CODI は既にデルタスパイクに含まれていますが、スコープには含まれていません。ただし、それらは分割されており、それらをインポートする方法はここで説明されています: http://os890.blogspot.fr/2013/07/add-on-codi-scopes-for-deltaspike.html遅かれ早かれ、代わりに Deltaspike でそれらが表示されます。

したがって、多くの異なるスコープを使用できるようになり、これを賢く選択できます。DB から 1 回だけ読み取る必要があるものは、@ApplicationScoped.

たとえば、データベースからシステム設定を読み取り、 で注釈が付けられた CDI Bean である 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 に答える