• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

spring-guides/tut-spring-boot-kotlin: Building web applications with Spring Boot ...

原作者: [db:作者] 来自: 网络 收藏 邀请

开源软件名称(OpenSource Name):

spring-guides/tut-spring-boot-kotlin

开源软件地址(OpenSource Url):

https://github.com/spring-guides/tut-spring-boot-kotlin

开源编程语言(OpenSource Language):

Kotlin 90.7%

开源软件介绍(OpenSource Introduction):

This tutorial shows you how to build efficiently a sample blog application by combining the power of Spring Boot and Kotlin.

If you are starting with Kotlin, you can learn the language by reading the reference documentation, following the online Kotlin Koans tutorial or just using Spring Framework reference documentation which now provides code samples in Kotlin.

Spring Kotlin support is documented in the Spring Framework and Spring Boot reference documentation. If you need help, search or ask questions with the spring and kotlin tags on StackOverflow or come discuss in the #spring channel of Kotlin Slack.

Creating a New Project

First we need to create a Spring Boot application, which can be done in a number of ways.

Using the Initializr Website

Visit https://start.spring.io and choose the Kotlin language. Gradle is the most commonly used build tool in Kotlin, and it provides a Kotlin DSL which is used by default when generating a Kotlin project, so this is the recommended choice. But you can also use Maven if you are more comfortable with it. Notice that you can use https://start.spring.io/#!language=kotlin&type=gradle-project to have Kotlin and Gradle selected by default.

  1. Select "Gradle Project" or let the default "Maven Project" depending on which build tool you want to use

  2. Enter the following artifact coordinates: blog

  3. Add the following dependencies:

    • Spring Web

    • Mustache

    • Spring Data JPA

    • H2 Database

    • Spring Boot DevTools

  4. Click "Generate Project".

initializr

The .zip file contains a standard project in the root directory, so you might want to create an empty directory before you unpack it.

Using command line

You can use the Initializr HTTP API from the command line with, for example, curl on a UN*X like system:

$ mkdir blog && cd blog
$ curl https://start.spring.io/starter.zip -d language=kotlin -d dependencies=web,mustache,jpa,h2,devtools -d packageName=com.example.blog -d name=Blog -o blog.zip

Add -d type=gradle-project if you want to use Gradle.

Using IntelliJ IDEA

Spring Initializr is also integrated in IntelliJ IDEA Ultimate edition and allows you to create and import a new project without having to leave the IDE for the command-line or the web UI.

To access the wizard, go to File | New | Project, and select Spring Initializr.

Follow the steps of the wizard to use the following parameters:

  • Artifact: "blog"

  • Type: Maven project or Gradle Project

  • Language: Kotlin

  • Name: "Blog"

  • Dependencies: "Spring Web Starter", "Mustache", "Spring Data JPA", "H2 Database" and "Spring Boot DevTools"

Understanding the Gradle Build

If you’re using a Maven Build, you can skip to the dedicated section.

Plugins

In addition to the obvious Kotlin Gradle plugin, the default configuration declares the kotlin-spring plugin which automatically opens classes and methods (unlike in Java, the default qualifier is final in Kotlin) annotated or meta-annotated with Spring annotations. This is useful to be able to create @Configuration or @Transactional beans without having to add the open qualifier required by CGLIB proxies for example.

In order to be able to use Kotlin non-nullable properties with JPA, Kotlin JPA plugin is also enabled. It generates no-arg constructors for any class annotated with @Entity, @MappedSuperclass or @Embeddable.

build.gradle.kts

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
  kotlin("plugin.jpa") version "1.4.32"
  id("org.springframework.boot") version "2.4.4"
  id("io.spring.dependency-management") version "1.0.11.RELEASE"
  kotlin("jvm") version "1.4.32"
  kotlin("plugin.spring") version "1.4.32"
}

Compiler options

One of Kotlin’s key features is null-safety - which cleanly deals with null values at compile time rather than bumping into the famous NullPointerException at runtime. This makes applications safer through nullability declarations and expressing "value or no value" semantics without paying the cost of wrappers like Optional. Note that Kotlin allows using functional constructs with nullable values; check out this comprehensive guide to Kotlin null-safety.

Although Java does not allow one to express null-safety in its type-system, Spring Framework provides null-safety of the whole Spring Framework API via tooling-friendly annotations declared in the org.springframework.lang package. By default, types from Java APIs used in Kotlin are recognized as platform types for which null-checks are relaxed. Kotlin support for JSR 305 annotations + Spring nullability annotations provide null-safety for the whole Spring Framework API to Kotlin developers, with the advantage of dealing with null related issues at compile time.

This feature can be enabled by adding the -Xjsr305 compiler flag with the strict options.

Notice also that Kotlin compiler is configured to generate Java 8 bytecode (Java 6 by default).

build.gradle.kts

tasks.withType<KotlinCompile> {
  kotlinOptions {
    freeCompilerArgs = listOf("-Xjsr305=strict")
    jvmTarget = "1.8"
  }
}

Dependencies

