模板引擎,一直以来,个人都比较喜欢velocity
,只是这货差不多7年没有更新,虽然前几天抽风似的发布了个2.0版本,但7年的脚步已经落后了。
后来看到Thymeleaf
挺不错,个人项目中也有在使用,这不在osc看到一篇文章:关于Thymeleaf的真相 和Thymeleaf开撕上了,最大的糟点应该是性能问题,看来有必要自己做个性能测试来验证下,毕竟下一步还打算在公司正式项目中使用Thymeleaf呢。
本测试仅针对本人个人使用情况,其使用场景、工具、硬件等均有可能对测试结果造成影响。
测试结果只是对本次测试做的一个记录,仅供参考,并不能说明引擎本身的好坏。
在对测试结果总结及模板引擎的选择上,难免会有个人的想法及主观意识,这只是个人选择喜好并不代表对引擎好坏的评判,请勿对号入座。
项目中以使用Spring Boot为主,所以测试的模板引擎自然要与其方便整合使用为上。
首先当然是官方提供默认集成的模板引擎了,在使用Spring Initializr工具创建Spring Boot项目时,发现新版本的Spring Boot已经不推荐使用velocity了(其实从spring 4.3开始就不推荐使用了),见下图:
这样一来官方提供默认集成的就只剩下FreeMarker和Thymeleaf了,听说Thymeleaf从3.0开始性能已大幅提高和FreeMarker相当了,刚好趁这次可以实际对比下。
Enjoy,JFinal出品的模板引擎,可脱离JFinal单独使用,项目地址:http://git.oschina.net/jfinal/enjoy
Beetl,上面文章中和Thymeleaf的开撕对象,项目地址:http://git.oschina.net/xiandafu/beetl2.0 本来想把它也加进去的,只可惜我按照他的官方文档 Spring Boot集成一节中的配置集成starter后,怎么也无法使用,访问页面时后台一直报错,不知道是不是我的使用方法不对:
javax.servlet.ServletException: Circular view path [index]: would dispatch back to the current handler URL [/index] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)
所以到最后测试的模板引擎只有FreeMarker
、Thymeleaf
、Enjoy
这三个了,其它的就不考虑了,毕竟项目中要使用测了才有意义。
因为使用Spring Boot,所以都是Spring Boot项目。
Thymeleaf号称使用SpringEL比使用ognl更快,这样对他也算公平。只是Spring Boot到现在默认集成的都是Thymeleaf 2.x版本,将版本号改成3.x版本之后不知道是否有影响。
配置文件中缓存配置统一设置为true,因为在Spring Boot中不使用DevTool的情况下Thymeleaf默认就是true开启缓存的,而FreeMarker默认是false不开启缓存的,当然Enjoy就没有这个配置了,在代码中把DevMode设置为false。
都使用Spring Boot的main方式启动项目,JVM参数统一设置为:-Xms512m -Xmx512m
为避免web容器的启停造成的内存波动,重启系统后打开所有项目并启动容器后再进行测试,三个项目使用三个不同的端口。这里不计算项目启动本身内存的占用。
启动项目后浏览器先进行一次访问,让项目完成第一次的编译加载。
上面的文章提到使用了TEB。
这里就换一个工具测测,使用wrk来测试web项目的实际吞吐量。
这里列出主要代码,下面附有各个项目的下载地址,可下载后查看其它配置项。
Controller代码,渲染100条列表记录:
@Controller
public class TestController {
private List<User> users = new ArrayList<>();
public TestController() {
for (int i = 0; i < 100; i++) {
User user = new User();
user.setUserId(Long.valueOf(i));
user.setUsername("username-" + i);
user.setPassword("123456-" + i);
user.setEmail(i + "[email protected]");
user.setMobile("13666666666");
users.add(user);
}
}
@RequestMapping("index")
public String index(ModelMap modelMap) {
modelMap.put("users", users);
return "index";
}
}
FreeMarker代码:
<#list users as item>
${item.userId}
${item.username}
${item.password}
${item.email}
${item.mobile}
Thymeleaf代码:
th:each="item : ${users}">
th:text="${item.userId}">
th:text="${item.username}">
th:text="${item.password}">
th:text="${item.email}">
th:text="${item.mobile}">
Enjoy代码:
#for(item : users)
#(item.userId)
#(item.username)
#(item.password)
#(item.email)
#(item.mobile)
tr>
#end
测试结果
总共三轮,下面是结果,可以参考看看。
第一轮,从上到下依次为FreeMarker、Thymeleaf、Enjoy:
selflydeMacBook-Pro:~ liyd$ wrk -t100 -c500 -d30s http://localhost:8083/index
Running 30s test @ http://localhost:8083/index
100 threads and 500 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 406.90ms 464.12ms 2.00s 83.35%
Req/Sec 18.75 26.83 343.00 90.49%
35301 requests in 30.10s, 553.82MB read
Socket errors: connect 0, read 223, write 1, timeout 1141
Requests/sec: 1172.60
Transfer/sec: 18.40MB
selflydeMacBook-Pro:~ liyd$ wrk -t100 -c500 -d30s http://localhost:8080/index
Running 30s test @ http://localhost:8080/index
100 threads and 500 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.51s 409.92ms 2.00s 71.07%
Req/Sec 1.70 2.73 20.00 91.16%
2007 requests in 30.10s, 32.75MB read
Socket errors: connect 0, read 332, write 0, timeout 1651
Requests/sec: 66.68
Transfer/sec: 1.09MB
selflydeMacBook-Pro:~ liyd$ wrk -t100 -c500 -d30s http://localhost:8082/index
Running 30s test @ http://localhost:8082/index
100 threads and 500 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 390.47ms 431.10ms 2.00s 84.32%
Req/Sec 19.97 27.40 349.00 90.14%
41910 requests in 30.11s, 660.88MB read
Socket errors: connect 0, read 167, write 0, timeout 843
Requests/sec: 1391.92
Transfer/sec: 21.95MB
第二轮:
selflydeMacBook-Pro:~ liyd$ wrk -t100 -c500 -d30s http://localhost:8083/index
Running 30s test @ http://localhost:8083/index
100 threads and 500 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 150.24ms 213.48ms 1.97s 89.05%
Req/Sec 54.68 25.05 480.00 75.12%
162974 requests in 30.10s, 2.49GB read
Socket errors: connect 0, read 153, write 0, timeout 42
Requests/sec: 5413.68
Transfer/sec: 84.80MB
selflydeMacBook-Pro:~ liyd$ wrk -t100 -c500 -d30s http://localhost:8080/index
Running 30s test @ http://localhost:8080/index
100 threads and 500 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 444.69ms 394.37ms 2.00s 56.10%
Req/Sec 14.22 17.09 363.00 92.74%
33532 requests in 30.10s, 529.98MB read
Socket errors: connect 0, read 256, write 4, timeout 500
Requests/sec: 1113.85
Transfer/sec: 17.60MB
selflydeMacBook-Pro:~ liyd$ wrk -t100 -c500 -d30s http://localhost:8082/index
Running 30s test @ http://localhost:8082/index
100 threads and 500 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 102.74ms 112.32ms 1.53s 90.77%
Req/Sec 61.42 25.19 555.00 82.25%
183754 requests in 30.10s, 2.83GB read
Socket errors: connect 0, read 134, write 1, timeout 0
Requests/sec: 6104.33
Transfer/sec: 96.25MB
第三轮:
selflydeMacBook-Pro:~ liyd$ wrk -t100 -c500 -d30s http://localhost:8083/index
Running 30s test @ http://localhost:8083/index
100 threads and 500 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 151.18ms 219.30ms 1.98s 89.11%
Req/Sec 55.34 25.82 600.00 79.00%
164058 requests in 30.09s, 2.51GB read
Socket errors: connect 0, read 240, write 0, timeout 42
Requests/sec: 5452.72
Transfer/sec: 85.41MB
selflydeMacBook-Pro:~ liyd$ wrk -t100 -c500 -d30s http://localhost:8080/index
Running 30s test @ http://localhost:8080/index
100 threads and 500 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 348.74ms 310.69ms 2.00s 78.22%
Req/Sec 16.70 11.52 250.00 78.23%
46761 requests in 30.10s, 737.67MB read
Socket errors: connect 0, read 274, write 0, timeout 240
Requests/sec: 1553.55
Transfer/sec: 24.51MB
selflydeMacBook-Pro:~ liyd$ wrk -t100 -c500 -d30s http://localhost:8082/index
Running 30s test @ http://localhost:8082/index
100 threads and 500 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 107.51ms 116.44ms 1.74s 89.56%
Req/Sec 59.45 27.25 670.00 81.62%
177719 requests in 30.10s, 2.74GB read
Socket errors: connect 0, read 130, write 0, timeout 0
Requests/sec: 5903.36
Transfer/sec: 93.08MB
selflydeMacBook-Pro:~ liyd$
可以看到第一轮跟后面两轮性能相差很大,这是因为JVM本身会对内存缓存等进行优化,这是预料之中的。
但这对每个引擎都是公平的,横向对比来看,Enjoy最出色,Thymeleaf虽然号称3.0开始已与FreeMarker相当但实际情况还是相差很大。
打的包大小
性能是很重要的一个方面,但打出来的包大小也不可忽视,这从某种方面来说也反应出项目的质量。
下面列出Spring Boot下各个引擎打出的包大小,大小单位显示是在mac下跟windows会有所不同:
FreeMarker:16.1M,单纯FreeMarker的jar没什么第三方依赖,大小在1.5M左右,能够接受。
Thymeleaf:20.7M,Thymeleaf会有groovy等依赖,对一个模板引擎来说应该是超大了。
Enjoy:14.6M,这个不用多说了,JFinal系列出品,简洁著称。
从上面可以看出,打出来的包大小区别还是比较大的,Enjoy非常优秀,FreeMarker正常范围,Thymeleaf就有点夸张了。
Thymeleaf网上很多文章都宣称是轻量级模板引擎,可是看着它打出的包实在感觉轻量不起来。
官方推荐的模板引擎?
为什么要扯这个?因为这多少会对模板引擎的选择产生一些主观的意向,至少我之前有。
网上一直在传Thymeleaf是Spring Boot官方推荐的模板引擎,之前我也一直被此误导。
在此次测试之后我特地到spring的官方网站查找了下,实在是没找到任何推荐的话语,只是列出了几个提供快速集成的引擎选择,Thymeleaf是其中之一。所以这应该只是小道消息。
如果一定要说Spring Boot的官方推荐,那么我在查看Spring的源代码时,发现不推荐使用的VelocityViewResolver类上有以下注释:
@deprecated as of Spring 4.3, in favor of FreeMarker
官方在注释中推荐使用的是FreeMarker
!
选择
从上面的测试可以看出,在不考虑模板引擎特有功能的情况下,Enjoy都是完胜。但是在选择时还是要综合考虑其它元素。
在之前我使用Thymeleaf比较多,在我的Spring Security系列文章中也可以看出来,以后应该会使用FreeMarker居多,主要原因有三点:
- 性能能够接受。虽然不是最出色,但也不会拖后腿,历经这么多年的考验足以说明问题。
- Spring Boot官方提供默认集成。在一个保守的传统金融行业公司中,是一条重量级的说服理由。
- IDE支持。特别是在团队中,idea对FreeMarker的支持堪称完美,提供了各种方便。
但是在一些个人项目及工具中应该会使用Enjoy,例如代码生成工具Enjoy就是最佳选择。