《Kotin 極簡(jiǎn)教程》第11章 使用Kotlin 集成 SpringBoot開發(fā)Web服務(wù)端

第11章 使用Kotlin集成SpringBoot開發(fā)Web服務(wù)端


《Kotlin極簡(jiǎn)教程》正式上架:

點(diǎn)擊這里 > 去京東商城購(gòu)買閱讀

點(diǎn)擊這里 > 去天貓商城購(gòu)買閱讀

非常感謝您親愛(ài)的讀者,大家請(qǐng)多支持!?。∮腥魏螁?wèn)題,歡迎隨時(shí)與我交流~


我們?cè)谇懊娴?章 “ 2.3 Web RESTFul HelloWorld ” 一節(jié)中,已經(jīng)介紹了使用 Kotlin 結(jié)合 SpringBoot 開發(fā)一個(gè)RESTFul版本的 Hello World。當(dāng)然,Kotlin與Spring家族的關(guān)系不止如此。在 Spring 5.0 M4 中引入了一個(gè)專門針對(duì)Kotlin的支持。

本章我們就一起來(lái)學(xué)習(xí)怎樣使用Kotlin集成SpringBoot、SpringMVC等框架來(lái)開發(fā)Web服務(wù)端應(yīng)用,同時(shí)簡(jiǎn)單介紹Spring 5.0對(duì)Kotlin的支持特性。

11.1 Spring Boot簡(jiǎn)介

SpringBoot是伴隨著Spring4.0誕生的。從字面理解,Boot是引導(dǎo)的意思,SpringBoot幫助開發(fā)者快速搭建Spring框架、快速啟動(dòng)一個(gè)Web容器等,使得基于Spring的開發(fā)過(guò)程更加簡(jiǎn)易。 大部分Spring Boot Application只要一些極簡(jiǎn)的配置,即可“一鍵運(yùn)行”。

SpringBoot的特性如下:

  • 創(chuàng)建獨(dú)立的Spring applications
  • 能夠使用內(nèi)嵌的Tomcat, Jetty or Undertow,不需要部署war
  • 提供定制化的starter poms來(lái)簡(jiǎn)化maven配置(gradle相同)
  • 追求極致的自動(dòng)配置Spring
  • 提供一些生產(chǎn)環(huán)境的特性,比如特征指標(biāo),健康檢查和外部配置。
  • 零代碼生成和零XML配置

Spring由于其繁瑣的配置,一度被人認(rèn)為“配置地獄”,各種XML文件的配置,讓人眼花繚亂,而且如果出錯(cuò)了也很難找出原因。而Spring Boot更多的是采用Java Config的方式對(duì)Spring進(jìn)行配置。

11.2 系統(tǒng)架構(gòu)技術(shù)棧

本節(jié)我們介紹使用 Kotlin 集成 Spring Boot 開發(fā)一個(gè)完整的博客站點(diǎn)的服務(wù)端Web 應(yīng)用, 它支持 Markdown 寫文章, 文章列表分頁(yè)、搜索查詢等功能。

其系統(tǒng)架構(gòu)技術(shù)棧如下表所示:

編程語(yǔ)言 Java,Kotlin
數(shù)據(jù)庫(kù) MySQL , mysql-jdbc-driver, Spring data JPA,
J2EE框架 Spring Boot, Spring MVC
視圖模板引擎 Freemarker
前端組件庫(kù) jquery,bootstrap, flat UI , Mditor , DataTables
工程構(gòu)建工具 Gradle

11.3 環(huán)境準(zhǔn)備

11.3.1 創(chuàng)建工程

首先,我們使用SPRING INITIALIZR來(lái)創(chuàng)建一個(gè)模板工程。

第一步:訪問(wèn) http://start.spring.io/, 選擇生成一個(gè)Gradle項(xiàng)目,使用Kotlin語(yǔ)言,使用的Spring Boot版本是2.0.0 M2。

第二步: 配置項(xiàng)目基本信息。
Group: com.easy.kotlin
Artifact:chapter11_kotlin_springboot
以及項(xiàng)目名稱、項(xiàng)目描述、包名稱等其他的選項(xiàng)。選擇jar包方式打包,使用JDK1.8 。

第三步:選擇項(xiàng)目依賴。我們這里分別選擇了:Web、DevTools、JPA、MySQL、Actuator、Freemarker。

以上三步如下圖所示:

螢?zāi)豢煺?2017-07-17 16.40.19.png

點(diǎn)擊生成項(xiàng)目,下載zip包,解壓后導(dǎo)入IDEA中,我們可以看到一個(gè)如下目錄結(jié)構(gòu)的工程:

.
├── build
│   └── kotlin-build
│       └── caches
├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── src
    ├── main
    │   ├── kotlin
    │   │   └── com
    │   │       └── easy
    │   │           └── kotlin
    │   │               └── chapter11_kotlin_springboot
    │   │                   └── Chapter11KotlinSpringbootApplication.kt
    │   └── resources
    │       ├── application.properties
    │       ├── static
    │       └── templates
    └── test
        └── kotlin
            └── com
                └── easy
                    └── kotlin
                        └── chapter11_kotlin_springboot
                            └── Chapter11KotlinSpringbootApplicationTests.kt

21 directories, 8 files

其中,Chapter11KotlinSpringbootApplication.kt是SpringBoot應(yīng)用的入口啟動(dòng)類。

11.3.2 Gradle配置文件說(shuō)明

整個(gè)工程的Gradle構(gòu)建配置文件build.gradle的內(nèi)容如下:

buildscript {
    ext {
        kotlinVersion = '1.1.3-2'
        springBootVersion = '2.0.0.M2'
    }
    repositories {
        mavenCentral()
        maven { url "https://repo.spring.io/snapshot" }
        maven { url "https://repo.spring.io/milestone" }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
        classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}")
    }
}

apply plugin: 'kotlin'
apply plugin: 'kotlin-spring'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}

repositories {
    mavenCentral()
    maven { url "https://repo.spring.io/snapshot" }
    maven { url "https://repo.spring.io/milestone" }
}


dependencies {
    compile('org.springframework.boot:spring-boot-starter-actuator')
    compile('org.springframework.boot:spring-boot-starter-data-jpa')
    compile('org.springframework.boot:spring-boot-starter-freemarker')
    compile('org.springframework.boot:spring-boot-starter-web')
    compile("org.jetbrains.kotlin:kotlin-stdlib-jre8:${kotlinVersion}")
    compile("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}")
    runtime('org.springframework.boot:spring-boot-devtools')
    runtime('mysql:mysql-connector-java')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}

其中主要的配置項(xiàng)如下表說(shuō)明:

配置項(xiàng) 功能說(shuō)明
spring-boot-gradle-plugin SpringBoot集成Gradle的插件
kotlin-gradle-plugin Kotlin集成Gradle的插件
kotlin-allopen Kotlin全開放插件。Kotlin 里類默認(rèn)都是final的,如果聲明的類需要被繼承則需要使用open 關(guān)鍵字來(lái)描述類,這個(gè)插件就是把Kotlin中的所有類都o(jì)pen打開,可被繼承
spring-boot-starter-actuator SpringBoot的健康檢查監(jiān)控組件啟動(dòng)器
spring-boot-starter-data-jpa JPA啟動(dòng)器
spring-boot-starter-freemarker 模板引擎freemarker啟動(dòng)器
kotlin-stdlib-jre8 Kotlin基于JRE8的標(biāo)準(zhǔn)庫(kù)
kotlin-reflect Kotlin反射庫(kù)
spring-boot-devtools SpringBoot開發(fā)者工具,例如:熱部署等
mysql-connector-java Java的MySQL連接器庫(kù)
spring-boot-starter-test 測(cè)試啟動(dòng)器

