beetl,freemarker,thymeleaf对比及springboot集成

article/2025/9/12 22:34:06

  1. 调研类型:

Freemarker,Thymeleaf,Beetl,Velocity

  1. 调研方向:

性能,活跃度,各自优缺点,应用实例

2.1、性能报告:

Jdk:1.8

Cpu: 8核12线程

Jvm : -Xms512m -Xmx512m

Benchmark

Version

Mode

Score

Score Error (99.9%)

Unit

Beetl

3.10.0.RELEASE

thrpt

206504.2209

3396.544778

ops/s

Freemarker

2.3.31

thrpt

6442.518356

965.471192

ops/s

Thymeleaf

3.1.0.M2

thrpt

4668.987054

906.704589

ops/s

Velocity

1.7

thrpt

24738.62415

251.33537

ops/s

 

2.2、活跃度:

 

 

 

springboot1.5以后就已经停止对velocity的支持了,不仅是springboot。spring5也不再支持velocity了

 

2.4、各自优缺点:

模板

优势

缺点

安全性

官网

Beetl

  1. 性能优越
  2. 已开源11年,一直在维护,比较稳定
  1. 比较小众,应用量无从考证社区已作废

Xss攻击:需要单独开发过滤器,工作量大:https://cxymm.net/article/weixin_33775582/91969237

https://www.kancloud.cn/xiandafu/beetl3_guide/1992542

FreeMarker

1、应用广泛

2、有社区,1999年Freemarker 1 发布,2002 年重写并发布Freemarker 2,至今已经20年;语法简单,功能丰富,文档完整

3、语法比较简单,html语法,js,css都适用

Xss攻击防范:默认在模板的头部加上<#escape x as x?html>在尾部加上</#escape>,对模板中所有的变量进行html转义;<#noautoesc>标签取消转义;

http://freemarker.foofun.cn/app_faq.html

Thymeleaf

  1. springboot推荐,有专业团队开发维护
  2. 语法简单

性能差

Xss攻击防范:默认会转义返回结果

https://www.docs4dev.com/docs/zh/thymeleaf/3.0/reference/using_thymeleaf.html#introducing-thymeleaf

2.5、应用实例:

<!--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>

<!--ibeetl-->

<dependency>

    <groupId>com.ibeetl</groupId>

    <artifactId>beetl-framework-starter</artifactId>

    <version>1.1.68.RELEASE</version>

</dependency>

thymeleaf

<!DOCTYPE html>

<html lang="en" xmlns:th="http://www.thymeleaf.org">

<head>

    <meta charset="UTF-8">

    <title>静态页展示!</title>

</head>

<body>

<table class="display table table-bordered" id="hidden-table-info" border="1">

    <thead>

    <tr>

        <td>序号</td>

        <td>姓名</td>

        <td>年龄</td>

        <td>学号</td>

    </tr>

    </thead>

    <tbody>

    <tr class="gradeX" th:each="stu:${stus}">

        <td th:text="${stuStat.count}"></td>

        <td th:text="${stu.name}"></td>

        <td th:text="${stu.age}">Trident</td>

        <td th:text="${stu.number}">Trident</td>

    </tr>

    </tbody>

</table>

</body>

</html>

@Controller

public class ThymeleafController {

    @RequestMapping("/thymeleaf")

    public String thymeleaf(Model model) {

        List<Student> stus = new ArrayList<>();

        //表格内容的遍历

        for (int i = 0; i < 1000; i++) {

            Student stu = new Student();

            stu.setName("thymeleaf" + i);

            stu.setAge(i);

            stu.setNumber(i);

            stu.setBirthday(new Date());

            stus.add(stu);

        }

        model.addAttribute("stus", stus);

        return "thymeleaf";

    }

}




import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

import java.io.*;
import java.util.Map;

/**
 * @ClassName ThymeleafUtil
 * @Description
 * @Date 2022/6/22
 **/
public class ThymeleafUtil {
    private static Logger logger = LoggerFactory.getLogger(ThymeleafUtil.class);

