thymeleaf + spring-boot 验证错误未显示在 html 上

Posted

技术标签:

【中文标题】thymeleaf + spring-boot 验证错误未显示在 html 上【英文标题】:thymleaf + spring-boot Validaiton error not displaying on html 【发布时间】:2021-11-17 00:46:39 【问题描述】:

我可以在控制台上看到验证错误,但百里香模板没有显示错误。它应该在百里香模板上显示错误。 我正在使用 Spring Boot 2.5.4

提前致谢。

我的 Pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.4</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.ttl</groupId>
    <artifactId>cps-admission-portal</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>cps-admission-portal</name>
    <description>CPS Admission Portal</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.oracle.database.jdbc</groupId>
            <artifactId>ojdbc8</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>bootstrap</artifactId>
            <version>4.0.0-2</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

我的控制器

package com.ttl.cpsadmissionportal.controller;


import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.validation.Valid;
import javax.validation.executable.ValidateOnExecution;
import javax.xml.bind.annotation.XmlRegistry;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
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 org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.ttl.cpsadmissionportal.model.AdmissionForm;
import com.ttl.cpsadmissionportal.service.CampusService;
import com.ttl.cpsadmissionportal.service.OfferProgramService;
import com.ttl.cpsadmissionportal.serviceimpl.AdmissionFormServiceImple;

@Controller
public class AdmissionFormController 
    
    @Autowired
    private OfferProgramService offerProgramService;
    
    @Autowired
    private AdmissionFormServiceImple admissionFormServiceImple;
    
    @Autowired
    private CampusService campusService;

    @GetMapping("admissionform")
    public String viewAdmissionForm(Model model) 
        AdmissionForm admissionForm = new AdmissionForm();
        model.addAttribute("admissionForm",admissionForm);
        model.addAttribute("listCampuses",campusService.getAllCampuses());
        return  "admissionform";
    
    

    
    @PostMapping("saveadmissionform")
    public String saveAdmissionForm(@Valid @ModelAttribute("admissionForm") AdmissionForm admissionForm
            ,RedirectAttributes redirectAttributes
            , BindingResult bindingResulted) 

        if (bindingResulted.hasErrors()) 
            System.out.println("ERROR IN VALIDATION");
            return "admissionform";
        
    
        admissionFormServiceImple.addAdmissionForm(admissionForm);
        redirectAttributes.addAttribute("candidateName",admissionForm.getCandidateName());
        redirectAttributes.addAttribute("refNbr",admissionForm.getRefNbr());
      return "redirect:/thankyou";
        
    
    
    @GetMapping("thankyou")
    public String thankyou() 
        return "thankyou";
    


型号

package com.ttl.cpsadmissionportal.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;

import com.sun.istack.NotNull;

@Entity
@Table(name="ADMISSION_FORM", schema = "UCP")
public class AdmissionForm 
    
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "af_id_gen")
    @SequenceGenerator(name="af_id_gen", sequenceName="SEQ_AF_ID", schema = "UCP",allocationSize=1)
    @Column(name="AF_ID")
    private int afId;
    
    @Column(name="OP_ID")
    private int opId;

    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ref_nbr_gen")
    @SequenceGenerator(name="ref_nbr_gen", sequenceName="SEQ_REF_NBR", schema = "UCP",allocationSize=1)
    @Column(name="REF_NBR")
    private String refNbr;

    @NotEmpty
    @NotNull
    @Size(min=10,max=50)
    @Column(name="CANDIDATE_NAME")
    private String candidateName;
    
    @NotEmpty
    @NotNull
    @Column(name="GENDER_IND")
    private String genderInd;
    
    @NotEmpty
    @NotNull
    @Column(name="FATHER_NAME")
    private String fatherName;
    
    @NotEmpty
    @NotNull
    @Column(name="RELATIONSHIP_IND")
    private String relationshipInd;
    
    @NotEmpty
    @NotNull
    @Column(name="PARENT_CNIC_NBR")
    private String parentCnicNbr;
    
    @NotEmpty
    @NotNull
    @Column(name="PARENT_CONTACT_NBR")
    private String parentContactNbr;
    
    @NotEmpty
    @NotNull
    @Column(name="STUDENT_CNIC_NBR")
    private String studentCnicNbr;
    
    @NotEmpty
    @NotNull
    @Column(name="STUDENT_CONTACT_NBR")
    private String studentContactNbr;
    
    @NotEmpty
    @NotNull
    @Email
    @Column(name="STUDENT_EMAIL_ADD")
    private String studentEmailAdd;
    
    @Column(name="ADDRESS")
    private String address;

    public int getAfId() 
        return afId;
    

    public void setAfId(int afId) 
        this.afId = afId;
    

    public int getOpId() 
        return opId;
    

    public void setOpId(int opId) 
        this.opId = opId;
    

    public String getRefNbr() 
        return refNbr;
    

    public void setRefNbr(String refNbr) 
        this.refNbr = refNbr;
    

    public String getCandidateName() 
        return candidateName;
    

    public void setCandidateName(String candidateName) 
        this.candidateName = candidateName;
    

    public String getGenderInd() 
        return genderInd;
    

    public void setGenderInd(String genderInd) 
        this.genderInd = genderInd;
    

    public String getFatherName() 
        return fatherName;
    

    public void setFatherName(String fatherName) 
        this.fatherName = fatherName;
    

    public String getRelationshipInd() 
        return relationshipInd;
    

    public void setRelationshipInd(String relationshipInd) 
        this.relationshipInd = relationshipInd;
    

    public String getParentCnicNbr() 
        return parentCnicNbr;
    

    public void setParentCnicNbr(String parentCnicNbr) 
        this.parentCnicNbr = parentCnicNbr;
    

    public String getParentContactNbr() 
        return parentContactNbr;
    

    public void setParentContactNbr(String parentContactNbr) 
        this.parentContactNbr = parentContactNbr;
    

    public String getStudentCnicNbr() 
        return studentCnicNbr;
    

    public void setStudentCnicNbr(String studentCnicNbr) 
        this.studentCnicNbr = studentCnicNbr;
    

    public String getStudentContactNbr() 
        return studentContactNbr;
    

    public void setStudentContactNbr(String studentContactNbr) 
        this.studentContactNbr = studentContactNbr;
    

    public String getStudentEmailAdd() 
        return studentEmailAdd;
    

    public void setStudentEmailAdd(String studentEmailAdd) 
        this.studentEmailAdd = studentEmailAdd;
    

    public String getAddress() 
        return address;
    

    public void setAddress(String address) 
        this.address = address;
    
    
    
    



