【问题标题】:Failed parsing Thymeleaf template when validating form with Spring Bean Validation API使用 Spring Bean Validation API 验证表单时解析 Thymeleaf 模板失败
【发布时间】:2019-10-27 10:00:09
【问题描述】:

我正在关注这本书的“Spring In Action 第 5 版”示例,但是每当对表单输入进行验证时发现无效的内容时都会出现此错误。

错误:

Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.

Sun Oct 27 17:26:07 SGT 2019
There was an unexpected error (type=Internal Server Error, status=500).
An error happened during template parsing (template: "class path resource [templates/design.html]")
org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "class path resource [templates/design.html]")
    at org.thymeleaf.templateparser.markup.AbstractMarkupTemplateParser.parse(AbstractMarkupTemplateParser.java:241)
    at org.thymeleaf.templateparser.markup.AbstractMarkupTemplateParser.parseStandalone(AbstractMarkupTemplateParser.java:100)
    at org.thymeleaf.engine.TemplateManager.parseAndProcess(TemplateManager.java:666)
    at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1098)
    at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1072)
    at org.thymeleaf.spring5.view.ThymeleafView.renderFragment(ThymeleafView.java:362)
    at org.thymeleaf.spring5.view.ThymeleafView.render(ThymeleafView.java:189)
    at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1371)
    at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1117)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1056)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:908)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:660)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:853)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1587)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:748)
Caused by: org.attoparser.ParseException: Error during execution of processor 'org.thymeleaf.spring5.processor.SpringInputGeneralFieldTagProcessor' (template: "design" - line 59, col 20)
    at org.attoparser.MarkupParser.parseDocument(MarkupParser.java:393)
    at org.attoparser.MarkupParser.parse(MarkupParser.java:257)
    at org.thymeleaf.templateparser.markup.AbstractMarkupTemplateParser.parse(AbstractMarkupTemplateParser.java:230)
    ... 52 more
Caused by: org.thymeleaf.exceptions.TemplateProcessingException: Error during execution of processor 'org.thymeleaf.spring5.processor.SpringInputGeneralFieldTagProcessor' (template: "design" - line 59, col 20)
    at org.thymeleaf.processor.element.AbstractAttributeTagProcessor.doProcess(AbstractAttributeTagProcessor.java:117)
    at org.thymeleaf.processor.element.AbstractElementTagProcessor.process(AbstractElementTagProcessor.java:95)
    at org.thymeleaf.util.ProcessorConfigurationUtils$ElementTagProcessorWrapper.process(ProcessorConfigurationUtils.java:633)
    at org.thymeleaf.engine.ProcessorTemplateHandler.handleStandaloneElement(ProcessorTemplateHandler.java:918)
    at org.thymeleaf.engine.TemplateHandlerAdapterMarkupHandler.handleStandaloneElementEnd(TemplateHandlerAdapterMarkupHandler.java:260)
    at org.thymeleaf.templateparser.markup.InlinedOutputExpressionMarkupHandler$InlineMarkupAdapterPreProcessorHandler.handleStandaloneElementEnd(InlinedOutputExpressionMarkupHandler.java:256)
    at org.thymeleaf.standard.inline.OutputExpressionInlinePreProcessorHandler.handleStandaloneElementEnd(OutputExpressionInlinePreProcessorHandler.java:169)
    at org.thymeleaf.templateparser.markup.InlinedOutputExpressionMarkupHandler.handleStandaloneElementEnd(InlinedOutputExpressionMarkupHandler.java:104)
    at org.attoparser.HtmlElement.handleStandaloneElementEnd(HtmlElement.java:79)
    at org.attoparser.HtmlMarkupHandler.handleStandaloneElementEnd(HtmlMarkupHandler.java:241)
    at org.attoparser.MarkupEventProcessorHandler.handleStandaloneElementEnd(MarkupEventProcessorHandler.java:327)
    at org.attoparser.ParsingElementMarkupUtil.parseStandaloneElement(ParsingElementMarkupUtil.java:96)
    at org.attoparser.MarkupParser.parseBuffer(MarkupParser.java:706)
    at org.attoparser.MarkupParser.parseDocument(MarkupParser.java:301)
    ... 54 more
Caused by: java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'design' available as request attribute
    at org.springframework.web.servlet.support.BindStatus.<init>(BindStatus.java:153)
    at org.springframework.web.servlet.support.RequestContext.getBindStatus(RequestContext.java:903)
    at org.thymeleaf.spring5.context.webmvc.SpringWebMvcThymeleafRequestContext.getBindStatus(SpringWebMvcThymeleafRequestContext.java:227)
    at org.thymeleaf.spring5.util.FieldUtils.getBindStatusFromParsedExpression(FieldUtils.java:306)
    at org.thymeleaf.spring5.util.FieldUtils.getBindStatus(FieldUtils.java:253)
    at org.thymeleaf.spring5.util.FieldUtils.getBindStatus(FieldUtils.java:227)
    at org.thymeleaf.spring5.processor.AbstractSpringFieldTagProcessor.doProcess(AbstractSpringFieldTagProcessor.java:174)
    at org.thymeleaf.processor.element.AbstractAttributeTagProcessor.doProcess(AbstractAttributeTagProcessor.java:74)
    ... 67 more