11.4 數(shù)據(jù)庫(kù)層配置

上面的模板工程,我們來(lái)直接運(yùn)行main函數(shù),會(huì)發(fā)現(xiàn)啟動(dòng)失敗,控制臺(tái)會(huì)輸出如下報(bào)錯(cuò)信息:

BeanCreationException: Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class]
...
***************************
APPLICATION FAILED TO START
***************************

Description:

Cannot determine embedded database driver class for database type NONE

Action:

If you want an embedded database please put a supported one on the classpath. If you have database settings to be loaded from a particular profile you may need to active it (no profiles are currently active).


因?yàn)槲覀冞€沒(méi)有配置數(shù)據(jù)源。我們先在MySQL中新建一個(gè)schema:

CREATE SCHEMA `blog` DEFAULT CHARACTER SET utf8 ;

11.4.1 配置數(shù)據(jù)源

接著,我們?cè)谂渲梦募pplication.properties中配置MySQL數(shù)據(jù)源:

# datasource
# datasource: unicode編碼的支持,設(shè)定為utf-8
spring.datasource.url=jdbc:mysql://localhost:3306/blog?zeroDateTimeBehavior=convertToNull&characterEncoding=utf8&characterSetResults=utf8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.testWhileIdle=true
spring.datasource.validationQuery=SELECT 1

其中,spring.datasource.url 中,為了支持中文的正確顯示(防止出現(xiàn)中文亂碼),我們需要設(shè)置一下編碼。

數(shù)據(jù)庫(kù)ORM(對(duì)象關(guān)系映射)層,我們使用spring-data-jpa :

spring.jpa.database=MYSQL
spring.jpa.show-sql=true
# Hibernate ddl auto (create, create-drop, update)
spring.jpa.hibernate.ddl-auto=update
# Naming strategy
spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.ImprovedNamingStrategy
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect

再次運(yùn)行啟動(dòng)類,控制臺(tái)輸出啟動(dòng)日志:

...
                        _  __     _   _ _
                       | |/ /    | | | (_)          _
                       | ' / ___ | |_| |_ _ __    _| |_
                       |  < / _ \| __| | | '_ \  |_   _|
                       | . \ (_) | |_| | | | | |   |_|
                       |_|\_\___/ \__|_|_|_| |_|


              _____            _             ____              _
             / ____|          (_)           |  _ \            | |
            | (___  _ __  _ __ _ _ __   __ _| |_) | ___   ___ | |_
             \___ \| '_ \| '__| | '_ \ / _` |  _ < / _ \ / _ \| __|
             ____) | |_) | |  | | | | | (_| | |_) | (_) | (_) | |_
            |_____/| .__/|_|  |_|_| |_|\__, |____/ \___/ \___/ \__|
                   | |                  __/ |
                   |_|                 |___/


2017-07-17 21:10:48.741  INFO 5062 --- [  restartedMain] c.Chapter11KotlinSpringbootApplicationKt : Starting Chapter11KotlinSpringbootApplicationKt on 192.168.1.6 with PID 5062 ...
...
2017-07-17 21:19:40.548  INFO 5329 --- [  restartedMain] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2017-07-17 21:11:03.017  INFO 5062 --- [  restartedMain] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/application/env || /application/env.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
...
2017-07-17 21:11:03.026  INFO 5062 --- [  restartedMain] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/application/beans || /application/beans.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2017-07-17 21:11:03.028  INFO 5062 --- [  restartedMain] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/application/health || /application/health.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint.invoke(javax.servlet.http.HttpServletRequest,java.security.Principal)
...
2017-07-17 21:11:03.060  INFO 5062 --- [  restartedMain] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/application/autoconfig || /application/autoconfig.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
...
2017-07-17 21:11:03.478  INFO 5062 --- [  restartedMain] o.s.ui.freemarker.SpringTemplateLoader   : SpringTemplateLoader for FreeMarker: using resource loader [org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@6aab8872: startup date [Mon Jul 17 21:10:48 CST 2017]; root of context hierarchy] and template loader path [classpath:/templates/]
2017-07-17 21:11:03.479  INFO 5062 --- [  restartedMain] o.s.w.s.v.f.FreeMarkerConfigurer         : ClassTemplateLoader for Spring macros added to FreeMarker configuration
2017-07-17 21:11:03.520  WARN 5062 --- [  restartedMain] o.s.b.a.f.FreeMarkerAutoConfiguration    : Cannot find template location(s): [classpath:/templates/] (please add some templates, check your FreeMarker configuration, or set spring.freemarker.checkTemplateLocation=false)
2017-07-17 21:11:03.713  INFO 5062 --- [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
2017-07-17 21:11:03.871  INFO 5062 --- [  restartedMain] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2017-07-17 21:11:03.874  INFO 5062 --- [  restartedMain] o.s.j.e.a.AnnotationMBeanExporter        : Bean with name 'dataSource' has been autodetected for JMX exposure
2017-07-17 21:11:03.886  INFO 5062 --- [  restartedMain] o.s.j.e.a.AnnotationMBeanExporter        : Located MBean 'dataSource': registering with JMX server as MBean [com.zaxxer.hikari:name=dataSource,type=HikariDataSource]
2017-07-17 21:11:03.901  INFO 5062 --- [  restartedMain] o.s.c.support.DefaultLifecycleProcessor  : Starting beans in phase 0
2017-07-17 21:11:04.232  INFO 5062 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8000 (http)
2017-07-17 21:11:04.240  INFO 5062 --- [  restartedMain] c.Chapter11KotlinSpringbootApplicationKt : Started Chapter11KotlinSpringbootApplicationKt in 16.316 seconds (JVM running for 17.68)


關(guān)于上面的日志,我們通過(guò)下面的表格作簡(jiǎn)要說(shuō)明:

日志內(nèi)容 簡(jiǎn)要說(shuō)明
LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory 初始化JAP實(shí)體管理器工廠
EndpointHandlerMapping : Mapped "{[/application/beans ...等 SpringBoot健康監(jiān)控Endpoint 等REST接口
FreeMarkerAutoConfiguration Freemarker模板引擎自動(dòng)配置,默認(rèn)視圖文件目錄是classpath:/templates/
AnnotationMBeanExporter : Bean with name 'dataSource' has been autodetected for JMX exposure ... Located MBean 'dataSource': registering with JMX server as MBean [com.zaxxer.hikari:name=dataSource,type=HikariDataSource] 數(shù)據(jù)源Bean通過(guò)annotation注解注冊(cè)MBean到JMX實(shí)現(xiàn)監(jiān)控其運(yùn)行狀態(tài)
TomcatWebServer : Tomcat started on port(s): 8000 (http) SpringBoot默認(rèn)內(nèi)嵌了Tomcat,端口我們可以在application.properties中配置
Started Chapter11KotlinSpringbootApplicationKt in 16.316 seconds (JVM running for 17.68) SpringBoot應(yīng)用啟動(dòng)成功

11.5 Endpoint監(jiān)控接口

我們來(lái)嘗試訪問(wèn):http://127.0.0.1:8000/application/beans ,瀏覽器顯示如下信息:

Whitelabel Error Page

This application has no explicit mapping for /error, so you are seeing this as a fallback.

Mon Jul 17 21:30:35 CST 2017
There was an unexpected error (type=Unauthorized, status=401).
Full authentication is required to access this resource.

提示沒(méi)有權(quán)限訪問(wèn)。我們?nèi)タ刂婆_(tái)日志可以看到下面這行輸出:

