响应式Spring Security关联非响应式JPA数据库获取用户信息的问题
响应式Spring Security关联非响应式JPA数据库获取用户信息的问题
嗨,我来帮你解决这个问题!你遇到的block()报错是响应式编程里的典型坑,完全不用因为非响应式数据库就放弃响应式Spring,只要调整下代码的写法,全程保持响应式的链式调用就好~
首先得搞清楚为什么会报错:Reactor的响应式线程(比如你看到的reactor-http-epoll-2)是专门用来处理非阻塞IO的,绝对不能在这些线程上做阻塞操作(比如block()),不然会彻底破坏响应式的性能模型,甚至导致线程死锁。
那正确的做法应该是把非响应式的数据库调用包装成响应式流,然后和SecurityContext的流做链式调用,全程不阻塞。具体代码可以这么写:
// 1. 从SecurityContext获取当前登录用户的用户名,得到一个Mono<String> Mono<String> currentUsername = ReactiveSecurityContextHolder.getContext() .map(SecurityContext::getAuthentication) .map(Authentication::getName); // 2. 用flatMap串联数据库调用,把非响应式的查询包装成Mono Mono<CustomUserDto> currentUserInfo = currentUsername.flatMap(username -> // 用fromCallable包装阻塞的DB调用,自动适配成响应式流 Mono.fromCallable(() -> userInfoRepository.findByEmail(username).orElse(null)) // 把阻塞操作调度到专门的阻塞线程池,不占用响应式IO线程 .subscribeOn(Schedulers.boundedElastic()) );
解释下这段代码的关键点:
- 用
ReactiveSecurityContextHolder.getContext()拿到的是Mono<SecurityContext>,我们通过map()链式操作取出用户名,全程都是响应式的流,没有阻塞 - 用
flatMap()来连接下一个流:把用户名传递给Mono.fromCallable(),这个方法会把里面的阻塞调用(也就是你的userInfoRepository.findByEmail())包装成一个响应式的Mono - 加上
subscribeOn(Schedulers.boundedElastic())非常重要:它会把数据库的阻塞调用放到Reactor专门的阻塞线程池里执行,不会占用响应式的IO线程,保证响应式线程的高效性
之后如果你需要在响应式的控制器或者服务里使用这个用户信息,直接返回Mono<CustomUserDto>就好,Spring WebFlux会自动处理订阅和返回结果,完全不需要调用block()。
举个在WebFlux控制器里使用的例子:
@GetMapping("/me") public Mono<CustomUserDto> getCurrentUser() { // 把上面的代码逻辑放这里,直接返回Mono return ReactiveSecurityContextHolder.getContext() .map(SecurityContext::getAuthentication) .map(Authentication::getName) .flatMap(username -> Mono.fromCallable(() -> userInfoRepository.findByEmail(username).orElse(null)) .subscribeOn(Schedulers.boundedElastic()) ); }
总结下:你完全不用切换回全非响应式的Spring,只要把非响应式的数据库调用用Mono.fromCallable()包装,再指定到阻塞线程池,就能完美结合响应式Spring Security和非响应式数据库了。
备注:内容来源于stack exchange,提问作者Khilarian
相关产品推荐
相关产品推荐

