Статьи

Использование более одного файла свойств в Spring MVC

Все слышали о порталах, которые объединяют одно веб-приложение в одно большое. Программное обеспечение портала работает как гибридные приложения — контент из нескольких источников собирается в одном сервисе, в основном, отображается на одной веб-странице. Программное обеспечение портала также позволяет изменять пользовательские настройки, такие как язык или тема, для всех отдельных веб-приложений (независимых модулей), встроенных в программное обеспечение портала. Кроме того, ожидается единый вход в систему (SSO), и он также должен работать. Это означает, что единый вход в систему позволяет пользователю получить доступ ко всем встроенным веб-приложениям. Было бы интересно узнать, существует ли в мире JEE простое и легкое решение для разработки модульных приложений JSF 2, для автоматического сбора и представления их в одном портальном веб-приложении. Конечно, существуют OSGi и сложные портальные мосты, обеспечивающие поддержку разработки портлетов, совместимых с JSR-168 или JSR-286. Но, к счастью, JSF 2 уже предоставляет простую возможность для этого «под капотом». С меньшими усилиями мы можем создать программное обеспечение, подобное порталу. Все, что нам нужно, это JSF 2 и CDI — де-факто стандартная структура DI в мире Java.

Тема этого поста не нова. Вы можете найти некоторые обсуждения и идеи в Интернете. Я бы упомянул здесь только две ссылки. Первая — статья «Как: модульные приложения Java EE с CDI и PrettyFaces» в блоге ocpsoft. Второе «Модульные веб-приложения с JSF2» было представлено в вики JBoss. Идея состоит в том, чтобы создать файлы JAR, содержащие отдельные веб-приложения, и снабдить их основным файлом WAR. Файл WAR объединяет JAR-файлы в процессе сборки, например, через зависимости Maven. Это означает, что файлы JAR находятся в WAR под WEB-INF / lib /. Файлы XHTML в JAR-файлах находятся ниже / META-INF / resources / и будут автоматически загружаться JSF 2. Они доступны для JSF, как если бы они находились в папке / webapp / resources /. Например, вы можете включить лицевые стороны из JAR с довольно распространенным интерфейсом: include. Это работает как шарм. Чтобы иметь возможность получать общую информацию о каждом модуле JSF во время выполнения, нам также нужен пустой CDI’s beans.xml в файлах JARs. Они расположены как обычно ниже
Папка META-INF.

Теперь давайте начнем с кодирования. Но сначала давайте определимся со структурой проекта. Вы можете найти полный реализованный пример на GitHub . Это всего лишь доказательство концепции легкой реализации, подобной порталу JSF 2, с демонстрационными веб-приложениями (написанными с использованием JSF 2.2). Есть 5 подпроектов:

  • jsftoolkit-jar Базовая структура, предоставляющая интерфейсы и утилиты для модульных приложений JSF.
  • modA-jar Первое веб-приложение (модуль A), которое зависит от jsftoolkit-jar.
  • modB-jar Второе веб-приложение (модуль B), которое зависит от jsftoolkit-jar.
  • portal-jar Java часть программного обеспечения, подобного порталу. Это также зависит от jsftoolkit-jar.
  • portal-war Веб-часть программного обеспечения, похожего на портал. Он объединяет все артефакты и является развертываемой WAR.

Базовая структура (jsftoolkit-jar) имеет интерфейсы, которые должны быть реализованы каждым модулем. Наиболее важными являются

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/**
 * Interface for modular JSF applications. This interface should be implemented by every module (JSF app.)
 * to allow a seamless integration into a "portal" software.
 */
public interface ModuleDescription {
 
    /**
     * Provides a human readable name of the module.
     *
     * @return String name of the module
     */
    String getName();
 
    /**
     * Provides a description of the module.
     *
     * @return String description
     */
    String getDescription();
 
    /**
     * Provides a module specific prefix. This is a folder below the context where all web pages and
     * resources are located.
     *
     * @return String prefix
     */
    String getPrefix();
 