s.b.a.e.m.MvcEndpointSecurityInterceptor : Full authentication is required to access actuator endpoints. Consider adding Spring Security or set 'management.security.enabled' to false.

意思是說(shuō),要訪問(wèn)這些Endpoints需要權(quán)限,可以通過(guò)Spring Security來(lái)實(shí)現(xiàn)權(quán)限控制,或者把權(quán)限限制去掉:把management.security.enabled設(shè)置為false。

我們?cè)赼pplication.properties里面添加配置:

management.security.enabled=false

重啟應(yīng)用,再次訪問(wèn),我們可以看到如下輸出:

[
  {
    "context": "application:8000",
    "parent": null,
    "beans": [
      {
        "bean": "chapter11KotlinSpringbootApplication",
        "aliases": [
          
        ],
        "scope": "singleton",
        "type": "com.easy.kotlin.chapter11_kotlin_springboot.Chapter11KotlinSpringbootApplication$$EnhancerBySpringCGLIB$$353fd63e",
        "resource": "null",
        "dependencies": [
          
        ]
      },
      ...
      {
        "bean": "org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration",
        "aliases": [
          
        ],
        "scope": "singleton",
        "type": "org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration$$EnhancerBySpringCGLIB$$24d31c25",
        "resource": "null",
        "dependencies": [
          "dataSource",
          "spring.jpa-org.springframework.boot.autoconfigure.orm.jpa.JpaProperties"
        ]
      },
      {
        "bean": "transactionManager",
        "aliases": [
          
        ],
        "scope": "singleton",
        "type": "org.springframework.orm.jpa.JpaTransactionManager",
        "resource": "class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.class]",
        "dependencies": [
          
        ]
      },
      ...
      {
        "bean": "spring.devtools-org.springframework.boot.devtools.autoconfigure.DevToolsProperties",
        "aliases": [
          
        ],
        "scope": "singleton",
        "type": "org.springframework.boot.devtools.autoconfigure.DevToolsProperties",
        "resource": "null",
        "dependencies": [
          
        ]
      },
      {
        "bean": "org.springframework.orm.jpa.SharedEntityManagerCreator#0",
        "aliases": [
          
        ],
        "scope": "singleton",
        "type": "com.sun.proxy.$Proxy86",
        "resource": "null",
        "dependencies": [
          "entityManagerFactory"
        ]
      }
    ]
  }


可以看出,我們一行代碼還沒(méi)寫,只是加了幾行配置,SpringBoot已經(jīng)自動(dòng)配置初始化了這么多的Bean。我們?cè)僭L問(wèn) http://127.0.0.1:8000/application/health



{
  "status": "UP",
  "diskSpace": {
    "status": "UP",
    "total": 120108089344,
    "free": 1724157952,
    "threshold": 10485760
  },
  "db": {
    "status": "UP",
    "database": "MySQL",
    "hello": 1
  }
}

從上面我們可以看到一些應(yīng)用的健康狀態(tài)信息,例如:應(yīng)用狀態(tài)、磁盤空間、數(shù)據(jù)庫(kù)狀態(tài)等信息。

11.6 數(shù)據(jù)庫(kù)實(shí)體類

我們?cè)谏厦嬉呀?jīng)完成了MySQL數(shù)據(jù)源的配置,下面我們來(lái)寫一個(gè)實(shí)體類。新建package com.easy.kotlin.chapter11_kotlin_springboot.entity ,然后新建Article實(shí)體類:

package com.easy.kotlin.chapter11_kotlin_springboot.entity

import java.util.*
import javax.persistence.*

@Entity
class Article {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long = -1
    @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 "Article(id=$id, version=$version, title='$title', content='$content', author='$author', gmtCreated=$gmtCreated, gmtModified=$gmtModified, isDeleted=$isDeleted, deletedDate=$deletedDate)"
    }

}


類似的實(shí)體類,我們?cè)贘ava中需要生成一堆getter/setter方法;如果我們用Scala寫還需要加個(gè) 注解@BeanProperty, 例如

package com.springboot.in.action.entity

import java.util.Date
import javax.persistence.{ Entity, GeneratedValue, GenerationType, Id }
import scala.language.implicitConversions
import scala.beans.BeanProperty

@Entity
class HttpApi {
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @BeanProperty
  var id: Integer = _

  @BeanProperty
  var httpSuiteId: Integer = _
  //用例名稱
  @BeanProperty
  var name: String = _

  //用例狀態(tài): -1未執(zhí)行 0失敗 1成功
  @BeanProperty
  var state: Integer = _
  ...

}

我們這個(gè)是一個(gè)博客文章的簡(jiǎn)單實(shí)體類。再次重啟運(yùn)行應(yīng)用,我們?nèi)ySQL的Schema: blog 里面去看,發(fā)現(xiàn)數(shù)據(jù)庫(kù)自動(dòng)生成了 Table: article , 它的表字段信息如下:

Field Type Null Key Default Extra
id bigint(20) NO PRI NULL auto_increment
author varchar(255) YES NULL
content varchar(255) YES NULL
deleted_date datetime YES NULL
gmt_created datetime YES NULL
gmt_modified datetime YES NULL
is_deleted int(11) NO NULL
title varchar(255) YES NULL
version bigint(20) NO NULL -

11.7 數(shù)據(jù)訪問(wèn)層代碼

在Spring Data JPA中,我們只需要實(shí)現(xiàn)接口CrudRepository<T, ID>即可獲得一個(gè)擁有基本CRUD操作的接口實(shí)現(xiàn)了:

interface ArticleRepository : CrudRepository<Article, Long>

JPA會(huì)自動(dòng)實(shí)現(xiàn)ArticleRepository接口中的方法,不需要我們寫基本的CRUD操作代碼。它的常用的基本CRUD操作方法的簡(jiǎn)單說(shuō)明如下表:

方法 功能說(shuō)明
S save(S entity) 保存給定的實(shí)體對(duì)象,我們可以使用這個(gè)保存之后返回的實(shí)例進(jìn)行進(jìn)一步操作(保存操作可能會(huì)更改實(shí)體實(shí)例)
findById(ID id) 根據(jù)主鍵id查詢
existsById(ID id) 判斷是否存在該主鍵id的記錄
findAll() 返回所有記錄
findAllById(Iterable<ID> ids) 根據(jù)主鍵id集合批量查詢
count() 返回總記錄條數(shù)
deleteById(ID id) 根據(jù)主鍵id刪除
deleteAll() 全部刪除

