«1. Обзор

При разработке веб-приложения его внешний вид или тема являются ключевым компонентом. Это влияет на удобство использования и доступность нашего приложения и может еще больше укрепить бренд нашей компании.

В этом руководстве мы рассмотрим шаги, необходимые для настройки тем в приложении Spring MVC.

2. Варианты использования

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

Мы можем использовать темы, чтобы:

    Создать общий внешний вид с фиксированной темой. Настроить бренд с темой брендинга — это обычное дело в приложении SAAS, где каждый клиент хочет разного внешнего вида. and-feel Решите проблемы доступности с помощью темы удобства использования — например, нам может понадобиться темная или высококонтрастная тема

3. Зависимости Maven

Итак, обо всем по порядку, давайте добавим зависимости Maven, которые мы будем использовать в первой части этого урока.

Нам потребуются зависимости Spring WebMVC и Spring Context:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.1.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.1.RELEASE</version>
</dependency>

А поскольку в нашем примере мы собираемся использовать JSP, нам понадобятся сервлеты Java, JSP и JSTL:

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
</dependency>
<dependency>
     <groupId>javax.servlet.jsp</groupId>
     <artifactId>javax.servlet.jsp-api</artifactId>
     <version>2.3.3</version>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
</dependency>

~ ~~ 4. Настройка темы Spring

4.1. Свойства темы

Теперь давайте настроим светлую и темную темы для нашего приложения.

Для темной темы давайте создадим dark.properties:

styleSheet=themes/black.css
background=black

А для светлой темы — light.properties:

styleSheet=themes/white.css
background=white

Из приведенных выше свойств видно, что один относится к файлу CSS. а другой относится к стилю CSS. Через мгновение мы увидим, как они проявляются в нашем представлении.

4.2. ResourceHandler

Судя по приведенным выше свойствам, файлы black.css и white.css должны быть помещены в каталог с именем /themes.

И мы должны настроить ResourceHandler, чтобы Spring MVC мог правильно находить файлы по запросу:

@Override 
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/themes/**").addResourceLocations("classpath:/themes/");
}

4.3. ThemeSource

Мы можем управлять этими специфичными для темы файлами .properties как ResourceBundles через ResourceBundleThemeSource:

@Bean
public ResourceBundleThemeSource resourceBundleThemeSource() {
    return new ResourceBundleThemeSource();
}

4.4. ThemeResolvers

Далее нам нужен ThemeResolver для разрешения правильной темы для приложения. В зависимости от наших потребностей в дизайне мы можем выбирать между существующими реализациями или создавать свои собственные.

Для нашего примера настроим CookieThemeResolver. Как видно из названия, это разрешает информацию о теме из файла cookie браузера или возвращается к значению по умолчанию, если эта информация недоступна:

@Bean
public ThemeResolver themeResolver() {
    CookieThemeResolver themeResolver = new CookieThemeResolver();
    themeResolver.setDefaultThemeName("light");
    return themeResolver;
}

Другие варианты ThemeResolver, поставляемые с платформой:

    FixedThemeResolver: Используется при наличии фиксированной темы для приложения. SessionThemeResolver: используется, чтобы позволить пользователю переключать темы для активного сеанса

4.5. Представление

Чтобы применить тему к нашему представлению, мы должны настроить механизм запроса пакетов ресурсов.

Мы оставим область видимости только для JSP, хотя аналогичный механизм поиска можно настроить и для альтернативных механизмов рендеринга представлений.

Для JSP мы можем импортировать библиотеку тегов, которая сделает всю работу за нас:

<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>

И затем мы можем обратиться к любому свойству, указав соответствующее имя свойства:

<link rel="stylesheet" href="<spring:theme code='styleSheet'/>"/>

Или:

<body bgcolor="<spring:theme code='background'/>">

Итак, давайте теперь добавим одно представление с именем index.jsp в наше приложение и поместим его в каталог WEB-INF/:

<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <link rel="stylesheet" href="<spring:theme code='styleSheet'/>"/>
        <title>Themed Application</title>
    </head>
    <body>
        <header>
            <h1>Themed Application</h1>
            <hr />
        </header>
        <section>
            <h2>Spring MVC Theme Demo</h2>
            <form action="<c:url value='/'/>" method="POST" name="themeChangeForm" id="themeChangeForm">
                <div>
                    <h4>
                        Change Theme
                    </h4>
                </div>
                <select id="theme" name="theme" onChange="submitForm()">
                    <option value="">Reset</option>
                    <option value="light">Light</option>
                    <option value="dark">Dark</option>
                </select>
            </form>
        </section>

        <script type="text/javascript">
            function submitForm() {
                document.themeChangeForm.submit();
            }
        </script>
    </body>
</html>

На самом деле, наше приложение будет работать на этом этапе, всегда выбирая нашу светлую тему .

Давайте посмотрим, как мы можем позволить пользователю изменить свою тему.

4.6. ThemeChangeInterceptor

Задача ThemeChangeInterceptor — понять запрос на изменение темы.

Давайте теперь добавим ThemeChangeInterceptor и настроим его для поиска параметра запроса темы:

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(themeChangeInterceptor());
}

@Bean
public ThemeChangeInterceptor themeChangeInterceptor() {
    ThemeChangeInterceptor interceptor = new ThemeChangeInterceptor();
    interceptor.setParamName("theme");
    return interceptor;
}

5. Дальнейшие зависимости

Теперь давайте реализуем наш собственный ThemeResolver, который сохраняет предпочтения пользователя в базе данных.

Для этого нам понадобится Spring Security для идентификации пользователя:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>5.2.1.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>5.2.1.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-taglibs</artifactId>
    <version>5.2.1.RELEASE</version>
</dependency>

И Spring Data, Hibernate и HSQLDB для хранения настроек пользователя:

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
    <version>2.2.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>5.4.9.Final</version>
</dependency>

<dependency>
    <groupId>org.hsqldb</groupId>
    <artifactId>hsqldb</artifactId>
    <version>2.5.0</version>
</dependency>

6. Custom ThemeResolver ~~ ~ Давайте теперь углубимся в ThemeResolver и реализуем один из наших собственных. Этот пользовательский ThemeResolver сохранит настройки темы пользователя в базе данных.

«Для этого давайте сначала добавим объект UserPreference:

Затем мы создадим UserPreferenceThemeResolver, который должен реализовать интерфейс ThemeResolver. Его основные обязанности заключаются в разрешении и сохранении информации о теме.

@Entity
@Table(name = "preferences")
public class UserPreference {
    @Id
    private String username;

    private String theme;
}

Давайте сначала обратимся к разрешению имени, реализовав UserPreferenceThemeResolver#resolveThemeName:

И теперь мы можем написать нашу реализацию для сохранения темы в UserPreferenceThemeResolver#setThemeName:

@Override
public String resolveThemeName(HttpServletRequest request) {
    String themeName = findThemeFromRequest(request)
      .orElse(findUserPreferredTheme().orElse(getDefaultThemeName()));
    request.setAttribute(THEME_REQUEST_ATTRIBUTE_NAME, themeName);
    return themeName;
}

private Optional<String> findUserPreferredTheme() {
    Authentication authentication = SecurityContextHolder.getContext()
            .getAuthentication();
    UserPreference userPreference = getUserPreference(authentication).orElse(new UserPreference());
    return Optional.ofNullable(userPreference.getTheme());
}

private Optional<String> findThemeFromRequest(HttpServletRequest request) {
    return Optional.ofNullable((String) request.getAttribute(THEME_REQUEST_ATTRIBUTE_NAME));
}
    
private Optional<UserPreference> getUserPreference(Authentication authentication) {
    return isAuthenticated(authentication) ? 
      userPreferenceRepository.findById(((User) authentication.getPrincipal()).getUsername()) : 
      Optional.empty();
}

И, наконец, давайте теперь изменим ThemeResolver в нашем приложении:

@Override
public void setThemeName(HttpServletRequest request, HttpServletResponse response, String theme) {
    Authentication authentication = SecurityContextHolder.getContext()
        .getAuthentication();
    if (isAuthenticated(authentication)) {
        request.setAttribute(THEME_REQUEST_ATTRIBUTE_NAME, theme);
        UserPreference userPreference = getUserPreference(authentication).orElse(new UserPreference());
        userPreference.setUsername(((User) authentication.getPrincipal()).getUsername());
        userPreference.setTheme(StringUtils.hasText(theme) ? theme : null);
        userPreferenceRepository.save(userPreference);
    }
}

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

@Bean 
public ThemeResolver themeResolver() { 
    return new UserPreferenceThemeResolver();
}

Альтернативным способом сохранения пользовательских настроек мог быть контроллер Spring MVC и отдельный API.

7. Заключение

В этой статье мы узнали, как настроить темы Spring MVC.

Мы также можем найти полный код на GitHub.

«