Spring Boot 和 Kotlin構建web應用程序

原文地址:https://spring.io/guides/tutorials/spring-boot-kotlin/

目錄

創建一個新項目

使用Initializr網站

使用命令行

使用IntelliJ IDEA

 支持構建方式

瞭解生成的應用程序

編寫您的第一個Kotlin控制器

使用JUnit 5進行測試

用Kotlin編寫JUnit 5測試

測試實例生命週期

創建自己的擴展

JPA的持久性

實施博客引擎

公開HTTP API

配置屬性

結論


本教程向您展示如何通過結合Spring BootKotlin的功能來有效地構建示例博客應用程序。

如果您是從Kotlin開始的,則可以通過閱讀參考文檔,遵循在線Kotlin Koans教程或僅使用Spring Framework參考文檔(現在在Kotlin中提供代碼示例)來學習該語言。

Spring Kotlin支持在Spring FrameworkSpring Boot參考文檔中有所記錄。如果您需要幫助,請在StackOverflow上使用springkotlin標記進行搜索或提出問題,或者在Kotlin Slack#spring頻道中進行討論。

創建一個新項目

首先,我們需要創建一個Spring Boot應用程序,可以通過多種方式來完成。

使用Initializr網站

訪問https://start.spring.io並選擇Kotlin語言。Gradle是Kotlin中最常用的構建工具,它提供了Kotlin DSL,在生成Kotlin項目時默認使用該DSL,因此這是推薦的選擇。但是,如果您更喜歡Maven,也可以使用它。請注意,您可以使用https://start.spring.io/#!language=kotlin&type=gradle-project默認情況下選擇Kotlin和Gradle。

1.選擇“ Gradle Project”或根據您要使用的構建工具設置默認的“ Maven Project”

2.輸入以下工件座標: blog

3.添加以下依賴項:

  • Spring Web

  • Mustache

  • Spring Data JPA

  • H2 Database

  • Spring Boot DevTools

4.點擊“生成項目”。

 

.zip文件在根目錄中包含一個標準項目,因此您可能需要在解壓縮之前創建一個空目錄。

使用命令行

您可以從命令行中使用Initializr HTTP API ,例如,在類似UN * X的系統上使用curl:

$ 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

-d type=gradle-project如果要使用Gradle,請添加。

 

使用IntelliJ IDEA

Spring Initializr還集成在IntelliJ IDEA Ultimate版本中,使您可以創建和導入新項目,而不必將IDE留給命令行或Web UI。

要訪問該向導,請轉到“文件” |“其他”。新增| 項目,然後選擇Spring Initializr。

請按照嚮導的步驟使用以下參數:

  • 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"

 支持構建方式

  • Gradle Build

  • Maven Build

瞭解生成的應用程序

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)
}

與Java相比,您會注意到缺少分號,在空類上缺少括號(如果需要通過@Bean註釋聲明bean,可以添加一些括號)以及使用runApplication頂層函數。runApplication<BlogApplication>(*args)是Kotlin的慣用替代品SpringApplication.run(BlogApplication::class.java, *args),可用於使用以下語法來自定義應用程序。

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

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

編寫您的第一個Kotlin控制器

讓我們創建一個簡單的控制器來顯示一個簡單的網頁。

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"
  }

}

請注意,這裏我們使用的是Kotlin擴展,該擴展允許向現有的Spring類型添加Kotlin函數或運算符。在這裏,我們導入org.springframework.ui.set擴展功能是爲了能夠model["title"] = "Blog"代替編寫model.addAttribute("title", "Blog")。在Spring框架KDOC API列出了所有提供豐富的Java API的科特林擴展。

我們還需要創建關聯的Mustache模板。

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}}

通過運行的main功能啓動Web應用程序BlogApplication.kt,然後轉到http://localhost:8080/,您應該會看到一個帶有“ Blog”標題的醒目的網頁。

使用JUnit 5進行測試