當(dāng)然,如果我們需要自己去實(shí)現(xiàn)SQL查詢邏輯,我們可以直接使用@Query注解。

interface ArticleRepository : CrudRepository<Article, Long> {
    override fun findAll(): MutableList<Article>

    @Query(value = "SELECT * FROM blog.article where title like %?1%", nativeQuery = true)
    fun findByTitle(title: String): MutableList<Article>

    @Query("SELECT a FROM #{#entityName} a where a.content like %:content%")
    fun findByContent(@Param("content") content: String): MutableList<Article>

    @Query(value = "SELECT * FROM blog.article  where author = ?1", nativeQuery = true)
    fun findByAuthor(author: String): MutableList<Article>
}

11.7.1 原生SQL查詢

其中,@Query注解里面的value的值就是我們要寫的 JP QL語(yǔ)句。另外,JPA的EntityManager API 還提供了創(chuàng)建 Query 實(shí)例以執(zhí)行原生 SQL 語(yǔ)句的createNativeQuery方法。

默認(rèn)是非原生的JP QL查詢模式。如果我們想指定原生SQL查詢,只需要設(shè)置
nativeQuery=true即可。

11.7.2 模糊查詢like寫法

另外,我們?cè)鶶QL模糊查詢like語(yǔ)法,我們?cè)趯憇ql的時(shí)候是這樣寫的

like '%?%'

但是在JP QL中, 這樣寫

like %?1%

11.7.3 參數(shù)占位符

其中,查詢語(yǔ)句中的 ?1 是函數(shù)參數(shù)的占位符,1代表的是參數(shù)的位置。

11.7.4 JP QL中的SpEL

另外我們使用JPA的標(biāo)準(zhǔn)查詢(Criteria Query):

SELECT a FROM #{#entityName} a where a.content like %:content%

其中的#{#entityName} 是SpEL(Spring表達(dá)式語(yǔ)言),用來(lái)代替本來(lái)實(shí)體的名稱,而Spring data jpa會(huì)自動(dòng)根據(jù)Article實(shí)體上對(duì)應(yīng)的默認(rèn)的 @Entity class Article ,或者指定@Entity(name = "Article") class Article 自動(dòng)將實(shí)體名稱填入 JP QL語(yǔ)句中。

通過(guò)把實(shí)體類名稱抽象出來(lái)成為參數(shù),幫助我們解決了項(xiàng)目中很多dao接口的方法除了實(shí)體類名稱不同,其他操作都相同的問(wèn)題。

11.7.5 注解參數(shù)

我們使用@Param("content") 來(lái)指定參數(shù)名綁定,然后在JP QL語(yǔ)句中這樣引用:

:content

JP QL 語(yǔ)句中通過(guò)": 變量"的格式來(lái)指定參數(shù),同時(shí)在方法的參數(shù)前面使用 @Param 將方法參數(shù)與 JP QL 中的命名參數(shù)對(duì)應(yīng)。

11.8 控制器層

我們新建子目錄controller,然后在下面新建控制器類:

@Controller
class ArticleController {
  
}

我們首先,裝配數(shù)據(jù)訪問(wèn)層的接口Bean:

@Autowired val articleRepository: ArticleRepository? = null

這個(gè)接口Bean的實(shí)例化由Spring data jpa完成。如果我們?nèi)?http://127.0.0.1:8000/application/beans 中查看這個(gè)Bean,我們可以看到信息如下:

      {
        "bean": "articleRepository",
        "aliases": [
          
        ],
        "scope": "singleton",
        "type": "com.easy.kotlin.chapter11_kotlin_springboot.dao.ArticleRepository",
        "resource": "null",
        "dependencies": [
          "(inner bean)#39c36d98",
          "(inner bean)#19d60142",
          "(inner bean)#1757cb01",
          "(inner bean)#6dd045f0",
          "jpaMappingContext"
        ]
      }

我們先來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的查詢所有記錄的REST接口。我們?cè)贏rticleRepository中重寫了findAll方法:

override fun findAll(): MutableList<Article>

然后,我們?cè)诳刂破鞔a中直接調(diào)用這個(gè)接口方法:

    @GetMapping("listAllArticle")
    @ResponseBody
    fun listAllArticle(): MutableList<Article>? {
        return articleRepository?.findAll()
    }

其中,注解@ResponseBody表示把方法返回值直接綁定到響應(yīng)體(response body)。

11.9 啟動(dòng)初始化CommandLineRunner

為了方便測(cè)試用,我們?cè)赟pringBoot應(yīng)用啟動(dòng)的時(shí)候初始化幾條數(shù)據(jù)到數(shù)據(jù)庫(kù)里。Spring Boot 為我們提供了一個(gè)方法,通過(guò)實(shí)現(xiàn)接口 CommandLineRunner 來(lái)實(shí)現(xiàn)。這是一個(gè)函數(shù)式接口:

@FunctionalInterface
public interface CommandLineRunner {
    void run(String... args) throws Exception;
}

我們只需要?jiǎng)?chuàng)建一個(gè)實(shí)現(xiàn)接口 CommandLineRunner 的類。很簡(jiǎn)單,只需要一個(gè)類就可以,無(wú)需其他配置。 這里我們使用Kotlin的Lambda表達(dá)式來(lái)寫:

    @Bean
    fun init(repository: ArticleRepository) = CommandLineRunner {
        val article: Article = Article()
        article.author = "Kotlin"
        article.title = "極簡(jiǎn)Kotlin教程 ${Date()}"
        article.content = "Easy Kotlin ${Date()}"
        repository.save(article)
    }

11.10 應(yīng)用啟動(dòng)類

我們?cè)趍ain函數(shù)中調(diào)用SpringApplication類的靜態(tài)run方法,我們的SpringBootApplication主類代碼如下:

package com.easy.kotlin.chapter11_kotlin_springboot

import com.easy.kotlin.chapter11_kotlin_springboot.dao.ArticleRepository
import com.easy.kotlin.chapter11_kotlin_springboot.entity.Article
import org.springframework.boot.CommandLineRunner
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.context.annotation.Bean
import java.util.*

@SpringBootApplication
class Chapter11KotlinSpringbootApplication {
    @Bean
    fun init(repository: ArticleRepository) = CommandLineRunner {
        val article: Article = Article()
        article.author = "Kotlin"
        article.title = "極簡(jiǎn)Kotlin教程 ${Date()}"
        article.content = "Easy Kotlin ${Date()}"
        repository.save(article)
    }
}

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



這里我們主要關(guān)注的是@SpringBootApplication注解,它包括三個(gè)注解,簡(jiǎn)單說(shuō)明如下表:

注解 功能說(shuō)明
@SpringBootConfiguration(它包括@Configuration) 表示將該類作用springboot配置文件類。
@EnableAutoConfiguration 表示SpringBoot程序啟動(dòng)時(shí),啟動(dòng)Spring Boot默認(rèn)的自動(dòng)配置。
@ComponentScan 表示程序啟動(dòng)時(shí)自動(dòng)掃描當(dāng)前包及子包下所有類。

11.10.1 啟動(dòng)運(yùn)行

