spring-kotlin-coroutine
is a repository that contains several libraries and a demo app which allows using Kotlin coroutines in
Spring applications as first-class citizens.
This project contains several modules:
spring-kotlin-coroutine
- a library which allow using Kotlin coroutines in Spring applications. It contains support for@Coroutine
annotation, application events, caching, scheduled tasks,CoroutineRestOperations
,DeferredRestOperations
,ListenableFuture
extensions.spring-webmvc-kotlin-coroutine
- a library which allow using Kotlin coroutines in Spring MVC applications. It contains support for web methods.spring-webflux-kotlin-coroutine
- a library which allow using Kotlin coroutines in Spring Web Flux applications. It contains support for Web Flux web methods,CoroutineWebClient
and functional style routes definition.spring-data-mongodb-kotlin-coroutine
- a library which allow using Kotlin coroutines in Spring Data Mongo applications. It contains support forCoroutineMongoRepository
andCoroutineMongoTemplate
.spring-boot-autoconfigure-kotlin-coroutine
- a library which contains autoconfiguration support forspring-data-mongodb-kotlin-coroutine
via@EnableCoroutineMongoRepositories
annotation.spring-kotlin-coroutine-demo
- contains a sample application which demonstrates the use ofspring-kotlin-coroutine
andspring-webmvc-kotlin-coroutine
.
Most of the supported features require adding an @EnableCoroutine
annotation to your Spring
@Configuration
class.
This annotation enables using the features described below. If it is possible to use a feature without this annotation it is explicitly stated so in the feature description. The annotation can be used as follows:
@Configuration
@EnableCoroutine(
proxyTargetClass = false, mode = AdviceMode.PROXY,
order = Ordered.LOWEST_PRECEDENCE, schedulerDispatcher = "")
open class MyAppConfiguration {
...
}
The proxyTargetClass
, mode
and order
attributes of @EnableCoroutine
follow the same semantics as @EnableCaching
.
schedulerDispatcher
is a CoroutineDispatcher
used to run @Scheduled
corroutines.
Note that currently only
AdviceMode.PROXY
mode is supported.
Because coroutines can be suspended and they have an additional implicit callback parameter they cannot be used
as web methods by default. With special parameter and result handling introduced in spring-webmvc-kotlin-coroutine
you can safely use coroutines as web methods. You can e.g. have the following component:
@RestController
open class MyController {
@GetMapping("/customers")
suspend open fun getCustomers(): List<Customer> {
...
}
}
Spring beans and its methods can be annotated with @Coroutine
. Using this annotation you can specify
the coroutine context via the context
attribute and the coroutine name via the name
attribute.
The context
specifies a name of a bean from which a CoroutineContext
can be created. Currently the following
contexts/bean types are supported:
CoroutineContext
type beans - used directlyCOMMON_POOL
- a constant specifying theCommonPool
contextDEFAULT_DISPATCHER
- a constant specifying theDefaultDispatcher
contextUNCONFINED
- a constant specifying theUnconfined
contextExecutor
type beans - converted toCoroutineContext
withasCoroutineDispatcher
- Rx1
Scheduler
type beans - converted toCoroutineContext
withasCoroutineDispatcher
- Rx2
Scheduler
type beans - converted toCoroutineContext
withasCoroutineDispatcher
- Reactor
Scheduler
type beans - converted toCoroutineContext
withasCoroutineDispatcher
TaskScheduler
type beans - converted toCoroutineContext
withasCoroutineDispatcher
method ofTaskSchedulerCoroutineContextResolver
You can also support your own types of beans or context names by providing Spring beans of type CoroutineContextResolver
:
interface CoroutineContextResolver {
fun resolveContext(beanName: String, bean: Any?): CoroutineContext?
}
Using @Coroutine
it is quite easy to achieve the same effect as with @Async
, although the code will look much simpler:
@RestController
open class MyController(
private val repository: MyRepository
) {
@GetMapping("/customers")
suspend open fun getCustomers(): List<Customer> {
return repository.getAllCustomers()
}
}
@Component
@Coroutine(COMMON_POOL)
open class MyRepository {
suspend open fun getAllCustomers(): List<Customer> {
// a blocking code
// which will be run with COMMON_POOL context
// or simply ForkJoinPool
...
}
}
Spring allows you to decouple senders and receivers of application events with the usage of ApplicationEventPublisher
and
@EventListener
methods. They cannot, however, be coroutines. spring-kotlin-coroutine
allows you to send
events in a way that allows the suspension of event processing. You can also have an @EventListener
method which is a
coroutine.
For sending a component with the following interface can be used:
interface CoroutineApplicationEventPublisher {
suspend fun publishEvent(event: ApplicationEvent)
suspend fun publishEvent(event: Any)
}
Your bean can inject this interface or it can implement a CoroutineApplicationEventPublisherAware
interface and have
it delivered via a setCoroutineApplicationEventPublisher
method:
interface CoroutineApplicationEventPublisherAware : Aware {
fun setCoroutineApplicationEventPublisher(publisher: CoroutineApplicationEventPublisher)
}
The events sent by either CoroutineApplicationEventPublisher
or ApplicationEventPublisher
can be received
by any method annotated with @EventListener
(a coroutine or a regular one). The result of coroutine listeners will
be handled in the same way as for regular listeners:
- if it is a
Unit
nothing will happen - if it returns a single value it will be treated as a newly published event
- if it returns an array or a collection of values it will be treated as a collection of newly published events
Due to an additional callback parameter and a special return value semantics coroutine return values cannot be cached using
default Spring caching feature. However with spring-kotlin-coroutine
it is possible to use a @Cacheable
annotation
on a coroutine, e.g.:
@Configuration
@EnableCaching
@EnableCoroutine
open class MyConfiguration {
}
@Component
class MyComponent {
@Cacheable
open suspend fun getCustomer(id: String): Customer {
...
}
}
Coroutines annotated with @Scheduled
will
not work with regular Spring. However, with spring-kotlin-coroutine
you can use them the same way you would do it with regular methods with the following caveats:
- They will be executed using
CoroutineDispatcher
obtained from:schedulerDispatcher
attribute of@EnableCoroutine
annotation (if a custom value is specified) - it works the same way as thecontext
attribute of@Coroutine
annotation (see@Coroutine
section), orTaskScheduler
used byScheduledTaskRegistrar
from theScheduledAnnotationBeanPostProcessor#registrar
converted intoTaskSchedulerDispatcher
(which is aCoroutineDispatcher
converted withTaskSchedulerCoroutineContextResolver
) - this mimics the default behaviour of Spring for regular@Scheduled
annotated method.
- The exception thrown from the coroutine will be handled using:
- Default
ErrorHandler
for repeating tasks ifTaskSchedulerDispatcher
is used, handleCoroutineException
otherwise.
- Default
Spring provides blocking RestOperations
to be used as a REST
client. spring-kotlin-coroutine
provides
CoroutineRestOperations
interface which has the same methods as RestOperations
, but as coroutines:
interface CoroutineRestOperations {
suspend fun <T : Any?> postForObject(url: String, request: Any?, responseType: Class<T>?, vararg uriVariables: Any?): T
suspend fun <T : Any?> postForObject(url: String, request: Any?, responseType: Class<T>?, uriVariables: Map<String, *>): T
suspend fun <T : Any?> postForObject(url: URI, request: Any?, responseType: Class<T>?): T
...
}
In order to create CoroutineRestOperations
use the following:
val restOps = CoroutineRestOperations(restOperations = RestTemplate(), context = null)
val defaultRestOps = CoroutineRestOperations()
If you do not specify any arguments when creating CoroutineRestOperations
it will delegate all calls to RestTemplate
instance and use a special coroutine context which will invoke the blocking method of RestTemplate
on a separate thread
(just like AsyncRestTemplate
). You can specify your own RestOperations
and CoroutineContext
to change that behaviour.
Note that
CoroutineRestOperations
does not need@EnableCoroutine
in order to work. UnderneathCoroutineRestOperations
usescreateCoroutineProxy
.
The kotlinx.coroutines
library provides Deferred<T>
type
for non-blocking cancellable future. Based on that spring-kotlin-coroutine
provides DeferredRestOperations
interface which has the same methods as
RestOperations
, but with Deferred<T>
return type:
interface DeferredRestOperations {
fun <T : Any?> postForObject(url: String, request: Any?, responseType: Class<T>?, vararg uriVariables: Any?): Deferred<T>
fun <T : Any?> postForObject(url: String, request: Any?, responseType: Class<T>?, uriVariables: Map<String, *>): Deferred<T>
fun <T : Any?> postForObject(url: URI, request: Any?, responseType: Class<T>?): Deferred<T>
...
}
In order to create DeferredRestOperations
use the following:
val restOps = DeferredRestOperations(restOperations = RestTemplate(), start = CoroutineStart.DEFAULT, context = COMMON_POOL)
val defaultRestOps = DeferredRestOperations()
If you do not specify any arguments when creating DeferredRestOperations
it will delegate all calls to RestTemplate
instance and use a special coroutine context which will immediately invoke the blocking method of RestTemplate
on a separate thread
(just like AsyncRestTemplate
). You can specify your own RestOperations
and CoroutineContext
to change that behaviour.
By changing the start
parameter value you can specify when the REST operation should be invoked (see CoroutineStart
for details).
Note that
DeferredRestOperations
does not need@EnableCoroutine
in order to work. UnderneathDeferredRestOperations
usescreateCoroutineProxy
.
By using spring-webflux-kotlin-coroutine
module instead of spring-webmvc-kotlin-coroutine
web methods which are
suspending functions will use Spring Web Flux. This enables them to use non-blocking I/O API.
CoroutineWebClient
is a counterpart of the Spring Web Flux WebClient
component. The differences between these components
can be found mainly in the functions which operate on reactive types - in CoroutineWebClient
they are suspending functions
operating on regular types. Also the naming of the methods can be slightly different (e.g. in WebClient
you can find bodyToMono
and in CoroutineWebClient
it is simply body
).
TBD
spring-data-mongodb-kotlin-coroutine
contains support for CoroutineMongoRepository
-based repositories. These repositories
work as regular Spring Data Mongo or Spring Data Mongo Reactive repositories, but have support for suspending functions
and ReceiveChannel
type.
CoroutineMongoTemplate
is a counterpart of regular MongoTemplate
, but contains suspending functions instead of regular ones.
The @EnableCoroutineMongoRepositories
annotation works just like @EnableMongoRepositories
annotation, but enables
the usage of CoroutineMongoRepository
repositories.
The kotlinx.coroutines
library provides interoperability functions with many existing asynchronous libraries (
RxJava v1
, RxJava v2
,
CompletableFuture
, etc.). However, there is no support for Spring specific ListenableFuture
interface. Therefore
spring-kotlin-coroutine
provides the following features:
fun <T> listenableFuture(context: CoroutineContext = DefaultDispatcher, block: suspend () -> T): ListenableFuture<T>
- it allows you to create aListenableFuture
from a coroutine with specific coroutine context.fun <T> Deferred<T>.asListenableFuture(): ListenableFuture<T>
- it allows you to create aListenableFuture
from Kotlin coroutine specificDeferred
type.suspend fun <T> ListenableFuture<T>.await(): T
- it allows you to create a coroutine from aListenableFuture
.
Note that these extensions do not need
@EnableCoroutine
in order to work.
Note that utility functions do not need
@EnableCoroutine
in order to work.
createCoroutineProxy
can be used to create a smart proxy - an instance of an interface which will delegate all
function invocations to a regular object with matching method signatures. The runtime characteristics of this
proxy call depends on the types of the interface methods, the types of the proxied object methods and
the proxy config. The createCoroutineProxy
is declared as follows:
fun <T: Any> createCoroutineProxy(coroutineInterface: Class<T>, obj: Any, proxyConfig: CoroutineProxyConfig): T
Currently supported proxy types are as follows:
Coroutine interface method | Object method | Proxy config |
---|---|---|
suspend fun m(a: A): T |
fun m(a: A): T |
DefaultCoroutineProxyConfig |
fun <T> m(a: A): Deferred<T> |
fun m(a: A): T |
DeferredCoroutineProxyConfig |
Method.isSuspend
allows you to check if a method is a coroutine. It is defined as follows:
val Method.isSuspend: Boolean
Note that this library is experimental and is subject to change.
The library is published to konrad-kaminski/maven Bintray repository.
Add Bintray repository:
repositories {
maven { url 'https://dl.bintray.com/konrad-kaminski/maven' }
}
Add dependencies:
compile 'org.springframework.kotlin:spring-kotlin-coroutine:0.3.5'
Note that some of the dependencies of
spring-kotlin-coroutine
are declared as optional. You should declare them as runtime dependencies of your application if you want to use the features that require them. The table below contains the details:
Feature Dependency Web methods org.springframework:spring-webmvc:5.0.7.RELEASE
Rx1 Scheduler
in@Coroutine
org.jetbrains.kotlinx:kotlinx-coroutines-rx1:0.23.3
Rx2 Scheduler
in@Coroutine
org.jetbrains.kotlinx:kotlinx-coroutines-rx2:0.23.3
Reactor Scheduler
in@Coroutine
org.jetbrains.kotlinx:kotlinx-coroutines-reactor:0.23.3
And make sure that you use the right Kotlin version:
buildscript {
ext.kotlin_version = '1.2.50'
}
It's a deliberate choice. In most cases Coroutine just sounded better to me and even though sometimes Suspending might've been a better choice for consistency Coroutine was used.
spring-kotlin-coroutine
is released under version 2.0 of the Apache License.