2.3 Web RESTFul HelloWorld
本节介绍使用 Kotlin 结合 SpringBoot 开发一个RESTFul版本的 Hello.World。
- 新建gradle,kotlin工程:
打开IDEA的File > New > Project , 如下图

按照界面操作,输入相应的工程名等信息,即可新建一个使用Gradle构建的标准Kotlin工程。
- 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添加到配置文件中了。
- 配置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. }
- 配置无参(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. }
- 配置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
- 整体工程架构
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. }
- 运行测试
点击Gradle的bootRun , 如下图

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

打开浏览器,访问请求:
<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>
