我正在使用 Spring Boot 创建一个应用程序,其中有
Employee
实体,其中 email
字段对于每个创建的实体来说必须是唯一的。
给定实体:
package com.example.restcrudapi.models;
import com.example.restcrudapi.validators.ShouldStartWith;
import com.example.restcrudapi.validators.UniqueEmail;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@NoArgsConstructor
@Table(name="employee")
@Getter
@Setter
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="id")
private int id;
@Column(name="first_name")
@NotBlank(message = "First name is required")
@Pattern(regexp = "^(?!\\s).*(?<!\\s)$",
message = "First name must not start or end with whitespace")
@ShouldStartWith("Max")
private String firstName;
@Column(name="last_name")
@Pattern(regexp = "^(?!\\s).*(?<!\\s)$",
message = "First name must not start or end with whitespace")
private String lastName;
@Column(name="email")
@Pattern(regexp = "^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$",
message="value must meet email format criteria")
@NotBlank(message="email is required")
@UniqueEmail
private String email;
@Min(0)
@Max(100)
@Column(name="number_of_points")
private int points;
public Employee(String firstName, String lastName, String email, int points) {
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.points = points;
}
@Override
public String toString() {
return "Employee {" +
"id = " + id +
", first name = " + firstName + '\'' +
", last name = " + lastName + '\'' +
", email = " + email + '\'' +
'}';
}
}
UniqueEmail
注释代码:
package com.example.restcrudapi.validators;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Constraint(validatedBy = UniqueEmailValidator.class)
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface UniqueEmail {
public String message() default "employee with given email is already registered";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
UniqueEmailValidator
package com.example.restcrudapi.validators;
import com.example.restcrudapi.repositories.employee.EmployeeRepository;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {
@Autowired
private EmployeeRepository employeeRepository;
@Override
public boolean isValid(String email, ConstraintValidatorContext context) {
if (email == null || email.isBlank()) {
return true; // Let other annotations like @NotBlank handle null/empty cases
}
var isUniqueEmail = employeeRepository.existsByEmail(email);
return isUniqueEmail;
}
}
和
EmployeeRepository
package com.example.restcrudapi.repositories.employee;
import com.example.restcrudapi.models.Employee;
import jakarta.transaction.Transactional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import java.util.Optional;
@RepositoryRestResource
@Transactional
public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
boolean existsByEmail(String email);
}
通过
spring-boot-starter-data-rest
设置 REST 端点。
使用示例请求正文调用 POST /myapi/employees 时:
{
"firstName": "This One",
"lastName": "Should not pass",
"email": "[email protected]",
"points": 21
}
我可以看到,尽管使用了
employeeRepository
和 UniqueEmailValidator
注释,但 @Component
中的 @Autowired
并未注入,并且保持为空,导致 HTTP 响应中出现 NullPointerException
和 HV000028: Unexpected exception during isValid call
。
我尝试过使用
@Service
代替 @Component
,也对 @Bean 进行了一些修改,但无济于事 - 结果总是相同的。
如何让Spring Boot正确注入
employeeRepository
?
如果 Spring 创建了
ConstraintValidator
实例,则会将所需的依赖项注入到其中。问题是 Spring 不仅在控制器接收到对象时验证对象时创建 ConstraintValidator
实例。 Hibenate 在将对象写入数据库之前验证对象时也会创建 ConstraintValidator
实例。在这种情况下,不会发生依赖注入。但是,您可以将注入的依赖项存储在静态字段中,如下所示。
@Component
public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {
private static final AtomicReference<EmployeeRepository> EMPLOYEE_REPOSITORY_HOLDER = new AtomicReference<>();
@Autowired
public void setEmployeeRepository(EmployeeRepository employeeRepository) {
EMPLOYEE_REPOSITORY_HOLDER.set(userRepository);
}
// ...
}