    /**
     * @param templateFile 模板名称
     * @param data         数据
     * @param suffix       模板后缀
     * @param path         静态html文件保存路径
     * @return 文件名称
     */
    public static String createHtml(String templateFile,  Map data, String path) {
        TemplateEngine templateEngine = SpringContextHolder.getBean(TemplateEngine.class);
        Writer out = null;
        try {
            //文件递归创建生成文件目录
            File realDirectory = new File(path);
            if (!realDirectory.exists()) {
                realDirectory.mkdirs();
            }
            //生成文件名
            String fileName = UUIDUtil.randomUUID() + ".html";
            //初始化一个IO流
            out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(path + fileName)), "UTF-8"));
            // 模板渲染出所要的内容
            Context context = new Context();
            context.setVariables(data);
            templateEngine.process(templateFile,context,out);

            return path+fileName;

        } catch (Exception e) {
            logger.error("生成html模板失败",e);
            throw new RuntimeException("生成html模板失败");
        } finally {
            try {
                out.flush();
                out.close();
            } catch (IOException e) {
                logger.error("输出流关闭失败",e);
            }
        }
    }
}

spring:
  thymeleaf:
    cache: false
    prefix: classpath:/templates/
    encoding: UTF-8 #编码
    suffix: .html #模板后缀
    mode: HTML #模板

Freemarker

<!DOCTYPE html>

<html>

<head>

    <meta charset="utf-8">

    <title>静态页展示!</title>

</head>

<body>

<table>

    <tr>

        <td>序号</td>

        <td>姓名</td>

        <td>年龄</td>

        <td>学号</td>

    </tr>

    <#list stus as stu>

        <tr>

            <td>${stu_index + 1}</td>

            <td <#if stu.name =='freemarker小明'>style="background:blue;"</#if>>${stu.name}</td>

            <td>${stu.age}</td>

            <td>${stu.number}</td>

        </tr>

    </#list>

</table>

</body>

</html>

@Controller

public class FreemarkerController {

    @Autowired

    private Configuration configuration;

    @GetMapping("/freemarker")

    public String test1(Map map) throws IOException, TemplateException {

        List<Student> stus = new ArrayList<>();

        //表格内容的遍历

        for (int i = 0; i < 1000; i++) {

            Student stu = new Student();

            stu.setName("freemarker" + i);

            stu.setAge(i);

            stu.setNumber(i);

            stu.setBirthday(new Date());

            stus.add(stu);

        }

        // 向数据模型放数据

        map.put("stus", stus);

        return "freemarker";

    }

}



import freemarker.template.Configuration;
import freemarker.template.Template;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.Map;
import java.util.Objects;

import static freemarker.template.Configuration.VERSION_2_3_30;

/**
 * FreeMarker生成HTML模板

 */
public class FreeMarkerUtil {

    private static Logger logger = LoggerFactory.getLogger(FreeMarkerUtil.class);

    /**
     * @param templateFile 模板名称
     * @param data         数据
     * @param suffix       模板后缀
     * @param path         静态html文件保存路径
     * @return 文件名称
     */
    public static String createHtml(String templateFile, String suffix, Map data, String path) {
        Configuration cfg = SpringContextHolder.getBean(Configuration.class);
        Writer out = null;
        try {
            //根据模板名称获取模板文件
            Template template = cfg.getTemplate(templateFile + suffix);
            if (Objects.isNull(template)) {
                logger.error("模板文件不存在");
                throw new RuntimeException("模板文件不存在");
            }
            //文件递归创建生成文件目录
            File realDirectory = new File(path);
            if (!realDirectory.exists()) {
                realDirectory.mkdirs();
            }
            //生成文件名
            String fileName = UUIDUtil.randomUUID() + ".html";
            //初始化一个IO流
            out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(path + fileName)), "UTF-8"));
            // 模板渲染出所要的内容
            template.process(data, out);

            return path+fileName;

        } catch (Exception e) {
            logger.error("生成html模板失败",e);
            throw new RuntimeException("生成html模板失败");
        } finally {
            try {
                out.flush();
                out.close();
            } catch (IOException e) {
                logger.error("输出流关闭失败",e);
            }
        }
    }
}

spring:
  freemarker:
    #指定HttpServletRequest的属性是否可以覆盖controller的model的同名项
    allow-request-override: false
    #req访问request
    request-context-attribute: request
    #后缀名freemarker默认后缀为.ftlh,当然你也可以改成自己习惯的.html
    suffix: .ftlh

    #设置响应的内容类型
    content-type: text/html;charset=utf-8
    #是否允许mvc使用freemarker
    enabled: true
    #是否开启template caching
    cache: false
    #设定模板的加载路径,多个以逗号分隔,默认: [“classpath:/templates/”]
    template-loader-path: classpath:/templates/
    #设定Template的编码
    charset: UTF-8

beetl

<!DOCTYPE html>

<html>