对象已传递给视图模板:

package tacos;

import java.util.List;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import lombok.Data;

@Data
public class Taco {

    @NotNull
    @Size(min=5, message="Name must be at least 5 characters long")
    private String name;

    @NotNull
    @Size(min=2, message="You must choose at least 2 ingredient")
    private List<String> ingredients;
}

Thymeleaf 视图模板:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<title>Taco Cloud</title>
<link rel="stylesheet" th:href="@{/styles.css}" />
</head>
<body>
<h1>Design your taco!</h1>
<img th:src="@{/images/TacoCloud.png}"/>
<form method="POST" th:object="${design}">
<div class="grid">
<div class="ingredient-group" id="wraps">
<h3>Designate your wrap:</h3>
<div th:each="ingredient : ${wrap}">
<input name="ingredients" type="checkbox" th:value="${ingredient.id}"
/>
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
<div class="ingredient-group" id="proteins">
<h3>Pick your protein:</h3>
<div th:each="ingredient : ${protein}">
<input name="ingredients" type="checkbox" th:value="${ingredient.id}"
/>
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
<div class="ingredient-group" id="cheeses">
<h3>Choose your cheese:</h3>
<div th:each="ingredient : ${cheese}">
<input name="ingredients" type="checkbox" th:value="${ingredient.id}"
/>
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
<div class="ingredient-group" id="veggies">
<h3>Determine your veggies:</h3>
<div th:each="ingredient : ${veggies}">
<input name="ingredients" type="checkbox" th:value="${ingredient.id}"
/>
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
<div class="ingredient-group" id="sauces">
<h3>Select your sauce:</h3>
<div th:each="ingredient : ${sauce}">
<input name="ingredients" type="checkbox" th:value="${ingredient.id}"
/>
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
</div>
<div>
<h3>Name your taco creation:</h3>
<input type="text" th:field="*{name}"/>
<span class="validationError"
                th:if="${#fields.hasErrors('name')}"
                th:errors="*{name}">Name Error</span>
<br/>
<button>Submit your taco</button>
</div>
</form>
</body>
</html>

控制器显示表单方法:

    @GetMapping
    public String showDesignForm(Model model)
    {
        List<Ingredient> ingredients = Arrays.asList(
                new Ingredient("FLTO", "Flour Tortilla", Type.WRAP),
                new Ingredient("COTO", "Corn Tortilla", Type.WRAP),
                new Ingredient("GRBF", "Ground Beef", Type.PROTEIN),
                new Ingredient("CARN", "Carnitas", Type.PROTEIN),
                new Ingredient("TMTO", "Diced Tomatoes", Type.VEGGIES),
                new Ingredient("LETC", "Lettuce", Type.VEGGIES),
                new Ingredient("CHED", "Cheddar", Type.CHEESE),
                new Ingredient("JACK", "Monterrey Jack", Type.CHEESE),
                new Ingredient("SLSA", "Salsa", Type.SAUCE),
                new Ingredient("SRCR", "Sour Cream", Type.SAUCE)
                );

        Type[] types = Ingredient.Type.values();
        for (Type type : types)
        {
            model.addAttribute(type.toString().toLowerCase(), 
                    filterByType(ingredients, type));
        }

        model.addAttribute("design", new Taco());

        return "design";
    }

控制器进程表单方法:

    @PostMapping
    public String processDesign(@Valid Taco taco, Errors errors)
    {
        if (errors.hasErrors())
        {
            return "design";
        }

        // Save the taco design...
        // We will do this in chapter 3
        log.info("Processing design: " + taco);

        return "redirect:/orders/current";
    }

我在显示表单时没有问题。所有输入字段都得到很好的接收和处理。但是每当出现验证错误,process form method 回调到表单页面“设计”时,表单就无法解析并出现上述错误。

我试过:
1。禁用对 Taco.name 字段的验证;
2。删除显示名称字段错误的&lt;span&gt;
但它们都不起作用。

提前感谢您的帮助。

【问题讨论】:

  • 我没有看到th:action
  • 嗨@vphilipnyc,没有th:action 的表单将回调到相同的请求路径。我在 POST 向我的控制器请求表单以进行表单输入验证时没有问题。感谢您的回复。
  • 您是否尝试过将您的 post 方法注释为模型属性,例如 @ModelAttribute("design") Taco taco

标签: spring thymeleaf bean-validation


【解决方案1】:

我尝试更改此代码

@PostMapping
public String processDesign(@Valid Taco taco, Errors errors)
{
    if (errors.hasErrors())
    {
        return "design";
    }

    // Save the taco design...
    // We will do this in chapter 3
    log.info("Processing design: " + taco);

    return "redirect:/orders/current";
}