如果是在IDEA中運(yùn)行,可以直接點(diǎn)擊main函數(shù)運(yùn)行,如下圖所示:

螢?zāi)豢煺?2017-07-18 17.44.31.png

如果想在命令行運(yùn)行,直接在項(xiàng)目根目錄下運(yùn)行命令:

$ gradle bootRun

我們可以看到控制臺(tái)的日志輸出:

2017-07-18 17:42:53.689  INFO 21239 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8000 (http)
Hibernate: insert into article (author, content, deleted_date, gmt_created, gmt_modified, is_deleted, title, version) values (?, ?, ?, ?, ?, ?, ?, ?)
2017-07-18 17:42:53.974  INFO 21239 --- [  restartedMain] c.Chapter11KotlinSpringbootApplicationKt : Started Chapter11KotlinSpringbootApplicationKt in 16.183 seconds (JVM running for 17.663)
<==========---> 83% EXECUTING [1m 43s]
> :bootRun

我們?cè)跒g覽器中直接訪問(wèn): http://127.0.0.1:8000/listAllArticle , 可以看到類似如下輸出:

[
  {
    "id": 1,
    "version": 0,
    "title": "極簡(jiǎn)Kotlin教程",
    "content": "Easy Kotlin ",
    "author": "Kotlin",
    "gmtCreated": 1500306475000,
    "gmtModified": 1500306475000,
    "deletedDate": 1500306475000
  },
  {
    "id": 2,
    "version": 0,
    "title": "極簡(jiǎn)Kotlin教程",
    "content": "Easy Kotlin ",
    "author": "Kotlin",
    "gmtCreated": 1500306764000,
    "gmtModified": 1500306764000,
    "deletedDate": 1500306764000
  }
]

至此,我們已經(jīng)完成了一個(gè)簡(jiǎn)單的REST接口從數(shù)據(jù)庫(kù)到后端的開發(fā)。

下面我們繼續(xù)來(lái)寫一個(gè)前端的文章列表頁(yè)面。

11.11 Model數(shù)據(jù)綁定

我們寫一個(gè)返回ModelAndView對(duì)象控制器類,其中數(shù)據(jù)模型Model中放入文章列表數(shù)據(jù),代碼如下:

    @GetMapping("listAllArticleView")
    fun listAllArticleView(model: Model): ModelAndView {
        model.addAttribute("articles", articleRepository?.findAll())
        return ModelAndView("list")
    }

其中,ModelAndView("list")中的"list"表示視圖文件的所在目錄的相對(duì)路徑。SpringBoot的默認(rèn)的視圖文件放在src/main/resources/templates目錄。

11.12 模板引擎視圖頁(yè)面

我們使用Freemarker模板引擎。我們?cè)趖emplates目錄下新建一個(gè)list.ftl文件,內(nèi)容如下:

<html>
<head>
    <title>Blog!!!</title>
</head>
<body>
<table>
    <thead>
    <th>序號(hào)</th>
    <th>標(biāo)題</th>
    <th>作者</th>
    <th>發(fā)表時(shí)間</th>
    <th>操作</th>
    </thead>
    <tbody>
    <#-- 使用FTL指令 -->
    <#list articles as article>
    <tr>
        <td>${article.id}</td>
        <td>${article.title}</td>
        <td>${article.author}</td>
        <td>${article.gmtModified}</td>
        <td><a href="#" target="_blank">編輯</a></td>
    </tr>
    </#list>
    </tbody>
</table>
</body>
</html>

其中,<#list articles as article>是Freemarker的循環(huán)指令,${}是 Freemarker引用變量的方式。

提示:關(guān)于Freemarker的詳細(xì)語(yǔ)法可參考 http://freemarker.org/

11.13 運(yùn)行測(cè)試

重啟應(yīng)用,瀏覽器訪問(wèn) : http://127.0.0.1:8000/listAllArticleView ,我們可以看到頁(yè)面輸出:

螢?zāi)豢煺?2017-07-18 23.52.35.png

到這里,我們已經(jīng)完成了一個(gè)從數(shù)據(jù)庫(kù)到前端頁(yè)面的完整的一個(gè)極簡(jiǎn)的Web應(yīng)用。

當(dāng)然,這樣的UI樣式未免太簡(jiǎn)陋了一些。下面我們加入前端UI組件美化一下。

11.14 引入前端組件

我們使用基于Bootstrap的前端UI庫(kù)Flat UI。首先去Flat UI的首頁(yè):http://www.bootcss.com/p/flat-ui/ 下載zip包,加壓后,放到我們的工程里,放置的目錄是:src/main/resources/static 。如下圖所示:

螢?zāi)豢煺?2017-07-19 01.12.49.png

我們?cè)趌ist.ftl頭部引入靜態(tài)資源文件:

<head>
    <meta charset="utf-8">
    <title>Blog</title>
    <meta name="description"
          content="Blog, using Flat UI Kit Free is a Twitter Bootstrap Framework design and Theme, this responsive framework includes a PSD and HTML version."/>

    <meta name="viewport" content="width=1000, initial-scale=1.0, maximum-scale=1.0">

    <!-- Loading Bootstrap -->
    <link href="/flatui/dist/css/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">

    <!-- Loading Flat UI -->
    <link href="/flatui/dist/css/flat-ui.css" rel="stylesheet">
    <link href="/flatui/docs/assets/css/demo.css" rel="stylesheet">

    <link rel="shortcut icon" href="/flatui/img/favicon.ico">

    <script src="/flatui/dist/js/vendor/jquery.min.js"></script>
    <script src="/flatui/dist/js/flat-ui.js"></script>
    <script src="/flatui/dist/js/vendor/html5shiv.js"></script>
    <script src="/flatui/dist/js/vendor/respond.min.js"></script>

    <link rel="stylesheet" href="/blog/blog.css">
    <script src="/blog/blog.js"></script>
</head>

其中,我們的這個(gè)SpringBoot應(yīng)用中默認(rèn)的靜態(tài)資源的跟路徑是src/main/resources/static,然后我們的HTML代碼中引用的路徑是在此根目錄下的相對(duì)路徑。

提示:更多的關(guān)于Spring Boot靜態(tài)資源處理內(nèi)容可以參考文章:
http://m.itdecent.cn/p/d127c4f78bb8

然后,我們?cè)侔盐覀兊奈恼铝斜聿季謨?yōu)化一下:

<div class="container">
    <h1>我的博客</h1>
    <table class="table table-responsive table-bordered">
        <thead>
        <th>序號(hào)</th>
        <th>標(biāo)題</th>
        <th>作者</th>
        <th>發(fā)表時(shí)間</th>
        <th>操作</th>
        </thead>
        <tbody>
        <#-- 使用FTL指令 -->
        <#list articles as article>
        <tr>
            <td>${article.id}</td>
            <td>${article.title}</td>
            <td>${article.author}</td>
            <td>${article.gmtModified}</td>
            <td><a href="#" target="_blank">編輯</a></td>
        </tr>
        </#list>
        </tbody>
    </table>
</div>

重新build工程,在此訪問(wèn)文章列表頁(yè),我們將看到一個(gè)比剛才漂亮多了的頁(yè)面:

螢?zāi)豢煺?2017-07-19 00.39.20.png

