无法解析 Hibernate + Spring 中的自定义验证消息

Can't resolve custom validation messages in Hibernate + Spring

我正在参考 https://spring.io/guides/gs/validating-form-input/ 开发 Spring MVC + Hibernate Validator 集成示例。我添加了一些要从 messages.properties 文件中解决的消息,但是 none 的消息得到解决并面临以下问题。请指导为什么应用程序无法识别 messages.properties 文件。我期待从 .properties 文件中获取实际消息,但为什么会这样 [{length.validation.country}]?

org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'person' on field 'country': rejected value [U]; 
codes [Length.person.country,Length.country,Length.java.lang.String,Length]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes 
[person.country,country]; arguments []; default message [country],50,2]; default message [{length.validation.country}]

Person.java

@Entity
@Table(name="PERSON")
public class Person {

    @Id
    @Column(name="id")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int id;

    @SafeHtml(whitelistType=WhiteListType.NONE, message="{html.validation.firstname}")
    @Length(max=Constant.MAX_FIRSTNAME,min=Constant.MIN_FIRSTNAME,message="{length.validation.firstname}")
    @Column(name="FIRST_NAME")
    private String firstname;

    @SafeHtml(whitelistType=WhiteListType.NONE, message="{html.validation.lastname}")
    @Length(max=Constant.MAX_LASTNAME,min=Constant.MIN_LASTTNAME,message="{length.validation.lastname}")
    @Column(name="LAST_NAME")
    private String lastname;

    @SafeHtml(whitelistType=WhiteListType.NONE, message="{html.validation.country}")
    @Length(max=Constant.MAX_COUNTRY,min=Constant.MIN_COUNTRY,message="{length.validation.country}")
    @Column(name="COUNTRY")
    private String country;

    @SafeHtml(whitelistType = SafeHtml.WhiteListType.NONE, message = "{html.validation.emailId}")
    @Length(max=Constant.MAX_EMAILID,min=Constant.MIN_EMAILID, message = "{length.validation.emailId}")
    @Pattern(regexp = "^(.+)@(.+)$")
    @Column(name="EMAIL_ID")
    private String emailId;
        // setters and getters
}

PersonController.java

@Controller
public class PersonController {

    private PersonService personService;

    public PersonController(){}

    public PersonController(PersonService personService){
        this.personService = personService;
    }

    @Autowired(required=true)
    @Qualifier(value="personService")
    public void setPersonService(PersonService ps){
        this.personService = ps;
    }

    @RequestMapping(value = "/persons", method = RequestMethod.GET)
    public String listPersons(Model model) {
        model.addAttribute("person", new Person());
        model.addAttribute("listPersons", this.personService.listPersons());
        return "person";
    }

    //For add and update person both
    @RequestMapping(value= "/person/add", method = RequestMethod.POST)
    public String addPerson(@Valid @ModelAttribute("person") Person p, BindingResult bindingResult){

        if(bindingResult.hasErrors()){
            System.out.println(bindingResult);
            return "redirect:/persons";
        }

        if(p.getId() == 0){
            //new person, add it
            this.personService.addPerson(p);
        }else{
            //existing person, call update
            this.personService.updatePerson(p);
        }

        return "redirect:/persons";

    }

    @RequestMapping("/remove/{id}")
    public String removePerson(@PathVariable("id") int id){
        this.personService.removePerson(id);
        return "redirect:/persons";
    }

    @RequestMapping("/edit/{id}")
    public String editPerson(@PathVariable("id") int id, Model model){
        model.addAttribute("person", this.personService.getPersonById(id));
        model.addAttribute("listPersons", this.personService.listPersons());
        return "person";
    }
}

messages.properties

html.validation.firstName=Invalid First Name
length.validation.firstName=First Name should be 2 to 50 lenght in characters
html.validation.lastName=Invalid Last Name
length.validation.lastName=Last Name should be 2 to 50 lenght in characters
html.validation.country=Invalid Country Name
length.validation.country=Country Name should be 2 to 50 lenght in characters
html.validation.emailId=Invalid Email ID
length.validation.emailId=Email ID length should not be more than 11 characters

