FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。
模板编写为FreeMarker Template Language (FTL)。它是简单的,专用的语言。 要准备数据在真实编程语言中来显示,比如数据库查询和业务运算, 之后模板显示已经准备好的数据。在模板中,可以专注于如何展现数据, 而在模板之外可以专注于要展示什么数据。
这种方式通常被称为 MVC (模型 视图 控制器) 模式,对于动态网页来说,是一种特别流行的模式。
它帮助从开发人员(Java 程序员)中分离出网页设计师(HTML设计师)。设计师无需面对模板中的复杂逻辑, 在没有程序员来修改或重新编译代码时,也可以修改页面的样式。
其它类似的java模板引擎还有Jsp、Thymeleaf 、Velocity 等。
freemarker作为springmvc一种视图格式,默认情况下SpringMVC支持freemarker视图格式。
此处创建Spring Boot+Freemarker工程用于测试模板。
代码demo:点击下载
创建一个maven工程:
1)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>com.xsh</groupId>
<artifactId>FreeMarker</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<dependencies>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
</project>
2)在resources目录下新建application.yml文件和templates目录。
freemarker默认会取templates目录下的文件,取其它的名字将会扫描不到。
application.yml:
server:
port: 8088 #服务端口
spring:
application:
name: test-freemarker #指定服务名
freemarker:
cache: false #关闭模板缓存,方便测试
settings:
template_update_delay: 0 #检查模板更新延迟时间,设置为0表示立即检查,如果时间大于0会有缓存不方便进行模板测试
3)创建测试实体类Student
package com.xsh.model;
import java.util.Date;
import java.util.List;
/**
* @author : xsh
* @create : 2020-06-23 - 22:26
* @describe:
*/
public class Student {
private String name;//姓名
private int age;//年龄
private Date birthday;//生日
private Float mondy;//钱包
private List<Student> friends;//朋友列表
private Student bestFriend;//最好的朋友
//此处已省略getter setter toString方法
}
在templates文件内新建test1.ftl文件,.ftl后缀名为freemarker模板文件,即FreeMarker Template Language
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello World!</title>
</head>
<body>
Hello ${name}!
</body>
</html>
4)创建controller
package com.xsh.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Map;
/**
* @author : xsh
* @create : 2020-06-23 - 22:33
* @describe:
*/
@Controller
public class FreeMarkerController {
@RequestMapping("/test1")
public String test1(Map<String,Object> map){
//map作为形参,里面的数据最终会作为request域响应给用户;
//freemarker会渲染map内的数据
map.put("name","xsh");
return "test1";
}
}
5)创建springBoot启动类
@SpringBootApplication
public class FreemarkerTestApplication {
public static void main(String[] args) {
SpringApplication.run(FreemarkerTestApplication.class,args);
}
}
运行项目,访问http://localhost:8088/test1
controller内返回数据准备:(返回增加了list和map类型)
package com.xsh.controller;
import com.xsh.model.Student;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.*;
/**
* @author : xsh
* @create : 2020-06-23 - 22:33
* @describe:
*/
@Controller
public class FreeMarkerController {
@RequestMapping("/test1")
public String test1(Map<String,Object> map){
//map作为形参,里面的数据最终会作为request域响应给用户;
//freemarker会渲染map内的数据
map.put("name","xsh");
Student stu1 = new Student();
stu1.setName("小明");
stu1.setAge(18);
stu1.setMondy(1000.86f);
stu1.setBirthday(new Date());
Student stu2 = new Student();
stu2.setName("小红");
stu2.setMondy(200.1f);
stu2.setAge(19);
List<Student> friends = new ArrayList<>();
friends.add(stu1);
stu2.setFriends(friends);
stu2.setBestFriend(stu1);
List<Student> stus = new ArrayList<>();
stus.add(stu1);
stus.add(stu2);
//向数据模型放数据
map.put("stus",stus);
//准备map数据
HashMap<String,Student> stuMap = new HashMap<>();
stuMap.put("stu1",stu1);
stuMap.put("stu2",stu2);
map.put("stu1",stu1);
map.put("stuMap",stuMap);
//返回freemarket模板的位置,基于resources/templates路径内
return "test1";
}
}
<table>
<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.mondy}</td>
</tr>
</#list>
</table>
-----渲染map数据方式一:在中括号中填写map的key--------<br/>
输出stu1的学生信息:<br/>
姓名:${stuMap['stu1'].name}<br/>
年龄:${stuMap['stu1'].age}<br/>
-----渲染map数据方式二: 在map后面直接.key--------<br/>
输出stu1的学生信息:<br/>
姓名:${stuMap.stu1.name}<br/>
年龄:${stuMap.stu1.age}<br/>
-----渲染map数据方式三--------<br/>
输出stu1的学生信息:<br/>
姓名:${stu1.name}<br/>
年龄:${stu1.age}<br/>
<hr>
遍历map
<table>
<tr>
<td>序号</td>
<td>姓名</td>
<td>年龄</td>
<td>钱包</td>
</tr>
<#list stuMap?keys as k>
<tr>
<td>${k_index + 1}</td>
<td>${stuMap[k].name}</td>
<td>${stuMap[k].age}</td>
<td>${stuMap[k].mondy}</td>
</tr>
</#list>
</table>
if 指令即判断指令,是常用的FTL指令,freemarker在解析时遇到if会进行判断,条件为真则输出if中间的内容,否则跳过内容不再输出。
<#list stus as stu>
<tr>
<td>${stu_index + 1}</td>
<td <#if stu.name =='小明'>style="background:red;"</#if>>${stu.name}</td>
<td>${stu.age}</td>
<td <#if stu.mondy gt 300>style="background:green;"</#if>>${stu.mondy}</td>
</tr>
</#list>
因为 FreeMarker会把>解释成FTL标签的结束字符,所以可以使用括号来避免这种情况,如:<#if (x>y)>,也可以使用比较运算符gt
FreeMarker表达式中完全支持算术运算,FreeMarker支持的算术运算符包括:+, - , * , / , %
逻辑运算符有如下几个: 逻辑与:&& ,逻辑或:|| ,逻辑非:! ,逻辑运算符只能作用于布尔值,否则将产生错误
表达式中支持的比较运算符有如下几个:
1)=或者== : 判断两个值是否相等.
2)!= : 判断两个值是否不等.
3)> 或者gt : 判断左边值是否大于右边值
4)>=或者gte : 判断左边值是否大于等于右边值
5)<或者lt : 判断左边值是否小于右边值
6) <=或者lte : 判断左边值是否小于等于右边值
因为freemarker模板内,当一个模板内一个渲染的数据为空时,将会报错。
所以可以判断某变量是否存在使用, “??” 用法为:variable??,如果该变量存在,返回true,否则返回false。
例:为防止stus为空报错可以加上判断如下:
<#if stus??>
<#list stus as stu>
......
</#list>
</#if>
使用 “!” 可以为变量指定为空时的默认值,当变量为空时则显示默认值。
例: ${name!' '}表示如果name为空显示空字符串。
如果是嵌套对象则建议使用()括起来。
例: ${(stu.bestFriend.name)!' '}表示,如果stu或bestFriend或name为空默认显示空字符串。
内建函数语法格式: 变量+?+函数名称
获取某个集合的大小
${集合名?size}
日期格式化
显示年月日: ${today?date}
显示时分秒:${today?time}
显示日期+时间:${today?datetime} <br>
自定义格式化: ${today?string("yyyy年MM月")}
函数 c
可以在后端使用map.put("point", 102920122);存放一个数值。
point是数字型,使用${point}会显示这个数字的值,并每三位使用逗号分隔。
如果不想显示为每三位分隔的数字,可以使用c函数将数字型转成字符串输出,${point?c}
将json字符串转成对象
其中用到了 assign标签,assign的作用是定义一个变量。
<br/>
<#assign text="{'bank':'工商银行','account':'10101920201920212'}" />
<#assign data=text?eval />
开户行:${data.bank} 账号:${data.account}
<hr>
Freemarker提供了3种加载模板目录的方法。 它使用Configuration类加载模板。
三种方法分别是:
public void setClassForTemplateLoading(Class clazz, String pathPrefix);
public void setDirectoryForTemplateLoading(File dir) throws IOException;
public void setServletContextForTemplateLoading(Object servletContext, String path);
第一种:基于类路径,HttpWeb包下的framemaker.ftl文件
configuration.setClassForTemplateLoading(this.getClass(), "/HttpWeb");
configuration.getTemplate("framemaker.ftl"); //framemaker.ftl为要装载的模板
第二种:基于文件系统
configuration.setDirectoryForTemplateLoading(new File("/template"))
configuration.getTemplate("framemaker.ftl"); //framemaker.ftl为要装载的模板
第三种:基于Servlet Context,指的是基于WebRoot下的template下的framemaker.ftl文件
HttpServletRequest request = ServletActionContext.getRequest();
configuration.setServletContextForTemplateLoading(request.getSession().getServletContext(), "/template");
configuration.getTemplate("framemaker.ftl");
在访问新闻、活动、商品、详情页面的时候,路径可以是xx【id】.html,服务器端根据请求id,动态生成html网页,下次访问数据时,无需再查下数据,直接将html静态页面返回。可以减少对数据库的交互,提高访问的性能。
//将数据模型抽取成一个方法,实际使用中为数据库查询出来的值
private Map getMap() {
Map<String, Object> map = new HashMap<>();
//向数据模型放数据
map.put("name", "xsh");
Student stu1 = new Student();
stu1.setName("小明");
stu1.setAge(18);
stu1.setMondy(1000.86f);
stu1.setBirthday(new Date());
Student stu2 = new Student();
stu2.setName("小红");
stu2.setMondy(200.1f);
stu2.setAge(19);
List<Student> friends = new ArrayList<>();
friends.add(stu1);
stu2.setFriends(friends);
stu2.setBestFriend(stu1);
List<Student> stus = new ArrayList<>();
stus.add(stu1);
stus.add(stu2);
//向数据模型放数据
map.put("stus", stus);
//准备map数据
HashMap<String, Student> stuMap = new HashMap<>();
stuMap.put("stu1", stu1);
stuMap.put("stu2", stu2);
//向数据模型放数据
map.put("stu1", stu1);
//向数据模型放数据
map.put("stuMap", stuMap);
return map;
}
@Test
public void testGenerateHtml() throws Exception {
//定义配置类
Configuration configuration=new Configuration(Configuration.getVersion());
//得到classpath路径,即模板存放路径
String classpath = this.getClass().getResource("/").getPath();
//定义模板路径
configuration.setDirectoryForTemplateLoading(new File(classpath+"/templates/"));
//获取模板文件内容
Template template = configuration.getTemplate("test1.ftl");
//定义数据模型
Map map = getMap();
//静态化
String content = FreeMarkerTemplateUtils.processTemplateIntoString(template, map);
System.out.println(content);
InputStream inputStream = IOUtils.toInputStream(content);
FileOutputStream fileOutputStream = new FileOutputStream(new File("d:/test1.html"));//静态文件输出目录
//输出文件
IOUtils.copy(inputStream,fileOutputStream);
}
//基于模板文件的内容(字符串)生成html文件
@Test
public void testGenerateHtmlByString() throws Exception{
//创建配置类
Configuration configuration=new Configuration(Configuration.getVersion());
//获取模板内容
//模板内容,这里测试时使用简单的字符串作为模板
String templateString="" +
"<html>\n" +
" <head></head>\n" +
" <body>\n" +
" 名称:${name}\n" +
" </body>\n" +
"</html>";
//加载模板
//模板加载器
StringTemplateLoader stringTemplateLoader = new StringTemplateLoader();
stringTemplateLoader.putTemplate("template",templateString);
configuration.setTemplateLoader(stringTemplateLoader);
Template template = configuration.getTemplate("template","utf-8");
//数据模型
Map map = getMap();
//静态化
String content = FreeMarkerTemplateUtils.processTemplateIntoString(template, map);
//静态化内容
System.out.println(content);
InputStream inputStream = IOUtils.toInputStream(content);
//输出文件
FileOutputStream fileOutputStream = new FileOutputStream(new File("d:/test1.html"));
IOUtils.copy(inputStream, fileOutputStream);
}
实现获取数据库内所有表和字段名,并自动生成实体类文件
模板文件javabean.ftl,用于生成java实体类文件
package ${packageName};
import java.util.Date;
public class ${className}{
<#-- 循环类型及属性 -->
<#list attrs as attr>
private ${attr.type} ${attr.name}; //${attr.remarks}
</#list>
<#-- 循环生成set get方法 -->
<#list attrs as attr>
public void set${attr.name}(${attr.type} ${attr.name}) {
this.${attr.name} = ${attr.name};
}
public ${attr.type} get${attr.name}() {
return ${attr.name};
}
</#list>
}
package com.xsh.util;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateExceptionHandler;
import java.io.File;
/**
* @author : xsh
* @create : 2020-06-25 - 14:57
* @describe: Freemarker加载类
* 项目不要存放在带有中文目录的文件夹下
* 否则获取classpath路径时会因为有中文名而乱码,获取不到文件,抛出java.io.FileNotFoundException异常
*/
public class FreeMarkerInit {
private static FreeMarkerInit single= new FreeMarkerInit();
private FreeMarkerInit() {}
//静态工厂方法
public static FreeMarkerInit getInstance() {
return single;
}
public Template getDefinedTemplate(String templateName) throws Exception{
//配置类
Configuration configuration = new Configuration(Configuration.getVersion());
//得到classpath路径,即模板存放路径
String classpath = this.getClass().getResource("/").getPath();
//定义模板路径
configuration.setDirectoryForTemplateLoading(new File(classpath+"/templates/"));
configuration.setDefaultEncoding("UTF-8");
configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
return configuration.getTemplate(templateName);
}
}
package com.xsh.util;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
/**
* @author : xsh
* @create : 2020-06-25 - 15:00
* @describe: 连接数据库获取元信息,得到表名,列信息。
*/
public class MetadataUtil {
private static Connection conn;
private static DatabaseMetaData meta;
static {
try {
Class.forName("com.mysql.jdbc.Driver");
}catch (ClassNotFoundException e){
e.printStackTrace();
System.out.println("数据库连接失败!,请检查数据库服务是否开启与依赖是否导入");
}
}
public static void openConnection(){
try {
if (conn==null||conn.isClosed()){
conn= DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test",
"root","123456");
meta=conn.getMetaData();
}
}catch (SQLException e){
e.printStackTrace();
}
}
//获取注解
public static String getCommentByTableName(String tableName) throws Exception{
openConnection();
Statement stmt=conn.createStatement();
ResultSet rs=stmt.executeQuery("SHOW CREATE TABLE "+tableName);
String comment=null;
if (rs!=null&&rs.next()){
comment=rs.getString(2);
}
rs.close();
stmt.close();
conn.close();
return comment;
}
//获取所有表名称
public static List<String> getTableNames(){
openConnection();
ResultSet rs=null;
List<String> nameList=new ArrayList<>();
try {
rs=meta.getTables(null,null,null,new String[]{"TABLE"});
while (rs.next()){
String tName=rs.getString("TABLE_NAME");
nameList.add(tName);
}
}catch (Exception e){
e.printStackTrace();
}
return nameList;
}
/**
* 列信息数组的集合。List中每个元素是一个数组,代表一个列的信息;
* 每个数组的元素1是列名,元素2是注释,元素3是类型
* @return
*/
public static List<String[]> getTableColumnsInfo(String tableName) throws Exception{
openConnection();
ResultSet rs=meta.getColumns(null,"%",tableName,"%");
List<String[]> columnInfoList=new ArrayList<>();
while (rs.next()){
String[] colInfo=new String[3];
colInfo[0] =rs.getString("COLUMN_NAME");
colInfo[1] =rs.getString("REMARKS");
colInfo[2] =rs.getString("TYPE_NAME");
columnInfoList.add(colInfo);
}
return columnInfoList;
}
public static DatabaseMetaData getMeta() {
return meta;
}
public static void setMeta(DatabaseMetaData meta) {
MetadataUtil.meta = meta;
}
public static Connection getConn() {
return conn;
}
public static void setConn(Connection conn) {
MetadataUtil.conn = conn;
}
}
从数据库直接获取的表名,列信息不符合java的命名规范,如驼峰命名法,所以需要对获取的表名和字段名进行名字转换
package com.xsh.util;
/**
* @author : xsh
* @create : 2020-06-25 - 15:01
* @describe:
* 驼峰命名:第一个单词以小写字母开始;第二个单词的首字母大写或每一个单词的首字母都采用大写字母
* 帕斯卡命名法:每个单词都大写,常用于类名,函数名,属性,命名空间。
*/
public class JavaNameUtil {
public static void main(String[] args) {
String str="imagines_age_name";
String javaname1 = JavaNameUtil.toPascal(str);
String javaname2 = JavaNameUtil.toCamel(str);
System.out.println("帕斯卡命名:"+javaname1);
System.out.println("驼峰命名转换:"+javaname2);
}
/**
* 将数据库(表、字段)转换以java命名方式帕斯卡或者驼峰
* @param unberscoreName
* @param isPascal 是否将首字母转化大写,true则转化为驼峰命名,false则转换为帕斯卡命名
* @return 驼峰或帕斯卡命名字符串
*/
public static String translate(String unberscoreName, boolean isPascal) {
StringBuilder result = new StringBuilder();
//从第一个字母
if (unberscoreName != null && unberscoreName.length() !=0) {
boolean flag = false;
char firstChar = unberscoreName.charAt(0); //得到首字母
if (isPascal) {
result.append(Character.toUpperCase(firstChar));
} else {
result.append(firstChar);
}
//从第二个字母以后开始
for (int i = 1, length = unberscoreName.length(); i < length; i++) {
char ch = unberscoreName.charAt(i);
if ('_' == ch) {
flag = true;
} else {
if (flag) { //标记上一个是下划线,就转化为大写。
result.append(Character.toUpperCase(ch));
flag = false;
} else {
result.append(ch);
}
}
}
}
return result.toString();
}
/**
* 调用translate() 转换为帕斯卡命名
* @param unberscoreName 数据库(表名、字段名)
* @return
*/
public static String toPascal(String unberscoreName) {
return translate(unberscoreName, true);
}
/**
* 调用translate() 转换为驼峰命名
* @param unberscoreName 数据库(表名、字段名)
* @return
*/
public static String toCamel(String unberscoreName) {
return translate(unberscoreName, false);
}
/**
*
* 将获取数据库类型转化为java类型
* @param dbTypeName 实际的数据库类型
* @return
*/
public static String dbTypeChangeJavaType(String dbTypeName){
String javaType=null;
switch(dbTypeName){
case "VARCHAR" :javaType="String";break;
case "BIGINT" :javaType="Long";break;
case "INT" :javaType="Integer";break;
case "DATETIME" :javaType="Date";break;
default:javaType="String";break;
}
return javaType;
}
}
Attribute.java:用于存放从数据库获取的字段类型,字段名字,字段备注
package com.xsh.model;
/**
* @author : xsh
* @create : 2020-06-25 - 15:11
* @describe:
*/
public class Attribute {
private String type;//类型
private String name;//名字
private String remarks;//备注
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRemarks() {
return remarks;
}
public void setRemarks(String remarks) {
this.remarks = remarks;
}
public Attribute() {
}
public Attribute(String type, String name, String remarks) {
this.type = type;
this.name = name;
this.remarks = remarks;
}
}
package com.xsh;
import com.xsh.model.Attribute;
import com.xsh.util.FreeMarkerInit;
import com.xsh.util.JavaNameUtil;
import com.xsh.util.MetadataUtil;
import freemarker.template.Template;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author : xsh
* @create : 2020-06-25 - 14:30
* @describe: 根据数据库内字段名,自动生成java实体类
*/
public class CreateJavaTemplateTest {
//生成java实体类
public void createBean() throws Exception{
//获取项目根目录
String basicPath=System.getProperty("user.dir");
//文件最终生成路径
String savePath=basicPath+"\\FreeMarker\\src\\main\\java\\com\\xsh\\pojo";
//判断文件夹是否存在,不存在则创建文件夹
File file=new File(savePath);
if(!file.exists()){
throw new Exception("文件夹不存在,请检查路径是否存在:"+savePath);
}
//获取模板
Template temp = FreeMarkerInit.getInstance().getDefinedTemplate("javabean.ftl");
//获取表名集合
List<String> strs= MetadataUtil.getTableNames();
for (String str1: strs
) {
//Attribute里面封装模板使用属性
List<String[]> strList=MetadataUtil.getTableColumnsInfo(str1);
List<Attribute> attr_list = new ArrayList<Attribute>();
for (String[] c:strList
) {
attr_list.add(new Attribute(JavaNameUtil.dbTypeChangeJavaType(c[2]), JavaNameUtil.toCamel(c[0]),c[1]));
}
//转换为帕斯卡命名
String str=JavaNameUtil.toPascal(str1);
Map<String, Object> root = new HashMap<String, Object>();
root.put("packageName", "com.xsh.pojo");//包名
root.put("className", str);//实体类文件名,即数据库表名
root.put("attrs", attr_list);
OutputStream fos = new FileOutputStream( new File(savePath, str+".java"));
Writer out = new OutputStreamWriter(fos);
temp.process(root, out);
fos.flush();
fos.close();
}
}
public static void main(String[] args) {
CreateJavaTemplateTest test = new CreateJavaTemplateTest();
try {
test.createBean();
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行测试类,pojo文件夹下根据数据库内表名自动生成实体类
数据库test:
代码demo:点击下载
评论