考慮到頭部的靜態(tài)資源文件基本都是公共的代碼,我們單獨(dú)抽取到一個(gè)head.ftl文件中, 然后在list.ftl中直接這樣引用:

<#include "head.ftl">

11.15 實(shí)現(xiàn)寫文章模塊

我們?cè)诹斜眄?yè)上面添加一個(gè)“寫文章”的入口:

<a href="addArticleView" target="_blank" class="btn btn-primary pull-right add-article">寫文章</a>

其中,btn btn-primary pull-right 這三個(gè)css樣式類是Flat UI組件的。add-article是我們自定義的樣式類:

.add-article {
    margin: 20px;
}

下面我們來(lái)寫新建文章的頁(yè)面。我們寫文章的跳轉(zhuǎn)頁(yè)面路徑是 <a href="addArticleView">, 我們先來(lái)新建一個(gè)寫文章頁(yè)面addArticleView.ftl:

<!DOCTYPE html>
<html>
<#include "head.ftl">
<body>
<div class="container">
    <h2>寫文章</h2>

    <form id="addArticleForm" class="form-horizontal">
        <div class="form-group">
            <input type="text" name="title" class="form-control" placeholder="文章標(biāo)題">
        </div>

        <div class="form-group">
            <textarea id="articleContentEditor" type="text" name="content" class="form-control" rows="20"
                      placeholder=""></textarea>
        </div>

        <div class="form-group save-article">
            <div class="col-sm-offset-2 col-sm-10">
                <button type="submit" class="btn btn-primary" id="addArticleBtn">保存并發(fā)表</button>
            </div>
        </div>
    </form>
</div>
</body>
</html>

然后,再添加控制器請(qǐng)求轉(zhuǎn)發(fā)。這里我們使用集成WebMvcConfigurerAdapter類,重寫實(shí)現(xiàn)addViewControllers方法的方式來(lái)添加一個(gè)不帶數(shù)據(jù)傳輸?shù)?,單純的?qǐng)求轉(zhuǎn)發(fā)的跳轉(zhuǎn)View的RequestMapping Controller:

@Configuration
class WebMvcConfig : WebMvcConfigurerAdapter() {
    // 注冊(cè)簡(jiǎn)單請(qǐng)求轉(zhuǎn)發(fā)跳轉(zhuǎn)View的RequestMapping Controller
    override fun addViewControllers(registry: ViewControllerRegistry?) {
        //寫文章的RequestMapping
        registry?.addViewController("addArticleView")?.setViewName("addArticleView")
    }
}

這樣前端瀏覽器來(lái)的請(qǐng)求addArticle會(huì)直接映射轉(zhuǎn)發(fā)到視圖addArticle.ftl文件渲染解析。

重啟應(yīng)用,進(jìn)入到我們的寫文章的頁(yè)面,如下圖:

螢?zāi)豢煺?2017-07-19 01.39.34.png

11.15.1 加上導(dǎo)航欄

為了方便頁(yè)面之間的跳轉(zhuǎn),我們給我們的博客站點(diǎn)加上導(dǎo)航欄,我們新建一個(gè)navbar.ftl文件,內(nèi)容如下:

<nav class="navbar navbar-default" role="navigation">
    <div class="container-fluid">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle" data-toggle="collapse"
                    data-target="#example-navbar-collapse">
                <span class="sr-only">切換導(dǎo)航</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#">我的博客</a>
        </div>
        <div class="collapse navbar-collapse" id="example-navbar-collapse">
            <ul class="nav navbar-nav">
                <li class=""><a href="listAllArticleView">文章列表</a></li>
                <li class="active"><a href="addArticleView">寫文章</a></li>
                <li><a href="#">關(guān)于</a></li>
                <li class="dropdown">
                    <a href="http://m.itdecent.cn/nb/12976878" class="dropdown-toggle" data-toggle="dropdown">
                        Kotlin <b class="caret"></b>
                    </a>
                    <ul class="dropdown-menu">
                        <li><a href="http://m.itdecent.cn/nb/12976878" target="_blank">Kotlin極簡(jiǎn)教程</a></li>
                        <li class="divider"></li>
                        <li><a href="#">Java</a></li>
                        <li><a href="#">Scala</a></li>
                        <li><a href="#">Groovy</a></li>
                        <li class="divider"></li>
                        <li><a href="http://m.itdecent.cn/nb/12066555" target="_blank">SpringBoot極簡(jiǎn)教程</a></li>
                    </ul>
                </li>
            </ul>
        </div>
    </div>
</nav>

11.15.2 抽取公共模板文件

考慮到head.ftl、navbar.ftl都是公共的文件,我們把他們單獨(dú)放到一個(gè)common目錄下。

然后,我們分別在addArticleView.ftl、listAllArticleView.ftl引用如下:

<!DOCTYPE html>
<html>
<#include "common/head.ftl">
<body>
<#include "common/navbar.ftl">

加入了導(dǎo)航欄之后,我們的頁(yè)面現(xiàn)在更加美觀了:

螢?zāi)豢煺?2017-07-19 01.57.56.png
螢?zāi)豢煺?2017-07-19 02.02.27.png

11.15.3 寫文章的控制器層接口

控制器層保存接口:

    @PostMapping("saveArticle")
    @ResponseBody
    fun saveArticle(article: Article): Article? {
        article.gmtCreated = Date()
        article.gmtModified = Date()
        article.version = 0
        return articleRepository?.save(article)
    }

另外,為了支持較多內(nèi)容的文章,我們把文章內(nèi)容字段設(shè)置成LONGTEXT:

ALTER TABLE `blog`.`article`
CHANGE COLUMN `content` `content` LONGTEXT NULL DEFAULT NULL ;

11.15.4 前端 ajax 請(qǐng)求

我們?cè)赽log.js中加上ajax POST請(qǐng)求后端接口的邏輯:

$(function () {

    $('#addArticleBtn').on('click', function () {
        saveArticle()
    })

    function saveArticle() {
        $.ajax({
            url: "saveArticle",
            data: $('#addArticleForm').serialize(),
            type: "POST",
            async: false,
            success: function (resp) {
                if (resp) {
                    saveArticleSuccess(resp)
                } else {
                    saveArticleFail()
                }
            },
            error: function () {
                saveArticleFail()
            }
        })
    }

    function saveArticleSuccess(resp) {
        alert('保存成功: ' + JSON.stringify(resp))
        window.open('detailArticleView?id=' + resp.id)
    }

    function saveArticleFail() {
        alert("保存失敗!")
    }


})

11.15.5 文章詳情頁(yè)

保存成功后,我們默認(rèn)跳轉(zhuǎn)該文章詳情頁(yè)。
后端控制器代碼:

    @GetMapping("detailArticleView")
    fun detailArticleView(id: Long, model: Model): ModelAndView {
        model.addAttribute("article", articleRepository?.findById(id)?.get())
        return ModelAndView("detailArticleView")
    }

注意,這里的articleRepository?.findById(id) 方法返回的是Optional<Article>, 我們調(diào)用其get()方法,返回真正的Article實(shí)體對(duì)象。

前端視圖detailArticleView.ftl代碼:

<!DOCTYPE html>
<html>
<#include "common/head.ftl">
<body>
<#include "common/navbar.ftl">
<div class="container">
    <h3>${article.title}</h3>
    <h6>${article.author}</h6>
    <h6>${article.gmtModified}</h6>
    <div>${article.content}</div>
</div>
</body>
</html>

我們?cè)谖恼铝斜眄?yè)中,給每篇文章標(biāo)題加上跳轉(zhuǎn)文章詳情的超鏈接:

<td><a target="_blank" href="detailArticleView?id=${article.id}">${article.title}</a></td>

現(xiàn)在我們的文章列表頁(yè)面如下:

螢?zāi)豢煺?2017-07-19 03.34.46.png

點(diǎn)擊一篇文章標(biāo)題,即可進(jìn)入詳情頁(yè):

螢?zāi)豢煺?2017-07-19 03.35.03.png

11.16 添加Markdown支持

我們寫技術(shù)博客文章,最常用的就是使用Markdown了。我們來(lái)為我們的博客添加Markdown的支持。我們使用前端js組件Mditor來(lái)支持Markdown的編輯。 Mditor是一個(gè)簡(jiǎn)潔、易于集成、方便擴(kuò)展、期望舒服的編寫 markdown 的編輯器。

11.16.1 引入靜態(tài)資源

    <link href="/mditor-master/dist/css/mditor.css" rel="stylesheet">
    <script src="/mditor-master/dist/js/mditor.js"></script>

11.16.2 初始化Mditor

我們?cè)趯懳恼碌捻?yè)面addArticleView.ftl,初始化Mditor如下:

<script>
    $(function () {
        //寫文章 mditor
        var mditor = Mditor.fromTextarea(document.getElementById('articleContentEditor'));

        //是否打開分屏
        mditor.split = true;    //打開
        //是否打開預(yù)覽
        mditor.preivew = true;  //打開
        //是否全屏
        mditor.fullscreen = false;  //關(guān)閉
        //獲取或設(shè)置編輯器的值
        mditor.on('ready', function () {
            mditor.value = '# ';
        });
        hljs.initHighlightingOnLoad();
        //源碼高亮
        $('pre code').each(function (i, block) {
            hljs.highlightBlock(block);
        });
    })
</script>

另外,我們還使用了代碼高亮插件highlight.js 。

這樣,寫文章的頁(yè)面對(duì)應(yīng)的textarea區(qū)域就變成了支持Markdown在線編輯+預(yù)覽的功能了:

螢?zāi)豢煺?2017-07-19 04.55.57.png

11.16.3 文章詳情顯示Markdown渲染

下面我們來(lái)使我們的詳情頁(yè)也能支持Markdown的渲染顯示。

詳情頁(yè)的視圖文件detailArticleView.ftl如下:

<!DOCTYPE html>
<html>
<#include "common/head.ftl">
<body>
<#include "common/navbar.ftl">
<div class="container">
    <h3>${article.title}</h3>
    <h6>${article.author}</h6>
    <h6>${article.gmtModified}</h6>
    <textarea id="articleContentShow" placeholder="<#escape  x as x?html>${article.content}</#escape>" style="display:
              none"></textarea>
    <div id="article-content" class="markdown-body"></div>

</div>
</body>
</html>

這里我們把文章內(nèi)容放到一個(gè)隱藏的textarea的placeholder屬性中:

<textarea id="articleContentShow" placeholder="<#escape  x as x?html>${article.content}</#escape>" style="display:
              none"></textarea>

注意,這里我們作了字符的轉(zhuǎn)義escape,防止有特殊字符導(dǎo)致頁(yè)面顯示錯(cuò)亂。
然后,我們?cè)趈s中獲取這個(gè)內(nèi)容:

<script>
    $(function () {
        // 文章詳情 mditor
        var parser = new Mditor.Parser();
        var articleContent = document.getElementById('articleContentShow').placeholder //直接取原本的字符串。不會(huì)被轉(zhuǎn)譯,默認(rèn)html頁(yè)面中textarea區(qū)域text需要escape編碼
        articleContent = unescape(articleContent);//unescape解碼
        var html = parser.parse(articleContent);

        $('#article-content').append(html);

        hljs.initHighlightingOnLoad();
        //源碼高亮
        $('pre code').each(function (i, block) {
            hljs.highlightBlock(block);
        });
    })
</script>

其中,我們是直接調(diào)用的Mditor.Parser()函數(shù)來(lái)解析Markdown字符文本的。

這樣我們的詳情頁(yè)也支持了Markdown的渲染顯示了:

螢?zāi)豢煺?2017-07-19 05.03.08.png

11.17 文章列表分頁(yè)搜索

為了方便檢索我們的博客文章,我們?cè)賮?lái)給文章列表頁(yè)面添加分頁(yè)、搜索、排序等功能。我們使用前端組件DataTables來(lái)實(shí)現(xiàn)。

提示:更多關(guān)于DataTables,可參考: http://www.datatables.club/

11.17.1 引入靜態(tài)資源文件

    <link href="/datatables/media/css/jquery.dataTables.css" rel="stylesheet">
    <script src="/datatables/media/js/jquery.dataTables.js"></script>

11.17.2 給表格加上id

我們給表格加個(gè)屬性id="articlesDataTable" :

    <table id="articlesDataTable" class="table table-responsive table-bordered">
        <thead>
        <th>序號(hào)</th>
        <th>標(biāo)題</th>
        <th>作者</th>
        <th>發(fā)表時(shí)間</th>
        <th>操作</th>
        </thead>
        <tbody>
        <#-- 使用FTL指令 -->
        <#list articles as article>
        <tr>
            <td>${article.id}</td>
            <td><a target="_blank" href="detailArticleView?id=${article.id}">${article.title}</a></td>
            <td>${article.author}</td>
            <td>${article.gmtModified}</td>
            <td><a href="#" target="_blank">編輯</a></td>
        </tr>
        </#list>
        </tbody>
    </table>

11.17.3 調(diào)用DataTable函數(shù)

首先,我們配置一下DataTable的選項(xiàng):

    var aLengthMenu = [7, 10, 20, 50, 100, 200]
    var dataTableOptions = {
        "bDestroy": true,
        dom: 'lfrtip',
        "paging": true,
        "lengthChange": true,
        "searching": true,
        "ordering": true,
        "info": true,
        "autoWidth": true,
        "processing": true,
        "stateSave": true,
        responsive: true,
        fixedHeader: false,
        order: [[1, "desc"]],
        "aLengthMenu": aLengthMenu,
        language: {
            "search": "<div style='border-radius:10px;margin-left:auto;margin-right:2px;width:760px;'>_INPUT_  <span class='btn btn-primary'><span class='fa fa-search'></span> 搜索</span></div>",

            paginate: {//分頁(yè)的樣式內(nèi)容
                previous: "上一頁(yè)",
                next: "下一頁(yè)",
                first: "第一頁(yè)",
                last: "最后"
            }
        },
        zeroRecords: "沒(méi)有內(nèi)容",//table tbody內(nèi)容為空時(shí),tbody的內(nèi)容。
        //下面三者構(gòu)成了總體的左下角的內(nèi)容。
        info: "總計(jì) _TOTAL_ 條,共 _PAGES_ 頁(yè),_START_ - _END_ ",//左下角的信息顯示,大寫的詞為關(guān)鍵字。
        infoEmpty: "0條記錄",//篩選為空時(shí)左下角的顯示。
        infoFiltered: ""http://篩選之后的左下角篩選提示
    }

