似乎我无法正确调试所有堆栈跟踪com.sun.faces.facelets.impl.DefaultFaceletFactory.createFacelet(URL)
,源代码与jsf-impl-2.2.12-jbossorg-2.jar
.
长话短说,我重写了缓存。
有了这个新的缓存,createFacelet(URL)
现在在每个请求上调用一次 facelet,有效地重新加载复合组件 facelets 更改。
这个缓存实现它没有经过全面测试,绝对不是生产就绪的,但它是一个开始。
尽管如此,它应该是线程安全的,因为内部半缓存是请求范围的。
请注意,我只使用了 API 导入 ( javax.faces.*
) 而没有使用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)需要深度重构,认真的:至少有5 个不同的工厂和2 个不同的缓存参与 facelets 和元数据的创建/缓存,大多数都在做简单的样板委托工作。
com.sun.faces.facelets.impl.DefaultFaceletCache._metadataFaceletCache
并且配对不佳com.sun.faces.application.view.FaceletViewHandlingStrategy.metadataCache
:它们包含完全相同的数据,并且具有依赖同步的单向处理。概念上错误且消耗内存。
- 默认的 facelet 刷新周期和我想的不一样:它是 2000 而不是 0。
所以另一种解决方法是设置:
<context-param>
<param-name>javax.faces.FACELETS_REFRESH_PERIOD</param-name>
<param-value>0</param-value>
</context-param>
在 web.xml 中,但老实说,这比我的简单缓存实现效率低得多,因为它会为每个复合组件实例创建两次 facelets 和元数据......
最后,在这个调试会话中,我从来没有遇到过修改后的 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
对于在页面评估期间遇到的每隔一次,Record
从缓存中检索并DefaultFaceletCache.Record.getNextRefreshTime()
调用两次(onget()
和containsKey()
)以验证过期。
- 复合组件被评估 2 次
- 假设整页评估在不到 2 秒的时间内完成,最终
DefaultFaceletCache.Record.getNextRefreshTime()
被调用 ((100 * 2) - 1) * 2 = 398 次
- 当被
DefaultFaceletCache.Record.getNextRefreshTime()
调用时,它会增加一个原子局部变量= 2000_nextRefreshTime
FACELET_REFRESH_PERIOD * 1000
- 所以,最后,
_nextRefreshTime = initial System.currentTimeMillis() + (398 * 2000 = 796 s)
现在这个 facelet 将在创建后 796 秒后过期。到期前每次访问此页面都会增加 796 秒!
问题是缓存检查与寿命延长相结合(2 ^ 2次!!)。
请参阅JAVASERVERFACES-4107和JAVASERVERFACES-4176(现在主要是JAVASERVERFACES-4178)以获取更多详细信息。
等待问题解决,我正在使用我自己的缓存 impl(需要 Java 8),也许它对您使用/适应也很有用(手动压缩在一个大类中,可能存在一些复制粘贴错误):
/**
* 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();
}
}
}
}