Использование нескольких ViewResolver в Spring Boot

View more categories:

1- Цель статьи

Обычно в приложении  Spring вам нужно использовать только одну технологию для уровня  View, это может быть  Thymeleaf, JSP или  FreeMarker,... Но вы так же можете использовать разные технологии одновременно для уровня  View. В данной статье я покажу вам как создать такое приложение.
OK, мы создадим приложение используя 3 технологии одновременно  Thymeleaf, JSP & FreeMarker для уровня  View.

2- Создать проект Spring Boot

3- Конфигурация pom.xml

Чтобы использовать  JSP, Thymeleaf, FreeMarker вам нужно имет следующие библиотеки:
<!-- Thymeleaf -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- FreeMarker -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!-- Web -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- JSP -->
<dependency>
   <groupId>org.apache.tomcat.embed</groupId>
   <artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<!-- JSTL -->
<dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>jstl</artifactId>
</dependency>
Полное содержание файла  pom.xml:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
      http://maven.apache.org/xsd/maven-4.0.0.xsd">
      
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.o7planning</groupId>
    <artifactId>SpringBootMultiViewResolver</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>SpringBootMultiViewResolver</name>
    <description>Spring Boot + Multi ViewResolver</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        
        <!-- Thymeleaf -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        
        <!-- FreeMarker -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>        
        
        <!-- Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!-- JSP -->
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>

        <!-- JSTL -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
        </dependency>
        

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

4- Конфигурация ViewResolver

При разработки проекта  Spring Boot, вы обычно используете только одну технологию для уровня  View ( JSP, Thymeleaf, ..), Spring Boot автоматически конфигурирует для вас  ViewResolver, чтобы вы работали с той технологией. Но в случае если вы используете много технологий для уровня  View, вам нужно самим конфигурировать все нужные  ViewResolver.
Это изображение потока (Flow) приложения  Spring в случае когда вы используете 1 ViewResolver.
В случае если вы используете много технологий для уровня View, будет много  ViewResolver участвующих в потоке (flow) приложения. ViewResolver расположены по порядку приоритета (0, 1, 2, ..). Если  ViewResolver (0) не находит нужный  "View Name", будет использован  ViewResolver (1), ...

Конфигурация Thymeleaf ViewResolver.

ThymeleafViewResolverConfig.java
package org.o7planning.sbmultiviewresolver.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
import org.thymeleaf.templateresolver.ITemplateResolver;

@Configuration
public class ThymeleafViewResolverConfig {

    @Bean
    public ViewResolver thymeleafViewResolver() {

        ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();

        viewResolver.setTemplateEngine(thymeleafTemplateEngine());
        viewResolver.setCharacterEncoding("UTF-8");
        viewResolver.setOrder(0);

        // Important!!
        // th_page1.html, th_page2.html, ...
        viewResolver.setViewNames(new String[] { "th_*" });

        return viewResolver;
    }

    // Thymeleaf template engine with Spring integration
    @Bean
    public SpringTemplateEngine thymeleafTemplateEngine() {

        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(thymeleafTemplateResolver());
        templateEngine.setEnableSpringELCompiler(true);

        return templateEngine;
    }

    @Bean
    public SpringResourceTemplateResolver springResourceTemplateResolver() {
        return new SpringResourceTemplateResolver();
    }

    // Thymeleaf template resolver serving HTML 5
    @Bean
    public ITemplateResolver thymeleafTemplateResolver() {

        ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();

        templateResolver.setPrefix("templates/");
        templateResolver.setCacheable(false);
        templateResolver.setSuffix(".html");
        templateResolver.setTemplateMode(TemplateMode.HTML);
        templateResolver.setCharacterEncoding("UTF-8");

        return templateResolver;
    }
 
}
В данном приложении мы конфигурируем  Thymeleaf ViewResolver с наивысшим приоритетом (order = 0).
Примечание: Thymeleaf ViewResolver выбрасывает исключение если не находит нужный  "View Name" (Нужный файл  html). Он отличается от вашего желания, будет использован  ViewResolver со следующим приоритетом. Поэтому вам нужно создать правило для  "View Name" которые будут обслужены с помощью  Thymeleaf ViewResolver.
// Important!!
// th_page1.html, th_page2.html, ...
viewResolver.setViewNames(new String[] { "th_*" });

Конфигурация FreeMarker ViewResolver.

FreeMarkerViewResolverConfig.java
package org.o7planning.sbmultiviewresolver.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;

@Configuration
public class FreeMarkerViewResolverConfig {

    @Bean(name = "viewResolver")
    public ViewResolver getViewResolver() {
        FreeMarkerViewResolver viewResolver = new FreeMarkerViewResolver();

        viewResolver.setCache(true);
        viewResolver.setPrefix("/freemarker/");
        viewResolver.setSuffix(".ftl");
        viewResolver.setOrder(1);
        return viewResolver;
    }

    @Bean(name = "freemarkerConfig")
    public FreeMarkerConfigurer getFreemarkerConfig() {
        FreeMarkerConfigurer config = new FreeMarkerConfigurer();

        // Folder containing FreeMarker templates.
        // 1 - "/WEB-INF/views/"
        // 2 - "classpath:/templates"
        config.setTemplateLoaderPath("classpath:/templates");
        return config;
    }
    
}
Принцип работы  FreeMarker ViewResolver:

Конфигурация JSP ViewResolver.

Примечание: JSP ViewResolver должно быть представлено в самым низким приоритетом. Это прокомментировано в документах у  Spring.
JspViewResolverConfig.java
package org.o7planning.sbmultiviewresolver.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

@Configuration
public class JspViewResolverConfig {

    @Bean
    public ViewResolver jspViewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        
        viewResolver.setViewClass(JstlView.class);
        viewResolver.setPrefix("/WEB-INF/jsp/");
        viewResolver.setSuffix(".jsp");
        viewResolver.setContentType("text/html");
        
        // Make sure > Thymeleaf order & FreeMarker order.
        viewResolver.setOrder(1000);
        
        return viewResolver;
    }
    
}
Принцип работы  JSP ViewResolver:

5- Controller

MainController.java
package org.o7planning.sbmultiviewresolver.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class MainController {

    @RequestMapping(value = { "/testJsp" }, method = RequestMethod.GET)
    public String testJspView() {

        return "testJsp";
    }

    @RequestMapping(value = { "/testThymeleaf" }, method = RequestMethod.GET)
    public String testThymeleafView() {

        return "th_page1";
    }
    
    @RequestMapping(value = { "/testFreeMarker" }, method = RequestMethod.GET)
    public String testFreeMarkerView() {

        return "testFreeMarker";
    }
    
}

6- Views

th_page1.html (Thymeleaf)
<!DOCTYPE html>

<html lang="en">
   <head>
       <title>Thymeleaf</title>
   </head>
   <body>
   
      <h2>Thymeleaf Page</h2>
      <p>templates/th_page1.html</p>
      
   </body>
</html>
freemaker/testFreeMarker.ftl (FreeMarker)
<!DOCTYPE html>

<html lang="en">
   <head>
       <title>FreeMarker</title>
   </head>
   <body>
   
      <h2>FreeMarker Page</h2>
      <p>templates/freemarker/testFreeMarker.ftl</p>
      
   </body>
</html>
testJsp.jsp
<!DOCTYPE html>

<html lang="en">
   <head>
       <title>JSP</title>
   </head>
   <body>
   
      <h2>JSP Page</h2>
      <p>WEB-INF/jsp/testJsp.jsp</p>
      
   </body>
</html>

View more categories: