0
点赞
收藏
分享

微信扫一扫

8.4 Spring Boot集成Kotlin混合Java开发

8.4 Spring Boot集成Kotlin混合Java开发

本章介绍Spring Boot集成Kotlin混合Java开发一个完整的spring boot应用:Restfeel,一个企业级的Rest API接口测试平台(在开源工程restfiddle[1]基础上开发而来)。

系统技术框架

编程语言:Java,Kotlin

数据库:Mongo

Spring框架:Spring data jpa,Spring data mongodb

前端:jquery,requireJS,

工程构建工具:Gradle

Kotlin简介


8.4 Spring Boot集成Kotlin混合Java开发_scala



Kotlin是一种优雅的语言,是JetBrains公司开发的静态类型JVM语言,与Java无缝集成。与Java相比,Kotlin的语法更简洁、更具表达性,而且提供了更多的特性,比如,高阶函数、操作符重载、字符串模板。它与Java高度可互操作,可以同时用在一个项目中。这门语言的目标是:

  • 创建一种兼容Java的语言
  • 编译速度至少同Java一样快
  • 比Java更安全,能够静态检测常见的陷阱。如:引用空指针
  • 比Java更简洁,通过支持 variable type inference,higher-order functions (closures),extension functions,mixins and first-class delegation 等实现。
  • 比最成熟的竞争者Scala还简单


Kotlin不像Scala另起炉灶,将类库,尤其是集合类都自己来了一遍。kotlin是对现有java的增强,通过扩展方法给java提供了很多诸如fp之类的特性,但同时始终保持对java的兼容。这也是是kotlin官网首页重点强调的:

100% interoperable with Java™。

Kotlin和Scala很像,对于用惯了Scala的人来说用起来很顺手,对于喜欢函数式的开发者,Kotlin是个不错的选择。有个小点,像Groovy动态类型就不用说了,Scala函数最后返回值都可以省去return,但是不晓得为啥Kotlin对return情有独钟。

另外,Kotlin可以编译成Java字节码,也可以编译成JavaScript,在没有JVM的环境运行。

Kotlin创建类的方式与Java类似,比如下面的代码创建了一个有三个属性的Person类:

class Person{
var name: String = ""
var age: Int = 0
var sex: String? = null
}

可以看到,Kotlin的变量声明方式略有些不同。在Kotline中,声明变量必须使用关键字var,而如果要创建一个只读/只赋值一次的变量,则需要使用val代替它。

Kotlin对函数式编程的实现恰到好处

一个函数指针的例子:

/**
* "Callable References" or "Feature Literals", i.e. an ability to pass
* named functions or properties as values. Users often ask
* "I have a foo() function, how do I pass it as an argument?".
* The answer is: "you prefix it with a `::`".
*/

fun main(args: Array<String>) {
val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd))
}


fun isOdd(x: Int) = x % 2 != 0

运行结果: [1, 3]

再看一个复合函数的例子。看了下面的复合函数的例子,你会发现Kotlin的FP的实现相当简洁。(跟纯数学的表达式,相当接近了)

/**
* The composition function return a composition of two functions passed to it:
* compose(f, g) = f(g(*)).
* Now, you can apply it to callable references.
*/

fun main(args: Array<String>) {
val oddLength = compose(::isOdd, ::length)
val strings = listOf("a", "ab", "abc")
println(strings.filter(oddLength))
}

fun isOdd(x: Int) = x % 2 != 0
fun length(s: String) = s.length

fun <A, B, C> compose(f: (B) -> C, g: (A) -> B): (A) -> C {
return { x -> f(g(x)) }
}

运行结果: [a,abc]

简单说明下
val oddLength = compose(::isOdd, ::length)
val strings = listOf("a", "ab", "abc")
println(strings.filter(oddLength))

这就是数学中,复合函数的定义:

h = h(f(g))

g: A->B
f: B->C
h: A->C

g(A)=B
h(A) = f(B) = f(g(A)) = C

只是代码中的写法是:

h=compose( f, g )
h=compose( f(g(A)), g(A) )

关于Kotlin,官网有一个非常好的交互式Kotlin学习教程:

​​http://try.kotlinlang.org/​​

想深入语言实现细节的,可以直接参考github源码[3]。

Spring Boot集成 Kotlin

1.build.gradle中添加kotlin相关依赖

使用插件

apply {
plugin "kotlin"
plugin "kotlin-spring"
plugin "kotlin-jpa"
plugin "org.springframework.boot"
plugin 'java'
plugin 'eclipse'
plugin 'idea'
plugin 'war'
plugin 'maven'
}

构建时插件依赖

buildscript {
ext {
kotlinVersion = '1.1.0'
springBootVersion = '1.5.2.RELEASE'
}

dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlinVersion"
classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlinVersion"
}

repositories {
mavenLocal()
mavenCentral()
maven { url "http://oss.jfrog.org/artifactory/oss-release-local" }
maven { url "http://jaspersoft.artifactoryonline.com/jaspersoft/jaspersoft-repo/" }
maven { url "https://oss.sonatype.org/content/repositories/snapshots" }

}
}

编译时jar包依赖

dependencies {
compile("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
compile("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion")
compile("com.fasterxml.jackson.module:jackson-module-kotlin:2.8.4")

...

}
2.配置源码目录sourceSets
sourceSets {
main {
kotlin { srcDir "src/main/kotlin" }
java { srcDir "src/main/java" }
}
test {
kotlin { srcDir "src/test/kotlin" }
java { srcDir "src/test/java" }
}
}

指定kotlin,java源码放置目录。让java的归java,Kotlin的归Kotlin。这样区分开来。这个跟scala的插件实现方式上有点区别。scala的插件,是允许你把scala,java代码随便放,插件会自动寻找scala代码编译。

整个工程大目录如下图所示:


8.4 Spring Boot集成Kotlin混合Java开发_scala_02



3.写Kotlin代码

Entity实体类

package com.restfeel.entity

import org.bson.types.ObjectId
import org.springframework.data.mongodb.core.mapping.Document
import java.util.*
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id
import javax.persistence.Version

@Document(collection = "blog") // 如果不指定collection,默认遵从命名规则
class Blog {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
var id: String = ObjectId.get().toString()
@Version
var version: Long = 0
var title: String = ""
var content: String = ""
var author: String = ""
var gmtCreated: Date = Date()
var gmtModified: Date = Date()
var isDeleted: Int = 0 //1 Yes 0 No
var deletedDate: Date = Date()
override fun toString(): String {
return "Blog(id='$id', version=$version, title='$title', content='$content', author='$author', gmtCreated=$gmtCreated, gmtModified=$gmtModified, isDeleted=$isDeleted, deletedDate=$deletedDate)"
}

}

Service类

package com.restfeel.service

import com.restfeel.entity.Blog
import org.springframework.data.mongodb.repository.MongoRepository
import org.springframework.data.mongodb.repository.Query
import org.springframework.data.repository.query.Param

interface BlogService : MongoRepository<Blog, String> {

@Query("{ 'title' : ?0 }")
fun findByTitle(@Param("title") title: String): Iterable<Blog>

}

Controller类

package com.restfeel.controller

import com.restfeel.entity.Blog
import com.restfeel.service.BlogService
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.context.annotation.ComponentScan
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.stereotype.Controller
import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.ResponseBody
import java.util.*
import javax.servlet.http.HttpServletRequest

/**
* 文章列表,写文章的Controller
* @author Jason Chen 2017/3/31 01:10:16
*/

@Controller
@EnableAutoConfiguration
@ComponentScan
@Transactional(propagation = Propagation.REQUIRES_NEW)
class BlogController(val blogService: BlogService) {
@GetMapping("/blogs.do")
fun listAll(model: Model): String {
val authentication = SecurityContextHolder.getContext().authentication
model.addAttribute("currentUser", if (authentication == null) null else authentication.principal as UserDetails)
val allblogs = blogService.findAll()
model.addAttribute("blogs", allblogs)
return "jsp/blog/list"
}

@PostMapping("/saveBlog")
@ResponseBody
fun saveBlog(blog: Blog, request: HttpServletRequest):Blog {
blog.author = (request.getSession().getAttribute("currentUser") as UserDetails).username
return blogService.save(blog)
}

@GetMapping("/goEditBlog")
fun goEditBlog(@RequestParam(value = "id") id: String, model: Model): String {
model.addAttribute("blog", blogService.findOne(id))
return "jsp/blog/edit"
}

@PostMapping("/editBlog")
@ResponseBody
fun editBlog(blog: Blog, request: HttpServletRequest) :Blog{
blog.author = (request.getSession().getAttribute("currentUser") as UserDetails).username
blog.gmtModified = Date()
blog.version = blog.version + 1
return blogService.save(blog)
}

@GetMapping("/blog")
fun blogDetail(@RequestParam(value = "id") id: String, model: Model): String {
model.addAttribute("blog", blogService.findOne(id))
return "jsp/blog/detail"
}

@GetMapping("/listblogs")
@ResponseBody
fun listblogs(model: Model) = blogService.findAll()

@GetMapping("/findBlogByTitle")
@ResponseBody
fun findBlogByTitle(@RequestParam(value = "title") title: String) = blogService.findByTitle(title)

}

对应的前端代码,这里就不多说了。可以参考工程源代码[4]。

SpringBoot的启动类: 我们用Kotlin写SpringBoot的启动类:

package com.restfeel

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.CommandLineRunner
import org.springframework.boot.SpringApplication
import org.springframework.core.env.Environment

/**
* Created by jack on 2017/3/29.
* @author jack
* @date 2017/03/29
*/
@RestFeelBoot
class RestFeelApplicationKotlin : CommandLineRunner {
@Autowired
private val env: Environment? = null

override fun run(vararg args: String?) {
println("RESTFEEL 启动完毕")
println("应用地址:" + env?.getProperty("application.host-uri"))
}
}

fun main(args: Array<String>) {
SpringApplication.run(RestFeelApplicationKotlin::class.java, *args)
}

其中,@RestFeelBoot是自定义注解,代码如下:

package com.restfeel;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
* Created by jack on 2017/3/23.
*
* @author jack
* @date 2017/03/23
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Configuration
@EnableAutoConfiguration
@ComponentScan
public @interface RestFeelBoot {
Class<?>[] exclude() default {};
}
运行测试

命令行输入gradle bootRun,运行应用。 访问 ​​http://127.0.0.1:5678/blogs.do​​

文章列表

8.4 Spring Boot集成Kotlin混合Java开发_java_03



写文章


8.4 Spring Boot集成Kotlin混合Java开发_scala_04



阅读文章


8.4 Spring Boot集成Kotlin混合Java开发_java_05



小结

本章示例工程源代码:

​​https://github.com/Jason-Chen-2017/restfeel/tree/restfeel_kotlin_2017.5.6​​

参考资料:
1.​​​https://github.com/AnujaK/restfiddle​​​2.​​http://www.jianshu.com/c/498ebcfd27ad​​3.​​https://github.com/JetBrains/kotlin​​4.​​https://github.com/Jason-Chen-2017/restfeel/tree/restfeel_kotlin_2017.5.6​​



举报

相关推荐

0 条评论