spring 使用`withContext`设置新安全上下文时,如何保存事务?

ruyhziif  于 2023-06-21  发布在  Spring
关注(0)|答案(1)|浏览(92)

在下面的演示中,我们创建了一个新的ReactiveSecurityContextHolder,以不同的用户身份运行一个操作(在我们的实际代码中,我们创建身份验证的方式有点复杂,但对于演示,下面显示了我们遇到的相同问题)。

@Service
final class SomeServiceImpl(
  private val repo: SomeRepository,
) : SomeService {

  @Transactional
  suspend fun foo() {
    val a = repo.save(Some())

    // expected: 1
    println(repo.count()); // actual: 1

    withContext(
      ReactorContext(
       ReactiveSecurityContextHolder.withAuthentication(
          RunAsUserToken(
            it.userId.toString(),
            it.userId,
            it.userId,
            mutableListOf(),
            AbstractAuthenticationToken::class.java,
          ),
        ),
      )
    ) {
      // expected: 1
      println(repo.count()); // actual: 0
    }
  }
}

为什么withContext块中的repo.count()不返回1而是返回0?
当使用withContext(Dispatchers.IO)时,内层也返回1。
对我来说,在上下文更改期间,事务似乎丢失了,但我不知道如何保留它。

vd2z7a6w

vd2z7a6w1#

回答我自己的问题

TL;DR

这是:

withContext(
      ReactorContext(...)
)

...覆盖coroutineContext中的ReactorContext键(注意小写的ccoroutineContext指的是当前协程中的CoroutineContext)。键被withContext覆盖,并且不合并。ReactorContext键不会与现有的ReactorContext合并,而是被覆盖。因此,保存当前事务的现有ReactorContext被隐藏。

更多解释和解决方案

重要的是要理解,在coroutineContext中有一个ReactorContext,并创建一个新的ReactorContext作为withContext的参数。这两个ReactorContext彼此独立存在,需要手动合并以获得单个ReactorContext,然后可以合并到coroutineContext(阅读:用合并不同ReactorContext中的项的项来覆盖现有的项。
下面是一个解决问题的实现:

@Service
final class SomeServiceImpl(
  private val repo: SomeRepository,
) : SomeService {

  @Transactional
  suspend fun foo() {
    val a = repo.save(Some())

    // expected: 1
    println(repo.count()); // actual: 1

    // get existing ReactorContext or create an empty one
    val reactorContext = (coroutineContext[ReactorContext] ?: ReactorContext(Context.empty()))

    // override security context in the reactorContext
   val reactorWithAuthContext = reactorContext.context.putAll(
      ReactiveSecurityContextHolder.withAuthentication(
          RunAsUserToken(
            it.userId.toString(),
            it.userId,
            it.userId,
            mutableListOf(),
            AbstractAuthenticationToken::class.java,
          ),
        ).readOnly(),
    )

    // merge the coroutineContext with the new ReactorContext which will hide the existing ReactorContext for the withContext block
    withContext(
     reactorWithAuthContext
    ) {
      // expected: 1
      println(repo.count()); // actual: 1
    }
  }
}

对我来说,最难理解的部分是:

  1. ReactorContext既是类的名称,也是(在Kotlin中)类的名称引用它的同伴对象,它恰好是一个Key,然后用于在CouroutineContext中查找某些内容,更清楚地说,coroutineContext[ReactorContext]实际上表现得像coroutineContext[ReactorContext.Key]
    1.每个ReactorContext都没有不同的密钥,ReactorContext中的密钥也不会传播到CoroutineContextReactorContext只是 Package 了reactor上下文,并通过CoroutineContext中的ReactorContext.Key将其提供给协程。我假设ReactorContext中的Map会自动合并到CoroutineContext中,这是不正确的。

相关问题