📜 ⬆️ ⬇️

We write a simple RESTful service on kotlin and spring boot

image

Introduction


In anticipation of the release of the Kotlin language from beta, I want to share my impression of its use.

Kotlin is a new wonderful language from JetBrains (developers IntelliJ Idea) for JVM, Android and a browser that at first glance looks like an improved java (or a simplified scala). But this is only at first glance, the language has not only absorbed many interesting solutions from other languages, but also presents its original ones:
')
- optional from swift, nullsafe to kotlin
- case classes from scala, data class in kotlin
- replacement of implicit methods from scala, extension functions
- delegates
- null safely
- smart cast
- and much more, more can be viewed on the official website kotlinlang .

For those who are familiar with java or scala, it will be interesting to compare kotlin & java , kotlin & scala .

The authors of the language strive to achieve two tasks:
- make compilation speed comparable to java
- the language must be sufficiently expressive, and at the same time be as simple as possible
Therefore, it is worth making a reservation that if you are currently happy with scala, with its “complexity” and compilation time, then you will most likely not need kotlin, for everyone else to read further:

For those who are in the tank for the first time hears about the language, below are a few examples from the official site:

Hello world
package hello fun main(args: Array<String>) { println("Hello World!") } 


Reading arguments
 fun main(args: Array<String>) { if (args.size() == 0) { println("Provide a name") return } println("Hello, ${args[0]}!") } 


hello world c OOP
 class Greeter(val name: String) { fun greet() { println("Hello, $name") } } fun main(args: Array<String>) { Greeter(args[0]).greet() } 


From the personal experience of using kotlin, I especially want to point out several advantages of the language:

- the first is of course the simplicity of interacting with java. All types and collections from java are converted to similar ones from kotlin, and vice versa. This is especially encouraging after all the “anarchy” that is going on in scala (yes there is scala.collection.JavaConversions._ and scala.collection.JavaConverters._, but still this does not compare with the completely transparent type conversion);
- it also cannot but please excellent support from Intellij Idea studio, although the language is in Beta 4, already at the current moment the plug-in for the studio allows you to work comfortably;
- and for fans of implicit methods from scala, kotlin presents a very convenient solution in the form of extension functions ;
- among other things, language developers aim to achieve a compile time comparable to java (hello scala), for which they just want to shake hands! This is especially pleasing after a long work in scala, when editing one line in a fairly small file is compiled at the same speed as a small java project;
- inline function - a great innovation. With their help, you can, for example, expand the current capabilities of the language, or in some situations achieve increased productivity;
- convenient functions of the standard library.
- comfortable lambda, in contrast to the same java 8. Very similar to the implementation of scala.

Nevertheless, the language has its drawbacks:

- there is a lack of pattern matching from scala, but in some situations it saves smart cast and Destructuring Declarations , in others it is necessary to get it out by other means. The lack of pattern matching is generally understandable, developers are trying to get as close as possible to the java compilation time, but its presence would significantly simplify the writing of some applications, so that we are content with what we have;
- try with resource is not implemented very well yet. But here the authors of the language promise to correct the situation in the near future. In the meantime, you can either use the existing solution or use the language extension:

try-with-resources
 internal class ResourceHolder : AutoCloseable { val resources = ArrayList<AutoCloseable>() fun <T : AutoCloseable> T.autoClose(): T { resources.add(this) return this } override fun close() { resources.reverse() resources.forEach { try { it.close() } catch (e: Throwable) { e.printStackTrace() } } } } inline internal fun <R> using(block: ResourceHolder.() -> R): R { val holder = ResourceHolder() try { return holder.block() } finally { holder.close() } } 


Usage example
 fun copy(from: Path, to: Path) { using { val input = Files.newInputStream(from).autoClose() val output = Files.newOutputStream(to).autoClose() input.copyTo(output) } } 


- while there is no async and yield, but according to the authors, after the release of 1.0 you can expect them to appear in the very near future.

