
在project reactor(spring webflux底层使用的响应式库)以及更广泛的reactive streams规范中,null值是被明确禁止作为流中元素的。这意味着,一个mono或flux不应该发出null值。当一个操作符(如map)的映射函数返回null时,reactor会将其视为一个无效的信号,并可能导致意外行为或运行时错误。因此,在响应式编程中,我们不能简单地让一个map操作返回null,然后期望后续的操作符能像处理空流一样处理它。
考虑以下Spring WebFlux中的响应式代码片段,其目标是从安全上下文中获取用户主体名称(userPrincipalName):
ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.map(authentication -> (UserAuthenticationToken) authentication)
.map(UserAuthenticationToken::getUserPrincipalName)
// 在这里,如果getUserPrincipalName返回null,我们希望抛出自定义异常如果UserAuthenticationToken::getUserPrincipalName方法可能返回null,并且我们希望在这种情况下抛出一个自定义的MissingPrincipalException,那么直接尝试使用switchIfEmpty或filter是无效的:
// 尝试1:使用switchIfEmpty - 无效
.map(UserAuthenticationToken::getUserPrincipalName)
.switchIfEmpty(Mono.error(new MissingPrincipalException("Missing email field in the JWT token")));
// 尝试2:使用filter结合switchIfEmpty - 无效
.map(UserAuthenticationToken::getUserPrincipalName)
.filter(Objects::nonNull) // 过滤掉null
.switchIfEmpty(Mono.error(new MissingPrincipalException("Missing email field in the JWT token")));这些尝试之所以无效,是因为switchIfEmpty操作符是用来处理上游Publisher发出完成信号且未发出任何元素(即流为空)的情况。当map操作返回null时,它仍然会尝试将null作为元素发出,这违反了Reactive Streams规范。filter(Objects::nonNull)虽然可以过滤掉null,但如果map本身已经发出了null,在filter之前就已经违反了规范。更关键的是,即使filter成功过滤了null,switchIfEmpty也只会在整个流变为空时才触发,而不是在单个元素为null时触发。
flatMap操作符允许我们将流中的每个元素映射到一个新的Publisher(Mono或Flux)。这使得我们可以在映射过程中执行条件逻辑,并根据条件返回不同的Publisher,包括一个错误Publisher。
示例代码:
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.security.core.context.SecurityContext;
import reactor.core.publisher.Mono;
// 假设UserAuthenticationToken和MissingPrincipalException已定义
// class UserAuthenticationToken implements Authentication { ... public String getUserPrincipalName() { ... } }
// class MissingPrincipalException extends RuntimeException { ... }
public class PrincipalService {
public Mono<String> getUserPrincipalNameSafely() {
return ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.map(authentication -> (UserAuthenticationToken) authentication)
.flatMap(token -> {
String principalName = token.getUserPrincipalName();
if (principalName == null) {
// 如果principalName为null,则返回一个包含错误的Mono
return Mono.error(new MissingPrincipalException("Missing email field in the JWT token"));
} else {
// 否则,返回一个包含非null principalName的Mono
return Mono.just(principalName);
}
});
}
}代码解析:
flatMap的这种用法完美地解决了在特定条件(值null)下抛出异常的需求,因为它允许我们完全控制每个元素如何被转换为后续的响应式流。
handle操作符是一个功能非常强大的通用操作符,它结合了map和filter的功能,并且允许更复杂的条件逻辑,包括发出错误信号。它接收一个BiConsumer,其中包含当前元素和SynchronousSink,通过SynchronousSink可以发出零个、一个或一个错误信号。
示例代码:
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.security.core.context.SecurityContext;
import reactor.core.publisher.Mono;
import reactor.core.publisher.SynchronousSink;
// 假设UserAuthenticationToken和MissingPrincipalException已定义
public class PrincipalService {
public Mono<String> getUserPrincipalNameSafelyWithHandle() {
return ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.map(authentication -> (UserAuthenticationToken) authentication)
.handle((token, sink) -> {
String principalName = token.getUserPrincipalName();
if (principalName == null) {
// 如果principalName为null,通过sink发出错误信号
sink.error(new MissingPrincipalException("Missing email field in the JWT token"));
} else {
// 否则,通过sink发出非null principalName
sink.next(principalName);
}
});
}
}代码解析:
handle操作符的优势在于其灵活性。它不仅可以用于条件性地发出错误,还可以用于过滤元素(不调用sink.next)、转换元素(调用sink.next一次)甚至将一个元素转换为零个或多个元素(尽管对于单值Mono来说,通常是零个或一个)。
在Spring WebFlux响应式编程中处理可能为null的值并抛出自定义异常时,请记住以下几点:
根据具体的业务逻辑和代码的简洁性需求,可以选择flatMap或handle来实现对null值的健壮处理和异常抛出。在大多数需要根据元素内容进行条件性错误处理的场景中,flatMap通常是更直观和简洁的选择。
以上就是Spring WebFlux中响应式流处理null值与自定义异常抛出的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号