<head>

    <meta charset="utf-8">

    <title>静态页展示!</title>

    <style type="text/css">

        /*<![CDATA[*/

        body {

            color: #333333;

            line-height: 150%;

        }

        thead {

            font-weight: bold;

            background-color: #CCCCCC;

        }

        .odd {

            background-color: #FFCCCC;

        }

        .even {

            background-color: #CCCCFF;

        }

        .minus {

            color: #FF0000;

        }

        /*]]>*/

    </style>

</head>

<body>

<table>

    <tr>

        <td>序号</td>

        <td>姓名</td>

        <td>年龄</td>

        <td>学号</td>

    </tr>

    <% for(stu in stus) { %>

        <% if(stuLP.index%2==0) { %>

        <tr class="even">

        <% }else{ %>

        <tr class="odd">

        <% } %>

            <td>${stuLP.index}</td>

            <td>${stu.name}</td>

            <td>${stu.age}</td>

            <td>${stu.number}</td>

            <td>${stu.birthday,"yyyy-MM/dd"}</td>

            <td>${date()} </td>

        </tr>

    <% } %>

</table>

</body>

</html>

@Controller

public class BeetlController {

    @GetMapping("/test")

    public String test(HttpServletRequest request) throws IOException {

        List<Student> stus = new ArrayList<>();

        //表格内容的遍历

        for (int i = 0; i < 20; i++) {

            Student stu = new Student();

            stu.setName("beetl" + i);

            stu.setAge(i);

            stu.setNumber(i);

            stu.setBirthday(new Date());

            stus.add(stu);

        }

        request.setAttribute("stus", stus);

        return "beetl.html";

    }

}



import org.beetl.core.GroupTemplate;
import org.beetl.core.Template;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.Map;
import java.util.Objects;

/**
 * @ClassName BeetlUtil
 * @Description

 * @Date 2022/6/22
 **/
public class BeetlUtil {
    private static Logger logger = LoggerFactory.getLogger(BeetlUtil.class);

    /**
     * @param templateFile 模板名称
     * @param data         数据
     * @param suffix       模板后缀
     * @param path         静态html文件保存路径
     * @return 文件名称
     */
    public static String createHtml(String templateFile, String suffix, Map data, String path) {
        Writer out = null;
        try {
            //根据模板名称获取模板文件
            GroupTemplate gt = SpringContextHolder.getBean(GroupTemplate.class);
            Template template = gt.getTemplate(templateFile + suffix);
            if (Objects.isNull(template)) {
                logger.error("模板文件不存在");
                throw new RuntimeException("模板文件不存在");
            }
            //文件递归创建生成文件目录
            File realDirectory = new File(path);
            if (!realDirectory.exists()) {
                realDirectory.mkdirs();
            }
            //生成文件名
            String fileName = UUIDUtil.randomUUID() + ".html";
            //初始化一个IO流
            out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(path + fileName)), "UTF-8"));
            // 模板渲染出所要的内容
            template.binding(data);
            template.renderTo(out);

            return path+fileName;

        } catch (Exception e) {
            logger.error("生成html模板失败",e);
            throw new RuntimeException("生成html模板失败");
        } finally {
            try {
                out.flush();
                out.close();
            } catch (IOException e) {
                logger.error("输出流关闭失败",e);
            }
        }
    }
}

注意:beetl使用时还须增加如下代码配置:



import org.beetl.core.resource.ClasspathResourceLoader;
import org.beetl.ext.spring.BeetlGroupUtilConfiguration;
import org.beetl.ext.spring.BeetlSpringViewResolver;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class BeetlConf {

  @Value("${beetl.templatesPath}") String templatesPath;//模板根目录 ,比如 "templates"
  @Bean(name = "beetlConfig")
  public BeetlGroupUtilConfiguration getBeetlGroupUtilConfiguration() {
    BeetlGroupUtilConfiguration beetlGroupUtilConfiguration = new BeetlGroupUtilConfiguration();
    //获取Spring Boot 的ClassLoader
    ClassLoader loader = Thread.currentThread().getContextClassLoader();
    if(loader==null){
      loader = BeetlConf.class.getClassLoader();
    }
    //beetlGroupUtilConfiguration.setConfigProperties(extProperties);//额外的配置,可以覆盖默认配置,一般不需要
    ClasspathResourceLoader cploder = new ClasspathResourceLoader(loader,
                                                                  templatesPath);
    beetlGroupUtilConfiguration.setResourceLoader(cploder);
    beetlGroupUtilConfiguration.init();
    //如果使用了优化编译器,涉及到字节码操作,需要添加ClassLoader
    beetlGroupUtilConfiguration.getGroupTemplate().setClassLoader(loader);
    return beetlGroupUtilConfiguration;

  }

  @Bean(name = "beetlViewResolver")
  public BeetlSpringViewResolver getBeetlSpringViewResolver(@Qualifier("beetlConfig") BeetlGroupUtilConfiguration beetlGroupUtilConfiguration) {
    BeetlSpringViewResolver beetlSpringViewResolver = new BeetlSpringViewResolver();
    beetlSpringViewResolver.setContentType("text/html;charset=UTF-8");
    beetlSpringViewResolver.setOrder(0);
    beetlSpringViewResolver.setConfig(beetlGroupUtilConfiguration);
    return beetlSpringViewResolver;
  }

}

