SpringBoot多模板引擎整合难题?一篇搞定JSP、Freemarker与Thymeleaf!

关注我的公众号:【编程朝花夕拾】,可获取首发内容。

01 引言

在现代Web应用开发中,模板引擎是实现前后端分离和视图渲染的重要工具。SpringBoot作为流行的Java开发框架,天然支持多种模板引擎。

每一个项目使用单一的模板引擎是标准输出。但是,总有一些老项目经历多轮迭代,人员更替,不同的开发都只是用自己熟悉的模版引擎,导致一个项目中包含了多种模板引擎。从而相互影响,甚至出现异常。这也是小编正在经历的痛苦。

本文将详细介绍如何在SpringBoot项目中同时集成JSP、Freemarker和Thymeleaf三种模板引擎,包括配置方法、使用场景、常见问题及解决方案。

02 项目搭建

本文基于Springboot 3.0.13,因为不同版本(2.x)对于部分包的做了更改。由于JSP的配置会影响其他的模板引擎,所以JSP的配置,放到最后说明。

2.1 Maven依赖

xml 复制代码
<!-- freemarker 模版引擎 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!-- thymeleaf 模版引擎 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

2.3 配置

properties 复制代码
#配置freemarker
spring.freemarker.template-loader-path=classpath:/templates/ftl/
spring.freemarker.suffix=.ftl
spring.freemarker.cache=false

# 配置thymeleaf
spring.thymeleaf.prefix=classpath:/templates/html/
spring.thymeleaf.suffix=.html
spring.thymeleaf.cache=false

2.4 最佳实践

页面

控制层

java 复制代码
@Controller
@RequestMapping("/page")
public class PageController {
    
    @RequestMapping("{engine}")
    public String toPage(@PathVariable("engine") String engine, Model model) {
        model.addAttribute("date", new Date());
        return engine + "_index";
    }
}

2.5 测试

到这里,会发现一切顺利。ThymeleafFreemarker都可以顺利解析。但是,引入JSP之后,发现不能生效。

03 SpringBoot继续集成JSP

3.1 Maven依赖

xml 复制代码
<!-- JSP支持 -->
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<!-- jstl 工具 -->
<dependency>
    <groupId>jakarta.servlet.jsp.jstl</groupId>
    <artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
</dependency>
<dependency>
    <groupId>org.glassfish.web</groupId>
    <artifactId>jakarta.servlet.jsp.jstl</artifactId>
</dependency>

这里要说明的jstl,低版本(3.x一下)的需要引入:

xml 复制代码
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
</dependency>

具体的依赖可以在Springboot官方文档中查看。

3.2 配置

properties 复制代码
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

3.3 创建包结构

因为SpringBoot默认不支持JSP,所以需要我们自己配置支持JSP。

包的路径地址:\src\main\webapp\WEB-INF

3.4 修改pom打包

build下增加resource

