使用 Kotlin 协程的 Spring Boot Rest 服务

Posted

技术标签:

【中文标题】使用 Kotlin 协程的 Spring Boot Rest 服务【英文标题】:Spring Boot Rest Service With Kotlin Coroutines 【发布时间】:2020-02-21 19:44:58 【问题描述】:

我正在用 Kotlin 编写 Spring Boot Rest Service。我想使用协程作为 Webflux 的替代方案来实现非阻塞异步行为。我正在使用 Spring Boot 2.1,并且知道我无法实现真正​​的非阻塞行为,因为我在 Controller 阻塞。不过,在 Spring Boot 2.2 普遍可用之前,我暂时可以接受。

我的应用是三层的,即Controller->Service->Repository。在存储库中,我正在调用其他服务,即网络调用,并将该方法标记为挂起。

我想确保这是否是正确的方法,此外,在ResourceService 中调用挂起乐趣会阻塞调用者线程吗?

在阅读 Roman Elizarov 的 https://medium.com/@elizarov/blocking-threads-suspending-coroutines-d33e11bf4761 之后,我不确定是否应该将 withContext 与我的所有暂停功能一起使用?

package my.springbootapp

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import mu.KotlinLogging
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.stereotype.Repository
import org.springframework.stereotype.Service
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RestController
import java.util.concurrent.Executors

val logger = KotlinLogging.logger  

@SpringBootApplication
class App

fun main(args: Array<String>) 
    SpringApplication.run(App::class.java, *args)


@RestController
class Controller(private val resourceService: ResourceService) 
    private val dispatcher = Executors.newFixedThreadPool(5).asCoroutineDispatcher()

    @GetMapping("/resource/id")
    fun getResource(@PathVariable("id") id: String) = runBlocking(dispatcher) 
        resourceService.get(id).also  logger.info  Thread.currentThread().name + "Returning $it"  
    


@Service
class ResourceService(private val networkResourceRepository: NetworkResourceRepository) 

    suspend fun get(id: String): Resource 
        logger.info  Thread.currentThread().name + "Getting resource" 
        return networkResourceRepository.get(id)
    


@Repository
class NetworkResourceRepository 
    suspend fun get(id: String): Resource = withContext(Dispatchers.IO) 
        logger.info  Thread.currentThread().name + "Getting resource from n/w" 
        //IO operation
        Resource("resource data")
    


data class Resource(val data: String)

【问题讨论】:

“但是,在 Spring Boot 2.2 普遍可用之前,我暂时可以接受。” ----> Spring Boot 2.2 上周发布了 GA,所以试试吧。这是一种更好的体验。 从您的端点中删除 runBlocking。无论如何,SpringBoot 中的每个请求都在单独的线程中处理。您的 ResourceService 不应公开挂起的功能。在内部制作协程,但对外界隐藏它们。 【参考方案1】:

我认为你应该使用带有 WebFlux 的协程,但不能代替 WebFlux。要将 WebFlux 与协程一起使用,您必须添加 WebFlux-Wrapper 以使 WebFlux 可暂停。 WebFlux+Coroutine example 协程本身不会使您的代码非阻塞,协程的目标是它们可以暂停。这个包装器只是在WebClient 上调用一些areYouFinished 方法(例如),如果它没有完成协程挂起,它将尝试在将来调用相同的方法(通过在未来执行中传递未到达的代码)。

【讨论】:

【参考方案2】:

您是否查看了how to use co-routines 上的新文档?

你感兴趣的是带有@Controller 的类支持以下内容:

Spring WebFlux 中的延迟和流返回值支持 在 Spring WebFlux 中暂停功能支持

所以我认为你可以使用挂起

@RestController
class Controller(private val resourceService: ResourceService) 

@GetMapping("/resource/id")
suspend fun getResource(@PathVariable("id") id: String) =
        resourceService.get(id).also  logger.info  Thread.currentThread().name + "Returning $it"  

【讨论】:

我在创建 bean 时遇到了错误:Error creating bean with name 'requestMappingHandlerMapping' defined in class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: Unsupported suspending handler method detected 也许你知道如何克服这个问题? 你需要提供使用的依赖和代码

以上是关于使用 Kotlin 协程的 Spring Boot Rest 服务的主要内容,如果未能解决你的问题,请参考以下文章

是否可以在 Spring Boot 中使用 @Transactional 和 kotlin 协程?

谈谈我对 Kotlin 中协程的理解

深入理解Kotlin协程使用Job控制协程的生命周期

使用协程的 Kotlin/Native 多线程

深入理解Kotlin协程协程的上下文 CoroutineContext

kotlin协程的理解