当前位置:网站首页 > Kotlin开发 > 正文

org.springframework.core.kotlindetector_什么是应用程序

使用Kotlin开发Spring Boot应用程序

最近把KeyOA从Java前移到了Kotlin进行开发,下面说一下需要注意的事项以及一些Kotlin的语法。

在Spring Boot中引入Kotlin

// build.gradle.kts import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { 
    id("org.springframework.boot") version "3.0.1" id("io.spring.dependency-management") version "1.1.0" kotlin("jvm") version "1.8.0" kotlin("plugin.spring") version "1.8.0" kotlin("plugin.jpa") version "1.8.0" } // 让Spring支持Kotlin的Null Safety tasks.withType<KotlinCompile> { 
    kotlinOptions { 
    freeCompilerArgs += "-Xjsr305=strict" } } 
// 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") // Jackson Kotlin支持 implementation("org.jetbrains.kotlin:kotlin-reflect") // Kotlin反射 runtimeOnly("org.springframework.boot:spring-boot-devtools") testImplementation("org.springframework.boot:spring-boot-starter-test") } 
<!-- 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> 
<!-- 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</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> 

应用的Main Class:

package com.jydjal.oa import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication @SpringBootApplication class OaApplication fun main(args: Array<String>) { 
    runApplication<OaApplication>(*args) { 
    setBannerMode(Banner.Mode.OFF) // 关闭Spring Boot Banner,可选 } } 

OOP&依赖注入

Kotlin中的类,成员变量和构造函数:

class Person( val name: String, val age: Int, // 前两个是构造函数参数,同时也是类的成员变量 height: Int, // 构造函数的普通参数,后面逗号可以不省略 ) { 
    val height: Int = height // 成员变量,但是未在构造函数中 } 

在Kotlin中使用@Autowired注解实现依赖注入,这个注解可以放在:Constructor、Method、Parameter、Field、AnnotationType

@Service class EmployeeService{ 
    // 逻辑代码略 } @Controller class EmployeeController(@Autowired val service: EmployeeService) { 
    // 逻辑代码略 } // 如果需要注入的成员变量过多,则可以把注解放在构造方法上,这是需要在构造方法前添加constructor关键字 @Controller class EmployeeController @Autowired constructor(val service: EmployeeService) { 
    // 逻辑代码略 } 

日志(以Slf4j为例)

在Java中,如果需要使用日志可以使用Lombok的@Slf4j注解,但是在Kotlin中,这个注解失效了,因此需要手动获取logger。

在这里我使用扩展方法来实现,然后解释原因:

// 获取logger的函数,这里使用Slf4j fun <T : Any> T.logger(): Lazy<Logger> { 
    // 使logger的名字始终和最外层的类一致,即使是在companion object中初始化属性 val ofClass = this.javaClass val clazz = ofClass.enclosingClass?.takeIf { 
    ofClass.enclosingClass.kotlin.companionObject?.java == ofClass } ?: ofClass return lazy { 
    LoggerFactory.getLogger(clazz) } } // 使用logger函数 class TestClass{ 
    val logger1 by logger() // 或者使用companion object companion object { 
    val logger2 by logger() } fun testLog() { 
    logger1.info("Name: logger1, Level: info.") logger2.debug("Name: logger2, Level: debug.") } } 

现在给出原因,首先是把logger作为类的成员变量获取:

class TestClass{ 
    val logger = LoggerFactory.getLogger(TestClass::class.java) fun testLog() = logger.info("This is a test log.") // 这是Kotlin中的单表达式函数,参见附录的Kotlin Idioms } 

但是这样会有两个弊端:1:每个logger都需要手动编写代码实现,比较麻烦;2:每个类的实例都会有一个logger变量(虽然日志框架会有logger缓存机制,影响不大)。

针对第一个问题,我们可以使用扩展方法实现:

fun <T: Any> T.logger(): Logger { 
    return LoggerFactory.getLogger(T::class.java) } 

针对第二个问题,可以考虑使用companion object

class TestClass{ 
    companion object { 
    val logger = LoggerFactory.getLogger(TestClass::class.java) } fun testLog() = logger.info("This is a test log.") } 

但是在companion object中的logger获取的类名不正确,变成了TestClass.Companion,因此需要让companion中的logger也能获取外部类的名称,于是就有了上面获取外部类的操作。

// 使logger的名字始终和最外层的类一致,即使是在companion object中初始化属性 val ofClass = this.javaClass val clazz = ofClass.enclosingClass?.takeIf { 
    ofClass.enclosingClass.kotlin.companionObject?.java == ofClass } ?: ofClass 

最后使用Lazy,让logger在使用时才进行计算,就得到了上面的方法。