現在在Spring Boot中默認使用的JUnit 5提供了Kotlin非常方便的各種功能,包括自動裝配構造函數/方法參數,該參數允許使用不可爲null的val屬性,並可以在常規的非靜態方法上使用@BeforeAll@AfterAll

用Kotlin編寫JUnit 5測試

爲了這個示例,讓我們創建一個集成測試以演示各種功能:

  • 我們在反引號之間使用實詞而不是駝峯式大小寫來提供表達性的測試函數名稱

  • JUnit 5允許注入構造函數和方法參數,這與Kotlin只讀和不可爲空的屬性非常吻合

  • 此代碼利用getForObjectgetForEntityKotlin擴展(您需要導入它們)

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>")
  }

}

測試實例生命週期

有時您需要在給定類的所有測試之前或之後執行一個方法。像Junit 4一樣,默認情況下,JUnit 5要求這些方法是靜態的(companion object在Kotlin中轉換爲,這很冗長且不簡單),因爲每個測試都會實例化一次測試類。

但是Junit 5允許您更改此默認行爲,並在每個類一次實例化測試類。這可以通過多種方式完成,這裏我們將使用屬性文件來更改整個項目的默認行爲:

src/test/resources/junit-platform.properties

junit.jupiter.testinstance.lifecycle.default = per_class

通過此配置,我們現在可以在常規方法中使用@BeforeAll@AfterAll註釋,如IntegrationTests上面更新版本中所示。

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")
  }

}

創建自己的擴展

並非像Java中那樣將util類與抽象方法一起使用,而是在Kotlin中通常通過Kotlin擴展來提供此類功能。在這裏,我們將向format()現有LocalDateTime類型添加一個函數,以生成具有英語日期格式的文本。

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(), "-")

我們將在下一部分中利用這些擴展。

JPA的持久性

爲了使延遲獲取按預期方式工作,實體應openKT-28525中所述。我們將allopen爲此目的使用Kotlin插件。

使用Gradle:build.gradle.kts

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

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

或使用Maven:pom.xml

<plugin>
  <artifactId>kotlin-maven-plugin</artifactId>
  <groupId>org.jetbrains.kotlin</groupId>
  <configuration>
    ...
    <compilerPlugins>
      ...
      <plugin>all-open</plugin>
    </compilerPlugins>
    <pluginOptions>
      <option>all-open:annotation=javax.persistence.Entity</option>
      <option>all-open:annotation=javax.persistence.Embeddable</option>
      <option>all-open:annotation=javax.persistence.MappedSuperclass</option>
    </pluginOptions>
  </configuration>
</plugin>

然後,我們使用Kotlin主要構造函數的簡潔語法創建模型,該語法允許同時聲明屬性和構造函數參數。

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

@Entity
class Article(
    var title: String,
    var headline: String,
    var content: String,
    @ManyToOne var author: User,
    var slug: String = title.toSlug(),
    var addedAt: LocalDateTime = LocalDateTime.now(),
    @Id @GeneratedValue var id: Long? = null)

@Entity
class User(
    var login: String,
    var firstname: String,
    var lastname: String,
    var description: String? = null,
    @Id @GeneratedValue var id: Long? = null)

注意,我們在這裏使用String.toSlug()擴展來爲構造函數的slug參數提供默認參數Article。具有默認值的可選參數定義在最後一個位置,以便在使用位置參數時可以忽略它們(Kotlin也支持命名參數)。請注意,在Kotlin中,將簡潔的類聲明分組在同一文件中並不少見。