Update In the comments, the most interesting is the comparison with scala, and in particular the advantage of kotlin:
Advantages of kotlin compared to scala
- interaction with java and back. On kotlin, in general, you can forget about the fact that you call something from java, everything is extremely transparent and convenient. The same is the opposite. From java it is also convenient to work with kotlin. Here even not only the similarity of types helps, as far as the orientation of kotlin itself to transparent work with java itself. The easiest way to demonstrate this is with examples:
Examples
1. Using lambda instead of anonymous classes (in scala, however, it may soon appear too, but so far there is no such possibility).
Code in java:
 ExecutorService executor = Executors.newFixedThreadPool(1); executor.execute(System.out::println); 


In scala, this will not turn out. But with kotlin no problem:
 val executor = Executors.newFixedThreadPool(1) executor.execute { println() } 


2. There is the same try-with-resources (yes, you can make an extension to the language, but this is not out of the box, which means you have to pull the solution from one project to another)
It is at least that the first comes to mind

- lack of implicit. By themselves, implicit is of course a powerful solution, but without a studio it is almost impossible to figure out someone else's code, which is to what. Especially if the authors are "very enthusiastic." And to add methods, kotlin allows you to write extensions, which in practice look much more convenient than similar solutions in scala. Example:
Scala:
 implicit class RichInt(val x: Int) extends AnyVal { def square: Int = x * x } object App { def print(): Unit = { val two = 2 println(s"The square of 2 is ${two.square}") } } 

Kotlin:
 fun Int.richInt(): Int = this * this object App { fun print(): Unit { val two = 2 println("The square of 2 is ${two.richInt()}") } } 


- nullable types instead of Option.

- convenient functions of the standard library

- and of course the compilation speed. In practice, everything really compiles very quickly, especially compared to scala.

- the resulting jar is less than the same in scala.

- and not least, JetBrains promise full backward compatibility (hello again scala with libs under 2.10, 2.11, 2.12, etc.)


Let us turn to an example in which a small RESTful application on a spring boot will be demonstrated, with an assembly through gradle .

Studio setup


To work, you need to install IntelliJ Idea Community (but you can use Eclipse, there is also a plug-in for it), in which, after installation, update the kotlin plugin. It is necessary to update it manually, through settings -> plugin , even if you have previously chosen to update the plugin via a pop-up window (at least for now, while the language is in beta).

It is also better to put a local gradle, and set it in the settings in the studio (settings -> build, execution, deployment -> gradle -> user local gradle distribution. Then specify the path to gradle in gradle home).

Project Setup


Create a gradle kotlin project (new project -> gradle -> kotlin) and change the contents of build.gradle to the following:

Build.gradle content
 buildscript { ext.kotlin_version = '1.0.0-beta-4584' repositories { mavenCentral() maven { url "http://repo.spring.io/snapshot" } maven { url "http://repo.spring.io/milestone" } } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:1.3.0.RELEASE") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version") } } apply plugin: 'idea' apply plugin: 'spring-boot' apply plugin: 'kotlin' jar { baseName = 'test-spring-kotlin-project' version = '0.1.0' } repositories { mavenCentral() maven { url "http://repo.spring.io/snapshot" } maven { url "http://repo.spring.io/milestone" } maven { url "http://10.10.10.67:8081/nexus/content/groups/public" } } dependencies { compile("org.springframework.boot:spring-boot-starter-web:1.3.0.RELEASE") compile("org.springframework:spring-jdbc:4.2.3.RELEASE") compile("com.fasterxml.jackson.module:jackson-module-kotlin:2.6.4") compile("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") } 


Create an application.properties file in the src / main / resources folder, where we specify the port to start spring boot:

application.properties
 server.port = 8080 


Create an Application.kt file in the src / main / kotlin / test.kotlin.spring.project folder. It will be the main settings for running spring boot:

Application.kt
 package test.kotlin.spring.project import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.registerKotlinModule import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.EnableAutoConfiguration import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration import org.springframework.boot.builder.SpringApplicationBuilder import org.springframework.boot.context.web.SpringBootServletInitializer import org.springframework.context.annotation.Bean import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter @SpringBootApplication @EnableAutoConfiguration(exclude = arrayOf(DataSourceAutoConfiguration::class)) open class Application : SpringBootServletInitializer() { @Bean open fun mapperForKotlinTypes(): MappingJackson2HttpMessageConverter { return MappingJackson2HttpMessageConverter().apply { objectMapper = jacksonMapper } } override fun configure(application: SpringApplicationBuilder): SpringApplicationBuilder = application.sources(Application::class.java) companion object { val jacksonMapper = ObjectMapper().registerKotlinModule() .setSerializationInclusion(JsonInclude.Include.NON_ABSENT) .enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES) @Throws(Exception::class) @JvmStatic fun main(args: Array<String>) { println("starting application...") SpringApplication.run(Application::class.java, *args) } } } 



You will also need to create a file with the settings of the rest service methods. There will be several methods:

- the method will issue an AckResponse to the first and last data entered with the query.
- method, the input is an array of strings, from which the smallest string is selected by length, which is then broken down by '_', sorted and assembled into a string already with the character ',' (demonstrates the capabilities of the language)

Create a file ServiceController.kt in the src / main / kotlin / test.kotlin.spring.project folder.

ServiceController.kt
 package test.kotlin.spring.project import org.springframework.http.MediaType import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestMethod import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController data class AckResponse(val status: Boolean, val result: String, val message: String? = null) @RestController class ServiceController { @RequestMapping( path = arrayOf("/request"), method = arrayOf(RequestMethod.GET), produces = arrayOf(MediaType.APPLICATION_JSON_UTF8_VALUE)) fun nameRequest( @RequestParam(value = "name") name: String, @RequestParam(value = "surname", required = false) surname: String?): AckResponse { return if (surname == null) AckResponse(status = true, result = "Hi $name", message = "surname is empty") else AckResponse(status = true, result = "Hi $surname,$name") } @RequestMapping( path = arrayOf("/sort_request"), method = arrayOf(RequestMethod.GET), produces = arrayOf(MediaType.APPLICATION_JSON_UTF8_VALUE)) fun findMinimum( @RequestParam(value = "values") values: Array<String>): AckResponse { println("values:") values.forEach { println(it) } val minValue = values.apply { sortBy { it.length } } .firstOrNull() ?.split("_") ?.sorted() ?.joinToString(",") ?: "" return AckResponse(status = true, result = minValue) } } 


Running and testing


We start application from Application.kt. In the case of a successful launch in the log will be something like:

