@Autowired 属性未由 Spring 注入用于唯一值验证器

问题描述 投票:0回答:1

我正在使用 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
null employeeRepository

我尝试过使用

@Service
代替
@Component
,也对 @Bean 进行了一些修改,但无济于事 - 结果总是相同的。

如何让Spring Boot正确注入

employeeRepository

java spring spring-boot hibernate spring-data-jpa
1个回答
0
投票

如果 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);
    }

    // ...
}
© www.soinside.com 2019 - 2024. All rights reserved.