Spring Boot 整合视图层技术 FreeMarker

大家好!我是今越。简单记录一下在 Spring Boot 框架中如何整合 Freemarker 及使用。

FreeMarker 简介

FreeMarker 是一款模板引擎:即一种基于模板和要改变的数据,并用来生成输出文本( HTML 网页,电子邮件,配置文件,源代码等)的通用工具。它不是面向最终用户的,而是一个 Java 类库,是一款程序员可以嵌入他们所开发产品的组件。

模板编写语言为 FreeMarker Template Language,它是简单的,专用的语言,不是像 PHP 那样成熟的编程语言。那就意味着要准备数据在真实编程语言中来显示,比如数据库查询和业务运算,之后模板显示已经准备好的数据在模板中,你可以专注于如何展现数据,而在模板之外可以专注于要展示什么数据。

FreeMarker官网插图

这种方式通常被称为 MVC(模型 视图 控制器)模式,对于动态网页来说,是一种特别流行的模式。它帮助从开发人员(Java 程序员)中分离出网页设计师(HTML 设计师)。设计师无需面对模板中的复杂逻辑,在没有程序员来修改或重新编译代码时,也可以修改页面的样式。而 FreeMarker 最初的设计,是被用来在 MVC 模式的 Web 开发框架中生成 HTML 页面的,它没有被绑定到 Servlet 或 HTML 或任意 Web 相关的东西上。它也可以用于非 Web 应用环境中。

FreeMarker 是免费的,基于 Apache 许可证 2.0 版本发布。

整合 Spring Boot

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>

org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration 类中,可以看到自动化的配置:

@AutoConfiguration
@ConditionalOnClass({Configuration.class, FreeMarkerConfigurationFactory.class})
@EnableConfigurationProperties({FreeMarkerProperties.class})
@Import({FreeMarkerServletWebConfiguration.class, FreeMarkerReactiveWebConfiguration.class, FreeMarkerNonWebConfiguration.class})
public class FreeMarkerAutoConfiguration {}

从这里可以看出,当 classpath 下存在 Configuration 以及 FreeMarkerConfigurationFactory 时,配置才会生效,也就是说当我们引入了 Freemarker 依赖之后,配置就会生效。但是这里的自动化配置只做了模板位置检查,其他配置则是在导入的 FreeMarkerServletWebConfiguration 配置中完成的。那么我们再来看看 FreeMarkerServletWebConfiguration 类,部分源码如下:

package org.springframework.boot.autoconfigure.freemarker;import javax.servlet.DispatcherType;
import javax.servlet.Servlet;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain;
import org.springframework.boot.autoconfigure.web.servlet.ConditionalOnMissingFilterBean;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.resource.ResourceUrlEncodingFilter;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfig;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;@Configuration(proxyBeanMethods = false
)
@ConditionalOnWebApplication(type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, FreeMarkerConfigurer.class})
@AutoConfigureAfter({WebMvcAutoConfiguration.class})
class FreeMarkerServletWebConfiguration extends AbstractFreeMarkerConfiguration {protected FreeMarkerServletWebConfiguration(FreeMarkerProperties properties) {super(properties);}@Bean@ConditionalOnMissingBean({FreeMarkerConfig.class})FreeMarkerConfigurer freeMarkerConfigurer() {FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();this.applyProperties(configurer);return configurer;}@Beanfreemarker.template.Configuration freeMarkerConfiguration(FreeMarkerConfig configurer) {return configurer.getConfiguration();}@Bean@ConditionalOnMissingBean(name = {"freeMarkerViewResolver"})@ConditionalOnProperty(name = {"spring.freemarker.enabled"},matchIfMissing = true)FreeMarkerViewResolver freeMarkerViewResolver() {FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();this.getProperties().applyToMvcViewResolver(resolver);return resolver;}@Bean@ConditionalOnEnabledResourceChain@ConditionalOnMissingFilterBean({ResourceUrlEncodingFilter.class})FilterRegistrationBean<ResourceUrlEncodingFilter> resourceUrlEncodingFilter() {FilterRegistrationBean<ResourceUrlEncodingFilter> registration = new FilterRegistrationBean(new ResourceUrlEncodingFilter(), new ServletRegistrationBean[0]);registration.setDispatcherTypes(DispatcherType.REQUEST, new DispatcherType[]{DispatcherType.ERROR});return registration;}
}