我的百里香模板

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymleaf.org">
<head>
<meta charset="ISO-8859-1">
<title>Admission Form</title>
<link th:rel="stylesheet"
    th:href="@/webjars/bootstrap/4.0.0-2/css/bootstrap.min.css " />
<link rel="stylesheet" href="sb-admin-2.css" />
<link th:href="@/css/sb-admin-2.css" rel="stylesheet" />
</head>
<body>

    <script
        src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js">
        
    </script>

    <form action="#" role="form" th:action="@/saveadmissionform"
        th:object="$admissionForm" method="post">
        
        <div class="container-fluid mt-n10">
            <div class="card card-header-actions mb-4">
                <div class="card-body">
                    <div class="row mt-2 mb-2">


                    <div class="row">
                        <div class="col-md-12">
                            <h2 class="heading">Personal Info</h2>
                        </div>
                    </div>

                    <div class="row mt-2 mb-2">
                        <div class="col-md-3">
                            <div class="form-group">
                                <label for=candidateName>Full Name <span style="color: red">
                                        *</span></label> <input type="text" th:field="*candidateName"
                                    class="form-control form-control-solid" id="candidateName"
                                    placeholder="Enter Full Name" required>
                                     
                                     <span th:if="$#fields.hasErrors('candidateName')" th:errors="*candidateName">Full name error</span>
                            </div>
                        </div>
</form>

</body>
</html>

出现以下错误,但应在候选人姓名前显示错误。

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

Thu Sep 23 20:14:01 PKT 2021
There was an unexpected error (type=Bad Request, status=400).
Validation failed for object='admissionForm'. Error count: 1
org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'admissionForm' on field 'candidateName': rejected value [a]; codes [Size.admissionForm.candidateName,Size.candidateName,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [admissionForm.candidateName,candidateName]; arguments []; default message [candidateName],50,10]; default message [size must be between 10 and 50]
    at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:175)
    at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)
    at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:170)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1064)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:681)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:764)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1726)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
    at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:748)

【问题讨论】:

【参考方案1】:

BindingResult 必须直接跟在控制器方法中的 ModelAttribute 参数之后。

这应该可行:

public String saveAdmissionForm(@Valid @ModelAttribute("admissionForm") 
     AdmissionForm admissionForm,
     BindingResult bindingResulted,
     RedirectAttributes redirectAttribute) 

见Cannot get validation working with Spring Boot and Thymeleaf

【讨论】:

以上是关于thymeleaf + spring-boot 验证错误未显示在 html 上的主要内容,如果未能解决你的问题,请参考以下文章

Spring-boot,thymeleaf 加载上传的图片,无需重启应用服务器

Spring-boot(二)--thymeleaf

Spring-Boot + Spring-MVC + Thymeleaf + Apache Tiles

Thymeleaf 无法检测到 spring-boot 项目中的模板

thymeleaf + spring-boot 验证错误未显示在 html 上

如何将布局方言添加到spring-boot thymeleaf自动配置文件