    /**
     * Provides a name for a logo image, e.g. "images/logo.png" (used in h:graphicImage).
     *
     * @return String logo name
     */
    String getLogoName();
 
    /**
     * Provides a start (home) URL to be navigated for the module.
     *
     * @return String URL
     */
    String getUrl();
}
 
/**
 * Any JSF app. implementing this interface can participate in an unified message handling
 * when all keys and messages are merged to a map and available via "msgs" EL, e.g. as #{msgs['mykey']}.
 */
public interface MessagesProvider {
 
    /**
     * Returns all mesages (key, text) to the module this interface is implemented for.
     *
     * @param  locale current Locale or null
     * @return Map with message keys and message text.
     */
    Map<String, String> getMessages(Locale locale);
}

Возможные реализации для модуля A выглядят следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
 * Module specific implementation of the {@link ModuleDescription}.
 */
@ApplicationScoped
@Named
public class ModADescription implements ModuleDescription, Serializable {
 
    @Inject
    private MessagesProxy msgs;
 
    @Override
    public String getName() {
        return msgs.get("a.modName");
    }
 
    @Override
    public String getDescription() {
        return msgs.get("a.modDesc");
    }
 
    @Override
    public String getPrefix() {
        return "moda";
    }
 
    @Override
    public String getLogoName() {
        return "images/logo.png";
    }
 
    @Override
    public String getUrl() {
        return "/moda/views/hello.jsf";
    }
}
 
/**
 * Module specific implementation of the {@link MessagesProvider}.
 */
@ApplicationScoped
@Named
public class ModAMessages implements MessagesProvider, Serializable {
 
    @Override
    public Map<String, String> getMessages(Locale locale) {
        return MessageUtils.getMessages(locale, "modA");
    }
}

Префикс этого модуля — мода. Это означает, что веб-страницы и ресурсы находятся в папке META-INF / resources / moda /. Это позволяет избежать коллизий путей (идентичных путей) во всех отдельных веб-приложениях. Служебный класс MessageUtils (из jsftoolkit-jar) здесь не представлен. Я покажу только класс MessagesProxy. Класс MessagesProxy — это компонент в области приложения, предоставляющий доступ ко всем доступным сообщениям в модульном веб-приложении JSF. Его можно использовать как в Java, так и в XHTML, поскольку он реализует интерфейс Map. Все найденные доступные реализации интерфейса MessagesProvider внедряются CDI автоматически во время выполнения. Мы используем экземпляр <MessagesProvider>.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
@ApplicationScoped
@Named(value = "msgs")
public class MessagesProxy implements Map<String, String>, Serializable {
 
    @Inject
    private UserSettingsData userSettingsData;
 
    @Any
    @Inject
    private Instance<MessagesProvider> messagesProviders;
 
    /** all cached locale specific messages */
    private Map<Locale, Map<String, String>> msgs = new ConcurrentHashMap<Locale, Map<String, String>>();
 
    @Override
    public String get(Object key) {
        if (key == null) {
            return null;
        }
 
        Locale locale = userSettingsData.getLocale();
        Map<String, String> messages = msgs.get(locale);
 
        if (messages == null) {
            // no messages to current locale are available yet
            messages = new HashMap<String, String>();
            msgs.put(locale, messages);
 
            // load messages from JSF impl. first
            messages.putAll(MessageUtils.getMessages(locale, MessageUtils.FACES_MESSAGES));
 
            // load messages from providers in JARs
            for (MessagesProvider messagesProvider : messagesProviders) {
                messages.putAll(messagesProvider.getMessages(locale));
            }
        }
 
        return messages.get(key);
    }
 
    public String getText(String key) {
        return this.get(key);
    }
 
    public String getText(String key, Object... params) {
        String text = this.get(key);
 
        if ((text != null) && (params != null)) {
            text = MessageFormat.format(text, params);
        }
 
        return text;
    }
 