我们来简单看下这段源码:

  • @ConditionalOnWebApplication 表示当前配置在 web 环境下才会生效。

  • ConditionalOnClass 表示当前配置在存在 Servlet 和 FreeMarkerConfigurer 时才会生效。

  • @AutoConfigureAfter 表示当前自动化配置在 WebMvcAutoConfiguration 之后完成。

  • 代码中,主要提供了 FreeMarkerConfigurer 和 FreeMarkerViewResolver。

  • FreeMarkerConfigurer 是 Freemarker 的一些基本配置,例如 templateLoaderPath、defaultEncoding 等

  • FreeMarkerViewResolver 则是视图解析器的基本配置,包含了 viewClass、suffix、allowRequestOverride、allowSessionOverride 等属性。

另外还有一点,在这个类的构造方法中,注入了 FreeMarkerProperties:

@ConfigurationProperties(prefix = "spring.freemarker"
)
public class FreeMarkerProperties extends AbstractTemplateViewResolverProperties {public static final String DEFAULT_TEMPLATE_LOADER_PATH = "classpath:/templates/";public static final String DEFAULT_PREFIX = "";public static final String DEFAULT_SUFFIX = ".ftlh";private Map<String, String> settings = new HashMap();private String[] templateLoaderPath = new String[]{"classpath:/templates/"};private boolean preferFileSystemAccess;
}

FreeMarkerProperties 中则配置了 Freemarker 的基本信息,例如模板位置在 classpath:/templates/ ,再例如模板后缀为 .ftlh,那么这些配置我们以后都可以在 application.properties 中进行修改。

spring.freemarker.allow-request-override=false
spring.freemarker.allow-session-override=false
spring.freemarker.cache=false
spring.freemarker.charset=UTF-8
spring.freemarker.check-template-location=true
spring.freemarker.content-type=text/html
spring.freemarker.expose-request-attributes=false
spring.freemarker.expose-session-attributes=false
spring.freemarker.suffix=.ftl
spring.freemarker.template-loader-path=classpath:/templates/

配置文件按照顺序依次解释如下:

  1. HttpServletRequest 的属性是否可以覆盖 controller 中 model 的同名项
  2. HttpSession 的属性是否可以覆盖 controller 中 model 的同名项
  3. 是否开启缓存
  4. 模板文件编码
  5. 是否检查模板位置
  6. Content-Type 的值
  7. 是否将 HttpServletRequest 中的属性添加到 Model 中
  8. 是否将 HttpSession 中的属性添加到 Model 中
  9. 模板文件后缀
  10. 模板文件位置

示例

创建类和接口

public class User {private Long id;private String username;private String address;// setter, getter
}
@Controller
public class UserController {@GetMapping("/hello")public String hello(Model model) {List<User> list = new ArrayList<>();for (int i = 0; i < 10; i++) {User user = new User();user.setId((long) i);user.setUsername("jackson>>>" + i);user.setAddress("Hangzhou>>>" + i);list.add(user);}model.addAttribute("users", list);return "hello";}
}

hello.ftlh 页面中渲染数据

<!doctype html>
<html lang="en">
<head><meta charset="UTF-8"><title>Document</title>
</head>
<body>
<table border="1"><tr><td>用户编号</td><td>用户名称</td><td>用户地址</td></tr><#list users as u><tr><td>${u.id}</td><td>${u.username}</td><td>${u.address}</td></tr></#list>
</table>
</body>
</html>

显示效果如下

FreeMarker官网插图

FreeMarker 使用细节

插值与表达式

直接输出值

1)字符串

<div>${"hello,我是直接输出的字符串"}</div>
<div>${"我的文件保存在C:\\盘"}</div>

注意:\ 需要转义

在目标字符串的引号前增加 r 标记,在 r 标记后的文本内容将会直接输出,例如

<div>${r"我的文件保存在C:\\盘"}</div>

2)数字

在 FreeMarker 中使用数值需要注意

a.数值不能省略小数点前面的 0,所以 “.5” 是错误的写法;

b.数值 8 , +8 , 8.00 都是相同的;

数字的其它用法:

将数字以钱的形式,以百分数的形式展示

<#assign price=99>
<div>${price?string.currency}</div>
<div>${price?string.percent}</div>

3)布尔

布尔类型可以直接定义,不需要引号,例如