@PostMapping
public String processDesign(@Valid Taco taco, Errors errors)
{
    if (errors.hasErrors())
    {
        return "redirect:/design";
    }

    // Save the taco design...
    // We will do this in chapter 3
    log.info("Processing design: " + taco);

    return "redirect:/orders/current";
}

它有效。

【讨论】:

    【解决方案2】:

    我遇到了类似的问题。这就是我为修复它所做的。

    1st - 将 design.html 中的开头标题更改为以下内容

    <form method="POST" th:object="${design}">
    

    第二个 - 我的 processDesign() 方法如下所示:

    public String processDesign(@Valid @ModelAttribute("design") Taco taco, Errors errors) {
    
        if (errors.hasErrors()) {
            return "design";
        }
    
        // this will save our Taco Design
        // log.info("Processing design: " + design);
    
        return "redirect:/orders/current";
    }
    

    我希望这会有所帮助,而且我还不算太晚。总的来说,书中有多个类似的错误,需要修复,但它周围的社区很棒,所以不用担心。

    干杯

    【讨论】:

      【解决方案3】:

      书中的代码有一些错误,这里是来自书的github的正确代码:

      // tag::head[]
      package tacos.web;
      
      import java.util.Arrays;
      import java.util.List;
      import java.util.stream.Collectors;
      
      import javax.validation.Valid;
      
      import org.springframework.stereotype.Controller;
      import org.springframework.ui.Model;
      import org.springframework.validation.Errors;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.ModelAttribute;
      import org.springframework.web.bind.annotation.PostMapping;
      import org.springframework.web.bind.annotation.RequestMapping;
      
      import lombok.extern.slf4j.Slf4j;
      import tacos.Ingredient;
      import tacos.Ingredient.Type;
      import tacos.Taco;
      
      @Slf4j
      @Controller
      @RequestMapping("/design")
      public class DesignTacoController {
      
      //end::head[]
      
      @ModelAttribute
      public void addIngredientsToModel(Model model) {
          List<Ingredient> ingredients = Arrays.asList(
            new Ingredient("FLTO", "Flour Tortilla", Type.WRAP),
            new Ingredient("COTO", "Corn Tortilla", Type.WRAP),
            new Ingredient("GRBF", "Ground Beef", Type.PROTEIN),
            new Ingredient("CARN", "Carnitas", Type.PROTEIN),
            new Ingredient("TMTO", "Diced Tomatoes", Type.VEGGIES),
            new Ingredient("LETC", "Lettuce", Type.VEGGIES),
            new Ingredient("CHED", "Cheddar", Type.CHEESE),
            new Ingredient("JACK", "Monterrey Jack", Type.CHEESE),
            new Ingredient("SLSA", "Salsa", Type.SAUCE),
            new Ingredient("SRCR", "Sour Cream", Type.SAUCE)
          );
      
          Type[] types = Ingredient.Type.values();
          for (Type type : types) {
            model.addAttribute(type.toString().toLowerCase(),
                filterByType(ingredients, type));
          }
      }
      
      //tag::showDesignForm[]
        @GetMapping
        public String showDesignForm(Model model) {
          model.addAttribute("design", new Taco());
          return "design";
        }
      
      //end::showDesignForm[]
      
      /*
      //tag::processDesign[]
        @PostMapping
        public String processDesign(Design design) {
          // Save the taco design...
          // We'll do this in chapter 3
          log.info("Processing design: " + design);
      
          return "redirect:/orders/current";
        }
      
      //end::processDesign[]
       */
      
      //tag::processDesignValidated[]
        @PostMapping
        public String processDesign(@Valid @ModelAttribute("design") Taco design, Errors errors, Model model) {
          if (errors.hasErrors()) {
            return "design";
          }
      
          // Save the taco design...
          // We'll do this in chapter 3
          log.info("Processing design: " + design);
      
          return "redirect:/orders/current";
        }
      
      //end::processDesignValidated[]
      
      //tag::filterByType[]
        private List<Ingredient> filterByType(
            List<Ingredient> ingredients, Type type) {
          return ingredients
                    .stream()
                    .filter(x -> x.getType().equals(type))
                    .collect(Collectors.toList());
        }
      
      //end::filterByType[]
      // tag::foot[]
      }
      // end::foot[]
      
      

      你可以从https://github.com/habuma/spring-in-action-5-samples获取所有源代码

      ps:在我看来,可能的原因是@valide Taco会将一个名为taco的Taco对象传输到模板中,而我们在模板中使用design,所以,模板找不到@987654326 @ 导致错误。而@ModelAttribute('design') 可以将Taco 对象重命名为design。另一方面,每当我们访问 tacoDesign html 时,我们都需要成分列表来初始化模板,这就是我们将addIngredientsToModel 方法分开并在其上方使用@ModelAttribute 的原因。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-02-08
        • 2019-02-20
        • 1970-01-01
        • 1970-01-01
        • 2021-09-25
        • 1970-01-01
        相关资源
        最近更新 更多