2
Mac OS X: Yosemite 10.10.5
NetBeans8.1beta or NetBeans8.1
Glassfish4.1 or Glassfish4.1.1
Mojarra 2.2.7 or 2.2.12 [2016-08-14 EDIT: or 2.2.8-17]
[EDIT: Primefaces 5.3]

私は経験豊富な NetBeans + JSF 開発者です。つまり、それがどのように機能するかを知っており、通常は機能しますが、これは何らかの理由で適切に機能しなくなりました。(そして、私が知る限り、1つだけ)MacBook Pro マシン [編集: 2016-08-14 および同じ OS X バージョンの MacMini でも]。

問題の簡単な説明:数日前、大規模な JSF/Primefaces Web アプリケーションを喜んで開発していたときに、作業中の複雑な JSF/Primefaces ページを数回リロードした後、行った変更の更新/反映が停止することに気付きました。 (および保存) 複合コンポーネントに。しかし、数分待つと、再び「スタック」するまで、CC の変更を反映して、数回リロードを実行できることがわかりました。

私の知る限り、メインの開発マシンでのみ発生しますこれは MacBook Pro 15 インチ (macbookpro11,3 Mid2014.) です。

[編集: 2016-08-14 現在、同じ OS X バージョンを実行し、同じ NetBeans/GlassFish セットアップ NB8.1Beta/GF4.1の (わずかに) 適合された*コピー*バージョンを実行している macmini4,1 Mid2010 でも再現されています。およびJSF 2.2.8-17を使用]

次のことは問題ではないようです。

  • 私は NetBeans-8.1beta/Glassfish4.1 または NetBeans8.1/Glassfish4.1.1を使用しています。 stackoverflow.com/questions/35681181/jsfobjectdb-why-might-deployment-of-a-large-web-app-to-glassfish-4-1-1-take-5]

  • 完全に新しい NetBeans+Glassfish インストールまたは既存のものを使用しています。

  • JDK1.7 (jdk1.7.0_51.jdk) または JDK1.8 (jdk1.8.0_60.jdk) を使用します (NetBeans/Glassfish および/またはソースコードのコンパイルと実行を含む)。

  • 私は Git を含むプロジェクトを使用しています (問題は最初に大規模なプロジェクトで発生しましたが、Git を使用しない最も単純なプロジェクトで再現しました。つまり、/build/web/ の下で facelets の変更を検出することだけに関係があります。 )。

  • 私は Primefaces を使用しているかどうかにかかわらず (非常に基本的な JSF アプリで発生させることができます)。

  • クリーン GET リロードまたはブラウザ コマンド リロードを使用します。

しかし、私が知る限り、古い MacMini (macmini4,1 Mid2010) でほぼ同じ設定を行った場合には発生しません。

[編集: 2016-08-14 はい、ミニ テスト アプリだけでなく、開発中の完全な大規模な Web アプリで JSF ページを頻繁にリロードすると、その MacMini でも発生します]

私がそれについて知っていると思う他のいくつかのこと:

  • これは、すべての場合で [保存時に展開] 機能がオフになっている場合です。

  • JSF テンプレートやインクルードには影響しないようで、複合コンポーネントにのみ影響するようです。

  • javax.faces.FACELETS_REFRESH_PERIOD (mojarra のデフォルトは 2) では問題ありません。これを 0 に変更すると、問題はなくなりますが (キャッシングはありません)、大規模で複雑な JSF ページのロード/リロード時間は苦痛になり、場合によっては数秒ではなく数分になります。

  • ある JSF ページから別のページに移動するだけでは役に立ちません。

  • 使用する JSF スコープに違いはありません。

  • /build/web にデプロイされたアプリケーションで発生します。

  • 複合コンポーネントの変更された XHTML ファイルのタイムスタンプは、NetBeans に保存すると確実に変更されます (/build/web/resources/... に正しくコピーされています)。

  • 何日も OS ソフトウェアの更新やインストールを行っていません。