xml 复制代码
<resources>
    <!-- 打包时将jsp文件拷贝到META-INF目录下-->
    <resource>
        <!-- 指定处理哪个目录下的资源文件 -->
        <directory>src/main/webapp</directory>
        <!--注意此次必须要放在此目录下才能被访问到-->
        <targetPath>META-INF/resources</targetPath>
        <includes>
            <include>**/**</include>
        </includes>
    </resource>
</resources>

3.5 测试

其他两个不受影响,但是发现配置的JSP并不生效,根据报错信息来看,默认使用了Thymeleaf解析的。

04 源码追踪

关键的类:org.springframework.web.servlet.view.ContentNegotiatingViewResolver

断点调试发现,图中①根据jsp_index视图,可以发现两个候选的ViewThymelearViewJstlView

图中②获取最优的视图返回了ThymelearView,从而解析错误。从getBestView()源码可以看到,仅仅做了遍历操作,并没有个给句特殊的规则去取。如图:

所以影响视图解析器的就是候选视图的顺序。

我们继续看候选视图的取值:

这里仍是只是遍历,我们需要继续追溯this.viewResolvers的来源:

关键代码AnnotationAwareOrderComparator.sort(this.viewResolvers)会对所有的视图排序,所以我们只需要指定JSP的视图为第一个就可以了。

05 配置JSP视图的顺序

因为JSP的视图使用的是InternalResourceViewResolver,所以我们只需要设置其顺序即可。

java 复制代码
@Configuration
public class BeanConfig {

    @Autowired
    InternalResourceViewResolver resolver;

    @PostConstruct
    public void init() {
        resolver.setOrder(1);
    }

由于其他的视图解析器默认是最级别,所以这里的设置只要比Integr.MAX小即可。

测试

我们发现源代码已经将JstlView变成了第一个,最优的视图自然也选择了JstlView,如图:

效果

我们发现JSP是正常显示了,但是其他两个又不好了。

真实让人头大!

06 解决JSP混合问题

6.1 解决方案

其实这里要使用一个属性可以永久的解决问题:viewName

每一个ViewResolver都有一段关键的源码:

这里是匹配关系,可以通过配置的view-names过滤不符合条件的视图:

6.2 重新修改配置

properties 复制代码
###配置freemarker
spring.freemarker.template-loader-path=classpath:/templates/
spring.freemarker.view-names=ftl/*
spring.freemarker.suffix=.ftl
spring.freemarker.cache=false
#
### 配置thymeleaf
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.view-names=html/*
spring.thymeleaf.suffix=.html
spring.thymeleaf.cache=false
##
### 配置JSP
spring.mvc.view.prefix=/WEB-INF/
spring.mvc.view.suffix=.jsp

这里的和之前不同的就是增加了spring.thymeleaf.view-namesspring.freemarker.view-names,并且classpath的路径少了一部分移动到view-names里面了。

JSPspring.mvc.view.prefix同样少了一部分需要配置。

6.3 重新修改Java配置

java 复制代码
@Configuration
public class BeanConfig {

    @Autowired
    InternalResourceViewResolver resolver;

    @PostConstruct
    public void init() {
        resolver.setViewNames("jsp/*");
    }

也可以使用Bean定义。使用Bean定义需要删除配置文件关于JSP的配置。

java 复制代码
@Bean
public ViewResolver jspViewResolver() {
    InternalResourceViewResolver resolver = new InternalResourceViewResolver();
    resolver.setPrefix("/WEB-INF/");
    resolver.setSuffix(".jsp");
    resolver.setViewNames("jsp/*");
    return resolver;
}

6.4 修改控制层

java 复制代码
@Controller
@RequestMapping("/page")
public class PageController {
 
    @RequestMapping("{engine}")
    public String toFtl(@PathVariable("engine") String engine, Model model) {
        model.addAttribute("date", new Date());
        return engine + "/" + engine + "_index";
    }
}

6.5 效果

相关推荐
丸码18 小时前
Java异常体系全解析
java·开发语言
v***885618 小时前
Springboot项目:使用MockMvc测试get和post接口(含单个和多个请求参数场景)
java·spring boot·后端
q***494518 小时前
Ubuntu介绍、与centos的区别、基于VMware安装Ubuntu Server 22.04、配置远程连接、安装jdk+Tomcat
java·ubuntu·centos
IMPYLH18 小时前
Lua 的 require 函数
java·开发语言·笔记·后端·junit·lua
曾经的三心草18 小时前
基于正倒排索引的Java文档搜索引擎1-实现索引模块-实现Parser类
java·开发语言·搜索引擎
爱找乐子的李寻欢18 小时前
线上批量导出 1000 个文件触发 OOM?扒开代码看本质,我是这样根治的
后端
vx_bscxy32218 小时前
告别毕设焦虑!Python 爬虫 + Java 系统 + 数据大屏,含详细开发文档 基于web的图书管理系统74010 (上万套实战教程,赠送源码)
java·前端·课程设计
字节拾光录19 小时前
Java工具库三足鼎立:Hutool、Apache Commons、Guava深度测评与场景化选型指南
java·apache·guava
爱学习的小可爱卢19 小时前
Java UDP编程实战:UDP数据报套接字编程DatagramPacket、DatagramSocket 、InetSocketAddress
java·udp·udp数据报
未来之窗软件服务19 小时前
幽冥大陆(三十五)S18酒店门锁SDK go语言——东方仙盟筑基期
java·前端·golang·智能门锁·仙盟创梦ide·东方仙盟·东方仙盟sdk