這裏我們不使用帶有屬性的dataval因爲JPA並非設計用於不可變的類或由data類自動生成的方法。如果您使用的是其他Spring Data風格,則大多數都旨在支持此類構造,因此您應使用諸如data class User(val login: String, …​)使用Spring Data MongoDB,Spring Data JDBC等之類的類。
  雖然Spring Data JPA可以通過使用自然ID(它可能是類中的login屬性UserPersistable,但由於KT-6653,它與Kotlin不太合適,因此建議始終在其中使用具有生成ID的實體科特林

我們還聲明瞭我們的Spring Data JPA存儲庫,如下所示。

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

interface ArticleRepository : CrudRepository<Article, Long> {
  fun findBySlug(slug: String): Article?
  fun findAllByOrderByAddedAtDesc(): Iterable<Article>
}

interface UserRepository : CrudRepository<User, Long> {
  fun findByLogin(login: String): User?
}

我們編寫JPA測試來檢查基本用例是否按預期工作。

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

@DataJpaTest
class RepositoriesTests @Autowired constructor(
    val entityManager: TestEntityManager,
    val userRepository: UserRepository,
    val articleRepository: ArticleRepository) {

  @Test
  fun `When findByIdOrNull then return Article`() {
    val juergen = User("springjuergen", "Juergen", "Hoeller")
    entityManager.persist(juergen)
    val article = Article("Spring Framework 5.0 goes GA", "Dear Spring community ...", "Lorem ipsum", juergen)
    entityManager.persist(article)
    entityManager.flush()
    val found = articleRepository.findByIdOrNull(article.id!!)
    assertThat(found).isEqualTo(article)
  }

  @Test
  fun `When findByLogin then return User`() {
    val juergen = User("springjuergen", "Juergen", "Hoeller")
    entityManager.persist(juergen)
    entityManager.flush()
    val user = userRepository.findByLogin(juergen.login)
    assertThat(user).isEqualTo(juergen)
  }
}

我們在這裏CrudRepository.findByIdOrNull使用Spring Data默認提供的Kotlin擴展,它是Optionalbased的可空變量CrudRepository.findById。閱讀Null是您的朋友,而不是錯誤的博客文章,以瞭解更多詳細信息。

實施博客引擎

我們更新了“博客” Mustache模板。

src/main/resources/templates/blog.mustache

{
              
               {> header}}

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

<div class="articles">

  {
              
               {#articles}}
    <section>
      <header class="article-header">
        <h2 class="article-title"><a href="/article/{
              
               {slug}}">{
              
               {title}}</a></h2>
        <div class="article-meta">By  <strong>{
              
               {author.firstname}}</strong>, on <strong>{
              
               {addedAt}}</strong></div>
      </header>
      <div class="article-description">
        {
              
               {headline}}
      </div>
    </section>
  {
              
               {/articles}}
</div>

{
              
               {> footer}}

我們創建了一個新的“文章”。

src/main/resources/templates/article.mustache

{
               
                {> header}}

<section class="article">
  <header class="article-header">
    <h1 class="article-title">{
               
                {article.title}}</h1>
    <p class="article-meta">By  <strong>{
               
                {article.author.firstname}}</strong>, on <strong>{
               
                {article.addedAt}}</strong></p>
  </header>

  <div class="article-description">
    {
               
                {article.headline}}

    {
               
                {article.content}}
  </div>
</section>

{
               
                {> footer}}

我們更新HtmlController,以使用格式化的日期呈現博客和文章頁面。ArticleRepository並且MarkdownConverter構造函數參數將自動自動關聯,因爲HtmlController只有一個構造函數(隱式@Autowired)。

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

@Controller
class HtmlController(private val repository: ArticleRepository) {

  @GetMapping("/")
  fun blog(model: Model): String {
    model["title"] = "Blog"
    model["articles"] = repository.findAllByOrderByAddedAtDesc().map { it.render() }
    return "blog"
  }

  @GetMapping("/article/{slug}")
  fun article(@PathVariable slug: String, model: Model): String {
    val article = repository
        .findBySlug(slug)
        ?.render()
        ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "This article does not exist")
    model["title"] = article.title
    model["article"] = article
    return "article"
  }

  fun Article.render() = RenderedArticle(
      slug,
      title,
      headline,
      content,
      author,
      addedAt.format()
  )

  data class RenderedArticle(
      val slug: String,
      val title: String,
      val headline: String,
      val content: String,
      val author: User,
      val addedAt: String)

}

然後,我們將數據初始化添加到新BlogConfiguration類中。

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

@Configuration
class BlogConfiguration {