以下に報告されているように、問題全体のスクリーンキャスト (ここでは利用できません) を作成しました。

オリジナルの非常に大規模な Web アプリの使用経験

私が最初に問題に遭遇したとき、それは非常に大きな Web アプリでした。ap:accordionPanel と p:tab 内で CC が使用されているスタイル クラス (アイコン用) を使用してテキストを生成する小さな複合コンポーネントでそれに気付きました。変更を数回リロードすると、変更のキャッチが停止することがわかりました。何分も、場合によっては 10 分も待つと、変更が「キャッチ」されることを発見したのは偶然でした。

その後、コミットを数日行ったところ、明らかに問題なく開発できるようになりましたが、問題が再び発生しました。私はこれを何度もテストしましたが、問題が何であれ、.git コミットにはありません (/nbproject/private を含みますが、/nbproject/private のすべてのサブフォルダーを含むわけではありません)。

小規模な Primefaces テスト Web アプリの経験

次に、いくつかの Primefaces テスト ページを含む、はるかに小さなテスト Web アプリで試してみました。index.html ページで使用されている小さな 1 つの実装行の複合コンポーネントを変更しながら、index.xhtml ページを数回リロードすると、問題を再現できました。その後、約 10 秒、場合によっては 1 分も待たなければならず、その後、変更が再び「キャッチ」されることがわかりました。

小さな JSF コア Web アプリの経験

1 つの index.xhtml と、1 つの h:outputText ワードを含む 1 つの複合コンポーネントがある場合、CC を保存してから index.xhtml を非常に迅速にリロードすると、問題が発生する可能性があります。私はそれが変化しているように見えないことについて話しているのではありません(javax.faces.FACELETS_REFRESH_PERIODを「打ち負かす」ためです)。ゴースト・イン・ザ・マシーンが自分自身を「ロック解除」することを決定するまで、ページをリロードする頻度。

通常、私は確かに例または「問題を再現する手順」を提供しますが、それを行う意味はほとんどありません。テスト プロジェクトをあるマシン (私の MacBook Pro) から別のマシン (同じ OS バージョンを実行している MacMini) に移動すると、問題はなくなります。そして、単一の CC を含む index.xhtml を使用する最も単純な NetBeans JSF Web アプリケーションを使用して、(メインの MacBook Pro 開発マシンで) それを実現できます。

[編集: 2016-08-14 確かに、同じ OS バージョンを実行している MacMini で再現できますが、これまでのところ、開発中の非常に大きな Web アプリでしか再現できませんでした。テスト (たとえば、ObjectDB データベースの依存関係を取り除き、ダミー データを提供する必要があります)]

通常、Stackoverflow で 1 つの質問をするだけですが、前進するのに役立つ可能性のあるこれらのいずれかへの回答をいただければ幸いです。

Q0: (Mac で) 似たようなことを経験した人はいますか?

Q1: 他に何を診断できますか? 私はアイデアがありません。

Q2:ビルド/Web フォルダーの変更のポーリング/検出に影響を与え、それを説明できる MacBook Pro に固有の何かを知っている人はいますか?

Q3: Facelets および/または Glassfish が /build/web にデプロイされたアプリケーションとどのように連携するかについて、それを説明する可能性のあるものはありますか?

4

1 に答える 1

3

を介してすべてのスタック トレースを正しくデバッグできないようです。com.sun.faces.facelets.impl.DefaultFaceletFactory.createFacelet(URL)ソース コードが のコンパイル済みクラスと一致していませんjsf-impl-2.2.12-jbossorg-2.jar

簡単に言うと、キャッシュを書き直しました。

この新しいキャッシュによりcreateFacelet(URL)、リクエストごとに facelet に対して が 1 回呼び出されるようになり、複合コンポーネントの facelets の変更が効果的にリロードされます。

