Thymeleaf th:replace使用动态变量路径时模板解析报错问题
问题原因
两个核心问题导致异常和功能失效:
- 枚举类的私有字段没有提供public getter方法,Thymeleaf的OGNL表达式无法读取私有属性值,这是抛出表达式解析异常的直接原因。另外枚举前两个模板路径写了多余的单引号,路径本身非法:
"templates/firstPageInclude'.html'"、"templates/summaryInclude'.html'"。 - Thymeleaf的
th:replace默认不会直接把${}表达式的返回值识别为模板路径,直接写th:replace="${summaryItem.pagePath}"时,引擎会把表达式结果当成普通变量输出,不会去加载对应的模板文件。同时你写的id="${summaryItem.anchor}"缺少th:前缀,动态表达式根本不会被解析,会原样输出字符串。
修复步骤
- 修正枚举错误,补全必要方法
先删掉前两个枚举值路径里多余的单引号,保证路径和实际文件位置一致,再给所有私有字段添加public getter方法:public enum FollowupPdfSummaryEnum { COVER("coverAnchor", "templates/firstPageInclude.html", "Couverture", FollowupBlocEnum.COVER_PAGE), SUMMARY("summaryAnchor", "templates/summaryInclude.html", "Sommaire"), SITUATION_PLAN("situationPlanAnchor", "templates/situationPlanInclude.html", "Plan de situation", FollowupBlocEnum.SITUATION_PLAN), BUILDING_VISUAL("buildingVisualAnchor", "templates/visualInclude.html", "Visuels", FollowupBlocEnum.BUILDING_VISUAL), HEALTH_BOOK("healthBookAnchor", "templates/healthBookBatInclude.html", "Carnet de santé", FollowupBlocEnum.HEALTH_BOOK), IDENTITY_CARD("identityCardAnchor", "templates/identityCardInclude.html", "Fiche d'identité", FollowupBlocEnum.IDENTITY_CARD), COUNTER_SITUATION_PLAN("counterSituationAnchor", "templates/counterSituationPlanInclude.html", "Plan des compteurs", FollowupBlocEnum.COUNTER_SITUATION_PLAN), DANGEROUS_MATERIAL("dangerousMaterialAnchor", "templates/dangerousMaterialInclude.html", "Diagnostic Matières Dangereuses", FollowupBlocEnum.DANGEROUS_MATERIAL_DIAGNOSIS), RECAP_WORK_TODO("workDoneAnchor", "templates/workTodoListInclude.html", "Récapitulatif des travaux à réaliser", FollowupBlocEnum.WORK_DONE_REPORT), VISIT_REPORT("visitReportAnchor", "templates/visitReportInclude.html", "Rapport de visite", FollowupBlocEnum.VISIT_REPORT), VISIT_REPORT_OCCUPATION("visitReportOccupationAnchor", "templates/occupationVisitReportInclude.html", "Rapport de visite : Occupations", FollowupBlocEnum.OCCUPATION_VISIT_REPORT), ANNEXE("annexeAnchor", "templates/annexeInclude.html", "Annexes"); private final String anchor; private final String pagePath; private final String title; private final FollowupBlocEnum block; private FollowupPdfSummaryEnum(String anchor, String pagePath, String title, FollowupBlocEnum block) { this.anchor = anchor; this.pagePath = pagePath; this.title = title; this.block = block; } private FollowupPdfSummaryEnum(String anchor, String pagePath, String title) { this.anchor = anchor; this.pagePath = pagePath; this.title = title; this.block = null; } // 补全getter方法 public String getAnchor() {return anchor;} public String getPagePath() {return pagePath;} public String getTitle() {return title;} public FollowupBlocEnum getBlock() {return block;} } - 修改模板代码,使用正确的Thymeleaf语法
动态模板路径需要用__${}__预处理语法:该语法会先执行内部表达式,把返回值拼接到属性位置,再让引擎按模板路径加载文件。所有绑定动态值的属性都要加th:前缀,遍历Model传参时优先用${}取值,修改后的代码如下:<th:block th:each="summaryItem : ${summary}"> <page-after th:id="${summaryItem.anchor}"> <div th:replace="__${summaryItem.pagePath}__"></div> </page-after> </th:block>
注意事项
- 所有被引入的子模板不要包含完整的
<html>、<body>根标签,只保留需要渲染的内容片段即可,避免OpenHtmlToPdf解析时出现标签嵌套错误。 - 如果需要给子模板传递参数,可以在路径后按标准
th:replace传参格式追加,例如th:replace="__${summaryItem.pagePath}__ (currentItem=${summaryItem})"。 - 确认模板文件实际存放路径和枚举配置的路径完全匹配,Spring Boot项目默认模板根路径为
src/main/resources/templates/,枚举中写的相对路径要和该根路径对应,同时注意路径大小写敏感。
内容的提问来源于stack exchange,提问作者Gauthier
相关产品推荐
相关产品推荐