beetl:
  enabled: true
  suffix: .html
  templatesPath: templates
beetl-beetlsql:
  dev: true  #监控模板变更


http://chatgpt.dhexx.cn/article/OPwCSXXu.shtml

相关文章

部分壳与脱壳

壳与脱壳 对网上部分壳与脱壳的摘录与总结&#xff0c;仅供参考&#xff0c;侵删 参考链接1 https://www.52pojie.cn/thread-138380-1-1.html 参考链接2 https://www.cnblogs.com/milantgh/p/3869083.html 参考链接3 http://blog.sina.com.cn/s/blog_3e28c8a5010132m6.html 壳…

谷粒商城项目学-分布式基础

项目框架图 分布式基础概念 • 微服务、注册中心、配置中心、远程调用、Feign、网关 • 2、基础开发 • SpringBoot2.0、SpringCloud、Mybatis-Plus、Vue组件化、阿里云对象存储 • 3、环境 • Vagrant、Linux、Docker、MySQL、Redis、逆向工程&人人开源 • 4、开发规范 •…

【笔记/后端】谷粒商城基础篇

目录 一、环境配置1 Docker1.1 Docker是什么&#xff1f;1.2 安装&启动1.2.1 阿里云镜像加速 1.3 安装MySQL1.4 安装Redis 2 开发环境2.1 Maven2.2 Git2.3 Node 二、创建微服务项目1 内容2 问题记录3 renren-generator 三、分布式组件1 Nacos1.1 注册中心1.2 配置中心1.2.1…

谷粒商城(二)

谷粒商城&#xff08;二&#xff09; 后台商品服务 - 三级分类1、查询1&#xff09;、接口编写2&#xff09;、树形展示三级分类数据3&#xff09;、配置网关路由1 更改前端 base 路径2 将服务注册进nacos3 网关模块配置路由4 测试 4&#xff09;、解决跨域 2、删除1&#xff0…

谷粒商城(五)

谷粒商城&#xff08;五&#xff09; 订单服务1、环境搭建1&#xff09;、页面2&#xff09;、代码 2、订单登录拦截3、订单确认页1&#xff09;、VO模型2&#xff09;、订单确认页数据查询1 接口编写2 调用远程服务 3&#xff09;、Feign远程调用丢失请求头启动服务报错解决 4…

谷粒商城(一)

谷粒商城&#xff08;一&#xff09; 1、环境搭建安装 dockerdocker 安装 mysqldocker 安装 redis安装配置 git准备工具 IDEA、VsCode从 gitee 初始化项目 2、创建微服务项目1&#xff09;、创建项目2&#xff09;、初始化数据库 3、使用人人开源搭建后台管理系统1&#xff09;…

谷粒商城:如何通过笔记复盘实现事半功倍?

前言 把谷粒商城做了一遍&#xff0c;其中遇的困难也记录了一下。将零散的笔记整理成有顺序的目录结构。方便自己回看、以及快速定位文章。特此记录、大部分在CSDN博客里边都可以搜索到。 大家想看的话也可以去这里看看&#xff1a;笔记地址传送门 后续还会继续维护这个笔记…

查壳、加壳、脱壳详细教程

查壳教程 1、打开软件后我们点击右上角的三个点&#xff0c;会弹出一个选择文件的窗口&#xff0c;我们选择要查壳的文件&#xff0c;同样也可以直接把需要查壳的软件拖到PEID页面里 2、这里拖入一个程序后出现如下信息页面 这里我们看到Borland Delphi 3.0,他不是一种壳&…

分布式项目-谷粒商城。

分布式项目一&#xff0c;分布图 二&#xff0c;环境搭建 1.安装linux 2.安装docker 1 卸载系统之前的docker sudo yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine2 设…

竞业限制是什么意思?

