2.3 Web RESTFul HelloWorld

本节介绍使用 Kotlin 结合 SpringBoot 开发一个RESTFul版本的 Hello.World。

  1. 新建gradle,kotlin工程:

打开IDEA的File > New > Project , 如下图

Kotlin极简教程

按照界面操作,输入相应的工程名等信息,即可新建一个使用Gradle构建的标准Kotlin工程。

  1. build.gradle 基本配置

IDEA自动生成的Gradle配置文件如下:

1. group 'com.easy.kotlin'
2. version '1.0-SNAPSHOT'

4. buildscript {
5. ext.kotlin_version = '1.1.2-2'

7. repositories {
8. mavenCentral()
9. }
10. dependencies {
11. //        Kotlin Gradle插件
12. classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

14. }
15. }

17. apply plugin: 'java'
18. apply plugin: 'kotlin'

20. sourceCompatibility = 1.8
21. targetCompatibility = 1.8

23. repositories {
24. mavenCentral()
25. }

27. dependencies {
28. compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
29. testCompile group: 'junit', name: 'junit', version: '4.12'

31. }

从上面的配置文件我们可以看出,IDEA已经自动把Gradle 构建Kotlin工程插件 kotlin-gradle-plugin,以及Kotlin标准库kotlin-stdlib添加到配置文件中了。

  1. 配置SpringBoot相关内容

下面我们来配置SpringBoot相关内容。首先在构建脚本里面添加ext变量springBootVersion。

1. ext.kotlin_version = '1.1.2-2'
2. ext.springboot_version = '1.5.2.RELEASE'

然后在构建依赖里添加spring-boot-gradle-plugin

1. buildscript {
2. ...
3. dependencies {
4. //        Kotlin Gradle插件
5. classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
6. //        SpringBoot Gradle插件
7. classpath("org.springframework.boot:spring-boot-gradle-plugin:$springboot_version")

9. //        Kotlin整合SpringBoot的默认无参构造函数,默认把所有的类设置open类插件
10. classpath("org.jetbrains.kotlin:kotlin-noarg:$kotlin_version")
11. classpath("org.jetbrains.kotlin:kotlin-allopen:$kotlin_version")
12. }
13. }
  1. 配置无参(no-arg)、全开放(allopen)插件

其中,org.jetbrains.kotlin:kotlin-noarg是无参(no-arg)编译器插件,它为具有特定注解的类生成一个额外的零参数构造函数。 这个生成的构造函数是合成的,因此不能从 Java 或 Kotlin 中直接调用,但可以使用反射调用。 这样我们就可以使用 Java Persistence API(JPA)实例化 data 类。

其中,org.jetbrains.kotlin:kotlin-allopen 是全开放编译器插件。我们使用Kotlin 调用Java的Spring AOP框架和库,需要类为 open(可被继承实现),而Kotlin 类和函数都是默认 final 的,这样我们需要为每个类和函数前面加上open修饰符。

这样的代码写起来,可费事了。还好,我们有all-open 编译器插件。它会适配 Kotlin 以满足这些框架的需求,并使用指定的注解标注类而其成员无需显式使用 open 关键字打开。 例如,当我们使用 Spring 时,就不需要打开所有的类,跟我们在Java中写代码一样,只需要用相应的注解标注即可,如 @Configuration 或 @Service。

完整的build.gradle配置文件如下

1. group 'com.easy.kotlin'
2. version '1.0-SNAPSHOT'

4. buildscript {
5. ext.kotlin_version = '1.1.2-2'
6. ext.springboot_version = '1.5.2.RELEASE'

8. repositories {
9. mavenCentral()
10. }
11. dependencies {
12. //        Kotlin Gradle插件
13. classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
14. //        SpringBoot Gradle插件
15. classpath("org.springframework.boot:spring-boot-gradle-plugin:$springboot_version")

17. //        Kotlin整合SpringBoot的默认无参构造函数,默认把所有的类设置open类插件
18. //        无参(no-arg)编译器插件为具有特定注解的类生成一个额外的零参数构造函数。 这个生成的构造函数是合成的,因此不能从 Java 或 Kotlin 中直接调用,但可以使用反射调用。 这允许 Java Persistence API(JPA)实例化 data 类,虽然它从 Kotlin 或 Java 的角度看没有无参构造函数
19. classpath("org.jetbrains.kotlin:kotlin-noarg:$kotlin_version")
20. //        全开放插件(kotlin-allopen)
21. classpath("org.jetbrains.kotlin:kotlin-allopen:$kotlin_version")
22. }
23. }

25. apply plugin: 'java'
26. apply plugin: 'kotlin'

28. //Kotlin整合SpringBoot需要的spring,jpa,org.springframework.boot插件

30. //Kotlin-spring 编译器插件,它根据 Spring 的要求自动配置全开放插件。
31. apply plugin: 'kotlin-spring'
32. //该插件指定 @Entity 和 @Embeddable 注解作为应该为一个类生成无参构造函数的标记。
33. apply plugin: 'kotlin-jpa'
34. apply plugin: 'org.springframework.boot'

36. sourceCompatibility = 1.8
37. targetCompatibility = 1.8

39. repositories {
40. mavenCentral()
41. }

43. dependencies {
44. compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
45. testCompile group: 'junit', name: 'junit', version: '4.12'

47. compile("org.springframework.boot:spring-boot-starter-web")
48. testCompile("org.springframework.boot:spring-boot-starter-test")
49. compile("org.springframework.boot:spring-boot-starter-data-jpa")
50. compile('mysql:mysql-connector-java:5.1.13')

52. }
  1. 配置application.properties
1. spring.datasource.url = jdbc:mysql://localhost:3306/easykotlin
2. spring.datasource.username = root
3. spring.datasource.password = root#spring.datasource.driverClassName = com.mysql.jdbc.Driver# Specify the DBMS
4. spring.jpa.database = MYSQL# Keep the connection alive if idle for a long time (needed in production)
5. spring.datasource.testWhileIdle = true
6. spring.datasource.validationQuery = SELECT 1# Show or not log for each sql query
7. spring.jpa.show-sql = true# Hibernate ddl auto (create, create-drop, update)
8. spring.jpa.hibernate.ddl-auto = update# Naming strategy
9. spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy# The SQL dialect makes Hibernate generate better SQL for the chosen database
10. spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect

12. server.port=8000
  1. 整体工程架构

UNIX操作系统说,“一切都是文件”。所以,我们 的所有的源代码、字节码、工程资源文件等等,一切都是文件。文件里面存的是字符串(01也当做是字符)。各种框架、库、编译器,解释器,都是对这些字符串流进行过滤,最后映射成01机器码(或者CPU微指令码等),最终落地到硬件上的高低电平。

整体工程目录如下:

1. .
2. ├── README.md
3. ├── build
4. │   └── kotlin-build
5. │       └── caches
6. │           └── version.txt
7. ├── build.gradle
8. ├── easykotlin.sql
9. ├── settings.gradle
10. └── src
11. ├── main
12. │   ├── java
13. │   ├── kotlin
14. │   │   └── com
15. │   │       └── easy
16. │   │           └── kotlin
17. │   │               ├── Application.kt
18. │   │               ├── controller
19. │   │               │   ├── HelloWorldController.kt
20. │   │               │   └── PeopleController.kt
21. │   │               ├── entity
22. │   │               │   └── People.kt
23. │   │               ├── repository
24. │   │               │   └── PeopleRepository.kt
25. │   │               └── service
26. │   │                   └── PeopleService.kt
27. │   └── resources
28. │       ├── application.properties
29. │       └── banner.txt
30. └── test
31. ├── java
32. ├── kotlin
33. └── resources

35. 19 directories, 13 files

一切尽在不言中,静静地看工程文件结构。

直接写个HelloWorldController

1. package com.easy.kotlin.controller

3. import org.springframework.web.bind.annotation.GetMapping
4. import org.springframework.web.bind.annotation.RestController

7. @RestController
8. class HelloWorldController {
9. @GetMapping(value = *arrayOf("/helloworld", "/"))
10. fun helloworld(): Any {
11. return "Hello,World!"
12. }
13. }

我们再写个访问数据库的标准四层代码

写领域模型类People

1. package com.easy.kotlin.entity

3. import java.util.*
4. import javax.persistence.Entity
5. import javax.persistence.GeneratedValue
6. import javax.persistence.GenerationType
7. import javax.persistence.Id