このキャッシュの実装は完全にはテストされておらず、完全に本番環境に対応していませんが、それは始まりです.

ただし、内部セミキャッシュはリクエスト スコープであるため、スレッド セーフである必要があります。

API インポート ( javax.faces.*) のみを使用し、 noを使用したことに注意してくださいcom.sun.faces.*。したがって、これは Mojarra/MyFaces 2.2.x 実装で動作するはずです。

public class DebugFaceletCacheFactory extends FaceletCacheFactory
{
    protected final FaceletCacheFactory wrapped;

    public DebugFaceletCacheFactory(FaceletCacheFactory wrapped)
    {
        this.wrapped = wrapped;
    }

    @Override
    public FaceletCacheFactory getWrapped()
    {
        return wrapped;
    }

    @Override
    public FaceletCache<?> getFaceletCache()
    {
        return new DebugFaceletCache();
    }

    public static class DebugFaceletCache extends FaceletCache<Facelet>
    {
        protected static final String MEMBER_CACHE_KEY = DebugFaceletCache.class.getName() + "#MEMBER_CACHE";

        protected static final String METADATA_CACHE_KEY = DebugFaceletCache.class.getName() + "#METADATA_CACHE";

        protected Map<URL, Facelet> getCache(String key)
        {
            Map<String, Object> requestMap = FacesContext.getCurrentInstance().getExternalContext().getRequestMap();

            Map<URL, Facelet> cache = (Map<URL, Facelet>) requestMap.get(key);
            if(cache == null)
            {
                cache = new HashMap<>();
                requestMap.put(key, cache);
            }

            return cache;
        }

        protected MemberFactory<Facelet> getFactory(String key)
        {
            if(MEMBER_CACHE_KEY.equals(key))
            {
                return getMemberFactory();
            }

            if(METADATA_CACHE_KEY.equals(key))
            {
                return getMetadataMemberFactory();
            }

            throw new IllegalArgumentException();
        }

        protected Facelet getFacelet(String key, URL url) throws IOException
        {
            Map<URL, Facelet> cache = getCache(key);
            Facelet facelet = cache.get(url);
            if(facelet == null)
            {
                MemberFactory<Facelet> factory = getFactory(key);
                facelet = factory.newInstance(url);

                cache.put(url, facelet);
            }

            return facelet;
        }

        @Override
        public Facelet getFacelet(URL url) throws IOException
        {
            return getFacelet(MEMBER_CACHE_KEY, url);
        }

        @Override
        public boolean isFaceletCached(URL url)
        {
            return getCache(MEMBER_CACHE_KEY).containsKey(url);
        }

        @Override
        public Facelet getViewMetadataFacelet(URL url) throws IOException
        {
            return getFacelet(METADATA_CACHE_KEY, url);
        }

        @Override
        public boolean isViewMetadataFaceletCached(URL url)
        {
            return getCache(METADATA_CACHE_KEY).containsKey(url);
        }
    }
}

そして、それは次の方法でアクティブ化されますfaces-config.xml:

<?xml version="1.0" encoding="utf-8"?>
<faces-config version="2.2" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd">

    ...
    
    <factory>
        <facelet-cache-factory>it.shape.core.jsf.factory.DebugFaceletCacheFactory</facelet-cache-factory>
    </factory>
</faces-config>

ハッピーコンポジットコーディング;)


アップデート

JRebel が Eclipse デバッガーに干渉していることがわかったので、それを無効にして再起動しました。

