Thymeleaf 表单中的空字段使用 Spring Boot,其他字段正常
Null fields in Thymeleaf form using Spring Boot, other fields fine
我对 Spring Boot 比较陌生。目前,我正在制作一个带有用户注册系统的 Spring 启动应用程序,但我 运行 遇到了问题。尽管请求 post 正确编辑,但表单中的某些字段在后端注册为“空”。
我有一个 HTML/ Thymeleaf 表单,它提交 8 个字段来创建一个 'User' 对象。这是表格:
<form th:action="@{/users/register_attempt}" th:object="${user}"
method="post" style="max-width: 600px; margin: 0 auto;">
<div class="m-3">
<div class="form-group row">
<label class="col-4 col-form-label">DOB: </label>
<div class="col-8">
<input type="text" th:field="*{dob}" class="form-control" th:required="required" />
</div>
</div>
<div class="form-group row">
<label class="col-4 col-form-label">First Name: </label>
<div class="col-8">
<input type="text" th:field="*{name}" class="form-control" th:required="required" />
</div>
</div>
<div class="form-group row">
<label class="col-4 col-form-label">Surname: </label>
<div class="col-8">
<input type="text" th:field="*{surname}" class="form-control" th:required="required" />
</div>
</div>
<div class="form-group row">
<label class="col-4 col-form-label">PPSN: </label>
<div class="col-8">
<input type="text" th:field="*{ppsn}" class="form-control" th:required="required" />
</div>
</div>
<div class="form-group row">
<label class="col-4 col-form-label">Address: </label>
<div class="col-8">
<input type="text" th:field="*{address}" class="form-control" th:required="required" />
</div>
</div>
<div class="form-group row">
<label class="col-4 col-form-label">Phone Number: </label>
<div class="col-8">
<input type="number" th:field="*{phone}" class="form-control" />
</div>
</div>
<div class="form-group row">
<label class="col-4 col-form-label">E-mail: </label>
<div class="col-8">
<input type="email" th:field="*{email}" class="form-control" th:required="required" />
</div>
</div>
<div class="form-group row">
<label class="col-4 col-form-label">Password: </label>
<div class="col-8">
<input type="password" th:field="*{password}" class="form-control"
required minlength="6" maxlength="10" th:required="required"/>
</div>
</div>
<div>
<button type="submit" class="btn btn-primary">Sign Up</button>
</div>
</div>
</form>
下面是表单代码要模仿的模型:
public class User {
@Id
@GeneratedValue
private Long id;
@NotBlank
private String dob;
@NotBlank
private String name;
@NotBlank
private String surname;
@NotBlank
private String ppsn;
@NotBlank
private String address;
@NotBlank
private String phone;
@NotBlank
@Column(unique = true)
private String email;
private String nextApptId;
private String dose1Date;
private String dose2Date;
private String lastLogin;
@NotBlank
private String password;
public User() {
super();
}
public User(String dob, String name, String surname, String ppsn, String address, String phone, String email, String password) {
super();
this.dob = dob;
this.name = name;
this.surname = surname;
this.ppsn = ppsn;
this.address = address;
this.phone = phone;
this.email = email;
this.password = password;
}
public Long getId() {
return id;
}
public String getDob() {
return dob;
}
public String getName() {
return name;
}
public String getSurname() {
return surname;
}
public String getPpsn() {
return ppsn;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getNextApptId() {
return nextApptId;
}
public void setNextApptId(String apptId) {
this.nextApptId = apptId;
}
public String getDose1Date() {
return dose1Date;
}
public void setDose1Date(String dose1Date) {
this.dose1Date = dose1Date;
}
public String getDose2Date() {
return dose2Date;
}
public void setDose2Date(String dose2Date) {
this.dose2Date = dose2Date;
}
public String getLastLogin() {
return lastLogin;
}
public void setLastLogin(String lastLogin) {
this.lastLogin = lastLogin;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
但出于某种原因我无法弄清楚,前四个字段 - 即 Dob(出生日期)、姓名、姓氏和 PPSN 在服务器端实例化用户对象时产生空错误, 例如:
`Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 4 errors<EOL>
Field error in object 'user' on field 'dob': rejected value [null]; codes [NotBlank.user.dob,NotBlank.dob,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.dob,dob]; arguments []; default message [dob]]; default message [must not be blank] `.
其他四个字段,地址、Phone、电子邮件和密码似乎工作正常。
据我所知,这不是错字问题(如果最终是这样,请原谅我)。我已经使用 Burp 拦截了 Post 请求,以检查字段的内容是否从表单中提取出来并进入请求,我的用户 class 中的字段名称是否正确,事实上他们都按预期在那里。
我想这意味着问题出在后端控制器代码如何根据模型解释此 post 请求,但我不知道如何或从哪里开始。这是当前控制器:
@GetMapping("/register")
public String startRegistration(Model model) {
model.addAttribute("user", new User());
return "register";
}
@PostMapping("/register_attempt")
public String registerAttempt(@Valid User newUser) {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String encodedPassword = passwordEncoder.encode(newUser.getPassword());
newUser.setPassword(encodedPassword);
userRepository.save(newUser);
return "registered_successfully";
}
编辑:进一步调试和澄清问题仍然存在
从 Post 映射中删除 @Valid
注释并使用老式打印语句显示相同的结果 - 前四个字段为空,原因不明。
@PostMapping("/register_attempt")
public String registerAttempt(@ModelAttribute("user") User newUser) {
System.out.println("Saving user ");
System.out.println("Name " + newUser.getName());
System.out.println("Last Name " + newUser.getSurname());
System.out.println("PPSN " +newUser.getPpsn());
System.out.println("DOB " +newUser.getDob());
System.out.println("email " +newUser.getEmail());
System.out.println("address " +newUser.getAddress());
System.out.println("encrypted password " +newUser.getPassword());
System.out.println("Phone num " +newUser.getPhone());
userRepository.save(newUser);
System.out.println("User saved");
return "registered_successfully";
}
使用以下虚拟数据:
发送这个Post请求到后端:
这些打印语句的结果:
Saving user
name null
last Name null
ppsn null
dob null
email foo@bar.com
address Here and there
password password
Phone num 987654321
这些错误消息:
javax.validation.ConstraintViolationException: Validation failed for classes [app.model.User] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='must not be blank', propertyPath=surname, rootBeanClass=class app.model.User, messageTemplate='{javax.validation.constraints.NotBlank.message}'}
ConstraintViolationImpl{interpolatedMessage='must not be blank', propertyPath=dob, rootBeanClass=class app.model.User, messageTemplate='{javax.validation.constraints.NotBlank.message}'}
ConstraintViolationImpl{interpolatedMessage='must not be blank', propertyPath=name, rootBeanClass=class app.model.User, messageTemplate='{javax.validation.constraints.NotBlank.message}'}
ConstraintViolationImpl{interpolatedMessage='must not be blank', propertyPath=ppsn, rootBeanClass=class app.model.User, messageTemplate='{javax.validation.constraints.NotBlank.message}'}
]
如果您对发生这种情况的原因有任何见解,我将不胜感激。
你的 Thymeleaf 模板中有 th:object="${user}"
,所以我必须假设你的控制器中的 @GetMapping
方法已将 User
的实例添加到 Model
使用 addAttribute
.
在你的 @PostMapping
中,你还应该使用 @ModelAttribute
:
@PostMapping("/register_attempt")
public String registerAttempt(@Valid @ModelAttribute("user") User newUser) {
...
您还需要为每个字段设置设置器。我看到 User
没有 setName(String name)
,没有 setDob(String dob)
,...
一些其他提示:
- 不要在控制器方法本身中创建
BCryptPasswordEncoder
个实例。当您使用 Spring 引导时,这应该是一个应用程序范围的单例(在 Spring 术语中称为 bean)。将 class 添加到您的应用程序中,例如ReservationSystemApplicationConfiguration
声明如下:
@Configuration
public class ReservationSystemApplicationConfiguration {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
然后在你的控制器中,注入密码编码器:
@Controller
@RequestMapping("...")
public class MyController {
private final PasswordEncoder passwordEncoder;
public MyController(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
@PostMapping("/register_attempt")
public String registerAttempt(@Valid @ModelAttribute("user") User newUser) {
String encodedPassword = passwordEncoder.encode(newUser.getPassword());
newUser.setPassword(encodedPassword);
userRepository.save(newUser);
return "registered_successfully";
}
}
Controller不应该直接调用Repository,而是在两者之间使用Service。
最好使用不同的对象来映射表单数据和将数据存储到数据库中。有关如何执行此操作的更多信息,请参阅 Form Handling with Thymeleaf。
我对 Spring Boot 比较陌生。目前,我正在制作一个带有用户注册系统的 Spring 启动应用程序,但我 运行 遇到了问题。尽管请求 post 正确编辑,但表单中的某些字段在后端注册为“空”。
我有一个 HTML/ Thymeleaf 表单,它提交 8 个字段来创建一个 'User' 对象。这是表格:
<form th:action="@{/users/register_attempt}" th:object="${user}"
method="post" style="max-width: 600px; margin: 0 auto;">
<div class="m-3">
<div class="form-group row">
<label class="col-4 col-form-label">DOB: </label>
<div class="col-8">
<input type="text" th:field="*{dob}" class="form-control" th:required="required" />
</div>
</div>
<div class="form-group row">
<label class="col-4 col-form-label">First Name: </label>
<div class="col-8">
<input type="text" th:field="*{name}" class="form-control" th:required="required" />
</div>
</div>
<div class="form-group row">
<label class="col-4 col-form-label">Surname: </label>
<div class="col-8">
<input type="text" th:field="*{surname}" class="form-control" th:required="required" />
</div>
</div>
<div class="form-group row">
<label class="col-4 col-form-label">PPSN: </label>
<div class="col-8">
<input type="text" th:field="*{ppsn}" class="form-control" th:required="required" />
</div>
</div>
<div class="form-group row">
<label class="col-4 col-form-label">Address: </label>
<div class="col-8">
<input type="text" th:field="*{address}" class="form-control" th:required="required" />
</div>
</div>
<div class="form-group row">
<label class="col-4 col-form-label">Phone Number: </label>
<div class="col-8">
<input type="number" th:field="*{phone}" class="form-control" />
</div>
</div>
<div class="form-group row">
<label class="col-4 col-form-label">E-mail: </label>
<div class="col-8">
<input type="email" th:field="*{email}" class="form-control" th:required="required" />
</div>
</div>
<div class="form-group row">
<label class="col-4 col-form-label">Password: </label>
<div class="col-8">
<input type="password" th:field="*{password}" class="form-control"
required minlength="6" maxlength="10" th:required="required"/>
</div>
</div>
<div>
<button type="submit" class="btn btn-primary">Sign Up</button>
</div>
</div>
</form>
下面是表单代码要模仿的模型:
public class User {
@Id
@GeneratedValue
private Long id;
@NotBlank
private String dob;
@NotBlank
private String name;
@NotBlank
private String surname;
@NotBlank
private String ppsn;
@NotBlank
private String address;
@NotBlank
private String phone;
@NotBlank
@Column(unique = true)
private String email;
private String nextApptId;
private String dose1Date;
private String dose2Date;
private String lastLogin;
@NotBlank
private String password;
public User() {
super();
}
public User(String dob, String name, String surname, String ppsn, String address, String phone, String email, String password) {
super();
this.dob = dob;
this.name = name;
this.surname = surname;
this.ppsn = ppsn;
this.address = address;
this.phone = phone;
this.email = email;
this.password = password;
}
public Long getId() {
return id;
}
public String getDob() {
return dob;
}
public String getName() {
return name;
}
public String getSurname() {
return surname;
}
public String getPpsn() {
return ppsn;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getNextApptId() {
return nextApptId;
}
public void setNextApptId(String apptId) {
this.nextApptId = apptId;
}
public String getDose1Date() {
return dose1Date;
}
public void setDose1Date(String dose1Date) {
this.dose1Date = dose1Date;
}
public String getDose2Date() {
return dose2Date;
}
public void setDose2Date(String dose2Date) {
this.dose2Date = dose2Date;
}
public String getLastLogin() {
return lastLogin;
}
public void setLastLogin(String lastLogin) {
this.lastLogin = lastLogin;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
但出于某种原因我无法弄清楚,前四个字段 - 即 Dob(出生日期)、姓名、姓氏和 PPSN 在服务器端实例化用户对象时产生空错误, 例如:
`Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 4 errors<EOL>
Field error in object 'user' on field 'dob': rejected value [null]; codes [NotBlank.user.dob,NotBlank.dob,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.dob,dob]; arguments []; default message [dob]]; default message [must not be blank] `.
其他四个字段,地址、Phone、电子邮件和密码似乎工作正常。
据我所知,这不是错字问题(如果最终是这样,请原谅我)。我已经使用 Burp 拦截了 Post 请求,以检查字段的内容是否从表单中提取出来并进入请求,我的用户 class 中的字段名称是否正确,事实上他们都按预期在那里。
我想这意味着问题出在后端控制器代码如何根据模型解释此 post 请求,但我不知道如何或从哪里开始。这是当前控制器:
@GetMapping("/register")
public String startRegistration(Model model) {
model.addAttribute("user", new User());
return "register";
}
@PostMapping("/register_attempt")
public String registerAttempt(@Valid User newUser) {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String encodedPassword = passwordEncoder.encode(newUser.getPassword());
newUser.setPassword(encodedPassword);
userRepository.save(newUser);
return "registered_successfully";
}
编辑:进一步调试和澄清问题仍然存在
从 Post 映射中删除 @Valid
注释并使用老式打印语句显示相同的结果 - 前四个字段为空,原因不明。
@PostMapping("/register_attempt")
public String registerAttempt(@ModelAttribute("user") User newUser) {
System.out.println("Saving user ");
System.out.println("Name " + newUser.getName());
System.out.println("Last Name " + newUser.getSurname());
System.out.println("PPSN " +newUser.getPpsn());
System.out.println("DOB " +newUser.getDob());
System.out.println("email " +newUser.getEmail());
System.out.println("address " +newUser.getAddress());
System.out.println("encrypted password " +newUser.getPassword());
System.out.println("Phone num " +newUser.getPhone());
userRepository.save(newUser);
System.out.println("User saved");
return "registered_successfully";
}
使用以下虚拟数据:
发送这个Post请求到后端:
这些打印语句的结果:
Saving user
name null
last Name null
ppsn null
dob null
email foo@bar.com
address Here and there
password password
Phone num 987654321
这些错误消息:
javax.validation.ConstraintViolationException: Validation failed for classes [app.model.User] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='must not be blank', propertyPath=surname, rootBeanClass=class app.model.User, messageTemplate='{javax.validation.constraints.NotBlank.message}'}
ConstraintViolationImpl{interpolatedMessage='must not be blank', propertyPath=dob, rootBeanClass=class app.model.User, messageTemplate='{javax.validation.constraints.NotBlank.message}'}
ConstraintViolationImpl{interpolatedMessage='must not be blank', propertyPath=name, rootBeanClass=class app.model.User, messageTemplate='{javax.validation.constraints.NotBlank.message}'}
ConstraintViolationImpl{interpolatedMessage='must not be blank', propertyPath=ppsn, rootBeanClass=class app.model.User, messageTemplate='{javax.validation.constraints.NotBlank.message}'}
]
如果您对发生这种情况的原因有任何见解,我将不胜感激。
你的 Thymeleaf 模板中有 th:object="${user}"
,所以我必须假设你的控制器中的 @GetMapping
方法已将 User
的实例添加到 Model
使用 addAttribute
.
在你的 @PostMapping
中,你还应该使用 @ModelAttribute
:
@PostMapping("/register_attempt")
public String registerAttempt(@Valid @ModelAttribute("user") User newUser) {
...
您还需要为每个字段设置设置器。我看到 User
没有 setName(String name)
,没有 setDob(String dob)
,...
一些其他提示:
- 不要在控制器方法本身中创建
BCryptPasswordEncoder
个实例。当您使用 Spring 引导时,这应该是一个应用程序范围的单例(在 Spring 术语中称为 bean)。将 class 添加到您的应用程序中,例如ReservationSystemApplicationConfiguration
声明如下:
@Configuration
public class ReservationSystemApplicationConfiguration {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
然后在你的控制器中,注入密码编码器:
@Controller
@RequestMapping("...")
public class MyController {
private final PasswordEncoder passwordEncoder;
public MyController(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
@PostMapping("/register_attempt")
public String registerAttempt(@Valid @ModelAttribute("user") User newUser) {
String encodedPassword = passwordEncoder.encode(newUser.getPassword());
newUser.setPassword(encodedPassword);
userRepository.save(newUser);
return "registered_successfully";
}
}
Controller不应该直接调用Repository,而是在两者之间使用Service。
最好使用不同的对象来映射表单数据和将数据存储到数据库中。有关如何执行此操作的更多信息,请参阅 Form Handling with Thymeleaf。