9. /**
10. * Created by jack on 2017/6/6.
11. */
12. @Entity
13. class People(
14. @Id @GeneratedValue(strategy = GenerationType.AUTO)
15. val id: Long?,
16. val firstName: String?,
17. val lastName: String?,
18. val gender: String?,
19. val age: Int?,
20. val gmtCreated: Date,
21. val gmtModified: Date
22. ) {
23. override fun toString(): String {
24. return "People(id=$id, firstName='$firstName', lastName='$lastName', gender='$gender', age=$age, gmtCreated=$gmtCreated, gmtModified=$gmtModified)"
25. }
26. }

写PeopleRepository

1. package com.easy.kotlin.repository

3. import com.easy.kotlin.entity.People
4. import org.springframework.data.repository.CrudRepository

6. interface PeopleRepository : CrudRepository<People, Long> {
7. fun findByLastName(lastName: String): List<People>?
8. }

写PeopleService

1. package com.easy.kotlin.service

3. import com.easy.kotlin.entity.People
4. import com.easy.kotlin.repository.PeopleRepository
5. import org.springframework.beans.factory.annotation.Autowired
6. import org.springframework.stereotype.Service

9. @Service
10. class PeopleService : PeopleRepository {

12. @Autowired
13. val peopleRepository: PeopleRepository? = null

15. override fun findByLastName(lastName: String): List<People>? {
16. return peopleRepository?.findByLastName(lastName)
17. }

19. override fun <S : People?> save(entity: S): S? {
20. return peopleRepository?.save(entity)
21. }

23. override fun <S : People?> save(entities: MutableIterable<S>?): MutableIterable<S>? {
24. return peopleRepository?.save(entities)
25. }

27. override fun delete(entities: MutableIterable<People>?) {
28. }

30. override fun delete(entity: People?) {
31. }

33. override fun delete(id: Long?) {
34. }

36. override fun findAll(ids: MutableIterable<Long>?): MutableIterable<People>? {
37. return peopleRepository?.findAll(ids)
38. }

40. override fun findAll(): MutableIterable<People>? {
41. return peopleRepository?.findAll()
42. }

44. override fun exists(id: Long?): Boolean {
45. return peopleRepository?.exists(id)!!
46. }

48. override fun count(): Long {
49. return peopleRepository?.count()!!
50. }

52. override fun findOne(id: Long?): People? {
53. return peopleRepository?.findOne(id)
54. }

56. override fun deleteAll() {
57. }
58. }

写PeopleController

1. package com.easy.kotlin.controller

3. import com.easy.kotlin.service.PeopleService
4. import org.springframework.beans.factory.annotation.Autowired
5. import org.springframework.stereotype.Controller
6. import org.springframework.web.bind.annotation.GetMapping
7. import org.springframework.web.bind.annotation.RequestParam
8. import org.springframework.web.bind.annotation.ResponseBody

11. @Controller
12. class PeopleController {
13. @Autowired
14. val peopleService: PeopleService? = null

16. @GetMapping(value = "/hello")
17. @ResponseBody
18. fun hello(@RequestParam(value = "lastName") lastName: String): Any {
19. val peoples = peopleService?.findByLastName(lastName)
20. val map = HashMap<Any, Any>()
21. map.put("hello", peoples!!)
22. return map
23. }

25. }
  1. 运行测试

点击Gradle的bootRun , 如下图

Kotlin极简教程

如果没有异常,启动成功,我们将看到以下输出:

Kotlin极简教程

打开浏览器,访问请求:

<http://127.0.0.1:8000/>

输出响应:

1. Hello,World!

访问

<http://127.0.0.1:8000/hello?lastName=chen>

1. // 20170607115700
2. // http://127.0.0.1:8000/hello?lastName=chen

4. {
5. "hello": [
6. {
7. "id": 1,
8. "firstName": "Jason",
9. "lastName": "Chen",
10. "gender": "Male",
11. "age": 28,
12. "gmtCreated": 1496768497000,
13. "gmtModified": 1496768497000
14. },
15. {
16. "id": 3,
17. "firstName": "Corey",
18. "lastName": "Chen",
19. "gender": "Female",
20. "age": 20,
21. "gmtCreated": 1496768497000,
22. "gmtModified": 1496768497000
23. }
24. ...
25. ]
26. }

本节示例工程源代码:

<https://github.com/EasyKotlin/easy_kotlin_chapter2_hello_world_springboot_restful>