そして、私はいくつかの新しい興味深いものを見つけました:

  1. JRebel が有効になっているキャッシュの実装は として読み取られますcom.sun.faces.facelets.impl.DefaultFaceletCache.NoCacheが、実際にはそうではありませんcom.sun.faces.util.ExpiringConcurrentCache。そのため、デバッグ中にソース コード行をごちゃ混ぜにしていたのです。
  2. JSF (特に Mojarra) には深刻なリファクタリングが必要です。真剣に: facelets とメタデータの作成/キャッシュには、少なくとも5 つの異なるファクトリ2 つの異なるキャッシュがあり、ほとんどが単純なボイラープレートの委任ジョブを実行しています。
  3. com.sun.faces.facelets.impl.DefaultFaceletCache._metadataFaceletCacheペアリングcom.sun.faces.application.view.FaceletViewHandlingStrategy.metadataCacheが不十分です。それらにはまったく同じデータが含まれており、依存同期された一方向処理があります。概念的に間違っており、メモリを消費します。
  4. デフォルトの Facelet 更新期間は、私が考えていたものとは異なります: 0 ではなく 2000 です。

したがって、別の回避策は次のように設定することです。

<context-param>
    <param-name>javax.faces.FACELETS_REFRESH_PERIOD</param-name>
    <param-value>0</param-value>
</context-param>

しかし正直なところ、これは単純なキャッシュの実装よりもはるかに効率的ではありません。これは、複合コンポーネント インスタンスごとに facelets とメタデータを 2 回作成するためです...

最後に、このデバッグ セッションでは、変更された facelet が更新されないケースに遭遇したことはありません。実装が非常に非効率的で分裂病的であっても、このバージョン (2.2.12) は機能しているようです。

私の場合、JRebel の問題だと思います。

しかし、今では最終的に JRebel を有効にして facelets をリロードして開発することができます。

隠されたケース (Eclipse が facelets をターゲット フォルダーにコピー/更新しない、および/またはエディターからの保存時に最終変更ファイルの日付を設定しないなど) に遭遇した場合は、この回答を更新します。


