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@Coroutineannotation, application events, caching, scheduled tasks,CoroutineRestOperations,DeferredRestOperations,ListenableFutureextensions.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,CoroutineWebClientand functional style routes definition.spring-data-mongodb-kotlin-coroutine- a library which allow using Kotlin coroutines in Spring Data Mongo applications. It contains support forCoroutineMongoRepositoryandCoroutineMongoTemplate.spring-boot-autoconfigure-kotlin-coroutine- a library which contains autoconfiguration support forspring-data-mongodb-kotlin-coroutinevia@EnableCoroutineMongoRepositoriesannotation.spring-kotlin-coroutine-demo- contains a sample application which demonstrates the use ofspring-kotlin-coroutineandspring-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.PROXYmode 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:
CoroutineContexttype beans - used directlyDEFAULT_DISPATCHER- a constant specifying theDispatchers.DefaultcontextUNCONFINED- a constant specifying theDispatchers.UnconfinedcontextExecutortype beans - converted toCoroutineContextwithasCoroutineDispatcher- Rx2
Schedulertype beans - converted toCoroutineContextwithasCoroutineDispatcher - Reactor
Schedulertype beans - converted toCoroutineContextwithasCoroutineDispatcher TaskSchedulertype beans - converted toCoroutineContextwithasCoroutineDispatchermethod 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(DEFAULT_DISPATCHER)
open class MyRepository {
suspend open fun getAllCustomers(): List<Customer> {
// a blocking code
// which will be run with DEFAULT_DISPATCHER 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
Unitnothing 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
CoroutineDispatcherobtained from:schedulerDispatcherattribute of@EnableCoroutineannotation (if a custom value is specified) - it works the same way as thecontextattribute of@Coroutineannotation (see@Coroutinesection), orTaskSchedulerused byScheduledTaskRegistrarfrom theScheduledAnnotationBeanPostProcessor#registrarconverted intoTaskSchedulerDispatcher(which is aCoroutineDispatcherconverted withTaskSchedulerCoroutineContextResolver) - this mimics the default behaviour of Spring for regular@Scheduledannotated method.
- The exception thrown from the coroutine will be handled using:
- Default
ErrorHandlerfor repeating tasks ifTaskSchedulerDispatcheris used, handleCoroutineExceptionotherwise.
- 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
CoroutineRestOperationsdoes not need@EnableCoroutinein order to work. UnderneathCoroutineRestOperationsusescreateCoroutineProxy.
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
DeferredRestOperationsdoes not need@EnableCoroutinein order to work. UnderneathDeferredRestOperationsusescreateCoroutineProxy.
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 aListenableFuturefrom a coroutine with specific coroutine context.fun <T> Deferred<T>.asListenableFuture(): ListenableFuture<T>- it allows you to create aListenableFuturefrom Kotlin coroutine specificDeferredtype.suspend fun <T> ListenableFuture<T>.await(): T- it allows you to create a coroutine from aListenableFuture.
Note that these extensions do not need
@EnableCoroutinein order to work.
Note that utility functions do not need
@EnableCoroutinein 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): TCurrently 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: BooleanNote 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.7'Note that some of the dependencies of
spring-kotlin-coroutineare 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.9.RELEASERx2 Schedulerin@Coroutineorg.jetbrains.kotlinx:kotlinx-coroutines-rx2:1.2.1Reactor Schedulerin@Coroutineorg.jetbrains.kotlinx:kotlinx-coroutines-reactor:1.2.1
And make sure that you use the right Kotlin version:
buildscript {
ext.kotlin_version = '1.3.30'
}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.