4

@ViewScopedJSFアノテーションを CDIに移植しようとしています。その理由は、必要性に基づくというよりも、より教育的なものです。この特定のスコープを選択した主な理由は、CDI で実装したいカスタム スコープの具体的な例が不足しているためです。

そうは言っても、私の出発点はJSF アノテーションを CDIに移植すること@ViewScopedでした。しかし、この実装は、 APIで言及されているContext (つまり、破棄)の一見非常に重要な責任を考慮していません。

コンテキスト オブジェクトは、Contextual のオペレーションを呼び出して、コンテキスト インスタンスを作成および破棄します。特に、コンテキスト オブジェクトは、インスタンスを Contextual.destroy(Object, CreationalContext) に渡すことによって、作成したコンテキスト インスタンスを破棄する責任があります。破棄されたインスタンスは、後で get() によって返されてはなりません。コンテキスト オブジェクトは、インスタンスを作成したときに Contextual.create() に渡したものと同じ CreationalContext のインスタンスを Contextual.destroy() に渡す必要があります。

Contextオブジェクトを作成して、この機能を追加することにしました。

  1. どのsContextualに対してどのオブジェクトを作成するかを追跡します。UIViewRoot
  2. ViewMapListenerインターフェイスを実装し、UIViewRootを呼び出して、それぞれのリスナーとして自身を登録しますUIViewRoot.subscribeToViewEvent(PreDestroyViewMapEvent.class, this)
  3. Contextualが呼び出されたときに作成された をすべて破棄し、ViewMapListener.processEvent(SystemEvent event)それ自体を登録解除しUIViewRootます。

これが私のContext実装です:

package com.example;

import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import javax.enterprise.context.spi.Context;
import javax.enterprise.context.spi.Contextual;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.Bean;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.PreDestroyViewMapEvent;
import javax.faces.event.SystemEvent;
import javax.faces.event.ViewMapListener;

public class ViewContext implements Context, ViewMapListener {

    private Map<UIViewRoot, Set<Disposable>> state;

    public ViewContext() {
        this.state = new HashMap<UIViewRoot, Set<Disposable>>();
    }

    // mimics a multimap put()
    private void put(UIViewRoot key, Disposable value) {
        if (this.state.containsKey(key)) {
            this.state.get(key).add(value);
        } else {
            HashSet<Disposable> valueSet = new HashSet<Disposable>(1);
            valueSet.add(value);
            this.state.put(key, valueSet);
        }
    }

    @Override
    public Class<? extends Annotation> getScope() {
        return ViewScoped.class;
    }

    @Override
    public <T> T get(final Contextual<T> contextual,
            final CreationalContext<T> creationalContext) {
        if (contextual instanceof Bean) {
            Bean bean = (Bean) contextual;
            String name = bean.getName();
            FacesContext ctx = FacesContext.getCurrentInstance();
            UIViewRoot viewRoot = ctx.getViewRoot();
            Map<String, Object> viewMap = viewRoot.getViewMap();
            if (viewMap.containsKey(name)) {
                return (T) viewMap.get(name);
            } else {
                final T instance = contextual.create(creationalContext);
                viewMap.put(name, instance);
                // register for events
                viewRoot.subscribeToViewEvent(
                        PreDestroyViewMapEvent.class, this);
                // allows us to properly couple the right contaxtual, instance, and creational context
                this.put(viewRoot, new Disposable() {

                    @Override
                    public void dispose() {
                        contextual.destroy(instance, creationalContext);
                    }

                });
                return instance;
            }
        } else {
            return null;
        }
    }

    @Override
    public <T> T get(Contextual<T> contextual) {
        if (contextual instanceof Bean) {
            Bean bean = (Bean) contextual;
            String name = bean.getName();
            FacesContext ctx = FacesContext.getCurrentInstance();
            UIViewRoot viewRoot = ctx.getViewRoot();
            Map<String, Object> viewMap = viewRoot.getViewMap();
            if (viewMap.containsKey(name)) {
                return (T) viewMap.get(name);
            } else {
                return null;
            }
        } else {
            return null;
        }
    }

    // this scope is only active when a FacesContext with a UIViewRoot exists
    @Override
    public boolean isActive() {
        FacesContext ctx = FacesContext.getCurrentInstance();
        if (ctx == null) {
            return false;
        } else {
            UIViewRoot viewRoot = ctx.getViewRoot();
            return viewRoot != null;
        }
    }