竞业限制是指用人单位与掌握商业秘密的职工约定在劳动合同解除或终止后的一定期限内&#xff0c;劳动者不得到有竞争关系的其他用人单位任职&#xff0c;也不得自己生产与原单位有竞争关系的同类产品或经营同类业务。 竞业限制对不同的人意义是不同的&#xff0c;比如&#xf…

所谓的1261考核法算不算是末尾淘汰?

【问题】 有无精通劳动法的老哥&#xff0c;分析一下所谓的1261考核法算不算是末尾淘汰&#xff1f; 已知最后考核的那个1会被约谈&#xff0c;连续两次都是最后就会调岗或者解除劳动合同 【解析】 算的&#xff01; 应该是“末位淘汰制” 劳动法不支持“末位淘汰制”&…

数字滤波器的实现——低通滤波器再探究

在探究完滤波器原理之后&#xff0c;又面临一个问题就是数字滤波器如何实现的问题&#xff0c;因为在实际应用过程中&#xff0c;如果不接触硬件的话&#xff0c;低通滤波器一般都是通过编程实现的&#xff0c;具体代码应该怎么编写&#xff0c;在应用过程中又应该注意什么问题…

数字图像处理之低通滤波器实现原理及方法(Matlab)

1.傅里叶变换与频域 在之前的文中&#xff0c;我们已经进行过一些基本的图像处理。比如&#xff0c;使用低通滤波可以将图像模糊&#xff0c;也有些许降噪的作用。这些都是在空间域内进行的滤波处理&#xff0c;这个处理主要是依靠卷积来进行计算的。首先&#xff0c;从连续的一…

滤波器_理想低通/高通滤波器原理

1.滤波器作用 消除干扰杂讯噪声&#xff0c;对信号进行频率成分的选择2.高通滤波 过滤低频信息&#xff0c;让高频信息通过3.低通滤波 过滤高频信息&#xff0c;让低频信息通过4.理想低通滤波 D0表示通带半径&#xff0c;D(u&#xff0c;v)是到频谱中心的距离(欧式距离),公式…

带通滤波器电路图大全(三款带通滤波器电路设计原理图详解)

带通滤波器电路图设计&#xff08;一&#xff09; 传统的带通滤波器设计方法中涉及了很多复杂的理论分析和计算。针对上述缺点&#xff0c;介绍一种使用EDA软件进行带通滤波器的设计方案&#xff0c;详细阐述了使用FilterPro软件进行有源带通滤波器电路的设计步骤&#xff0c;…

T滤波器(低通滤波器)

1.电路原理 T滤波器&#xff0c;其基本原理是基于低通滤波器设计&#xff0c;实现阻高频通低频的需求&#xff0c;其电路图及传递函数如下。 2.传递函数 r(t)为输入,c(t)为输出&#xff0c;从电路原理我们得到输入输出公式&#xff1a; 将公式进行拉氏变换得到&#xff1a; 3.系…

低通滤波器和高通滤波器的程序实现原理推导

傅立叶变换,拉普拉斯变换和Z变换 对于信号分析而言,傅立叶变换是必不可少的,我们都知道傅立叶变换是把系统从时域变换到频域进行分析,那么拉普拉斯变换和Z变换是干什么的?简单的来说,由于傅里叶变换的收敛有一个狄利克雷条件&#xff0c;要求信号绝对可积/绝对可和。对于那些…

数字低通滤波器的原理及实现

首先说一下&#xff0c;数字滤波器是怎么实现的 1.首先根据电路建立低通滤波器时域系统微分方程&#xff0c;得出低通滤波器t域模型 2.其次将对时域微分方程进行拉式变换&#xff0c;得出低通滤波器的s域模型 3.将模拟滤波器转换为数字滤波器&#xff0c;对连续系统进行离散化…

简单易理解的RC滤波器(含电路仿真)

滤波器 滤波器是对波进行过滤的器件&#xff0c;是一种让某一频带内信号通过&#xff0c;同时又阻止这一频带外信号通过的电路。 滤波器主要有低通滤波器、高通滤波器和带通滤波器三种&#xff0c;按照电路工作原理又可分为无源和有源滤波器两大类。本文主要对低通、高通还有带…

一文读懂:常见低通、高通、带通三种滤波器的工作原理

滤波器 滤波器是对波进行过滤的器件&#xff0c;是一种让某一频带内信号通过&#xff0c;同时又阻止这一频带外信号通过的电路。 滤波器主要有低通滤波器、高通滤波器和带通滤波器三种&#xff0c;按照电路工作原理又可分为无源和有源滤波器两大类。今天&#xff0c;小编主要…