然后把我們剛才添加了id的表格使用JQuery選擇器獲取對(duì)象,然后直接調(diào)用:

$('#articlesDataTable').DataTable(dataTableOptions)

再次看我們的文章列表頁(yè):

螢?zāi)豢煺?2017-07-19 05.23.21.png

已經(jīng)具備了分頁(yè)、搜索、排序等功能了。

到這里,我們的這個(gè)較為完整的極簡(jiǎn)博客站點(diǎn)應(yīng)用基本就開發(fā)完成了。

11.18 Spring 5.0對(duì)Kotlin的支持

Kotlin 關(guān)鍵性能之一就是能與 Java 庫(kù)很好地互用。但要在 Spring 中編寫慣用的 Kotlin 代碼,還需要一段時(shí)間的發(fā)展。 Spring 對(duì) Java 8 的新支持:函數(shù)式 Web 編程、bean 注冊(cè) API , 這同樣可以在 Kotlin 中使用。

Kotlin 擴(kuò)展是Kotlin 的編程利器。它能對(duì)現(xiàn)有的 API 實(shí)現(xiàn)非侵入式的擴(kuò)展,從而向 Spring中加入 Kotlin 的專有的功能特性。

11.18.1 一種注冊(cè) Bean 的新方法

Spring Framework 5.0 引入了一種注冊(cè) Bean 的新方法,作為利用 XML 或者 JavaConfig 的 @Configuration 或者 @Bean 的替代方案。簡(jiǎn)言之就是 Lambda 表達(dá)式。

例如用 Java 代碼我們會(huì)這樣寫:

GenericApplicationContext context = new GenericApplicationContext();
context.registerBean(Foo.class);
context.registerBean(Bar.class, () -> new
    Bar(context.getBean(Foo.class))
);

而使用 Kotlin 我們可以將代碼寫成這樣:

val context = GenericApplicationContext {
    registerBean<foo>()
    registerBean { Bar(it.getBean<foo>()) }
}

11.18.2 Spring Web 函數(shù)式 API

Spring 5.0 中的 RouterFunctionDsl 可以讓我們使用干凈且優(yōu)雅的 Kotlin 代碼來(lái)使用嶄新的 Spring Web 函數(shù)式 API:

fun route(request: ServerRequest) = RouterFunctionDsl {
    accept(TEXT_HTML).apply {
            (GET("/user/") or GET("/users/")) { findAllView() }
            GET("/user/{login}") { findViewById() }
    }
    accept(APPLICATION_JSON).apply {
            (GET("/api/user/") or GET("/api/users/")) { findAll() }
            POST("/api/user/") { create() }
            POST("/api/user/{login}") { findOne() }
    }
 } (request)

11.18.3 Reactor Kotlin 擴(kuò)展

Reactor 是 Spring 5.0 中提供的響應(yīng)式框架。而 reactor-kotlin 項(xiàng)目則是對(duì) Reactor 中使用Kotlin 的支持。目前該項(xiàng)目正在早期階段。

11.18.4 基于 Kotlin腳本的 Gradle 構(gòu)建配置

之前我們的 Gradle 構(gòu)建配置文件都是用Groovy 來(lái)編寫的,這導(dǎo)致我們基于 Gradle 的 Kotlin 工程還要配置 Groovy 的語(yǔ)法的構(gòu)建配置文件。

在gradle-script-kotlin 項(xiàng)目中,我們可以直接用 Kotlin 腳本來(lái)編寫 Gradle 的構(gòu)建配置文件了。而且 IDE 還為我們提供了在編寫配置文件過(guò)程中的自動(dòng)完成功能和重構(gòu)功能的支持。

11.18.5 基于模板的 Kotlin 腳本

從 4.3 版本開始,Spring 提供了一個(gè) ScriptTemplateView,用于利用支持 JSR-223 的腳本引擎來(lái)渲染模板。 Kotlin 1.1-M04 提供了這樣的支持,并支持渲染基于 Kotlin 的模板,類似下面這樣:

import io.spring.demo.User
import io.spring.demo.joinToLine

"""
${include("header", bindings)}
<h1>Title : $title</h1>
<ul>
    ${(users as List<User>).joinToLine{ "<li>User ${it.firstname} ${it.lastname}</li>" }}
</ul>
${include("footer")}
"""

本章小結(jié)

本章我們較為細(xì)致完整地介紹了使用Kotlin集成SpringBoot進(jìn)行服務(wù)后端開發(fā),并結(jié)合簡(jiǎn)單的前端開發(fā),完成了一個(gè)極簡(jiǎn)的技術(shù)博客Web站點(diǎn)。我們可以看到,使用Kotlin結(jié)合Spring Boot、Spring MVC、JPA等Java框架的無(wú)縫集成,關(guān)鍵是大大簡(jiǎn)化了我們的代碼。同時(shí),在本章最后我們簡(jiǎn)單介紹了Spring 5.0中對(duì)Kotlin的支持諸多新特性,這些新特性令人驚喜。

使用Kotlin編寫Spring Boot應(yīng)用程序越多,我們?cè)接X(jué)得這兩種技術(shù)有著共同的目標(biāo),讓我們廣大程序員可以使用——

  • 富有表達(dá)性
  • 簡(jiǎn)潔優(yōu)雅
  • 可讀

的代碼來(lái)更高效地編寫應(yīng)用程序,而Spring Framework 5 Kotlin支持將這些技術(shù)以更加自然,簡(jiǎn)單和強(qiáng)大的方式來(lái)展現(xiàn)給我們。

未來(lái)Spring Framework 5.0 和 Kotlin 結(jié)合的開發(fā)實(shí)踐更加值得我們期待。

在下一章中我們將一起學(xué)習(xí)Kotlin 集成 Gradle 開發(fā)的相關(guān)內(nèi)容。

本章項(xiàng)目源碼: https://github.com/EasyKotlin/chapter11_kotlin_springboot


Kotlin 開發(fā)者社區(qū)

國(guó)內(nèi)第一Kotlin 開發(fā)者社區(qū)公眾號(hào),主要分享、交流 Kotlin 編程語(yǔ)言、Spring Boot、Android、React.js/Node.js、函數(shù)式編程、編程思想等相關(guān)主題。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,695評(píng)論 19 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,288評(píng)論 6 342
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong閱讀 22,968評(píng)論 1 92
  • 第10章 Kotlin與Java互操作 《Kotlin極簡(jiǎn)教程》正式上架: 點(diǎn)擊這里 > 去京東商城購(gòu)買閱讀 點(diǎn)擊...
    光劍書架上的書閱讀 3,681評(píng)論 0 24
  • 感覺(jué)該換筆了,不聽(tīng)我使喚了。這支算是用得最順手的了,但也到了說(shuō)再見(jiàn)之時(shí)。 今天的字分兩回寫。早上上班前擠出時(shí)間寫了...
    敏非敏閱讀 320評(píng)論 4 14

友情鏈接更多精彩內(nèi)容