    // dispose all of the beans associated with the UIViewRoot that fired this event
    @Override
    public void processEvent(SystemEvent event)
            throws AbortProcessingException {
        if (event instanceof PreDestroyViewMapEvent) {
            UIViewRoot viewRoot = (UIViewRoot) event.getSource();
            if (this.state.containsKey(viewRoot)) {
                Set<Disposable> valueSet = this.state.remove(viewRoot);
                for (Disposable disposable : valueSet) {
                    disposable.dispose();
                }
                viewRoot.unsubscribeFromViewEvent(
                        PreDestroyViewMapEvent.class, this);
            }
        }
    }

    @Override
    public boolean isListenerForSource(Object source) {
        return source instanceof UIViewRoot;
    }

}

Disposableインターフェースは次のとおりです。

package com.example;

public interface Disposable {

    public void dispose();

}

スコープの注釈は次のとおりです。

package com.example;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.enterprise.context.NormalScope;

@Inherited
@NormalScope
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD,
    ElementType.FIELD, ElementType.PARAMETER})
public @interface ViewScoped {

}

CDI 拡張宣言は次のとおりです。

package com.example;

import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.AfterBeanDiscovery;
import javax.enterprise.inject.spi.Extension;

public class CustomContextsExtension implements Extension {

    public void afterBeanDiscovery(@Observes AfterBeanDiscovery event) {
        event.addContext(new ViewContext());
    }

}

上記をCDIに正しく登録するために、javax.enterprise.inject.spi.ExtensionファイルをMETA-INF/services含むの下に追加しました。com.example.CustomContextsExtension

次のような Bean を作成できるようになりました (カスタム@ViewScoped実装の使用に注意してください)。

package com.example;

import com.concensus.athena.framework.cdi.extension.ViewScoped;
import java.io.Serializable;
import javax.inject.Named;

@Named
@ViewScoped
public class User implements Serializable {
    ...
}

Bean は適切に作成され、JSF ページに適切に注入されます (つまり、ビューごとに同じインスタンスが返され、ビューが作成されたときにのみ新しいインスタンスが作成され、同じインスタンスが同じビューへの複数の要求に対して注入されます)。どうすればわかりますか?上記のコードにデバッグ用のコードが散らばっていると想像してください。

問題は、 myViewContext.isListenerForSource(Object source)ViewContext.processEvent(SystemEvent event)が呼び出されないことです。ビュー マップはセッション マップに格納されているため、少なくともセッションの有効期限が切れたときにこれらのイベントが呼び出されることを期待していました (正しいですか?)。セッションのタイムアウトを 1 分に設定して待機し、タイムアウトが発生したことを確認しましたが、リスナーはまだ呼び出されていません。

また、次のものを追加しようとしましたfaces-config.xml(ほとんどの場合、アイデアが不足しているため)。

<system-event-listener>
    <system-event-listener-class>com.example.ViewContext</system-event-listener-class>
    <system-event-class>javax.faces.event.PreDestroyViewMapEvent</system-event-class>
    <source-class>javax.faces.component.UIViewRoot</source-class>
</system-event-listener>

最後に、私の環境はJBoss AS 7.1.1ですMojarra 2.1.7

手がかりは大歓迎です。

編集: さらなる調査。

PreDestroyViewMapEventが期待どおりに起動されている間は、まったく起動されていないようですPostConstructViewMapEvent- 新しいビュー マップが作成されるたびに、特にUIViewRoot.getViewMap(true). ドキュメントには、ビューマップで呼び出されるPreDestroyViewMapEventたびに起動する必要があると記載されています。clear()それは疑問に思います-そもそもclear()呼び出される必要がありますか? もしそうなら、いつ?

このような要件を見つけることができたドキュメント内の唯一の場所は次のFacesContext.setViewRoot()とおりです。

現在の UIViewRoot が null でなく、引数ルートで equals() を呼び出し、現在の UIViewRoot を渡して false を返す場合、UIViewRoot#getViewMap から返されたマップで clear メソッドを呼び出す必要があります。

これは通常の JSF ライフサイクルで、つまりプログラムで呼び出すことなく発生することはありますUIViewRoot.setViewMap()か? 何の兆候も見当たらない。

4

2 に答える 2

1

これは、JSF2.2 仕様で修正されている JSF 仕様の問題に関連しています。こちらを参照してください。また、Apache DeltaSpike で問題を作成したため、修正を試みる可能性があります。こちらを参照してください。DeltaSpike で修正された場合、CODI や Seam にもマージされる可能性があります。

于 2012-11-18T23:00:29.050 に答える