    @Bean
    fun databaseInitializer(userRepository: UserRepository,
                            articleRepository: ArticleRepository) = ApplicationRunner {

        val smaldini = userRepository.save(User("smaldini", "Stéphane", "Maldini"))
        articleRepository.save(Article(
                title = "Reactor Bismuth is out",
                headline = "Lorem ipsum",
                content = "dolor sit amet",
                author = smaldini
        ))
        articleRepository.save(Article(
                title = "Reactor Aluminium has landed",
                headline = "Lorem ipsum",
                content = "dolor sit amet",
                author = smaldini
        ))
    }
}
 

請注意使用命名參數來使代碼更具可讀性。 

並且我們還將相應地更新集成測試。

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>", "Reactor")
  }

  @Test
  fun `Assert article page title, content and status code`() {
    println(">> Assert article page title, content and status code")
    val title = "Reactor Aluminium has landed"
    val entity = restTemplate.getForEntity<String>("/article/${title.toSlug()}")
    assertThat(entity.statusCode).isEqualTo(HttpStatus.OK)
    assertThat(entity.body).contains(title, "Lorem ipsum", "dolor sit amet")
  }

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

}

啓動(或重新啓動)Web應用程序,然後轉到http://localhost:8080/,您應該看到帶有可單擊鏈接的文章列表,以查看特定文章。

公開HTTP API

現在,我們將通過帶@RestController註釋的控制器來實現HTTP API 。

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

@RestController
@RequestMapping("/api/article")
class ArticleController(private val repository: ArticleRepository) {

  @GetMapping("/")
  fun findAll() = repository.findAllByOrderByAddedAtDesc()

  @GetMapping("/{slug}")
  fun findOne(@PathVariable slug: String) =
      repository.findBySlug(slug) ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "This article does not exist")

}

@RestController
@RequestMapping("/api/user")
class UserController(private val repository: UserRepository) {

  @GetMapping("/")
  fun findAll() = repository.findAll()

  @GetMapping("/{login}")
  fun findOne(@PathVariable login: String) =
      repository.findByLogin(login) ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "This user does not exist")
}

對於測試,而不是集成測試,我們將利用@WebMvcTestMockk相似的Mockk,但更適合Kotlin。

由於@MockBean@SpyBean註釋是針對的Mockito,我們要充分利用SpringMockK提供類似@MockkBean@SpykBean爲Mockk註釋。

使用Gradle:build.gradle.kts

testImplementation("org.springframework.boot:spring-boot-starter-test") {
  exclude(module = "junit")
  exclude(module = "mockito-core")
}
testImplementation("org.junit.jupiter:junit-jupiter-api")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
testImplementation("com.ninja-squad:springmockk:1.1.3")

或使用Maven:pom.xml

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
  <exclusions>
    <exclusion>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
    </exclusion>
    <exclusion>
      <groupId>org.mockito</groupId>
      <artifactId>mockito-core</artifactId>
    </exclusion>
  </exclusions>
</dependency>
<dependency>
  <groupId>org.junit.jupiter</groupId>
  <artifactId>junit-jupiter-engine</artifactId>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>com.ninja-squad</groupId>
  <artifactId>springmockk</artifactId>
  <version>1.1.3</version>
  <scope>test</scope>
</dependency>

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

@WebMvcTest
class HttpControllersTests(@Autowired val mockMvc: MockMvc) {

  @MockkBean
  private lateinit var userRepository: UserRepository

  @MockkBean
  private lateinit var articleRepository: ArticleRepository

  @Test
  fun `List articles`() {
    val juergen = User("springjuergen", "Juergen", "Hoeller")
    val spring5Article = Article("Spring Framework 5.0 goes GA", "Dear Spring community ...", "Lorem ipsum", juergen)
    val spring43Article = Article("Spring Framework 4.3 goes GA", "Dear Spring community ...", "Lorem ipsum", juergen)
    every { articleRepository.findAllByOrderByAddedAtDesc() } returns listOf(spring5Article, spring43Article)
    mockMvc.perform(get("/api/article/").accept(MediaType.APPLICATION_JSON))
        .andExpect(status().isOk)
        .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
        .andExpect(jsonPath("\$.[0].author.login").value(juergen.login))
        .andExpect(jsonPath("\$.[0].slug").value(spring5Article.slug))
        .andExpect(jsonPath("\$.[1].author.login").value(juergen.login))
        .andExpect(jsonPath("\$.[1].slug").value(spring43Article.slug))
  }

