如何通过Ajax POST请求从Spring ResponseEntity获取渲染后的Thymeleaf片段
我明白你现在的困扰——你想返回不同的HTTP状态码,同时让Thymeleaf渲染指定的提示片段作为响应体,但现在拿到的只是片段的表达式字符串,完全没被渲染。这是@ResponseBody的工作机制导致的,我来帮你一步步解决这个问题。
问题根源
你之前直接返回"fragments/components :: ajaxResponse"这类字符串,加上@ResponseBody注解后,Spring会把它原封不动地作为响应体返回,不会触发Thymeleaf的渲染流程。只有当你返回视图名称或者ModelAndView时,Spring才会自动调用模板引擎处理内容。所以核心解决方案是:手动调用Thymeleaf模板引擎渲染片段,再把渲染后的HTML作为响应体返回。
具体解决步骤
第一步:注入Thymeleaf的TemplateEngine
首先在你的Controller里注入TemplateEngine,它是Thymeleaf渲染模板的核心类:
@Autowired private TemplateEngine templateEngine;
第二步:编写片段渲染工具方法
写一个辅助方法,把Controller的Model属性传递给Thymeleaf上下文,然后渲染指定的片段,返回最终的HTML字符串:
private String renderAjaxResponseFragment(Model model) { // 创建Thymeleaf上下文,用于传递模板变量 Context thymeleafContext = new Context(); // 把Controller Model中的所有属性复制到上下文 model.asMap().forEach(thymeleafContext::setVariable); // 渲染指定的Thymeleaf片段,返回HTML字符串 return templateEngine.process("fragments/components :: ajaxResponse", thymeleafContext); }
第三步:修改Controller的返回逻辑
现在调整你的deleteMultipleMyObjects方法,不再返回片段表达式,而是返回渲染后的HTML。对于需要同时返回数据(比如已删除ID列表)的场景,我们可以创建一个简单的DTO来封装数据和HTML:
先创建DTO类(放在合适的包下)
public class DeleteMultipleResponse { private List<Long> deletedIds; private String htmlFragment; // 构造器 public DeleteMultipleResponse(List<Long> deletedIds, String htmlFragment) { this.deletedIds = deletedIds; this.htmlFragment = htmlFragment; } // Getter方法(必须,否则JSON序列化会失败) public List<Long> getDeletedIds() { return deletedIds; } public String getHtmlFragment() { return htmlFragment; } }
修改Controller方法
@PostMapping("/deleteMultipleMyObjects") @ResponseBody public ResponseEntity<Object> deleteMultipleMyObjects(Model model, @ModelAttribute("myObjectIds") String myObjectIds) { if (myObjectIds.length() == 0) { MessageHelper.addInfoAttribute(model, "objects.delete.none"); // 渲染片段得到最终HTML String renderedHtml = renderAjaxResponseFragment(model); return ResponseEntity.badRequest().body(renderedHtml); } Pair<List<Long>, List<Long>> res = myObjectService.deleteMultipleMyObjects(myObjectIds); if (!res.getValue1().isEmpty()) { MessageHelper.addWarningAttribute(model, "objects.delete.warn", res.getValue1()); String renderedHtml = renderAjaxResponseFragment(model); // 封装已删除ID和渲染后的HTML DeleteMultipleResponse response = new DeleteMultipleResponse(res.getValue0(), renderedHtml); return ResponseEntity.status(HttpStatus.PRECONDITION_FAILED).body(response); } MessageHelper.addSuccessAttribute(model, "objects.delete.ok"); String renderedHtml = renderAjaxResponseFragment(model); return ResponseEntity.accepted().body(renderedHtml); }
第四步:调整前端JavaScript逻辑
现在前端收到的要么是直接的HTML字符串,要么是包含deletedIds和htmlFragment的JSON对象,所以需要调整JS代码:
function deleteMultipleMyObjects() { let myObjectIds = getAllCheckedObjects(); let fragment; $.post("deleteMultipleMyObjects", {myObjectIds: myObjectIds}) .done(function (data, status, xhr){ hideAllCheckedObjects(); fragment = data; // 这里data已经是渲染好的HTML }) .fail(function(xhr, status, error){ if (xhr.responseJSON) { // 处理已删除的ID列表 hideLinesWithId(xhr.responseJSON.deletedIds); // 拿到渲染好的HTML片段 fragment = xhr.responseJSON.htmlFragment; } else { fragment = xhr.responseText; } }) .always(function() { $("#alertDiv").empty(); // 清空容器,比text("")更干净 $("#alertDiv").append(fragment); // 直接插入渲染好的HTML }); }
补充说明
这样调整后,无论是成功、空ID还是部分删除的场景,前端都会收到已经渲染好的Thymeleaf片段HTML,而不是片段表达式字符串。同时你也能保持不同的HTTP状态码,满足业务对响应状态的要求。
另外要确保你的Thymeleaf配置正确,TemplateEngine能正确找到fragments/components.html模板文件(通常放在src/main/resources/templates目录下)。
备注:内容来源于stack exchange,提问作者Orangeade

