SpringBoot整合Thymeleaf与ECharts:后端数据直传前端图表实战

发布时间:2026/8/15 3:30:09
SpringBoot整合Thymeleaf与ECharts:后端数据直传前端图表实战 1. 项目背景与核心价值最近在做一个内部数据看板后端用SpringBoot前端页面想用ECharts做个漂亮的图表。一开始想着前后端分离用Vue或者React去调接口拿数据但项目时间紧而且这个看板页面结构不复杂就是几个图表加表格。为了快速上线我决定直接用SpringBoot默认支持的模板引擎Thymeleaf来渲染页面然后把后端处理好的数据直接“喂”给前端的ECharts。这个组合听起来简单但真做起来从数据怎么从Controller传到页面到ECharts怎么接收并渲染中间有不少细节需要捋清楚比如数据格式转换、异步加载的时机、还有Thymeleaf的表达式在JavaScript里怎么用。今天就把这套“Thymeleaf ECharts”显示后端数据的完整流程包括我踩过的坑和优化技巧详细拆解一遍。这么做有几个明显的好处。首先是开发效率高你不需要单独起一个前端项目所有东西都在一个SpringBoot工程里改完后端逻辑刷新页面就能看到效果特别适合原型开发或者内部管理后台。其次它降低了技术栈的复杂度对于全栈工程师或者后端主导的项目非常友好你不需要去深究前端框架的状态管理、路由这些概念。最后数据传递是服务端渲染的页面首次加载时数据就已经嵌入在HTML里了对于SEO虽然看板类页面不太需要和首屏速度有一定优势。当然它不适合需要高度交互、组件化的大型单页应用但在“快速展示数据”这个核心需求上它非常能打。2. 环境搭建与项目初始化要跑通这个流程首先得有一个基础的SpringBoot项目骨架。我习惯用IntelliJ IDEA的Spring Initializr或者直接上 Spring Initializr官网 生成。依赖选择上核心就两个Spring Web和Thymeleaf。Spring Web提供了MVC框架和嵌入式TomcatThymeleaf就是我们的模板引擎。如果你需要连接数据库比如MySQL再加上Spring Data JPA和MySQL Driver。这里为了演示我们先不涉及数据库数据在内存里模拟。项目创建好后pom.xml里关键的依赖长这样dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId /dependency !-- 可选Lombok简化实体类 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies接下来是目录结构。在src/main/resources/templates目录下我们放Thymeleaf模板文件比如index.html。static目录下放静态资源比如我们要用的ECharts的JS库。你可以去 ECharts官网 下载完整版或者按需定制的版本我一般直接下载echarts.min.js放到static/js目录下。application.properties或application.yml文件里可以配置Thymeleaf的缓存开发时建议关掉这样改模板不用重启。# application.properties spring.thymeleaf.cachefalse spring.thymeleaf.prefixclasspath:/templates/ spring.thymeleaf.suffix.html spring.thymeleaf.modeHTML spring.thymeleaf.encodingUTF-8注意很多新手会忘记把ECharts的JS文件放到static目录下或者路径引用错误。在Thymeleaf模板里引用静态资源的正确方式是使用{}语法比如script th:src{/js/echarts.min.js}/script。这个路径是相对于static目录的根路径。3. 后端数据准备与Controller层设计数据从哪里来在实际项目中可能是数据库查询、调用外部API、或者是复杂的业务计算。这里我们模拟一个简单的场景一个销售数据看板需要展示过去一周每天的销售额和订单数。首先定义两个简单的Java对象DTO来承载数据。一个代表单个数据点比如某一天的销售情况另一个代表整个图表需要的数据结构。import lombok.Data; import java.math.BigDecimal; Data public class SalesDataPoint { private String date; // 日期如 2023-10-26 private BigDecimal amount; // 销售额 private Integer orderCount; // 订单数 } Data public class ChartData { private ListString dateList; // X轴日期列表 private ListBigDecimal amountList; // 销售额数据系列 private ListInteger orderCountList; // 订单数数据系列 private String title 销售数据看板; // 图表标题 }为什么用BigDecimal表示金额这是金融计算的基本要求避免使用double或float可能带来的精度丢失问题。String类型的日期是为了方便直接传给ECharts做X轴标签。接下来在Controller里我们模拟数据并传递给视图。关键点在于使用Model对象。import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import java.math.BigDecimal; import java.util.Arrays; import java.util.List; Controller public class DashboardController { GetMapping(/) public String index(Model model) { // 1. 模拟构建数据 ChartData chartData new ChartData(); chartData.setDateList(Arrays.asList(10-20, 10-21, 10-22, 10-23, 10-24, 10-25, 10-26)); chartData.setAmountList(Arrays.asList( new BigDecimal(1200.50), new BigDecimal(1800.00), new BigDecimal(950.75), new BigDecimal(2100.30), new BigDecimal(1650.20), new BigDecimal(1900.60), new BigDecimal(2200.00) )); chartData.setOrderCountList(Arrays.asList(45, 52, 38, 60, 48, 55, 62)); // 2. 将数据对象放入Model键名为“chartData” model.addAttribute(chartData, chartData); // 3. 返回视图名称Thymeleaf会自动解析到 templates/index.html return index; } }这里有一个非常重要的细节数据序列化。ChartData对象会被Thymeleaf和后续的JavaScript使用。Thymeleaf在渲染页面时需要将Java对象转换成能在HTML/JS中使用的格式。默认情况下当我们将对象通过model.addAttribute放入Model并在Thymeleaf模板中使用${chartData}表达式时Thymeleaf会调用对象的toString()方法。但这对于复杂对象远远不够。我们需要的是将Java对象转换成JSON字符串然后内嵌到页面的JavaScript变量中。这样ECharts才能直接使用。Spring Boot默认集成了Jackson库我们可以利用它。所以更常见的做法是在Controller里直接将对象转换成JSON字符串再放入Model或者使用Thymeleaf的内置工具。4. Thymeleaf模板中嵌入数据与ECharts初始化这是最核心的一步连接后端与前端。我们在templates/index.html中操作。首先在HTML头部引入ECharts库和Thymeleaf的命名空间。!DOCTYPE html html langzh xmlns:thhttp://www.thymeleaf.org head meta charsetUTF-8 title销售数据看板/title script th:src{/js/echarts.min.js}/script /head body !-- 图表容器 -- div idmainChart stylewidth: 800px;height:500px;/div script // 这里是JavaScript代码区域 /script /body /html关键来了如何把后端的chartData对象变成JavaScript里的一个变量这里有几种方法我推荐最清晰、最安全的一种使用Thymeleaf的th:inlinejavascript和内联表达式。方法一使用th:inlinejavascript和内联表达式我们在script标签上加上th:inlinejavascript属性这样在脚本块里就可以使用[[${...}]]或[(${...})]来输出模型属性。[[...]]是转义输出[(...)]是不转义输出。对于要放入JS变量的JSON我们通常用[(${...})]。script th:inlinejavascript /*![CDATA[*/ // 使用Thymeleaf表达式将后端数据转换为JSON字符串并赋值给JS变量 var chartDataJson /*[(${chartData})]*/ null; // 注意这里直接输出对象Thymeleaf会调用其toString()不是我们想要的JSON console.log(Raw:, chartDataJson); /*]]*/ /script但是直接输出chartData对象你会在控制台看到类似ChartData(dateList[...], ...)的字符串这不是标准的JSONECharts无法直接解析。方法二推荐在Controller中将对象转为JSON字符串我们修改Controller使用Jackson的ObjectMapper手动将对象转为JSON字符串。import com.fasterxml.jackson.databind.ObjectMapper; // ... 其他import Controller public class DashboardController { Autowired private ObjectMapper objectMapper; // Spring Boot会自动配置 GetMapping(/) public String index(Model model) throws JsonProcessingException { ChartData chartData ... // 构建数据同上 // 将chartData对象序列化为JSON字符串 String chartDataJson objectMapper.writeValueAsString(chartData); // 将JSON字符串放入Model model.addAttribute(chartDataJson, chartDataJson); model.addAttribute(chartData, chartData); // 原始对象也可以留着以备其他用途 return index; } }然后在模板中我们就可以安全地使用这个JSON字符串了。script th:inlinejavascript /*![CDATA[*/ // 现在chartDataJson是一个标准的JSON格式字符串 var chartDataJsonString /*[(${chartDataJson})]*/ {}; // 将其解析为JavaScript对象 var chartDataObj JSON.parse(chartDataJsonString); console.log(Parsed Object:, chartDataObj); console.log(Dates:, chartDataObj.dateList); console.log(Amounts:, chartDataObj.amountList); /*]]*/ /script为什么多此一举要先转成JSON字符串再JSON.parse因为[(${chartDataJson})]输出的是一个字符串其内容已经是JSON格式。如果直接var obj [(${chartDataJson})]Thymeleaf会原样输出字符串内容但两边的引号可能会引起语法错误或解析问题。通过JSON.parse可以确保我们得到一个纯净的JavaScript对象。实操心得这里最容易出的错就是JSON格式错误或者特殊字符转义问题。如果数据中包含换行符、引号等直接内嵌可能导致JS语法错误。使用ObjectMapper序列化和JSON.parse反序列化是最稳妥的方式。另外一定要在浏览器控制台(console.log)检查chartDataObj的结构是否正确这是后续ECharts配置的基础。5. ECharts配置与数据绑定拿到JavaScript对象后就可以初始化ECharts并配置图表了。我们继续在同一个script块里写。// 基于准备好的dom初始化echarts实例 var myChart echarts.init(document.getElementById(mainChart)); // 使用从后端解析的数据配置图表 var option { title: { text: chartDataObj.title // 使用后端传来的标题 }, tooltip: { trigger: axis }, legend: { data: [销售额, 订单数] }, xAxis: { type: category, // 关键这里直接使用后端传来的dateList作为X轴数据 data: chartDataObj.dateList }, yAxis: [ { type: value, name: 销售额元, axisLabel: { formatter: {value} } }, { type: value, name: 订单数, axisLabel: { formatter: {value} } } ], series: [ { name: 销售额, type: line, // 折线图 yAxisIndex: 0, // 对应第一个Y轴 // 关键这里直接使用后端传来的amountList作为系列数据 data: chartDataObj.amountList, itemStyle: { color: #5470c6 // 自定义颜色 } }, { name: 订单数, type: bar, // 柱状图 yAxisIndex: 1, // 对应第二个Y轴 // 关键这里直接使用后端传来的orderCountList作为系列数据 data: chartDataObj.orderCountList, itemStyle: { color: #91cc75 } } ] }; // 使用刚指定的配置项和数据显示图表。 myChart.setOption(option);至此一个基本的后端数据驱动ECharts图表就完成了。启动SpringBoot应用访问http://localhost:8080应该能看到一个包含折线销售额和柱状图订单数的双Y轴图表X轴是日期数据完全来自后端Controller。6. 高级技巧与常见问题排查基础功能跑通后我们会遇到一些更实际的需求和问题。6.1 处理大数据量或复杂对象结构上面的例子数据很简单。如果数据量很大比如上千个点或者结构复杂比如多层嵌套的对象全部内嵌到页面会导致HTML体积暴增影响加载速度。这时可以考虑异步加载。思路是页面初次加载只渲染空的图表容器和基本配置。然后通过JavaScript的fetch或axios发起AJAX请求从后端专门的API接口获取JSON数据再用myChart.setOption动态更新图表数据。后端新增一个API接口RestController // 注意这里是RestController返回JSON RequestMapping(/api) public class ChartDataApiController { GetMapping(/sales-data) public ChartData getSalesData() { // ... 构建ChartData对象的逻辑 return chartData; // 直接返回对象SpringBoot会用Jackson自动转为JSON } }前端模板修改移除内嵌数据的部分增加异步请求逻辑。script th:inlinejavascript var myChart echarts.init(document.getElementById(mainChart)); // 先设置一个空的或骨架配置 myChart.setOption({ title: { text: 数据加载中... }, xAxis: {}, yAxis: {}, series: [] }); // 异步获取数据 fetch(/api/sales-data) .then(response response.json()) .then(chartDataObj { // 使用获取到的数据构建完整的option var fullOption { /* ... 基于chartDataObj构建option ... */ }; myChart.setOption(fullOption); }) .catch(error { console.error(加载数据失败:, error); myChart.setOption({ title: { text: 数据加载失败 } }); }); /script这种方式更接近前后端分离的模式适合数据量大或需要频繁更新的场景。6.2 Thymeleaf表达式在JS中的使用限制与转义在th:inlinejavascript块内使用[(${...})]时如果变量值包含特殊字符如换行符\n、引号、或Unicode字符Thymeleaf会尝试进行HTML转义但这可能破坏JSON结构。这就是为什么我强烈建议在Controller层将对象转为标准的JSON字符串。JSON字符串本身是安全的文本Thymeleaf原样输出后再由JSON.parse处理能完美规避转义问题。6.3 ECharts配置的动态化与复用实际项目中图表类型、颜色、提示框格式等可能根据数据或用户选择动态变化。我们可以把ECharts的基础配置也做成可配置的。例如在后端的ChartData对象里增加一个options字段存放一些前端配置项。Data public class ChartData { // ... 原有字段 private MapString, Object chartOptions; // 例如 {theme: dark, grid: { left: 3% }} }前端在配置option时可以用Object.assign或扩展运算符...来合并后端传来的配置和前端固定配置。var baseOption { tooltip: {...}, legend: {...} }; var finalOption { ...baseOption, ...chartDataObj.chartOptions, // 后端动态配置 xAxis: { data: chartDataObj.dateList }, series: [...] };6.4 数字格式化与显示在热词里看到了thymeleaf 格式化数字#numbers.formatdecimal这确实是Thymeleaf的一个强大功能但主要用于在HTML模板中直接显示格式化的文本。例如在表格中显示金额td th:text${#numbers.formatDecimal(salesData.amount, 1, 2)}0.00/td这会将金额格式化为保留两位小数。但是这个格式化发生在服务端渲染阶段生成的是字符串。如果你需要将格式化后的字符串传给ECharts作为轴标签或者提示框内容可以在Controller里就格式化好放到一个单独的列表里传给前端。如果是在前端ECharts配置中格式化应该使用ECharts自己的axisLabel.formatter或tooltip.formatter函数它们是JavaScript函数无法直接调用Thymeleaf的表达式。6.5 常见问题排查清单图表不显示检查浏览器控制台(F12)是否有JS错误。常见原因ECharts库路径错误、DOM容器id写错、echarts.init在DOM加载完成前执行可以把JS代码放在body末尾或包裹在window.onload里。数据没加载在JS里console.log(chartDataObj)看是否成功解析。如果输出undefined或报错检查Thymeleaf表达式是否正确JSON字符串格式是否合法可以用在线JSON校验工具检查chartDataJson字符串。图表配置错误ECharts配置项非常严格比如series里data必须是数组如果后端传来的是null图表就会出错。确保所有必要的字段都有默认值或非空检查。中文乱码确保SpringBoot应用和HTML模板的编码都是UTF-8。可以在application.properties中设置spring.http.encoding.charsetUTF-8和spring.http.encoding.enabledtrue。页面刷新数据不变可能是浏览器缓存了旧的JS或HTML。开发时禁用Thymeleaf缓存spring.thymeleaf.cachefalse并强制浏览器刷新CtrlF5。7. 项目部署与优化考虑开发完成后部署到生产环境需要考虑以下几点7.1 静态资源处理Spring Boot打包成Jar后static目录下的资源会被打包到classpath下并通过内置的静态资源处理器提供服务。确保你的ECharts库文件在打包后依然存在于static/js/路径下。使用Maven或Gradle构建时一般无需特殊配置。7.2 开启Thymeleaf缓存在生产环境一定要在application-prod.properties中设置spring.thymeleaf.cachetrue这能显著提升模板渲染性能。7.3 前端资源优化使用ECharts按需引入如果只用到折线图、柱状图等少数组件可以引入定制版的ECharts减小JS文件体积。这需要在构建前端资源时处理对于纯Thymeleaf项目稍显复杂可以考虑在static下放置按需构建好的文件。CDN加速可以考虑使用公共CDN服务加载ECharts但要注意网络可用性和版本控制。script srchttps://cdn.jsdelivr.net/npm/echarts5.5.1/dist/echarts.min.js/script7.4 后端性能如果数据查询复杂要考虑在Controller层或Service层加入缓存如Spring Cache Redis避免每次请求都进行昂贵的计算或数据库查询。7.5 安全考虑虽然本项目不涉及用户输入但也要注意。如果图表数据涉及敏感信息要确保接口有适当的权限控制。Thymeleaf模板本身可以防止XSS攻击但如果你在JS中拼接HTML比如在tooltip.formatter中要小心避免注入。对于热词中提到的springboot解决pdf xss攻击那是另一个话题但安全意识是通用的。8. 总结与扩展方向通过以上步骤我们完成了一个从SpringBoot后端到Thymeleaf模板再到ECharts前端图表的完整数据流。这套方案的核心优势在于简单直接、开发效率高特别适合需要快速验证想法、构建内部工具、或者技术栈以Java为主的项目。我个人在几个内部运维监控项目中都采用了这种模式。一个很深的体会是将数据准备格式、结构与图表展示配置、样式解耦非常重要。定义清晰、稳定的后端数据接口即使是返回给同一个项目的模板能让前后端开发更清晰。比如可以先在Controller里用RestController写好返回JSON的接口用Postman测试数据是否正确然后再做Thymeleaf模板集成。这样未来如果需要支持移动端H5或第三方系统数据API可以直接复用。扩展方向上你可以集成更多图表类型ECharts支持地图、3D图表如热词中的echarts 3d饼图、关系图等。只需在后端组织好对应的数据格式前端修改option配置即可。增加交互性利用ECharts的事件系统实现点击图表某部分跳转到详情页、下钻数据、或者与页面其他部分联动。结合其他UI组件Thymeleaf可以配合Bootstrap、Element-UI等CSS框架让整个页面更美观。向微前端或前后端分离演进如果项目越来越复杂可以考虑将图表部分抽离成独立的Vue/React组件通过API与后端通信。但初期用ThymeleafECharts快速出成果绝对是一个性价比极高的选择。最后再分享一个调试小技巧在开发过程中遇到ECharts显示问题时除了看浏览器控制台还可以在图表初始化后调用console.log(myChart.getOption())打印出ECharts当前使用的完整配置对象与你预期的配置对比往往能快速定位是数据问题还是配置项写错了。