<#assign flag=true>
<div>${flag?string("yes","no")}</div>
<!--如果 flag 为 true,则输出 yes,否则输出 no-->

4)集合

集合也可以现场定义现场输出,例如

<#list [2+2,"anson","jackson"] as x><div>${x}</div>
</#list>
<#list 5..1 as x><div>${x}</div>
</#list>
<#list 1..5 as x><div>${x}</div>
</#list>

其中,x 代表集合中的每一个元素。

也可以定义 Map 集合,Map 集合用一个 {} 来描述,例如

<#assign userinfo={"name":"jackson","address":"hanghzou西湖"}>
<#list userinfo?keys as key><div>${key}--${userinfo[key]}</div>
</#list>
<hr/>
<#list userinfo?values as value><div>${value}</div>
</#list>
<hr/>
<div>${userinfo.name}</div>
<div>${userinfo['address']}</div>

上面两个循环分别表示遍历 Map 中的 key 和 value。

输出变量

@Controller
public class UserController {@GetMapping("/hello")public String hello(Model model) {List<User> list = new ArrayList<>();for (int i = 0; i < 10; i++) {User user = new User();user.setId((long) i);user.setUsername("jackson>>>" + i);user.setAddress("Hangzhou>>>" + i);list.add(user);}model.addAttribute("users", list);Map<String, Object> info = new HashMap<>();info.put("name", "向往的今越");info.put("age", 18);info.put("address", "市中心");model.addAttribute("info", info);model.addAttribute("name", "shengsheng");model.addAttribute("birthday", new Date());return "hello";}
}

1)普通变量

普通变量的展示,例如

<div>${name}</div>

2)集合

直接遍历:

<div><table border="1"><#list users as u><tr><td>${u.id}</td><td>${u.username}</td><td>${u.address}</td></tr></#list></table>
</div>

输出集合中索引为 3 的元素:

<div>${users[3].address}</div>

输出子集合:

<div><table border="1"><#list users[3..5] as u><tr><td>${u.id}</td><td>${u.username}</td><td>${u.address}</td><td>${u_index}</td><td>${u_has_next?string("yes","no")}</td></tr></#list></table>
</div>

遍历时,可以通过 变量名_index 获取遍历的下标,变量名_has_next 判断是否有后继元素。

3)Map

直接获取 Map 中的值有不同的写法,例如

<div>${info.name}</div>
<div>${info['age']}</div>
<div>${info['address']}</div>

获取 Map 中的所有 key,并根据 key 获取 value

<div><#list info?keys as key><div>${key}--${info[key]}</div></#list>
</div>

获取 Map 中的所有 value

<div><#list info?values as value><div>${value }</div></#list>
</div>

字符串操作

字符串的拼接有两种方式

<div>${"hello ${name}"}</div>
<div>${"hello " + name}</div>

也可以从字符串中截取子串

<div>${name[0]}${name[1]}</div>
<div>${name[1..3]}</div>

集合操作

集合相加

<div><#list [1,2,3] + [4,5,6] as x>${x},</#list>
</div>

Map 相加

<div><#list (info+{"weather":"sunny"})?keys as key>${key},</#list>
</div>

算术运算

+*/% 运算都是支持的。

<div><#assign age=99><div>${age*99/99+99-1}</div>
</div>

比较运算

比较运算和 Thymeleaf 比较类似:

  • = 或者 == 判断两个值是否相等

  • != 判断两个值是否不等

  • > 或者 gt 判断左边值是否大于右边值

  • >= 或者 gte 判断左边值是否大于等于右边值

  • < 或者 lt 判断左边值是否小于右边值

  • <= 或者 lte 判断左边值是否小于等于右边值

<div><#assign age=99><#if age=99>age=99</#if><br/><#if age gt 99>age gt 99</#if><br/><#if (age > 99)>age > 99</#if><br/><#if age gte 99>age gte 99</#if><br/><#if age lt 99>age lt 99</#if><br/><#if age lte 99>age lte 99</#if><br/><#if age!=99>age!=99</#if><br/><#if age==99>age==99</#if><br/>
</div>

提示:带 < 或者 > 的符号,也都有别名,建议使用别名。

逻辑运算

逻辑运算符有三个:

  • 逻辑与 &&
  • 逻辑或 ||
  • 逻辑非 !
