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 效果

相关推荐
点光5 小时前
使用Sentinel作为Spring Boot应用限流组件
后端
不要秃头啊6 小时前
别再谈提效了:AI 时代的开发范式本质变了
前端·后端·程序员
有志6 小时前
Java 项目添加慢 SQL 查询工具实践
后端
jonjia7 小时前
引入新维度化解权衡难题
程序员
jonjia7 小时前
优秀的工程师如何打破规则
程序员
jonjia7 小时前
在大厂交付大型项目的策略
程序员
jonjia7 小时前
RFC 与设计文档
程序员
jonjia7 小时前
为什么你(或任何人)应该成为一名研发经理?
程序员
jonjia7 小时前
管理技术质量 (Manage Technical Quality)
程序员
jonjia7 小时前
大厂软件工程师职业发展路径
程序员