其实还有其他的方法可以获取logger的实例,详见参考链接第三条StackOverflow上的回答。

测试

我们将使用Spring Boot Starter Test完成教学,包含JUnit5、MockMvc和AssertJ三部分。

JUnit5

@SpringBootTest class TestEmployee { 
    @Test fun `Test employee setter`() { 
    // 在Kotlin中,测试方法名称可以用反引号(数字1左边那个)括起来作为测试的名称 val employee: Employee? = Employee("张三", "男") assertThat(employee).isNotNull() } } 

关于JUnit5的测试实例生命周期:在JUnit5中支持使用@BeforeAll@AfterAll注解在测试开始前和结束后做一些额外的工作,这需要被标记上注解的方法要是静态方法,在Kotlin中这需要通过companion object实现,这样做非常麻烦。但是JUnit5可以让测试在每个类上实例化,这就需要我们手动修改JUnit5的测试生命周期:

# src/test/resources/junit-platform.properties junit.jupiter.testinstance.lifecycle.default = per_class 

这样@BeforeAll和@AfterAll注解就可以加在普通方法上,实现起来也就容易了许多。

MockMvc

这里直接给出实例:

@SpringBootTest @AutoConfigureMockMvc class LoginTest @Autowired constructor( private val mockMvc: MockMvc, private val mapper: ObjectMapper) { 
    private lateinit var token: String fun testLoginSuccess() { 
    val loginDto = mapOf("account" to "admin", "password" to "1234qwer") mockMvc.post("/login") { 
    contentType = MediaType.APPLICATION_JSON content = mapper.writeValueToString(loginDto) header("isAdmin", true) }.andExpect{ 
    jsonPath("$.message") { 
    value("OK") } jsonPath("$.data") { 
    exists() } }.andDo{ 
    print() handle{ 
    val content = it.response.contentAsString token = mapper.readValue(content, object: TypeReference<JsonResponse<String>>() { 
   }).data } } } } 

具体用法见参考链接中的MockMvc Kotlin Dsl。

AssertJ

这部分使用Kotlin和使用Java没有太大区别,有区别的地方按照IDE提示修改即可,主要有两点:

  1. assertThat(something).as(”Description”):IDE会提示在as上扩上反引号
  2. 有些地方比如assertThat(”string”).isNotEmpty():IDE会提示将isNotEmpty的括号去掉,改成属性访问的形式

数据库实体定义

首先引入依赖

// build.gradle.kts plugins { 
    ... kotlin("plugin.allopen") version "1.8.0" } allOpen { 
    annotation("jakarta.persistence.Entity") annotation("jakarta.persistence.Embeddable") annotation("jakarta.persistence.MappedSuperclass") } 
<!-- 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=jakarta.persistence.Entity</option> <option>all-open:annotation=jakarta.persistence.Embeddable</option> <option>all-open:annotation=jakarta.persistence.MappedSuperclass</option> </pluginOptions> </configuration> </plugin> 

实体类定义:

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

注意:因为Spring Data Jpa无法处理不可变的类型,因此在这里成员变量需要是可变的;如果你使用其他的框架比如Spring Data Jdbc、Spring Data MongoDB等,则可以把var改成val。

同时建议在每个实体类上使用Generated Id(@Id @GeneratedValue),原因如下:Kotlin properties do not override Java-style getters and setters。

开发杂项

变量的相等判断

在Kotlin中,“ == ”相当于Java中的equals()方法,只判断值是否相等;“ === ”相当于Java中的“ == ”,同时判断地址是否一致。

异常处理

Kotlin支持Java异常的大部分写法,但有部分不一致:

// 抛出一个异常 throw Exception("This is an exception.") // 捕获一个异常 try { 
    ... } catch (e: Exception) { 
    ... } finally { 
    ... } // try表达式 val a: Int? = try { 
    input.toInt() } catch (e: NumberFormatException) { 
    null } // throws的Kotlin写法,等价于 void function() throws Exception {} @Throws(Exception::class) fun function() { 
   } 

同时Kotlin还有一个叫Nothing的类型,用来表示永远不可能有返回结果,即总会返回异常,详见参考链接中的Kotlin Nothing Type。

匿名类和内部类

// 匿名类的使用,摘自Kotlin Documentation window.addMouseListener(object : MouseAdapter() { 
    override fun mouseClicked(e: MouseEvent) { 
    /*...*/ } override fun mouseEntered(e: MouseEvent) { 
    /*...*/ } }) // 内部类的使用,同Java一样,内部类也可以访问外部类的数据 class OuterClass{ 
    var num: Int = 1 inner class InnerClass{ 
    fun foo() = num } } 

内部类还有this的作用域问题,详见参考链接中的Kotlin This。

常量定义

在Java中定义常量需要放在类中进行:

// Constant.java class Constant { 
    public static final String str = "This is a constant string."; } 

在Kotlin中的常量可以直接写在文件中,例如:

// Constant.kt const val str: String = "This is a constant string" // 使用常量 // UseConstant.kt import Constant.str println(str) 

常用数据结构及相关方法

Kotlin中支持的数据结构如下图所示:
在这里插入图片描述

其中MutableList、MutableSet、MutableMap支持向其中添加或删除元素,List、Set、Map则不支持。

Kotlin的List支持的常用操作:

val numbers = listOf(1, 2, 3, 4, 5) println(numbers.indexOf(2)) println(numbers.lastIndexOf(4)) println(numbers.indexOfFirst{ 
    it > 3 }) println(numbers.indexOfLast{ 
    it < 5 }) println(numbers.binarySearch(3)) // 使用二分查找 

Kotlin的Set支持交并补的操作:

val numbers = setOf("one", "two", "three") println(numbers intersect setOf("two", "one")) // 交 println(numbers union setOf("four", "five")) // 并 println(numbers subtract setOf("three", "four")) // 补 

值得注意的是,Kotlin的Map支持使用方括号的形式访问元素:

val map: Map<String, int> = mutableMapOf("one" to 1, "two" to 2) // 此方法创建LinkedHashMap,保存记录顺序,此外还有不保存记录顺序的HashMap map["three"] = 3 // 等价于map.push("three", 3),Java和Kotlin同理 println(map["one"]) // 1 map -= "one" // 等价于map.remove("one") 

在Kotlin中,filter、map、groupBy等操作也有相应的支持,详见参考链接中的Kotlin Idioms和Kotlin Collections。

类型判断、类型转换和空值判断

Kotlin支持可空类型,举例如下:

var str: String? = null // 使用Type?来定义一个可空类型 val str2: String = null // 报错 str = "123" val str3 = str!! // 使用!!将可空类型转成不可空类型,如果此变量值为null那么就会报错 val len = str?.length // 如果str不为null,则返回它的长度,否则返回null str?.let{ 
    println(str) } // 如果str不为null,则进行函数调用,否则不执行 

Kotlin的类型判断和类型转换:

val b: Int? = a as? Int // 尝试类型转换,如果不成功就返回null val b: Int? = a as Int // 尝试类型转换,如果不成功就报错 // Kotlin类型判断 if (obj is String) { 
    println(obj.length) } // 这里会自动转换类型 if (obj !is String) { 
   } // 等价于if (!(obj is String)) 

Kotlin中有一个名为Any的类型,表示任何类型,但是Java中的Object对应Kotlin中的Any?,因为Java中的Object可以是null。

函数和扩展方法

Kotlin的函数支持默认参数和按名传参:

fun test(a: Int? = null, str: String = "") { 
    ... } test(str = "") 

如果Kotlin的函数比较简短,那么可以变成一个单表达式函数:

fun test(): Int = 42 

Kotlin支持扩展函数,从而更加方便地为已有的类型添加功能:

// 定义方法:fun Type.funcName() { this.xxxxx },扩展函数同样支持参数列表 fun String.spaceToCamelCase() { 
    ... this.xxx() // 使用this访问对象 ... } // 使用扩展函数 "Convert this to camelcase".spaceToCamelCase() 

参考链接

  1. Getting Started | Building web applications with Spring Boot and Kotlin
  2. Kotlin Idioms
  3. https://stackoverflow.com/a//
  4. Is it possible to use Lombok with Kotlin? - Stack Overflow
  5. MockMvc Kotlin Dsl
  6. Kotlin Nothing Type
  7. Kotlin This
  8. Kotlin Collections
  9. Kotlin Docs | Kotlin Documentation (kotlinlang.org)
到此这篇org.springframework.core.kotlindetector_什么是应用程序的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!

版权声明


相关文章:

  • kotlin程序开发入门精要_kotlin web开发2024-11-04 15:50:07
  • kotlin开发windows界面_kotlin入门2024-11-04 15:50:07
  • Kotlin从零到精通--搭建Kotlin开发环境_kotlin用什么开发工具2024-11-04 15:50:07
  • kotlin-android_android开发工具箱2024-11-04 15:50:07
  • Android快速转战Kotlin教程2024-11-04 15:50:07
  • org.springframework.core.kotlindetector_开发模式2024-11-04 15:50:07
  • kotlin开发ios app_安卓原生开发2024-11-04 15:50:07
  • kotlin编程权威指南_kotlin协程原理2024-11-04 15:50:07
  • kotlin用什么开发工具_linux开发环境的搭建2024-11-04 15:50:07
  • 基于kotlin的android应用程序开发_Android sdk2024-11-04 15:50:07
  • 全屏图片