    public FacesMessage getMessage(FacesMessage.Severity severity, String key, Object... params) {
        String summary = this.get(key);
        String detail = this.get(key + "_detail");
 
        if ((summary != null) && (params != null)) {
            summary = MessageFormat.format(summary, params);
        }
 
        if ((detail != null) && (params != null)) {
            detail = MessageFormat.format(detail, params);
        }
 
        if (summary != null) {
            return new FacesMessage(severity, summary, ((detail != null) ? detail : StringUtils.EMPTY));
        }
 
        return new FacesMessage(severity, "???" + key + "???", ((detail != null) ? detail : StringUtils.EMPTY));
    }
 
    /////////////////////////////////////////////////////////
    // java.util.Map interface
    /////////////////////////////////////////////////////////
 
    public int size() {
        throw new UnsupportedOperationException();
    }
 
    // other methods ...
}

Что ж. Но где подобраны экземпляры ModuleDescription? Логика в портале-банке. Я использую тот же механизм с инстансом CDI. CDI выяснит все доступные реализации ModuleDescription для нас.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
 * Collects all available JSF modules.
 */
@ApplicationScoped
@Named
public class PortalModulesFinder implements ModulesFinder {
 
    @Any
    @Inject
    private Instance<ModuleDescription> moduleDescriptions;
 
    @Inject
    private MessagesProxy msgs;
 
    private List<FluidGridItem> modules;
 
    @Override
    public List<FluidGridItem> getModules() {
        if (modules != null) {
            return modules;
        }
 
        modules = new ArrayList<FluidGridItem>();
 
        for (ModuleDescription moduleDescription : moduleDescriptions) {
            modules.add(new FluidGridItem(moduleDescription));
        }
 
        // sort modules by names alphabetically
        Collections.sort(modules, ModuleDescriptionComparator.getInstance());
 
        return modules;
    }
}

Теперь мы можем создавать динамические плитки в пользовательском интерфейсе, которые представляют точки входа в соответствующие веб-модули.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
<pe:fluidGrid id="fluidGrid" value="#{portalModulesFinder.modules}" var="modDesc"
              fitWidth="true" hasImages="true">
    <pe:fluidGridItem styleClass="ui-widget-header">
        <h:panelGrid columns="2" styleClass="modGridEntry" columnClasses="modLogo,modTxt">
            <p:commandLink process="@this" action="#{navigationContext.goToPortlet(modDesc)}">
                <h:graphicImage library="#{modDesc.prefix}" name="#{modDesc.logoName}"/>
            </p:commandLink>
 
            <h:panelGroup>
                <p:commandLink process="@this" action="#{navigationContext.goToPortlet(modDesc)}">
                    <h:outputText value="#{modDesc.name}" styleClass="linkToPortlet"/>
                </p:commandLink>
 
                <p/>
                <h:outputText value="#{modDesc.description}"/>
            </h:panelGroup>
        </h:panelGrid>
    </pe:fluidGridItem>
</pe:fluidGrid>

Плитки были созданы компонентом pe: fluidGrid из PrimeFaces Extensions . Они отзывчивы, то есть они перестраиваются при изменении размера окна браузера. На следующем рисунке показано, как выглядит веб-приложение портала после запуска. Он показывает два модульных демонстрационных приложения, найденных в пути к классам. Каждое модульное веб-приложение отображается в виде плитки, содержащей логотип, название и краткое описание. Логотипы и названия кликабельны. Щелчок перенаправляет на соответствующее веб-приложение.

webportal1

Как видите, на главной странице портала вы можете переключать текущий язык и тему. На втором рисунке показано, что происходит, если пользователь нажимает на модуль A. Показано веб-приложение для модуля A. Вы можете видеть кнопку «Назад к порталу», поэтому возможна обратная навигация на домашнюю страницу портала.

webportal2

Два примечания в конце:

  1. Каждый модуль можно запускать как отдельное веб-приложение. Мы можем проверить во время выполнения (опять же с помощью CDI), находится ли модуль в «портале» или нет, и использовать разные главные шаблоны. Подсказка здесь .
  2. Я не реализовал экран входа в систему, но с единой регистрацией проблем не возникает, потому что у нас только одно большое приложение (одна WAR) Все доставлено туда.