  @Test
  fun `List users`() {
    val juergen = User("springjuergen", "Juergen", "Hoeller")
    val smaldini = User("smaldini", "Stéphane", "Maldini")
    every { userRepository.findAll() } returns listOf(juergen, smaldini)
    mockMvc.perform(get("/api/user/").accept(MediaType.APPLICATION_JSON))
        .andExpect(status().isOk)
        .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
        .andExpect(jsonPath("\$.[0].login").value(juergen.login))
        .andExpect(jsonPath("\$.[1].login").value(smaldini.login))
  }
}
  $ 需要在字符串中轉義,因爲它用於字符串插值。

配置屬性

在Kotlin中,推薦的管理應用程序屬性的方法是利用@ConfigurationProperties@ConstructorBinding以便能夠使用只讀屬性。

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

@ConstructorBinding
@ConfigurationProperties("blog")
data class BlogProperties(var title: String, val banner: Banner) {
  data class Banner(val title: String? = null, val content: String)
}

然後,我們在BlogApplication級別上啓用它。

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

@SpringBootApplication
@EnableConfigurationProperties(BlogProperties::class)
class BlogApplication {
  // ...
}

要生成自己的元數據,以獲得您的IDE認識到這些自定義屬性,kapt應配置spring-boot-configuration-processor依賴如下。

build.gradle.kts

plugins {
  ...
  kotlin("kapt") version "1.3.61"
}

dependencies {
  ...
  kapt("org.springframework.boot:spring-boot-configuration-processor")
}

請注意,由於kapt提供的模型的限制,某些功能(例如檢測默認值或不推薦使用的項目)無法正常工作。此外,由於KT-18022,Maven還不支持註釋處理,有關更多詳細信息,請參見initializr#438

在IntelliJ IDEA中:

  • 確保在菜單File | File中啓用了Spring Boot插件。設置| 插件| SpringBoot

  • 通過菜單文件|啓用註釋處理 設置| 構建,執行,部署| 編譯器 註釋處理器| 啓用註釋處理

  • 由於Kapt尚未集成在IDEA中,因此您需要手動運行命令./gradlew kaptKotlin以生成元數據

現在,在編輯application.properties(自動完成,驗證等)時,應該可以識別您的自定義屬性。

src/main/resources/application.properties

blog.title=Blog
blog.banner.title=Warning
blog.banner.content=The blog will be down tomorrow.

相應地編輯模板和控制器。

src/main/resources/templates/blog.mustache

{
                  
                   {> header}}

<div class="articles">

  {
                  
                   {#banner.title}}
  <section>
    <header class="banner">
      <h2 class="banner-title">{
                  
                   {banner.title}}</h2>
    </header>
    <div class="banner-content">
      {
                  
                   {banner.content}}
    </div>
  </section>
  {
                  
                   {/banner.title}}

  ...

</div>

{
                  
                   {> footer}}

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

@Controller
class HtmlController(private val repository: ArticleRepository,
           private val properties: BlogProperties) {

  @GetMapping("/")
  fun blog(model: Model): String {
    model["title"] = properties.title
    model["banner"] = properties.banner
    model["articles"] = repository.findAllByOrderByAddedAtDesc().map { it.render() }
    return "blog"
  }

  // ...

重新啓動Web應用程序,刷新http://localhost:8080/,您應該在博客主頁上看到橫幅。

結論

現在,我們已完成構建此示例Kotlin博客應用程序。源代碼可在Github上獲得。如果您需要有關特定功能的更多詳細信息,還可以查看Spring FrameworkSpring Boot參考文檔。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章