如何在使用反应式数据源的 WebFlux 上编写自定义验证器

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

在 Spring MVC 中,我有一个

@UniqueEmail
自定义休眠验证器(用于在注册时检查电子邮件的唯一性),如下所示:

public class UniqueEmailValidator
implements ConstraintValidator<UniqueEmail, String> {

    private UserRepository userRepository;

    public UniqueEmailValidator(UserRepository userRepository) {

        this.userRepository = userRepository;
    }

    @Override
    public boolean isValid(String email, ConstraintValidatorContext context) {

        return !userRepository.findByEmail(email).isPresent();
    }
}

现在我正在使用反应式 MongoDB 迁移到 WebFlux,我的代码如下:

public class UniqueEmailValidator
implements ConstraintValidator<UniqueEmail, String> {

    private MongoUserRepository userRepository;

    public UniqueEmailValidator(MongoUserRepository userRepository) {

        this.userRepository = userRepository;
    }

    @Override
    public boolean isValid(String email, ConstraintValidatorContext context) {

        return userRepository.findByEmail(email).block() == null;
    }
}

首先,像上面那样使用

block
看起来不太好。其次,它不起作用,这是错误:

Caused by: java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-3

这该怎么办?我当然可以使用 MongoTemplate 阻塞方法,但是有没有办法反应性地处理这个问题?我可以在服务方法中手动执行此操作,但我希望将此错误与其他错误一起显示给用户(例如“短”密码)。

spring spring-data-mongodb spring-webflux project-reactor
3个回答
3
投票

从 Reactor 3.2.0 开始,禁止在并行或单个

Scheduler
中使用阻塞 API,并抛出您所看到的异常。所以,当你说它看起来不太好时,你说得对——它不仅对你的应用程序真的很糟糕(它可能会阻止新请求的处理并使整个事情崩溃),而且它太糟糕了,以至于 Reactor 团队决定将其视为错误。

现在的问题是您想在

isValid

 调用中执行一些 I/O 相关工作。该方法的完整签名是:

boolean isValid(T value, ConstraintValidatorContext context)

签名表明它是阻塞的(它不返回反应类型,也不提供结果作为回调)。所以你不能在那里做 I/O 相关的或涉及延迟的工作。在这里,您想要检查数据库中的条目,该条目恰好属于该类别。

我认为您不能将其作为此验证合同的一部分,并且我不知道有任何替代方案。


0
投票
我遇到了同样的问题,最后我决定使用 ConstraintValidator 检查简单验证,并检查应用程序逻辑中的反应性验证。我不知道是否还有其他更好的解决方案,但这可能是一个很好的方法。


0
投票
我现在同意 Spring Framework 应该有一个反应式验证器,但在那之前我在控制器或服务层中执行此操作,这是一个简单的文件管理器,我需要在其中检查父文件夹的权限,并且如果父级存在:

private Mono<Folder> validate(Folder folder, UserReference user) { // init var errors = new SimpleErrors(folder); // validate annotations this.validator.validate(folder, errors); if (errors.hasErrors()) { if (errors.getFieldError() != null) { return Mono.error(new BadRequestException( errors.getFieldError().getDefaultMessage())); } if (errors.getGlobalError() != null) { return Mono.error(new BadRequestException( errors.getGlobalError().getDefaultMessage())); } return Mono.error(new BadRequestException("Invalid folder")); } // validate parent if (folder.getParentId() != null) { return repository.findById(folder.getParentId()) .switchIfEmpty( Mono.error(new BadRequestException("Parent folder not found."))) .flatMap(parent -> { var access = AccessControlList.AccessType.WRITE; if (!parent.getAcl().hasAccess(user.getId(), access)) { return Mono.error(new BadRequestException( "User does not have write access to parent folder.")); } return Mono.just(folder); }); } // done return Mono.just(folder); }
    
© www.soinside.com 2019 - 2024. All rights reserved.