Application logs
  starting application ...

   .  ____ _ __ _ _
  / \\ / ___'_ __ _ _ (_) _ __ __ _ \ \ \ \
 (() \ ___ | '_ |' _ | | '_ \ / _` | \ \ \ \
  \\ / ___) |  | _) |  |  |  |  |  ||  (_ | |))))
   '| ____ |  .__ | _ |  | _ | _ |  | _ \ __, |  / / / /
  ========= | _ | ============== | ___ / = / _ / _ / _ /
  :: Spring Boot :: (v1.3.0.RELEASE)

 2016-01-12 12: 47: 48.242 INFO 88 --- [main] tksproject.Application $ Companion: Starting Application.Companion on Lenovo-PC with PID 88 (D: \ IDA_Projects \ test \ build \ classes \ main started by admin in D: \ IDA_Projects \ test)
 2016-01-12 12: 47: 48.247 INFO 88 --- [main] tksproject.Application $ Companion: No profiles are active
 2016-01-12 12: 47: 48.413 INFO 88 --- [main] ationConfigEmbeddedWebApplicationContext: Refreshing org.springframework.boot.context.embeddedWebApplicationContext@dbf57b3: startup date [Tue Jan 12 12:47:48 MSK 2016]]  root of context hierarchy
 2016-01-12 12: 47: 50.522 INFO 88 --- [main] osbfsDefaultListableBeanFactory: Overriding bean for beanNameViewResolver with a different definition: replacing [Root bean: class [null];  scope =;  abstract = false;  lazyInit = false;  autowireMode = 3;  dependencyCheck = 0;  autowireCandidate = true;  primary = false;  factoryBeanName = org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration $ WhitelabelErrorViewConfiguration;  factoryMethodName = beanNameViewResolver;  initMethodName = null;  destroyMethodName = (inferred);  [org / springframework / boot / autoconfigure / web / ErrorMvcAutoConfiguration $ WhitelabelErrorViewConfiguration.class]] [Root bean: class [null];  scope =;  abstract = false;  lazyInit = false;  autowireMode = 3;  dependencyCheck = 0;  autowireCandidate = true;  primary = false;  factoryBeanName = org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration $ WebMvcAutoConfigurationAdapter;  factoryMethodName = beanNameViewResolver;  initMethodName = null;  destroyMethodName = (inferred);  defined in class path resource [org / springframework / boot / autoconfigure / web / WebMvcAutoConfiguration $ WebMvcAutoConfigurationAdapter.class]]
 2016-01-12 12: 47: 51.066 INFO 88 --- [main] trationDelegate $ BeanPostProcessorChecker: Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [class org.springframework.transaction.annotation.ProxyTransactManthramathrantMathr.AffraTeanMatery.transferamework.transaction.annotation. $$ ede1977c] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
 2016-01-12 12: 47: 51.902 INFO 88 --- [main] sbcetTomcatEmbeddedServletContainer: Tomcat initialized with port (s): 8080 (http)
 2016-01-12 12: 47: 51.930 INFO 88 --- [main] o.apache.catalina.core.StandardService: Starting service Tomcat
 2016-01-12 12: 47: 51.937 INFO 88 --- [main] org.apache.catalina.core.StandardEngine: Starting Servlet Engine: Apache Tomcat / 8.0.28
 2016-01-12 12: 47: 52.095 INFO 88 --- [ost-startStop-1] oaccC [Tomcat]. [Localhost]. [/]: Initializing Spring embedded WebApplicationContext
 2016-01-12 12: 47: 52.095 INFO 88 --- [ost-startStop-1] osweb.context.ContextLoader: Root WebApplicationContext: initialization completed 3688 ms
 2016-01-12 12: 47: 52.546 INFO 88 --- [ost-startStop-1] osbceServletRegistrationBean: Mapping servlet: 'dispatcherServlet' to [/]
 2016-01-12 12: 47: 52.556 INFO 88 --- [ost-startStop-1] osbcembedded.FilterRegistrationBean: Mapping filter: 'characterEncodingFilter' to: [/ *]
 2016-01-12 12: 47: 52.557 INFO 88 --- [ost-startStop-1] osbcembedded.FilterRegistrationBean: Mapping filter: 'hiddenHttpMethodFilter' to: [/ *]
 2016-01-12 12: 47: 52.559 INFO 88 --- [ost-startStop-1] osbcembedded.FilterRegistrationBean: Mapping filter: 'httpPutFormContentFilter' to: [/ *]
 2016-01-12 12: 47: 52.559 INFO 88 --- [ost-startStop-1] osbcembedded.FilterRegistrationBean: Mapping filter: 'requestContextFilter' to: [/ *]
 2016-01-12 12: 47: 52.985 INFO 88 --- [main] swsmmaRequestMappingHandlerAdapter: Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@dbf57b3: launch date [Tue Jan 12 12 MSK 2016];  root of context hierarchy
 2016-01-12 12: 47: 53.089 INFO 88 --- [main] swsmmaRequestMappingHandlerMapping: Mapped "{[/ request], methods = [GET], produces = [application / json; charset = UTF-8]}" " public final test.kotlin.spring.project.AckResponse test.kotlin.spring.project.ServiceController.pullUpdate (java.lang.String, java.lang.String)
 2016-01-12 12: 47: 53.094 INFO 88 --- [main] swsmmaRequestMappingHandlerMapping: Mapped "{[/ error]}": java.util.Map <java.lang.String: public org.springframework.http.ResponseEntity <java.util.Map <java.lang.String , java.lang.Object >> org.springframework.boot.autoconfigure.web.BasicErrorController.error (javax.servlet.http.HttpServletRequest)
 2016-01-12 12: 47: 53.094 INFO 88 --- [main] swsmmaRequestMappingHandlerMapping: Mapped "{[/ error], produces = [text / html]}" is public org.springframework.web.servlet.ModelAndView org. springframework.boot.autoconfigure.web.BasicErrorController.errorHtml (javax.servlet.http.HttpServletRequest)
 2016-01-12 12: 47: 53.138 INFO 88 --- [main] oswshandler.SimpleUrlHandlerMapping: Mapped URL path [/ webjars / **] for class of [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
 2016-01-12 12: 47: 53.139 INFO 88 --- [main] oswshandler.SimpleUrlHandlerMapping: Mapped URL path [/ **] for handler type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
 2016-01-12 12: 47: 53.195 INFO 88 --- [main] oswshandler.SimpleUrlHandlerMapping: Mapped URL path [/**/favicon.ico] [class org.springframework.web.servlet.resource. ResourceHttpRequestHandler]
 2016-01-12 12: 47: 53.512 INFO 88 --- [main] osjeaAnnotationMBeanExporter: Registering beans for JMX exposure on startup
 2016-01-12 12: 47: 53.612 INFO 88 --- [main] sbcetTomcatEmbeddedServletContainer: Tomcat started on port (s): 8080 (http)
 2016-01-12 12: 47: 53.620 INFO 88 --- [main] tksproject.Application $ Companion: Started Application.Companion in 6.076 seconds (JVM running for 7.177)
 2016-01-12 12: 47: 57.874 INFO 88 --- [nio-8080-exec-1] oaccC [Tomcat]. [Localhost]. [/]: Initializing Spring Framework Framework 'dispatcherServlet'
 2016-01-12 12: 47: 57.874 INFO 88 --- [nio-8080-exec-1] osweb.servlet.DispatcherServlet: FrameworkServlet 'dispatcherServlet': initialization started
 2016-01-12 12: 47: 57.897 INFO 88 --- [nio-8080-exec-1] osweb.servlet.DispatcherServlet: FrameworkServlet 'dispatcherServlet': initialization completed in 23 ms 


After a successful launch, try to open the Request page with the name . The answer should look like this:
 { status: true, result: "Hi Kris", message: "surname is empty" } 

And the request with the name and surname , then the answer will be slightly different:

 { status: true, result: "Hi Eagle, Kris" } 

Call to check data sorting: Sort . The result should be:

 { status: true, result: "1,3,value,virst" } 

The same call, but with an empty array: Call
 { status: true, result: "" } 

If necessary, you can build the entire project into one runnable jar, with the command: gradle build. As a result, the project will be compiled into one archive containing all dependencies without unpacking. With this approach, the project build time is significantly increased, compared to the same assemble, when a project is assembled into one archive with unpacking of all dependencies.

Conclusion


In conclusion, I would like to note that kotlin turned out to be a very convenient language for working on any project that uses java as its replacement. The ecosystem of the language is not as extensive as the same scala, but now you can use it in the same big data, where there is a java api. In addition, from kotlin it is very easy to interact with java, so everything that is in java can be used in kotlin. In addition, from the studio there is the possibility of easy conversion of java files to similar ones on kotlin (although you will need to tweak the file a bit with hands after the conversion). JetBrains have done a wonderful job in creating an ideal language to replace java and scala. And I hope in the future the trend towards the use of kotlin will only grow.

Sources are available on github .

Thank you all for your attention.

Source: https://habr.com/ru/post/274997/


All Articles