<div><#assign age=99><#if age=99 && 1==1>age=99 && 1==1</#if><#if age=99 || 1==0>age=99 || 1==0</#if><#if !(age gt 99)>!(age gt 99)</#if>
</div>

注意:逻辑运算符只能作用于布尔值,否则将产生错误。

空值处理

为了处理缺失变量,Freemarker 提供了两个运算符:

  • !:指定缺失变量的默认值

  • ??:判断某个变量是否存在

如果某个变量不存在,则设置其为 jackson,例如

<div>${aaa!"jackson"}</div>

如果某个变量不存在,则设置其为空字符串,例如

<div>${aaa!}</div>

! 后面的东西如果省略了,默认就是空字符串。

判断某个变量是否存在,例如

<div><#if aaa??>aaa</#if></div>

内建函数

内建函数可参考官网文档:http://freemarker.foofun.cn/ref_builtins.html

<div><#--cap_first 使字符串第一个字母大写--><div>${"hello"?cap_first}</div><#--lower_case 将字符串转换成小写--><div>${"HELLO"?lower_case}</div><#--upper_case 将字符串转换成大写--><div>${"hello"?upper_case}</div><#--trim 去掉字符串前后的空白字符--><div>${" hello "?trim}</div><#--size 获取序列中元素的个数--><div>${users?size}</div><#--int 取得数字的整数部分,结果带符号--><div>${-3.14?int}</div><#--日期格式化--><div>${birthday?string("yyyy-MM-dd")}</div>
</div>

常用指令

if/else

分支控制指令,作用类似于 Java 语言中的 if

<div><#assign age=23><#if (age>60)>老年人<#elseif (age>40)>中年人<#elseif (age>20)>青年人<#else> 少年人</#if>
</div>

比较符号中用了 (),因此不用转义。

switch

分支指令,类似于 Java 中的 switch

<div><#assign age=99><#switch age><#case 23>23<#break><#case 24>24<#break><#default>9999</#switch>
</div>

<#break> 是提前退出,也可以用在 <#list> 中。

noparse

如果想在页面展示一些 Freemarker 语法而不被渲染,则可以使用 noparse 标签,如下:

<#noparse><div>hhh</div>
<#noparse>

include

include 包含外部页面进来。

<#include "./test.ftlh">

macro

macro 用来定义一个宏。例如定义一个名为 book 的宏,并引用它:

<#macro book>三国演义
</#macro>
<@book/>

最终页面中会输出宏中所定义的内容。

在定义宏的时候,也可以传入参数,那么引用时,也需要传入参数:

<#macro book bs><table border="1"><#list bs as b><tr><td>${b}</td></tr></#list></table>
</#macro>
<@book ["三国演义","水浒传"]/>

bs 就是需要传入的参数。可以通过传入多个参数,多个参数跟在 bs 后面即可,中间用空格隔开。

还可以使用 <#nested> 引入用户自定义指令的标签体,像下面这样:

<#macro book bs><table border="1"><#list bs as b><tr><td>${b}</td></tr></#list></table><#nested>
</#macro>
<@book ["三国演义","水浒传"]><h1>hello javaboy!</h1>
</@book>

在宏定义的时候,<#nested> 相当于是一个占位符,在调用的时候,<@book> 标签中的内容会出现在 <#nested> 位置。

前面的案例中,宏都是定义在当前页面中,宏也可以定义在一个专门的页面中。新建 mymarcro.ftlh 页面,内容如下:

<#macro book bs title><table><#list bs as b><tr><td>${b}</td></tr></#list></table><#nested>
</#macro>

此时,需要先通过 <#import> 标签导入宏,然后才能调用,如下:

<#import "./mymacro.ftlh" as com>
<@com.book bs=["三国演义", "水浒传"] title="hhh"><h1>hello jackson!</h1>
</@com.book>

唯有热爱可抵岁月漫长。我是今越,欢迎大家点赞、收藏和评论,感谢支持!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/5011.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vscode中ModuleNotFoundError: No module named ‘torch‘解决方法

文章目录 遇到的问题解决方法参考 遇到的问题 使用vscode训练模型&#xff0c;没有使用远程服务器&#xff0c;使用本地运行代码&#xff0c;显示“ModuleNotFoundError: No module named ‘torch’” 解决方法 这是因为没有选择合适的python解释器。如何选择正确的解释器呢&…

