一、Freemark介绍
1.1 作用
FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。
1.2 与之替代方案的缺陷
- 通过文章Id读取DB数据, 加载到前端返回, 但对于DB的IO压力就比较大
- 将文章提前加载到Redis中, 可以提高响应速度, 减轻DB的压力, 但成本较高
1.3 同类型产品
Jsp、Freemarker、Thymeleaf 、Velocity 等
- Jsp 为 Servlet 专用,不能单独进行使用。
- Thymeleaf 为新技术,功能较为强大,但是执行的效率比较低。
- Velocity从2010年更新完 2.0 版本后,便没有在更新。Spring Boot 官方在 1.4 版本后对此也不在支持,虽然 Velocity 在 2017 年版本得到迭代,但为时已晚。
- Freemarker技术的功能可以和JSP一样,都可以实现动态技术。
1.4 JSP VS Freemarker
相同点:都可以作为动态技术去使用
不同点:Freemarker可以作为静态化技术去使用,而JSP不行。
二、Freemark
2.1 基本使用
创建项目
导入依赖
<!--Freemarker-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
配置application.yml
server:
port: 8881 #服务端口
spring:
application:
name: freemarker-demo #指定服务名
freemarker:
template-loader-path: classpath:/freemarker/ # 默认路径: classpath:/templates/
suffix: .ftl # 更改模板文件后缀名, 默认: .ftlh
cache: false #关闭模板缓存,方便测试
settings:
template_update_delay: 0 #检查模板更新延迟时间,设置为0表示立即检查,如果时间大于0会有缓存不方便进行模板测试
编写启动类
@SpringBootApplication
public class FreemarkerDemotApplication {
public static void main(String[] args) {
SpringApplication.run(FreemarkerDemotApplication.class,args);
}
}
编写domain
@Data
public class Student {
private String name;//姓名
private int age;//年龄
private Date birthday;//生日
private Float money;//钱包
}
编写Controller
@Controller //用于跳转页面,而非返回json数据
public class HelloController {
/**
* 入门
*/
@GetMapping("/hello")
public String hello(Model model){
// 也可以使用HttpServletRequest中的setAttribute, Model底层也是封装了HttpServletRequest
//存储数据
model.addAttribute("name","jack");
Student student = new Student();
student.setName("如花");
model.addAttribute("student",student);
//路径: prefix+hello+suffix
//classpath:/templates/hello.ftl
return "hello";//返回展示数据的页面名称
}
}
模板显示数据
在resources下建立templates目录,在templates目录下创建hello.ftl模板文件
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello World!</title>
</head>
<body>
name属性值:${name}
<hr/>
学生的姓名:${student.name}
</body>
</html>
2.2 语法基础
注释, 即 , 介于之间的内容会被freemarker忽略
<!-- cherishmengxin 文本被忽略 -->
插值(interpolation), 即 . . . 部分 , f r e e e m a r k e r 会用真实的值替代 {...}部分, freeemarker会用真实的值替代 ...部分,freeemarker会用真实的值替代{…}
meng ${xin}
FTL指令, 和HTML标记类似, 名字前加#予以区分, freemarker会解析标签中的表达式或逻辑
<#>FTL指令</#>
文本, 仅文本信息, 这些不是freemarker的注释、插值、FTL指令的内容会被freemarker忽略解析, 直接出书内容
CherishMengXin
2.3 FTL指令
遍历List
指令格式:
<#list 被遍历的变量名称 as 元素名称, 自定义></list>
Eg
<h2>遍历List集合</h2>
<table border="1">
<tr>
<td>姓名</td>
<td>年龄</td>
<td>钱包</td>
<td>生日</td>
</tr>
<#list stus as stu>
<tr>
<td>${stu_index + 1}</td> <!-- 获取循环下标 -->
<td>${stu.name}</td>
<td>${stu.age}</td>
<td>${stu.money}</td>
<td>${stu.birthday?string("yyyy年MM月dd日")}</td>
</tr>
</#list>
</table>
解释:
后端传递了stus集合变量到前端, 自定义元素名称为stu, 取值时, 使用EL表达式, 使用元素名称.属性名称, 即可获取值
${k_index}:得到循环的下标,使用方法是在stu后边加"_index",它的值是从0开始
遍历Map
指令格式:
<#list stuMap?keys as key></#list>
Eg
<h2>遍历Map集合</h2>
<table border="1">
<tr>
<td>姓名</td>
<td>年龄</td>
<td>钱包</td>
<td>生日</td>
</tr>
<#list stuMap?keys as key>
<tr>
<td>${stuMap[key].name}</td>
<td>${stuMap[key].age}</td>
<td>${stuMap[key].money}</td>
<td>${stuMap[key].birthday?string("yyyy年MM月dd日")}</td>
</tr>
</#list>
</table>
解释:
stuMap是后端传递的Map集合遍历名称, keys相当于是stuMap集合变量中所有的key, key是keys中单个key的元素名称, keys 与 key 都是自定义的
取值的方式:
使用EL表达式, 后端传递的变量名称[单个key的元素名称].属性名称
${stuMap[key].name}
if判断
指令格式
<#if ><#else></#if>
Eg
<#list stus as stu>
<#if stu.name=='小红'>
<tr style="color: red">
<td>${stu_index+1}</td>
<td>${stu.name}</td>
<td>${stu.age}</td>
<td>${stu.money}</td>
</tr>
<#else>
<tr>
<td>${stu_index+1}</td>
<td>${stu.name}</td>
<td>${stu.age}</td>
<td>${stu.money}</td>
</tr>
</#if>
</#list>
解释:
只有条件stu.name是小红时, 此行数据会变更为红色, 其余不满足条件的, 则不变更颜色, 若没有esle, 则不满足条件的不会显示出来
空值判断
- 判断某变量是否存在使用 ??
用法为:variable??,如果该变量存在,返回true,否则返回false a?? -> a!=null
Eg
<#if stus??>
<#list stus as stu>
<#if stu.name=='小红'>
<tr style="color: red">
<td>${stu_index+1}</td>
<td>${stu.name}</td>
<td>${stu.age}</td>
<td>${stu.money}</td>
</tr>
<#else>
<tr>
<td>${stu_index+1}</td>
<td>${stu.name}</td>
<td>${stu.age}</td>
<td>${stu.money}</td>
</tr>
</#if>
</#list>
</#if>
stus?? 相当于 <#if stus!=null && stus?size > 0>
- 缺失变量默认值使用 !
使用!要以指定一个默认值,当变量为空时显示默认值
${name!‘’}表示如果name为空显示空字符串。
- 如果是嵌套对象则建议使用()括起来
Eg
${(stu.bestFriend.name)!''}
解释
如果stu或bestFriend或name为空默认显示空字符串。
在freemarker中,判断是否相等,=与==是一样的
2.4 日期类型的处理
- 显示年月日: ${today?date}
- 显示时分秒:${today?time}
- 显示日期+时间:${today?datetime}
- 自定义格式化:${today?string(“yyyy年MM月”)}
2.5 运算符
2.5.1 数学运算
- 加法: +
- 减法: -
- 乘法: *
- 除法: /
- 求模运算: %
100+5 运算: ${100 + 5 }<br/>
100 - 5 * 5运算:${100 - 5 * 5}<br/>
5 / 2运算:${5 / 2}<br/>
12 % 10运算:${12 % 10}<br/>
2.5.2 比较运算
注意事项:
=和!=可以用于字符串、数值和日期来比较是否相等
=和!=两边必须是相同类型的值,否则会产生错误
字符串 “x” 、"x " 、"X"比较是不等的.因为FreeMarker是精确比较
gt代替>, FreeMarker会把>解释成FTL标签的结束字符,可使用括号避免这种情况,如:<#if (x>y)>
2.5.3 逻辑运算
- 逻辑与: &&
- 逻辑或: ||
- 逻辑非: !
Eg
<#if (10 lt 12 )&&( 10 gt 5 ) >
(10 lt 12 )&&( 10 gt 5 ) 显示为 true
</#if>
<br/>
<br/>
<#if !false>
false 取反为true
</#if>
2.6 内建函数
语法: 变量+?+函数名称
Eg:
1. 获取集合大小: ${变量名?size}
2. 显示年月日: ${today?date}3. 显示时分秒:${today?time}
4. 显示日期+时间:${today?datetime}
5. 自定义格式化:${today?string(“yyyy年MM月”)}
6. 内建函数c: ${point?c}6.1 model.addAttribute(“point”, 102920122);
point是数字型,使用${point}会显示这个数字的值,每三位使用逗号分隔。
如果不想显示为每三位分隔的数字,可以使用c函数将数字型转成字符串输出**7. 将json字符串转成对象: **
Eg:
<#assign text="{'bank':'工商银行','account':'10101920201920212'}" />
#assign data=text?eval />
开户行:${data.bank} 账号:${data.account}
其中用到了 assign标签,assign的作用是定义一个变量
三、生成静态页面文件
3.1 Api
Configuration: 获取Freemarker的Bean对象, 通过对象调用getTemplate(“模板名称.ftl / ftlh”); 获取指定模板的对象
@Autowired
private Configuration configuration;
getTemplate: 获取指定模板的对象
// 获取Freemarker 模板对象
Template template = configuration.getTemplate("mengxin.ftl");
process: 通过打印输出流, 将指定的数据写入模板中
// 获取打印输出流
PrintWriter printWriter = new PrintWriter("E:\\Practical project\\freemarker-demo\\src\\main\\resources\\freemarker\\mengxin.html");
// 调用freemarker 模板对象的 process方法, 通过输出流, 生成静态页面文件,
template.process(templateValue, printWriter);
templateValue是从数据库获取到的数据, 这个变量的数据类型是Map集合
Eg: 若使用@Controller, 则输出静态页面文件时, 会出现异常ServletException, 使用@RestController则不会, 此异常可以忽略, 对结果没有影响
@Controller
public class FreemarkerController {
@Autowired
private Configuration configuration;
@GetMapping("CherishMengXin")
public void freemarkerTest(HttpServletRequest request, Model model) throws Exception{
// 获取Freemarker 模板对象
Template template = configuration.getTemplate("mengxin.ftl");
// 获取模板中动态部分的数据, 也就是需要给模板传递的值
Map<String, Object> templateValue = getTemplateValue();
// 获取打印输出流
PrintWriter printWriter = new PrintWriter("E:\\Practical project\\freemarker-demo\\src\\main\\resources\\freemarker\\mengxin.html");
// 调用freemarker 模板对象的 process方法, 通过输出流, 生成静态页面文件,
template.process(templateValue, printWriter);
}
public Map<String, Object> getTemplateValue(){
Student stu1 = new Student();
stu1.setName("小强");
stu1.setAge(18);
stu1.setMoney(1000.86f);
stu1.setBirthday(new Date());
//小红对象模型数据
Student stu2 = new Student();
stu2.setName("小红");
stu2.setMoney(200.1f);
stu2.setAge(19);
stu2.setBirthday(new Date());
//将两个对象模型数据存放到List集合中
List<Student> stus = new ArrayList<>();
stus.add(stu1);
stus.add(stu2);
HashMap<String,Student> stuMap = new HashMap<>();
stuMap.put("stu1",stu1);
stuMap.put("stu2",stu2);
String carName = "宝马";
Map<String, Object> data = new HashMap<>();
data.put("stus",stus);
data.put("stuMap",stuMap);
data.put("carName",carName);
return data;
}
}
四、Minio 与 Freemarker
4.1 Minio基本使用
依赖
<!--MinIo-->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.3.3</version>
</dependency>
Minio Api
//列出所有的Buckets
public List<Bucket> listBuckets()
Eg: 创建Bucket
@Test
public void minioClientTest(){
// 使用建造者模式, 获取Minion客户端的对象, 并设置url、Account、Password
MinioClient minioClient = MinioClient.builder().endpoint(endpoint).credentials(username, password).build();
try {
// 判断Meng的Bucket是否存在, 若存在, 则不创建新的Bucket
if (minioClient.bucketExists(BucketExistsArgs.builder().bucket("meng").build())) {
log.info("Bucket exists");
}else{
// 创建名为Meng的Bucket
minioClient.makeBucket(MakeBucketArgs.builder().bucket("meng").build());
log.info("Create new bucket");
}
} catch (Exception e) {
e.printStackTrace();
}
}
存储桶命名规则
- 存储桶名称的长度必须介于 3(最小)到 63(最大)个字符之间。
- 存储桶名称只能由小写字母、数字、点 (.) 和连字符 (-) 组成。
- 存储桶名称不得包含两个相邻的句点。
- 存储桶名称不得格式化为 IP 地址 (例如,192.168.5.4)。
- 存储桶名称不得以前缀 xn 开头–.
- 存储桶名称不得以后缀 -s3alias 结尾。此后缀是为接入点别名保留的。
- 存储桶名称在分区中必须是唯一的。
Eg: 上传文件
// 获取上传文件的对象
@Test
public void minioClientTest(){
// 使用建造者模式, 获取Minion客户端的对象, 并设置url、Account、Password
MinioClient minioClient = MinioClient.builder().endpoint(endpoint).credentials(username, password).build();
try {
// 判断Meng的Bucket是否存在, 若存在, 则不创建新的Bucket
if (minioClient.bucketExists(BucketExistsArgs.builder().bucket("meng").build())) {
log.info("Bucket exists");
}else{
// 创建名为Meng的Bucket
minioClient.makeBucket(MakeBucketArgs.builder().bucket("meng").build());
log.info("Create new bucket");
}
// 获取所有的Bucket
// List<Bucket> buckets = minioClient.listBuckets();
FileInputStream fileInputStream = new FileInputStream("E:\\Practical project\\freemarker-demo\\src\\main\\resources\\freemarker\\mengxin.html");
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.bucket("meng") // Bucket名称
.object("com/itcast/freemarker/mengxin.html") // 指定上传文件的路径, 若路径不存在, 则会自动创建路径
.stream(fileInputStream, fileInputStream.available(), - 1) // 通过文件输入流, 上传文件, 第二个参数: 文件的大小, 第三个参数: 需要上传多大, -1是全部上传, 也可以指定上传的大小
.contentType("text/html") // 上传文件的类型
.build();
// 上传文件
minioClient.putObject(putObjectArgs);
fileInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
// 通过文件输入流上传指定文件对象中指定的文件按照指定的路径上传到指定的Bucket中
public ObjectWriteResponse putObject(PutObjectArgs args)
// 获取指定Bucket中的指定路径中的指定文件的对象
GetObjectArgs objectArgs = GetObjectArgs.builder().bucket("meng").object("com/itcast/freemarker/mengxin.html").build();
// 下载指定文件, 返回值为输出流
public InputStream getObject(GetObjectArgs args)
Eg: 下载文件
@Test
public void upload() throws Exception{
// 获取Bucket Client 对象
MinioClient minioClient = MinioClient.builder().endpoint(endpoint).credentials(username, password).build();
// 获取文件对象
GetObjectArgs objectArgs = GetObjectArgs.builder().bucket("meng").object("com/itcast/freemarker/mengxin.html").build();
// 获取文件对象的输入流
InputStream inputStream = minioClient.getObject(objectArgs);
// 获取文件输出流
FileOutputStream outputStream = new FileOutputStream("E:\\Practical project\\freemarker-demo\\src\\main\\resources\\download\\mengxin.html");
// 写入磁盘
byte[] bytes = new byte[8 * 1024];
int length;
while(- 1 != (length = inputStream.read())){
outputStream.write(bytes,0,length);
}
// 关闭输入输出流
outputStream.close();
inputStream.close();
}
// 删除指定Bucket中指定路径的文件的对象
RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().bucket("meng").object("com/itcast/freemarker/mengxin.html").build();
// 根据指定的RemoveObjectArgs对象, 删除指定Bucket中指定路径的指定文件
public void removeObject(RemoveObjectArgs args)
// 获取删除指定Bucket的对象
RemoveBucketArgs removeBucketArgs = RemoveBucketArgs.builder().bucket("meng").build();
// 根据指定的RemoveBucketArgs对象, 删除指定的Bucket
public void removeBucket(RemoveBucketArgs args)
删除文件 与 Bucket
@Test
public void removeFile() throws Exception{
// 获取Minio Client对象
MinioClient minioClient = MinioClient.builder().endpoint(endpoint).credentials(username, password).build();
// 删除指定Bucket中指定路径的文件的对象
RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().bucket("meng").object("com/itcast/freemarker/mengxin.html").build();
// 删除文件
minioClient.removeObject(removeObjectArgs);
// 获取删除指定Bucket的对象
RemoveBucketArgs removeBucketArgs = RemoveBucketArgs.builder().bucket("meng").build();
// 删除桶
minioClient.removeBucket(removeBucketArgs);
}