Thymeleaf表单复用片段问题:如何适配不同DTO?
问题根源
你遇到的报错Neither BindingResult nor plain target object for bean name 'objectForForm' available as request attribute,本质是Thymeleaf的th:field和字段错误校验表达式(#fields.hasErrors)依赖当前表单的th:object上下文。当你把输入片段作为参数传递给表单片段时,片段里的*{name}表达式会优先在调用页面(create.html)的上下文解析——而页面上下文里并没有objectForForm这个属性,所以报错;换成具体的shoppingListCreateRequest时,页面上下文存在该属性,因此能正常运行。
可行解决方案
方案一:动态传递DTO变量名(推荐)
通过给输入片段传递DTO的变量名,利用Thymeleaf的预处理表达式(__${}__)动态解析字段绑定,让输入片段适配任意DTO:
- 修改
input.html片段,新增objectName参数:
<div th:fragment="create(objectName)"> <input type="text" class="form-control" placeholder="" required minlength="3" maxlength="50" id="shoppingListName" th:field="*{__${objectName}__['name']}"> <label for="shoppingListName">Name</label> <div class="invalid-feedback">Please insert a name (at least 3 and most 50 characters).</div> <div th:if="${#fields.hasErrors(__${objectName}__ + '.name')}" th:errors="*{__${objectName}__['name']}"></div> </div>
- 在
create.html中调用片段时,传递DTO的变量名:
<div th:replace="~{fragment/form :: formWithContent(~{fragment/shopping-list/input :: create('shoppingListCreateRequest')},shoppingListCreateRequest)}"></div>
预处理表达式__${objectName}__会先解析出变量名(比如shoppingListCreateRequest),再让th:field和错误校验表达式关联到表单的th:object,完美适配不同DTO。
方案二:统一表单上下文绑定
如果不想修改输入片段的表达式,可以在表单片段中通过th:with传递表单对象的上下文,确保输入片段继承表单的th:object环境:
修改form片段:
<div th:fragment="formWithContent(inputs, objectForForm)"> <form th:action="@{/happyEat/shopping-lists/create}" th:method="POST" th:object="${objectForForm}"> <!-- 用th:with传递表单对象上下文,确保输入片段的*{}表达式正确解析 --> <div th:replace="${inputs}" th:with="formObject=${objectForForm}"></div> <input type="submit" value="Create"> <a th:href="@{/happyEat/shopping-lists}">Abort</a> </form> </div>
这种方式不需要修改输入片段的代码,但依赖Thymeleaf的上下文继承特性,兼容性稍弱于方案一。
总结
方案一的动态变量名传递最灵活,完全满足你用同一个表单片段适配不同DTO的需求,同时避免了硬编码具体DTO变量名的问题。
内容的提问来源于stack exchange,提问作者Bianca