servlet-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:jpa="http://www.springframework.org/schema/data/jpa"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">


    <!-- Enables the Spring MVC @Controller programming model -->
    <annotation-driven />
    <context:component-scan base-package="com.journaldev.spring.controller" />

    <!-- Load database.properties file -->
    <context:property-placeholder location="classpath:database.properties" />

    <!-- Enable JPA Repositories -->
    <jpa:repositories base-package="com.journaldev.spring.repository" />

    <!-- Enable Transaction Manager -->
    <tx:annotation-driven transaction-manager="transactionManager" />

    <!-- Necessary to get the entity manager injected into the factory bean -->
    <beans:bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />


    <!-- ====== MYSQL DataSource ====== -->
    <beans:bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <beans:property name="driverClassName" value="${mysql.jdbc.driverClassName}" />
        <beans:property name="url" value="${mysql.jdbc.url}" />
        <beans:property name="username" value="${mysql.jdbc.userName}" />
        <beans:property name="password" value="${mysql.jdbc.password}" />
    </beans:bean>

    <!-- ====== Hibernate JPA Vendor Adaptor ======= -->
    <beans:bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
        <beans:property name="showSql" value="true"/>
        <beans:property name="generateDdl" value="true"/>
        <beans:property name="database" value="MYSQL"/>
    </beans:bean>


    <!-- Beans -->
    <beans:bean id="personService" class="com.journaldev.spring.service.PersonServiceImpl" />
    <beans:bean id="visitService" class="com.journaldev.spring.service.VisitServiceImpl" />


    <beans:bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <beans:property name="dataSource" ref="dataSource"/>
        <beans:property name="jpaVendorAdapter" ref="jpaVendorAdapter"/>
        <beans:property name="packagesToScan" value="com.journaldev.spring.*" />
        <beans:property name="jpaProperties">
            <beans:props>
                <beans:prop key="hibernate.hbm2ddl.auto">update</beans:prop>  <!-- validate | update | create | create-drop -->
                <beans:prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</beans:prop>
                <!-- <beans:prop key="hibernate.cache.use_query_cache">true</beans:prop> --> 
            </beans:props>
        </beans:property>
    </beans:bean>

    <beans:bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <beans:property name="entityManagerFactory" ref="entityManagerFactory" />
    </beans:bean>

    <beans:bean id="validationMessageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
        <beans:property name="basename" value="classpath:messages" />
        <beans:property name="defaultEncoding" value="UTF-8" />
    </beans:bean>

    <beans:bean name="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
        <beans:property name="validationMessageSource">
           <beans:ref bean="validationMessageSource"/>
        </beans:property>
    </beans:bean>
    <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <beans:property name="prefix" value="/WEB-INF/views/" />
        <beans:property name="suffix" value=".jsp" />
    </beans:bean>
</beans:beans>

Person.jsp

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<%@ page session="false" %>
<html>
<head>
    <title>Person Details Page</title>
    <style type="text/css">
        .tg  {border-collapse:collapse;border-spacing:0;border-color:#ccc;}
        .tg td{font-family:Arial, sans-serif;font-size:14px;padding:10px 5px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;border-color:#ccc;color:#333;background-color:#fff;}
        .tg th{font-family:Arial, sans-serif;font-size:14px;font-weight:normal;padding:10px 5px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;border-color:#ccc;color:#333;background-color:#f0f0f0;}
        .tg .tg-4eph{background-color:#f9f9f9}
    </style>
</head>
<body>
<h1>
    Add a Person
</h1>

<c:url var="addAction" value="/person/add" ></c:url>

<form:form action="${addAction}" commandName="person">
<table>
    <c:if test="${!empty person.firstname}">
    <tr>
        <td>
            <form:label path="id">
                <spring:message text="ID"/>
            </form:label>
        </td>
        <td>
            <form:input path="id" readonly="true" size="8"  disabled="true" />
            <form:hidden path="id" />
        </td> 
    </tr>
    </c:if>
    <tr>
        <td>
            <form:label path="firstname">
                <spring:message text="First Name"/>
            </form:label>
        </td>
        <td>
            <form:input path="firstname" />
        </td> 
    </tr>

    <tr>
        <td>
            <form:label path="lastname">
                <spring:message text="Last Name"/>
            </form:label>
        </td>
        <td>
            <form:input path="lastname" />
        </td> 
    </tr>

    <tr>
        <td>
            <form:label path="emailId">
                <spring:message text="EmailId"/>
            </form:label>
        </td>
        <td>
            <form:input path="emailId" />
        </td> 
    </tr>

    <tr>
        <td>
            <form:label path="country">
                <spring:message text="Country"/>
            </form:label>
        </td>
        <td>
            <form:input path="country" />
        </td>
    </tr>
    <tr>
        <td colspan="2">
            <c:if test="${!empty person.firstname}">
                <input type="submit"
                    value="<spring:message text="Edit Person"/>" />
            </c:if>
            <c:if test="${empty person.firstname}">
                <input type="submit"
                    value="<spring:message text="Add Person"/>" />
            </c:if>
        </td>
    </tr>
</table>    
</form:form>
<br>
<h3>Persons List</h3>
<c:if test="${!empty listPersons}">
    <table class="tg">
    <tr>
        <th width="80"><b>PersonID</b></th>
        <th width="120"><b>FirstName</b></th>
        <th width="120"><b>LastName</b></th>
        <th width="120"><b>EmailID</b></th>
        <th width="120"><b>Country</b></th>
        <th width="60"><b>Edit</b></th>
        <th width="60"><b>Delete</b></th>
    </tr>
    <c:forEach items="${listPersons}" var="person">
        <tr>
            <td>${person.id}</td>
            <td>${person.firstname}</td>
            <td>${person.lastname}</td>
            <td>${person.emailId}</td>
            <td>${person.country}</td>
            <td><a href="<c:url value='/edit/${person.id}' />" >Edit</a></td>
            <td><a href="<c:url value='/remove/${person.id}' />" >Delete</a></td>
        </tr>
    </c:forEach>
    </table>
</c:if>
</body>
</html>

图片: pom.xml

<properties>
        <java.version>1.7</java.version>
        <org.springframework-version>4.1.9.RELEASE</org.springframework-version>
        <org.aspectj-version>1.7.4</org.aspectj-version>
        <org.slf4j-version>1.7.5</org.slf4j-version>
        <hibernate.version>5.1.0.Final</hibernate.version>
        <validation-api.version>1.1.0.Final</validation-api.version>
        <hibernate-validator.version>5.1.1.Final</hibernate-validator.version>
        <jsoup-version>1.8.1</jsoup-version>
        <spring-data-jpa-version>1.9.2.RELEASE</spring-data-jpa-version>
    </properties>

    <dependencies>
        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${org.springframework-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${org.springframework-version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>${spring-data-jpa-version}</version>
        </dependency>


        <!-- Hibernate Entity Manager -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>${hibernate.version}</version>
        </dependency>


        <!-- Validations -->
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>${validation-api.version}</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>${hibernate-validator.version}</version>
        </dependency>

        <!-- Apache Commons DBCP -->
        <dependency>
            <groupId>commons-dbcp</groupId>
            <artifactId>commons-dbcp</artifactId>
            <version>1.4</version>
        </dependency>
        <!-- Spring ORM -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${org.springframework-version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${org.springframework-version}</version>
        </dependency>

        <!-- Querydsl dependencies -->
        <dependency>
            <groupId>com.mysema.querydsl</groupId>
            <artifactId>querydsl-core</artifactId>
            <version>3.3.2</version>
        </dependency>
        <dependency>
            <groupId>com.mysema.querydsl</groupId>
            <artifactId>querydsl-apt</artifactId>
            <version>3.3.2</version>
        </dependency>
        <dependency>
            <groupId>com.mysema.querydsl</groupId>
            <artifactId>querydsl-jpa</artifactId>
            <version>3.3.2</version>
        </dependency>

        <!-- LOG4J -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
            <scope>runtime</scope>
        </dependency>


        <!-- Needed for Hibernate Validation -->
        <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>${jsoup-version}</version>
        </dependency>

        <dependency>
            <groupId>com.google.code.findbugs</groupId>
            <artifactId>jsr305</artifactId>
            <version>3.0.0</version>
        </dependency>

        <!-- @Inject -->
        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
        </dependency>

        <!-- JSP API -->
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.1</version>
            <scope>provided</scope>
        </dependency>
        <!-- Servlet API -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>

        <!-- JSTL -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>

        <!-- Junit Test -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.37</version>
        </dependency>
    </dependencies>

    <!-- Project Build -->
    <build>
        <finalName>spring-mvc-jpa-hibernate</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.5.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <compilerArgument>-Xlint:all</compilerArgument>
                    <showWarnings>true</showWarnings>
                    <showDeprecation>true</showDeprecation>
                </configuration>
            </plugin>


            <plugin>
                <groupId>com.mysema.maven</groupId>
                <artifactId>apt-maven-plugin</artifactId>
                <version>1.1.1</version>
                <executions>
                    <execution>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>process</goal>
                        </goals>
                        <configuration>
                            <!-- Specifies the directory in which the query types are generated -->
                            <outputDirectory>target/generated-sources</outputDirectory>
                            <!-- States that the APT code generator should look for JPA annotations -->
                            <processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor</processor>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

将您的 MessageSource bean id 定义为 'messageSource' 并从您的验证程序中将其引用为 'messageSource'。

<beans:bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
    <beans:property name="basenames">
       <beans:list>
            <beans:value>classpath:messages</beans:value>
            <beans:value>classpath:ValidationMessages</beans:value>
        </beans:list>        
    </beans:bean>
    <beans:property name="defaultEncoding" value="UTF-8" />
</beans:bean>

对于 Hibernate 验证,您还需要 ValidationMessages.properties 并确保使用了正确的密钥名称。请记住,外部消息属性需要覆盖验证框架的默认值以提供自定义消息。见下文

ValidationMessages.properties 

# FirstName
html.validation.firstName=Invalid First Name
#length.validation.firstName=First Name should be 2 to 50 lenght in characters
Length.person.firstname=First Name should be 2 to 50 lenght in characters

# LastName
html.validation.lastName=Invalid Last Name
#length.validation.lastName=Last Name should be 2 to 50 lenght in characters
Length.person.lastname=Last Name should be 2 to 50 lenght in characters

# Country
html.validation.country=Invalid Country Name
#length.validation.country=Country Name should be 2 to 50 lenght in characters
Length.person.country=Country Name should be 2 to 50 lenght in characters

#EmailId
html.validation.emailId=Invalid Email ID
#length.validation.emailId=Email ID length should not be more than 11 characters
Length.person.emailId=Email ID length should not be more than 11 characters 
Pattern.person.emailId=Invalid Email Format