PS
インターフェイスはステートレスであり、すべての概念パターンに適しているわけではないため、場合によっては抽象クラスを使用します。単一クラスの継承は、IMO で最も深刻な Java の問題です。ただし、Java 8 では、問題を軽減するのに役立つ default/defender メソッドがあります。それにもかかわらず、それらは JSF ExpressionLanguage 3.0 から呼び出すことはできません :(


結論

問題が見つかりました。説明するのは簡単ではなく、再現するには特別な (一般的ではありますが) 条件が必要です。

あなたが持っていると仮定します:

  1. FACELET_REFRESH_PERIOD=2
  2. という名前の複合コンポーネントx:myComp
  3. x:myComp100回利用されたページ

これがボンネットの下で起こっていることです。

  1. ページの評価中に初めて ax:myCompに遭遇すると、キャッシュRecordが作成されます_creation=System.currentTimeMillis()
  2. x:myCompページの評価中に が1 回おきにRecord検出され、キャッシュから取得され、有効期限を確認するためDefaultFaceletCache.Record.getNextRefreshTime()に 2 回 (get()および で) 呼び出されます。containsKey()
  3. 複合コンポーネントは 2 回評価されます
  4. ページ全体の評価が 2 秒未満で完了すると仮定すると、最終的DefaultFaceletCache.Record.getNextRefreshTime()には ((100 * 2) - 1) * 2 = 398 回呼び出されました。
  5. DefaultFaceletCache.Record.getNextRefreshTime()呼び出されると、アトミック ローカル変数_nextRefreshTimeFACELET_REFRESH_PERIOD * 1000= 2000だけインクリメントします。
  6. したがって、最終的には、_nextRefreshTime = initial System.currentTimeMillis() + (398 * 2000 = 796 s)

この facelet は、作成されてから 796 秒で期限切れになります。有効期限が切れる前にこのページにアクセスするたびに、さらに 796 秒追加されます!

問題は、キャッシュ チェックが (2^2 回!!) 寿命の延長と結び付いていることです。

JAVASERVERFACES -4107およびJAVASERVERFACES-4176詳細については(現在は主にJAVASERVERFACES-4178 ) を参照してください。


問題の解決を待っています。私は独自のキャッシュ impl ( Java 8 が必要です) を使用しています。おそらく、使用/適応するのにも役立ちます (1 つの大きなクラスに手動で凝縮されているため、コピー アンド ペーストのミスがある可能性があります)。

/**
 * A factory for creating ShapeFaceletCache objects.
 *
 * @author Michele Mariotti
 */
public class ShapeFaceletCacheFactory extends FaceletCacheFactory
{
    protected FaceletCacheFactory wrapped;

    public ShapeFaceletCacheFactory(FaceletCacheFactory wrapped)
    {
        this.wrapped = wrapped;
    }

    @Override
    public FaceletCacheFactory getWrapped()
    {
        return wrapped;
    }

    @Override
    public ShapeFaceletCache getFaceletCache()
    {
        String param = FacesContext.getCurrentInstance()
            .getExternalContext()
            .getInitParameter(ViewHandler.FACELETS_REFRESH_PERIOD_PARAM_NAME);

        long period = NumberUtils.toLong(param, 2) * 1000;

        if(period < 0)
        {
            return new UnlimitedFaceletCache();
        }

        if(period == 0)
        {
            return new DevelopmentFaceletCache();
        }

        return new ExpiringFaceletCache(period);
    }

    public static abstract class ShapeFaceletCache extends FaceletCache<Facelet>
    {
        protected static volatile ShapeFaceletCache INSTANCE;

        protected Map<URL, FaceletRecord> memberCache = new ConcurrentHashMap<>();

        protected Map<URL, FaceletRecord> metadataCache = new ConcurrentHashMap<>();

        protected ShapeFaceletCache()
        {
            INSTANCE = this;
        }

        public static ShapeFaceletCache getInstance()
        {
            return INSTANCE;
        }

        protected Facelet getFacelet(FaceletCacheKey key, URL url)
        {
            Map<URL, FaceletRecord> cache = getLocalCache(key);
            FaceletRecord record = cache.compute(url, (u, r) -> computeFaceletRecord(key, u, r));
            Facelet facelet = record.getFacelet();
            return facelet;
        }

        protected boolean isCached(FaceletCacheKey key, URL url)
        {
            Map<URL, FaceletRecord> cache = getLocalCache(key);
            FaceletRecord record = cache.computeIfPresent(url, (u, r) -> checkFaceletRecord(key, u, r));
            return record != null;
        }

        protected FaceletRecord computeFaceletRecord(FaceletCacheKey key, URL url, FaceletRecord record)
        {
            if(record == null || checkFaceletRecord(key, url, record) == null)
            {
                return buildFaceletRecord(key, url);
            }

            return record;
        }

        protected FaceletRecord buildFaceletRecord(FaceletCacheKey key, URL url)
        {
            try
            {
                MemberFactory<Facelet> factory = getFactory(key);
                Facelet facelet = factory.newInstance(url);
                long lastModified = URLUtils.getLastModified(url);
                FaceletRecord record = new FaceletRecord(facelet, lastModified);
                return record;
            }
            catch(IOException e)
            {
                throw new FacesException(e.getMessage(), e);
            }
        }

        protected FaceletRecord checkFaceletRecord(FaceletCacheKey key, URL url, FaceletRecord record)
        {
            return record;
        }

        protected Map<URL, FaceletRecord> getLocalCache(FaceletCacheKey key)
        {
            if(key == FaceletCacheKey.MEMBER)
            {
                return memberCache;
            }

            if(key == FaceletCacheKey.METADATA)
            {
                return metadataCache;
            }

            throw new IllegalArgumentException();
        }

        protected MemberFactory<Facelet> getFactory(FaceletCacheKey key)
        {
            if(key == FaceletCacheKey.MEMBER)
            {
                return getMemberFactory();
            }

            if(key == FaceletCacheKey.METADATA)
            {
                return getMetadataMemberFactory();
            }

            throw new IllegalArgumentException();
        }

        @Override
        public Facelet getFacelet(URL url) throws IOException
        {
            return getFacelet(FaceletCacheKey.MEMBER, url);
        }

        @Override
        public Facelet getViewMetadataFacelet(URL url) throws IOException
        {
            return getFacelet(FaceletCacheKey.METADATA, url);
        }

        @Override
        public boolean isFaceletCached(URL url)
        {
            return isCached(FaceletCacheKey.MEMBER, url);
        }

        @Override
        public boolean isViewMetadataFaceletCached(URL url)
        {
            return isCached(FaceletCacheKey.METADATA, url);
        }

        public void clearFacelets()
        {
            getLocalCache(FaceletCacheKey.MEMBER).clear();
        }

        public void clearViewMetadataFacelets()
        {
            getLocalCache(FaceletCacheKey.METADATA).clear();
        }

        public void clearAll()
        {
            clearViewMetadataFacelets();
            clearFacelets();
        }
    }

    public static class UnlimitedFaceletCache extends ShapeFaceletCache
    {
        public UnlimitedFaceletCache()
        {
            super();
        }
    }

    public static class DevelopmentFaceletCache extends ShapeFaceletCache
    {
        public DevelopmentFaceletCache()
        {
            super();
        }

        @Override
        protected FaceletRecord checkFaceletRecord(FaceletCacheKey key, URL url, FaceletRecord record)
        {
            try
            {
                Set<URL> urls = (Set<URL>) FacesContext.getCurrentInstance()
                    .getAttributes()
                    .computeIfAbsent(key, x -> new HashSet<>());

                if(urls.add(url))
                {
                    long lastModified = URLUtils.getLastModified(url);
                    if(lastModified != record.getLastModified())
                    {
                        return null;
                    }
                }

                return record;
            }
            catch(IOException e)
            {
                throw new FacesException(e.getMessage(), e);
            }
        }
    }

    public static class ExpiringFaceletCache extends ShapeFaceletCache
    {
        protected final long period;

        public ExpiringFaceletCache(long period)
        {
            super();
            this.period = period;
        }

        @Override
        protected FaceletRecord checkFaceletRecord(FaceletCacheKey key, URL url, FaceletRecord record)
        {
            try
            {
                long now = System.currentTimeMillis();
                if(now > record.getLastChecked() + period)
                {
                    long lastModified = URLUtils.getLastModified(url);
                    if(lastModified != record.getLastModified())
                    {
                        return null;
                    }

                    record.setLastChecked(now);
                }

                return record;
            }
            catch(IOException e)
            {
                throw new FacesException(e.getMessage(), e);
            }
        }
    }

    public static class FaceletRecord
    {
        protected final Facelet facelet;

        protected final long lastModified;

        protected long lastChecked;

        public FaceletRecord(Facelet facelet, long lastModified)
        {
            this.facelet = facelet;
            this.lastModified = lastModified;
            lastChecked = System.currentTimeMillis();
        }

        public long getLastModified()
        {
            return lastModified;
        }

        public Facelet getFacelet()
        {
            return facelet;
        }
        
        public long getLastChecked()
        {
            return lastChecked;
        }

        public void setLastChecked(long lastChecked)
        {
            this.lastChecked = lastChecked;
        }
    }

    public static enum FaceletCacheKey
    {
        MEMBER,
        METADATA;

        @Override
        public String toString()
        {
            return getClass().getName() + "." + name();
        }
    }

    public static class URLUtils
    {
        public static long getLastModified(URL url) throws IOException
        {
            URLConnection urlConnection = url.openConnection();

            if(urlConnection instanceof JarURLConnection)
            {
                JarURLConnection jarUrlConnection = (JarURLConnection) urlConnection;
                URL jarFileUrl = jarUrlConnection.getJarFileURL();

                return getLastModified(jarFileUrl);
            }

            try(InputStream input = urlConnection.getInputStream())
            {
                return urlConnection.getLastModified();
            }
        }
    }
}
于 2016-08-12T10:31:56.087 に答える