如何将 JSF 消息包放在 WAR 之外,以便无需重新部署即可对其进行编辑



我们在 WildFly 8 上有一个 JSF 应用程序,它使用传统的国际化文本机制,在 WAR 的 WEB-INFclasses 文件夹中包含德语和英语的消息包,并在faces-config.xml将名称映射到它并列出语言环境。应用程序没有数据库连接,但使用 REST 服务与第二个应用程序通信。

现在我们需要能够更轻松地更改文本,这意味着在更改文本时不必构建新的 WAR 文件并进行部署。因此,我需要一种机制来将消息包放在 WAR 之外,同时能够在 XHTML 页面中像以前一样使用它。

两个可选要求是更改文本并刷新应用程序中的消息,而无需重新启动应用程序(优先级 2(,以及在 WAR 中有一个默认捆绑包,该捆绑包被外部捆绑包覆盖(优先级 3(。

我的想法是使用类似 Apache 公共配置的东西来读取应用程序范围的 bean 中的属性文件,并在之前使用的 EL 名称下公开一个 getter。但不知何故,感觉就像必须重新实现现有机制,这应该以某种方式更容易,甚至可能只使用 Java EE 核心。

是否有人以这种方式使用此机制,并且可以向我指出有关详细信息的一些示例/描述,或者有更好的想法来实现列出的要求?

如何将 JSF 消息包放在 WAR 之外?

两种方式:

  1. 将其路径添加到服务器的运行时类路径。

  2. 使用Control创建自定义ResourceBundle实现。


更改文本并刷新应用程序中的消息,而无需重新启动应用程序

更改文本将变得微不足道。然而,刷新并非微不足道。Mojarra在内部将其缓存得很激进。如果您想选择方式 1,则必须考虑到这一点。Arjan Tijms 在这个相关问题中发布了一个 Mojarra 特定的技巧来清除其内部资源包缓存:如何在 Web 应用程序中重新加载资源包?

如果更改文本发生在 Web 应用程序本身中,则只需在 save 方法中执行缓存清理即可。但是,如果更改文本可以在外部发生,则需要注册文件系统监视服务以侦听更改(此处的教程(,然后对于方式 1 清除捆绑包缓存,或者对于方式 2 在 handleGetObject() 内部重新加载。


在 WAR 中有一个默认捆绑包,该捆绑包被外部捆绑包覆盖

当从类路径加载它们时,默认行为是相反的(WAR 中的资源具有更高的类加载优先级(,所以这肯定会刮擦方式 1,给我们留下方式 2。

下面是方式 2 的启动示例。这假定您使用的是基本名称为 text(即没有包(的属性资源包,并且外部路径位于 /var/webapp/i18n 中。

public class YourBundle extends ResourceBundle {
    protected static final Path EXTERNAL_PATH = Paths.get("/var/webapp/i18n");
    protected static final String BASE_NAME = "text";
    protected static final Control CONTROL = new YourControl();
    private static final WatchKey watcher;
    static {
        try {
            watcher = EXTERNAL_PATH.register(FileSystems.getDefault().newWatchService(), StandardWatchEventKinds.ENTRY_MODIFY);
        } catch (IOException e) {
            throw new ExceptionInInitializerError(e);
        }
    }
    private Path externalResource;
    private Properties properties;
    public YourBundle() {
        Locale locale = FacesContext.getCurrentInstance().getViewRoot().getLocale();
        setParent(ResourceBundle.getBundle(BASE_NAME, locale, CONTROL));
    }
    private YourBundle(Path externalResource, Properties properties) {
        this.externalResource = externalResource;
        this.properties = properties;
    }
    @Override
    protected Object handleGetObject(String key) {
        if (properties != null) {
            if (!watcher.pollEvents().isEmpty()) { // TODO: this is naive, you'd better check resource name if you've multiple files in the folder and keep track of others.
                synchronized(properties) {
                    try (InputStream input = new FileInputStream(externalResource.toFile())) {
                        properties.load(input);
                    } catch (IOException e) {
                        throw new IllegalStateException(e);
                    }
                }
            }
            return properties.get(key);
        }
        return parent.getObject(key);
    }
    @Override
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public Enumeration<String> getKeys() {
        if (properties != null) {
            Set keys = properties.keySet();
            return Collections.enumeration(keys);
        }
        return parent.getKeys();
    }
    protected static class YourControl extends Control {
        @Override
        public ResourceBundle newBundle
            (String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
                throws IllegalAccessException, InstantiationException, IOException
        {
            String resourceName = toResourceName(toBundleName(baseName, locale), "properties");
            Path externalResource = EXTERNAL_PATH.resolve(resourceName);
            Properties properties = new Properties();
            try (InputStream input = loader.getResourceAsStream(resourceName)) {
                properties.load(input); // Default (internal) bundle.
            }
            try (InputStream input = new FileInputStream(externalResource.toFile())) {
                properties.load(input); // External bundle (will overwrite same keys).
            }
            return new YourBundle(externalResource, properties);
        }
    }
}

为了使其运行,请在faces-config.xml中按以下方式注册。

<application>
    <resource-bundle>
        <base-name>com.example.YourBundle</base-name>
        <var>i18n</var>
    </resource-bundle>
</application>

最新更新