C语言复习笔记5

1.函数 #include<stdio.h>void Add(int *p) {(*p); }int main() {int time0;Add(&time);printf("%d\n",time);return 0; }2.二分查找 #include<stdio.h>void Add(int *p) {(*p); }int main() {int time0;Add(&time);printf("%d\n",t…

PCL点云处理之多角度剖面切割(一百九十五)

PCL点云处理之多角度切割点云剖面(一百九十五) 一、算法介绍二、具体实现1.沿法向量方向切割剖面2.沿竖直方向切割剖面3.沿水平方向切割剖面一、算法介绍 点云的剖面往往隐藏着很多有用信息,而且分析更加简单一些,这里自己实现一系列不同角度的点云剖面切割,包括沿着法向量…

Java基础学习

import java.util.*; public class task1 {//用final定义常量public static final double CM10;public static void main(String[] args) {// TODO Auto-generated method stubScanner readernew Scanner(System.in);//用Math.sqrt&#xff08;被开方数&#xff09;计算一个数值…

如何用javascript 实现条形码和二维码

条形码和二维码 条形码和二维码都是一种用于存储信息的编码系统&#xff0c;它们可以被扫描设备或图像识别设备读取。 1. 条形码&#xff1a; 由一组垂直线条组成&#xff0c;线条的粗细和间距不同可以表示不同的数字或字符。通常用于商品标识和销售管理&#xff0c;以便在商…

geoserver发布arcgis server离线瓦片

1.使用tif文件也可以发布服务&#xff0c;但是我下载的tif文件发布的服务总数模糊不清&#xff0c;原因可能是地图比例尺问题。 2.仔细研究&#xff0c;发现下载的arcgis server瓦片都是高清的&#xff0c;于是想到直接加载arcgis瓦片&#xff0c;这样图片/坐标系之间问题都完…

arcgis栅格影像--镶嵌

1、打开软件导入数据&#xff0c;如下&#xff1a; 2、在搜索栏中搜索“镶嵌至新栅格”&#xff0c;如下&#xff1a; 3、双击打开镶嵌对话框&#xff0c;如下&#xff1a; 4、点击确定按钮&#xff0c;进行栅格镶嵌&#xff0c;镶嵌结果如下&#xff1a; 5、去除黑边&#xff…

STM32F4 WiFi上传温度【ds18b20传感器、网络通信】

通过WIFI或GPRS上传温度到云端 本篇博客将介绍如何使用WIFI或GPRS模块将温度数据上传到云端。我们将涵盖连接网络的过程、上传数据的过程以及相关代码。 准备工作 在开始之前&#xff0c;我们需要准备以下材料&#xff1a; STM32F4开发板温度传感器&#xff08;例如18B20&a…

【Java面试题】Java基础——面向对象

文章目录 重载和重写的区别★★★Java的三大特性请说明一下Super关键字的作用&#xff1f;static关键字的作用&#xff1f;final关键字的作用&#xff1f;super关键字和this关键字的作用&#xff1f;面向对象的三大特性★★★成员变量和局部变量的区别&#xff1f;Java能实现多…

Spring Boot 中的 @Configuration 注解

Spring Boot 中的 Configuration 注解 在 Spring Boot 中&#xff0c;我们经常使用注解来简化代码&#xff0c;提高效率。其中&#xff0c;Configuration 注解是一个非常重要的注解&#xff0c;它用于声明一个类作为 Spring 应用程序上下文的配置类。 在本文中&#xff0c;我…

无缝数据转换!使用C++ 实现 Excel文件与CSV之间的相互转换

CSV格式是一种通用的文本文件格式&#xff0c;可在多个应用程序之间共享和使用。相比之下&#xff0c;Excel文件是一种电子表格格式&#xff0c;通常只能在Microsoft Excel中编辑和查看。因此&#xff0c;将Excel文件转换为CSV格式可使数据更方便地在其他应用程序中使用&#x…

【动态规划算法练习】day12

文章目录 一、978. 最长湍流子数组1.题目简介2.解题思路3.代码4.运行结果 二、413. 等差数列划分1.题目简介2.解题思路3.代码4.运行结果 三、1567. 乘积为正数的最长子数组长度1.题目简介2.解题思路3.代码4.运行结果 总结 一、978. 最长湍流子数组 1.题目简介 978. 最长湍流子…