を介してすべてのスタック トレースを正しくデバッグできないようです。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 デバッガーに干渉していることがわかったので、それを無効にして再起動しました。
そして、私はいくつかの新しい興味深いものを見つけました:
- JRebel が有効になっているキャッシュの実装は として読み取られます
com.sun.faces.facelets.impl.DefaultFaceletCache.NoCache
が、実際にはそうではありませんcom.sun.faces.util.ExpiringConcurrentCache
。そのため、デバッグ中にソース コード行をごちゃ混ぜにしていたのです。
- JSF (特に Mojarra) には深刻なリファクタリングが必要です。真剣に: facelets とメタデータの作成/キャッシュには、少なくとも5 つの異なるファクトリと2 つの異なるキャッシュがあり、ほとんどが単純なボイラープレートの委任ジョブを実行しています。
com.sun.faces.facelets.impl.DefaultFaceletCache._metadataFaceletCache
ペアリングcom.sun.faces.application.view.FaceletViewHandlingStrategy.metadataCache
が不十分です。それらにはまったく同じデータが含まれており、依存同期された一方向処理があります。概念的に間違っており、メモリを消費します。
- デフォルトの 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 から呼び出すことはできません :(
結論
問題が見つかりました。説明するのは簡単ではなく、再現するには特別な (一般的ではありますが) 条件が必要です。
あなたが持っていると仮定します:
- FACELET_REFRESH_PERIOD=2
- という名前の複合コンポーネント
x:myComp
x:myComp
100回利用されたページ
これがボンネットの下で起こっていることです。
- ページの評価中に初めて a
x:myComp
に遭遇すると、キャッシュRecord
が作成されます_creation=System.currentTimeMillis()
x:myComp
ページの評価中に が1 回おきにRecord
検出され、キャッシュから取得され、有効期限を確認するためDefaultFaceletCache.Record.getNextRefreshTime()
に 2 回 (get()
および で) 呼び出されます。containsKey()
- 複合コンポーネントは 2 回評価されます
- ページ全体の評価が 2 秒未満で完了すると仮定すると、最終的
DefaultFaceletCache.Record.getNextRefreshTime()
には ((100 * 2) - 1) * 2 = 398 回呼び出されました。
- が
DefaultFaceletCache.Record.getNextRefreshTime()
呼び出されると、アトミック ローカル変数_nextRefreshTime
をFACELET_REFRESH_PERIOD * 1000
= 2000だけインクリメントします。
- したがって、最終的には、
_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();
}
}
}
}