3 Kotlin specific libraries are required for such Spring Boot web application and configured by default:

  • kotlin-stdlib-jdk8 is the Java 8 variant of Kotlin standard library

  • kotlin-reflect is Kotlin reflection library

  • jackson-module-kotlin adds support for serialization/deserialization of Kotlin classes and data classes (single constructor classes can be used automatically, and those with secondary constructors or static factories are also supported)

build.gradle.kts

dependencies {
  implementation("org.springframework.boot:spring-boot-starter-data-jpa")
  implementation("org.springframework.boot:spring-boot-starter-mustache")
  implementation("org.springframework.boot:spring-boot-starter-web")
  implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
  implementation("org.jetbrains.kotlin:kotlin-reflect")
  implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
  runtimeOnly("com.h2database:h2")
  runtimeOnly("org.springframework.boot:spring-boot-devtools")
  testImplementation("org.springframework.boot:spring-boot-starter-test")
}

Spring Boot Gradle plugin automatically uses the Kotlin version declared via the Kotlin Gradle plugin.

Understanding the Maven Build

Plugins

In addition to the obvious Kotlin Maven plugin, the default configuration declares the kotlin-spring plugin which automatically opens classes and methods (unlike in Java, the default qualifier is final in Kotlin) annotated or meta-annotated with Spring annotations. This is useful to be able to create @Configuration or @Transactional beans without having to add the open qualifier required by CGLIB proxies for example.

In order to be able to use Kotlin non-nullable properties with JPA, Kotlin JPA plugin is also enabled. It generates no-arg constructors for any class annotated with @Entity, @MappedSuperclass or @Embeddable.

pom.xml

<build>
    <sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
    <testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
      <plugin>
        <groupId>org.jetbrains.kotlin</groupId>
        <artifactId>kotlin-maven-plugin</artifactId>
        <configuration>
          <compilerPlugins>
            <plugin>jpa</plugin>
            <plugin>spring</plugin>
          </compilerPlugins>
          <args>
            <arg>-Xjsr305=strict</arg>
          </args>
        </configuration>
        <dependencies>
          <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-noarg</artifactId>
            <version>${kotlin.version}</version>
          </dependency>
          <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-allopen</artifactId>
            <version>${kotlin.version}</version>
          </dependency>
        </dependencies>
      </plugin>
    </plugins>
  </build>

One of Kotlin’s key features is null-safety - which cleanly deals with null values at compile time rather than bumping into the famous NullPointerException at runtime. This makes applications safer through nullability declarations and expressing "value or no value" semantics without paying the cost of wrappers like Optional. Note that Kotlin allows using functional constructs with nullable values; check out this comprehensive guide to Kotlin null-safety.

Although Java does not allow one to express null-safety in its type-system, Spring Framework provides null-safety of the whole Spring Framework API via tooling-friendly annotations declared in the org.springframework.lang package. By default, types from Java APIs used in Kotlin are recognized as platform types for which null-checks are relaxed. Kotlin support for JSR 305 annotations + Spring nullability annotations provide null-safety for the whole Spring Framework API to Kotlin developers, with the advantage of dealing with null related issues at compile time.

This feature can be enabled by adding the -Xjsr305 compiler flag with the strict options.

Notice also that Kotlin compiler is configured to generate Java 8 bytecode (Java 6 by default).

Dependencies

3 Kotlin specific libraries are required for such Spring Boot web application and configured by default:

  • kotlin-stdlib-jdk8 is the Java 8 variant of Kotlin standard library

  • kotlin-reflect is Kotlin reflection library (mandatory as of Spring Framework 5)

  • jackson-module-kotlin adds support for serialization/deserialization of Kotlin classes and data classes (single constructor classes can be used automatically, and those with secondary constructors or static factories are also supported)

pom.xml

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mustache</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>com.fasterxml.jackson.module</groupId>
    <artifactId>jackson-module-kotlin</artifactId>
  </dependency>
  <dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-reflect</artifactId>
  </dependency>
  <dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-stdlib-jdk8</artifactId>
  </dependency>

  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
  </dependency>
  <dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
  </dependency>
</dependencies>

Understanding the generated Application

src/main/kotlin/com/example/blog/BlogApplication.kt

package com.example.blog

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class BlogApplication

fun main(args: Array<String>) {
  runApplication<BlogApplication>(*args)
}

Compared to Java, you can notice the lack of semicolons, the lack of brackets on empty class (you can add some if you need to declare beans via @Bean annotation) and the use of runApplication top level function. runApplication<BlogApplication>(*args) is Kotlin idiomatic alternative to SpringApplication.run(BlogApplication::class.java, *args) and can be used to customize the application with following syntax.

src/main/kotlin/com/example/blog/BlogApplication.kt

fun main(args: Array<String>) {
  runApplication<BlogApplication>(*args) {
    setBannerMode(Banner.Mode.OFF)
  }
}

Writing your first Kotlin controller

Let’s create a simple controller to display a simple web page.

src/main/kotlin/com/example/blog/HtmlController.kt

package com.example.blog

import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.ui.set
import org.springframework.web.bind.annotation.GetMapping

@Controller
class HtmlController {

  @GetMapping("/")
  fun blog(model: Model): String {
    model["title"] = "Blog"
    return "blog"
  }

}

