Spring JPA自定义邮箱校验器查询不存在邮箱时抛空指针异常
我最近做了一个自定义校验器用来检查邮箱的唯一性,奇怪的是当输入数据库里已存在的邮箱时,校验能正常工作;但输入不存在的邮箱时,findByEmail()方法居然抛出了java.lang.NullPointerException。以下是我的相关代码和问题情况:
相关代码
User仓库代码
@Repository("userRepository") public interface UserRepository extends JpaRepository<User, Long>{ User findByUsername(String username); User findByEmail(String email); }
自定义邮箱校验器代码
public class EmailValidator implements ConstraintValidator<EmailConstraint, String> { @Autowired private UserRepository userRepository; @Override public void initialize(EmailConstraint emailConstraint) {} @Override public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) { User user = userRepository.findByEmail(s); if(user != null) return false; return true; } }
User模型代码
import com.sun.istack.internal.NotNull; import org.hibernate.validator.constraints.Email; import org.hibernate.validator.constraints.NotEmpty; import uz.upay.validator.EmailConstraint; import javax.persistence.*; import java.util.Date; @Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; @Column(name = "username") private String username; @Email(message = "Please, provide a valid Email") @Column(name = "email") @EmailConstraint private String email; public User() {} // getter和setter方法 public long getId() { return id; } public void setId(long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } }
我尝试用try-catch处理,但逻辑明显有问题,不管邮箱存不存在都会返回true:
@Override public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) { User user = null; try { user = userRepository.findByEmail(s); return false; } catch (Exception e){} finally { return true; } }
首先得搞清楚:你遇到的NullPointerException根本不是findByEmail()返回null导致的,而是**userRepository本身是null**!
核心问题:ConstraintValidator的依赖注入问题
默认情况下,ConstraintValidator的实例是由Hibernate Validator而不是Spring容器创建的,所以你用@Autowired注入的userRepository根本不会被Spring初始化,始终是null。当你调用userRepository.findByEmail()时,自然就抛出了NPE。
修复步骤
1. 让EmailValidator成为Spring管理的Bean
给EmailValidator加上@Component注解,同时用构造函数注入替代@Autowired(这是Spring推荐的依赖注入方式,避免空指针风险):
@Component public class EmailValidator implements ConstraintValidator<EmailConstraint, String> { private final UserRepository userRepository; // 构造函数注入,Spring会自动帮你注入UserRepository实例 public EmailValidator(UserRepository userRepository) { this.userRepository = userRepository; } @Override public void initialize(EmailConstraint emailConstraint) {} @Override public boolean isValid(String email, ConstraintValidatorContext context) { // 先处理空值情况(如果你的模型已经用@NotEmpty校验过,这步可以简化) if (email == null || email.isBlank()) { return true; } // 逻辑很清晰:找不到用户(返回null)表示邮箱可用,校验通过;找到则校验失败 return userRepository.findByEmail(email) == null; } }
2. 修正错误的try-catch逻辑
你之前写的try-catch完全逻辑混乱:try块里不管查询结果如何都返回false,然后finally块又强制返回true——finally的返回值会覆盖try里的返回值,导致你的校验器永远返回"校验通过",完全起不到作用。现在我们已经修复了依赖注入问题,不需要用这种错误的try-catch来掩盖问题。
3. 确保Spring使用自定义的Validator(可选,Spring Boot项目通常自动处理)
如果是普通Spring项目,可能需要配置LocalValidatorFactoryBean来让Spring识别我们的自定义校验器:
@Configuration public class ValidatorConfig { @Bean public LocalValidatorFactoryBean validator() { LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean(); // 如果你需要自定义校验提示信息,可以配置messageSource factoryBean.setValidationMessageSource(messageSource()); return factoryBean; } @Bean public MessageSource messageSource() { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); messageSource.setBasename("messages"); messageSource.setDefaultEncoding("UTF-8"); return messageSource; } }
这样处理后,你的校验器就能正常工作了:输入已存在的邮箱返回false(校验失败),输入不存在的邮箱返回true(校验通过),再也不会抛出NPE了。
内容的提问来源于stack exchange,提问作者Sarvar Nishonboyev

