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 开发人员,也就是说我知道它应该如何工作,并且通常可以工作,但由于某种原因,这不再正常工作,一个(据我所知只有一个)MacBook Pro 机器 [编辑:2016-08-14 以及具有相同 OS X 版本的 MacMini]。

问题的简短描述:几天前,当我愉快地开发一个大型 JSF/Primefaces Web 应用程序时,我发现在重新加载了几次复杂的 JSF/Primefaces 页面后,我正在处理它停止更新/反映我所做的更改(并保存)在复合组件中。然而,我发现如果我等待几分钟,我可以再次执行重新加载,几次,反映 CC 的变化,直到它再次“卡住”。

据我所知,它只发生在我的主要开发机器上这是 MacBook Pro 15" (macbookpro11,3 Mid2014.)。

[编辑:2016-08-14 现在也在 macmini4,1 Mid2010 上复制,运行相同的 OS X 版本并运行(稍微)改编的*复制*版本的整个相同的 NetBeans/GlassFish 设置 NB8.1Beta/GF4.1,并使用 JSF 2.2.8-17]

是否:

  • 我使用 NetBeans-8.1beta/Glassfish4.1 或 NetBeans8.1/Glassfish4.1.1 [旁白:我主要使用 NB8.1beta/GF4.1 而不是 NB8.1/GF4.1.1 的原因在:https:// 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 上的应用程序会发生这种情况。

  • 当我将它们保存在 NetBeans 中时,复合组件的已更改 XHTML 文件的时间戳肯定会发生变化(它们被正确复制到 /build/web/resources/...)。

  • 我已经很多天没有进行任何操作系统软件更新或安装。

我制作了整个问题的截屏视频(此处不可用),如下所述。

体验原始的超大型网络应用程序

当我第一次遇到这个问题时,它是在一个非常大的网络应用程序中。我注意到它带有一个很小的复合组件,它生成一些带有样式类(用于图标)的文本,CC 在 ap:accordionPanel 和 p:tab 中使用。我发现在重新加载更改几次后,它会停止捕获更改。我只是偶然发现,如果我等待几分钟,有时长达 10 分钟,它就会“捕捉”变化。

几天后我又回到了我的提交中,那时我显然能够毫无问题地开发,但问题又发生了!我已经对此进行了多次测试,无论问题是什么,它都不在 .git 提交中(包括 /nbproject/private 但不包括 /nbproject/private 的所有子文件夹)。

使用较小的 Primefaces 测试网络应用程序的经验

然后我尝试了一个小得多的测试网络应用程序和一些 Primefaces 测试页面。如果我重新加载 index.xhtml 页面几次,我能够重现该问题,同时更改 index.html 页面中使用的一个微小的单实现行复合组件。然后我发现我必须等待大约 10 秒,有时甚至一分钟,然后更改会再次“捕获”。

使用小型 JSF 核心 Web 应用程序的经验

使用一个 index.xhtml 和一个带有单个 h:outputText 字的单个复合组件,如果我保存 CC 然后非常快速地重新加载 index.xhtml,我可能会遇到问题。我不是在谈论它似乎没有改变(因为一个“击败” javax.faces.FACELETS_REFRESH_PERIOD)我在谈论它“锁定”,以便在那之后它根本不会捕捉到 CC 中的变化,不管多久重新加载一次页面,直到机器中的幽灵决定“解锁”自己。

通常我确实会提供一个示例或“重现问题的步骤”,但这样做没有什么意义;当我将测试项目从一台机器(我的 MacBook Pro)移动到另一台机器(运行相同操作系统版本的 MacMini)时,问题就消失了。我可以使用最简单的 NetBeans JSF Web 应用程序(在我的主要 MacBook Pro 开发机器上)实现它,该应用程序具有包含单个 CC 的 index.xhtml。

[编辑:2016-08-14 我确实可以在运行相同操作系统版本的 MacMini 上重现它,但到目前为止我只能使用我正在开发的非常大的网络应用程序来重现它,这不容易提供给其他人测试(例如,我需要去除 ObjectDB 数据库依赖项并提供虚拟数据)]

我意识到通常有人会在 Stackoverflow 上提出一个问题,但如果回答其中任何一个问题,可能会帮助我继续前进,我们将不胜感激:

Q0:有没有人经历过类似的事情(在 Mac 上)?

Q1:我还能尝试诊断它吗?我没主意了。

Q2:有没有人知道 MacBook Pro 的任何特定内容可能会影响轮询/检测 build/web 文件夹中的更改,可以解释它?

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,有效地重新加载复合组件 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 调试器,所以我禁用它并重新启动。

我发现了一些新的有趣的东西:

  1. 启用 JRebel 的缓存实现被读取为,com.sun.faces.facelets.impl.DefaultFaceletCache.NoCache但实际上是这样com.sun.faces.util.ExpiringConcurrentCache。这就是为什么我在调试时打乱了源代码行。
  2. JSF(特别是 Mojarra)需要深度重构,认真的:至少有5 个不同的工厂2 个不同的缓存参与 facelets 和元数据的创建/缓存,大多数都在做简单的样板委托工作。
  3. com.sun.faces.facelets.impl.DefaultFaceletCache._metadataFaceletCache并且配对不佳com.sun.faces.application.view.FaceletViewHandlingStrategy.metadataCache:它们包含完全相同的数据,并且具有依赖同步的单向处理。概念上错误且消耗内存。
  4. 默认的 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 不能调用它们 :(


结论

好的,我发现了问题。解释起来并不简单,并且需要特殊(尽管常见)的条件才能重现。

假设你有:

  1. FACELET_REFRESH_PERIOD=2
  2. 一个名为的复合组件x:myComp
  3. x:myComp被使用 100 次的页面

现在这是引擎盖下发生的事情。

  1. 在页面评估期间第一次遇到 a 时,会创建x:myComp一个缓存Record_creation=System.currentTimeMillis()
  2. x:myComp对于在页面评估期间遇到的每隔一次,Record从缓存中检索并DefaultFaceletCache.Record.getNextRefreshTime()调用两次(onget()containsKey())以验证过期。
  3. 复合组件被评估 2 次
  4. 假设整页评估在不到 2 秒的时间内完成,最终DefaultFaceletCache.Record.getNextRefreshTime()被调用 ((100 * 2) - 1) * 2 = 398 次
  5. 当被DefaultFaceletCache.Record.getNextRefreshTime()调用时,它会增加一个原子局部变量= 2000_nextRefreshTimeFACELET_REFRESH_PERIOD * 1000
  6. 所以,最后,_nextRefreshTime = initial System.currentTimeMillis() + (398 * 2000 = 796 s)

现在这个 facelet 将在创建后 796 秒后过期。到期前每次访问此页面都会增加 796 秒!

问题是缓存检查与寿命延长相结合(2 ^ 2次!!)。

请参阅JAVASERVERFACES-4107JAVASERVERFACES-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();
            }
        }
    }
}
于 2016-08-12T10:31:56.087 回答