Notice that we are using here a Kotlin extension that allows to add Kotlin functions or operators to existing Spring types. Here we import the org.springframework.ui.set extension function in order to be able to write model["title"] = "Blog" instead of model.addAttribute("title", "Blog"). The Spring Framework KDoc API lists all the Kotlin extensions provided to enrich the Java API.

We also need to create the associated Mustache templates.

src/main/resources/templates/header.mustache

<html>
<head>
  <title>{{title}}</title>
</head>
<body>

src/main/resources/templates/footer.mustache

</body>
</html>

src/main/resources/templates/blog.mustache

{{> header}}

<h1>{{title}}</h1>

{{> footer}}

Start the web application by running the main function of BlogApplication.kt, and go to http://localhost:8080/, you should see a sober web page with a "Blog" headline.

Testing with JUnit 5

JUnit 5 now used by default in Spring Boot provides various features very handy with Kotlin, including autowiring of constructor/method parameters which allows to use non-nullable val properties and the possibility to use @BeforeAll/@AfterAll on regular non-static methods.

Writing JUnit 5 tests in Kotlin

For the sake of this example, let’s create an integration test in order to demonstrate various features:

  • We use real sentences between backticks instead of camel-case to provide expressive test function names

  • JUnit 5 allows to inject constructor and method parameters, which is a good fit with Kotlin read-only and non-nullable properties

  • This code leverages getForObject and getForEntity Kotlin extensions (you need to import them)

src/test/kotlin/com/example/blog/IntegrationTests.kt

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class IntegrationTests(@Autowired val restTemplate: TestRestTemplate) {

  @Test
  fun `Assert blog page title, content and status code`() {
    val entity = restTemplate.getForEntity<String>("/")
    assertThat(entity.statusCode).isEqualTo(HttpStatus.OK)
    assertThat(entity.body).contains("<h1>Blog</h1>")
  }

}

Test instance lifecycle

Sometimes you need to execute a method before or after all tests of a given class. Like Junit 4, JUnit 5 requires by default these methods to be static (which translates to companion object in Kotlin, which is quite verbose and not straightforward) because test classes are instantiated one time per test.

But Junit 5 allows you to change this default behavior and instantiate test classes one time per class. This can be done in various ways, here we will use a property file to change the default behavior for the whole project:

src/test/resources/junit-platform.properties

junit.jupiter.testinstance.lifecycle.default = per_class

With this configuration, we can now use @BeforeAll and @AfterAll annotations on regular methods like shown in updated version of IntegrationTests above.

src/test/kotlin/com/example/blog/IntegrationTests.kt

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class IntegrationTests(@Autowired val restTemplate: TestRestTemplate) {

  @BeforeAll
  fun setup() {
    println(">> Setup")
  }

  @Test
  fun `Assert blog page title, content and status code`() {
    println(">> Assert blog page title, content and status code")
    val entity = restTemplate.getForEntity<String>("/")
    assertThat(entity.statusCode).isEqualTo(HttpStatus.OK)
    assertThat(entity.body).contains("<h1>Blog</h1>")
  }

  @Test
  fun `Assert article page title, content and status code`() {
    println(">> TODO")
  }

  @AfterAll
  fun teardown() {
    println(">> Tear down")
  }

}

Creating your own extensions

Instead of using util classes with abstract methods like in Java, it is usual in Kotlin to provide such functionalities via Kotlin extensions. Here we are going to add a format() function to the existing LocalDateTime type in order to generate text with the English date format.

src/main/kotlin/com/example/blog/Extensions.kt

fun LocalDateTime.format() = this.format(englishDateFormatter)

private val daysLookup = (1..31).associate { it.toLong() to getOrdinal(it) }

private val englishDateFormatter = DateTimeFormatterBuilder()
    .appendPattern("yyyy-MM-dd")
    .appendLiteral(" ")
    .appendText(ChronoField.DAY_OF_MONTH, daysLookup)
    .appendLiteral(" ")
    .appendPattern("yyyy")
    .toFormatter(Locale.ENGLISH)

private fun getOrdinal(n: Int) = when {
  n in 11..13 -> "${n}th"
  n % 10 == 1 -> "${n}st"
  n % 10 == 2 -> "${n}nd"
  n % 10 == 3 -> "${n}rd"
  else -> "${n}th"
}

fun String.toSlug() = toLowerCase()
    .replace("\n", " ")
    .replace("[^a-z\\d\\s]".toRegex(), " ")
    .split(" ")
    .joinToString("-")
    .replace("-+".toRegex(), "-")

We will leverage these extensions in the next section.

Persistence with JPA

In order to make lazy fetching working as expected, entities should be open as described in KT-28525. We are going to use the Kotlin allopen plugin for that purpose.

With Gradle:

build.gradle.kts

plugins {
  ...
  kotlin("plugin.allopen") version "1.4.32"
}

allOpen {
  annotation("javax.persistence.Entity")
  annotation("javax.persistence.Embeddable")
  annotation("javax.persistence.MappedSuperclass")
}

Or with Maven:

pom.xml


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap