Thymeleaf + Spring 中的验证

位置:首页>文章>详情   分类: Java教程 > 编程技术   阅读(219)   2024-01-15 15:34:57

概述

我们将讨论的重要主题是处理空值、空字符串和输入验证,这样我们就不会在数据库中输入无效数据。

在处理空值时,我们谈到了 Java 1.8 中引入的 java.util.Optional 的使用。

0 – Spring Boot + Thymeleaf 示例表单验证应用程序

我们正在为一所大学构建一个 Web 应用程序,允许潜在的学生请求有关他们课程的信息。

查看并下载代码 Github

1 – 项目结构

2 – 项目依赖

除了我们典型的 Spring Boot 依赖项之外,我们还使用嵌入式 HSQLDB 数据库和 nekohtml 用于 LEGACYHTML5 模式。

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.michaelcgood</groupId>
	<artifactId>michaelcgood-validation-thymeleaf</artifactId>
	<version>0.0.1</version>
	<packaging>jar</packaging>

	<name>michaelcgood-validation-thymeleaf</name>
	<description>Michael C  Good - Validation in Thymeleaf Example Application</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.7.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.hsqldb</groupId>
			<artifactId>hsqldb</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<!-- legacy html allow -->
		<dependency>
			<groupId>net.sourceforge.nekohtml</groupId>
			<artifactId>nekohtml</artifactId>
			<version>1.9.21</version>
		</dependency>
	</dependencies>

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


</project>

3 – 型号

在我们的模型中,我们定义:

  • 一个自动生成的 id 字段
  • 不能为空的名称字段
  • 名称必须介于 2 到 40 个字符之间
  • @Email 注释验证的电子邮件字段
  • 一个布尔字段“openhouse”,允许潜在学生表明她是否想参加 openhouse
  • 用于订阅电子邮件更新的布尔字段“订阅”
  • 可选的评论字段,因此没有最低字符数要求,但有最大字符数要求
package com.michaelcgood.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.hibernate.validator.constraints.Email;

@Entity
public class Student {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Long id;
	@NotNull
    @Size(min=2, max=40)
	private String name;
	@NotNull
	@Email
	private String email;
	private Boolean openhouse;
	private Boolean subscribe;
	 @Size(min=0, max=300)
	private String  comments;
	
	public Long getId() {
		return id;
	}
	public void setId(Long id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getEmail() {
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}
	public Boolean getOpenhouse() {
		return openhouse;
	}
	public void setOpenhouse(Boolean openhouse) {
		this.openhouse = openhouse;
	}
	public Boolean getSubscribe() {
		return subscribe;
	}
	public void setSubscribe(Boolean subscribe) {
		this.subscribe = subscribe;
	}
	public String getComments() {
		return comments;
	}
	public void setComments(String comments) {
		this.comments = comments;
	}
	

}

4 – 存储库

我们定义一个存储库。

package com.michaelcgood.dao;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.michaelcgood.model.Student;

@Repository
public interface StudentRepository extends JpaRepository<Student,Long> {

}

5 – 控制器

我们注册 StringTrimmerEditor 以自动将空字符串转换为空值。

当用户发送 POST 请求时,我们希望接收 Student 对象的值,因此我们使用 @ModelAttribute 来做到这一点。

为了确保用户发送的值是有效的,我们接下来使用适当命名的 @Valid 注释。

BindingResult 必须紧随其后,否则用户在提交无效数据时会看到一个错误页面,而不是留在表单页面上。

我们使用 if...else 来控制用户提交表单时发生的情况。如果用户提交无效数据,用户将停留在当前页面,服务器端不会发生任何事情。否则,应用程序将使用用户的数据,用户可以继续。

在这一点上,检查学生的姓名是否为 null 有点多余,但我们这样做了。然后,我们调用下面定义的方法 checkNullString 来查看评论字段是空字符串还是 null。

package com.michaelcgood.controller;

import java.util.Optional;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.propertyeditors.StringTrimmerEditor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import com.michaelcgood.dao.StudentRepository;
import com.michaelcgood.model.Student;

@Controller
public class StudentController {
	@InitBinder
	public void initBinder(WebDataBinder binder) {
	    binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
	}
	public String finalString = null;
	@Autowired
	private StudentRepository studentRepository;
	@PostMapping(value="/")
	public String addAStudent(@ModelAttribute @Valid Student newStudent, BindingResult bindingResult, Model model){
		if (bindingResult.hasErrors()) {
			System.out.println("BINDING RESULT ERROR");
			return "index";
		} else {
			model.addAttribute("student", newStudent);

			if (newStudent.getName() != null) {
				try {
					// check for comments and if not present set to 'none'
					String comments = checkNullString(newStudent.getComments());
					if (comments != "None") {
						System.out.println("nothing changes");
					} else {
						newStudent.setComments(comments);
					}
				} catch (Exception e) {

					System.out.println(e);

				}
				studentRepository.save(newStudent);
				System.out.println("new student added: " + newStudent);
			}

			return "thanks";
		}
	}
	
	@GetMapping(value="thanks")
	public String thankYou(@ModelAttribute Student newStudent, Model model){
		model.addAttribute("student",newStudent);
		
		return "thanks";
	}
	
	@GetMapping(value="/")
	public String viewTheForm(Model model){
		Student newStudent = new Student();
		model.addAttribute("student",newStudent);
		return "index";
	}
	
	public String checkNullString(String str){
		String endString = null;
		if(str == null || str.isEmpty()){
			System.out.println("yes it is empty");
			str = null;
			Optional<String> opt = Optional.ofNullable(str);
			endString = opt.orElse("None");
			System.out.println("endString : " + endString);
		}
		else{
			; //do nothing
		}
		
		
		return endString;
		
	}

}

Optional.ofNullable(str);表示String会变成Optional的数据类型,但String可能是null值。

endString = opt.orElse(“None”); 如果变量 opt 为空,则将字符串值设置为“None”。

6 – Thymeleaf 模板

正如您在上面的控制器映射中看到的,有两个页面。 index.html 是我们的主页,其中包含面向潜在大学生的表单。

我们的主要对象是 Student,所以我们的 th:object 当然指的是它。我们模型的字段分别进入 th:field

为了格式化目的,我们将表单的输入包装在一个表格中。

在每个表格单元格 (td) 下方,我们都有一个条件语句,如下所示:[…] th:if=”${#fields.hasErrors('name')}” th:errors=”*{name}” […]

上面的条件语句意味着如果用户在该字段中输入的数据不符合我们在 Student 模型中对该字段的要求,然后提交表单,则在用户返回到该页面时显示输入要求。

index.html

<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:th="http://www.thymeleaf.org">

<head>
<!-- CSS INCLUDE -->
<link rel="stylesheet"
	href="https://www.javacodegeeks.com/wp-content/litespeed/localres/aHR0cHM6Ly9tYXhjZG4uYm9vdHN0cmFwY2RuLmNvbS8=bootstrap/3.3.7/css/bootstrap.min.css"
	integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
	crossorigin="anonymous"></link>

<!-- EOF CSS INCLUDE -->
</head>
<body>

	<!-- START PAGE CONTAINER -->
	<div class="container-fluid">
		<!-- PAGE TITLE -->
		<div class="page-title">
			<h2>
				<span class="fa fa-arrow-circle-o-left"></span> Request University
				Info
			</h2>
		</div>
		<!-- END PAGE TITLE -->
		<div class="column">
			<form action="#" th:action="@{/}" th:object="${student}"
				method="post">
				<table>
					<tr>
						<td>Name:</td>
						<td><input type="text" th:field="*{name}"></input></td>
						<td th:if="${#fields.hasErrors('name')}" th:errors="*{name}">Name
							Error</td>
					</tr>
					<tr>
						<td>Email:</td>
						<td><input type="text" th:field="*{email}"></input></td>
						<td th:if="${#fields.hasErrors('email')}" th:errors="*{email}">Email
							Error</td>
					</tr>
					<tr>
						<td>Comments:</td>
						<td><input type="text" th:field="*{comments}"></input></td>
					</tr>
					<tr>
						<td>Open House:</td>
						<td><input type="checkbox" th:field="*{openhouse}"></input></td>
				
					</tr>
					<tr>
						<td>Subscribe to updates:</td>
						<td><input type="checkbox" th:field="*{subscribe}"></input></td>
				
					</tr>
					<tr>
						<td>
							<button type="submit" class="btn btn-primary">Submit</button>
						</td>
					</tr>
				</table>
			</form>

		</div>
		<!-- END PAGE CONTENT -->
		<!-- END PAGE CONTAINER -->
	</div>
	<script src="https://www.javacodegeeks.com/wp-content/litespeed/localres/aHR0cHM6Ly9jb2RlLmpxdWVyeS5jb20vjquery-1.11.1.min.js"
		integrity="sha256-VAvG3sHdS5LqTT+5A/aeq/bZGa/Uj04xKxY8KM/w9EE="
		crossorigin="anonymous"></script>


	<script
		src="https://www.javacodegeeks.com/wp-content/litespeed/localres/aHR0cHM6Ly9tYXhjZG4uYm9vdHN0cmFwY2RuLmNvbS8=bootstrap/3.3.7/js/bootstrap.min.js"
		integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
		crossorigin="anonymous"></script>


</body>
</html>

这里我们有用户在成功完成表单后看到的页面。我们使用 th:text 向用户显示他或她在该字段中输入的文本。

thanks.html

<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:th="http://www.thymeleaf.org">

<head>
<!-- CSS INCLUDE -->
<link rel="stylesheet"
	href="https://www.javacodegeeks.com/wp-content/litespeed/localres/aHR0cHM6Ly9tYXhjZG4uYm9vdHN0cmFwY2RuLmNvbS8=bootstrap/3.3.7/css/bootstrap.min.css"
	integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
	crossorigin="anonymous"></link>

<!-- EOF CSS INCLUDE -->


</head>
<body>

	<!-- START PAGE CONTAINER -->
	<div class="container-fluid">

		<!-- PAGE TITLE -->
		<div class="page-title">
			<h2>
				<span class="fa fa-arrow-circle-o-left"></span> Thank you
			</h2>
		</div>
		<!-- END PAGE TITLE -->
		<div class="column">
			<table class="table datatable">
				<thead>
					<tr>
						<th>Name</th>
						<th>Email</th>
						<th>Open House</th>
						<th>Subscribe</th>
						<th>Comments</th>
					</tr>
				</thead>
				<tbody>
					<tr th:each="student : ${student}">
						<td th:text="${student.name}">Text ...</td>
						<td th:text="${student.email}">Text ...</td>
						<td th:text="${student.openhouse}">Text ...</td>
						<td th:text="${student.subscribe}">Text ...</td>
						<td th:text="${student.comments}">Text ...</td>
					</tr>
				</tbody>
			</table>
		</div>	
		</div>
		<!-- END PAGE CONTAINER -->
	</div>
		<script
  src="https://www.javacodegeeks.com/wp-content/litespeed/localres/aHR0cHM6Ly9jb2RlLmpxdWVyeS5jb20vjquery-1.11.1.min.js"
  integrity="sha256-VAvG3sHdS5LqTT+5A/aeq/bZGa/Uj04xKxY8KM/w9EE="
  crossorigin="anonymous"></script>
 

	<script
		src="https://www.javacodegeeks.com/wp-content/litespeed/localres/aHR0cHM6Ly9tYXhjZG4uYm9vdHN0cmFwY2RuLmNvbS8=bootstrap/3.3.7/js/bootstrap.min.js"
		integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
		crossorigin="anonymous"></script>

</body>
</html>

7 – 配置

使用 Spring Boot Starter 并包括 Thymeleaf 依赖项,您将自动拥有 /templates/ 的模板位置,并且 Thymeleaf 开箱即用。所以这些设置中的大部分都不需要。

需要注意的一个设置是 nekohtml 提供的 LEGACYHTM5。如果我们愿意,这允许我们使用更随意的 HTML5 标签。否则,Thymeleaf 会非常严格,可能不会解析您的 HTML。例如,如果您不关闭 input 标签,Thymeleaf 将不会解析您的 HTML。

应用程序属性

#==================================
# = Thymeleaf configurations 
#==================================
spring.thymeleaf.check-template-location=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.content-type=text/html
spring.thymeleaf.cache=false
spring.thymeleaf.mode=LEGACYHTML5

server.contextPath=/

8 – 演示

主页

我们到达主页。

无效数据

我在名称字段和电子邮件字段中输入了无效数据。

有效数据无注释

现在我在所有字段中输入有效数据,但不提供评论。不需要提供评论。在我们的控制器中,我们将所有空字符串设为空值。如果用户没有提供评论,则 String 值设为“None”。

9 – 总结

包起来

此演示应用程序演示了如何在 Thymeleaf 表单中验证用户输入。在我看来,Spring 和 Thymeleaf 与 javax.validation.constraints 配合使用可以很好地验证用户输入。 源码在Github

笔记

出于演示目的,Java 8 的 Optional 被强加到此应用程序中,我想指出的是,它在使用 @RequestParam 时更有机地工作,如我的 PagingAndSortingRepository 教程 所示。

但是,如果您没有使用 Thymeleaf,您可能会将我们不需要的字段设置为可选。在这里,Vlad Mihalcea 讨论了将可选实体属性映射到 JPA 和 Hibernate 的最佳方式

标签2: Java教程
地址:https://www.cundage.com/article/jcg-validation-thymeleaf-spring.html

相关阅读

Java HashSet 教程展示了如何使用 Java HashSet 集合。 Java哈希集 HashSet 是一个不包含重复元素的集合。此类为基本操作(添加、删除、包含和大小)提供恒定时间性...
SpringApplicationBuilder 教程展示了如何使用 SpringApplicationBuilder 创建一个简单的 Spring Boot 应用程序。 春天 是用于创建企业应...
通道是继 buffers 之后 java.nio 的第二个主要新增内容,我们在之前的教程中已经详细了解了这一点。通道提供与 I/O 服务的直接连接。 通道是一种在字节缓冲区和通道另一端的实体(通...
课程大纲 Elasticsearch 是一个基于 Lucene 的搜索引擎。它提供了一个分布式的、支持多租户的全文搜索引擎,带有 HTTP Web 界面和无模式的 JSON 文档。 Elasti...
解析器是强大的工具,使用 ANTLR 可以编写可用于多种不同语言的各种解析器。 在这个完整的教程中,我们将: 解释基础:什么是解析器,它可以用来做什么 查看如何设置 ANTLR 以便在 Java...
Java 是用于开发各种桌面应用程序、Web 应用程序和移动应用程序的最流行的编程语言之一。以下文章将帮助您快速熟悉 Java 语言,并迈向 API 和云开发等更复杂的概念。 1. Java语言...
Java中的继承是指子类继承或获取父类的所有非私有属性和行为的能力。继承是面向对象编程的四大支柱之一,用于提高层次结构中类之间的代码可重用性。 在本教程中,我们将了解 Java 支持的继承类型,...
Java Message Service 是一种支持正式通信的 API,称为 网络上计算机之间的消息传递。 JMS 为支持 Java 程序的标准消息协议和消息服务提供了一个通用接口。 JMS 提...
之前,我介绍了spring 3 + hibernate 集成 示例和struts 2 hello world 示例。在本教程中,我将讨论在将 spring 框架与 struts 与 hibern...
Java 项目中的一项常见任务是将日期格式化或解析为字符串,反之亦然。解析日期意味着你有一个代表日期的字符串,例如“2017-08-3”,你想把它转换成一个代表 Java 中日期的对象,例如Ja...