8.2 Spring Boot集成Groovy、Grails開發(fā)
本章介紹Spring Boot集成Groovy,Grails開發(fā)。我們將開發(fā)一個(gè)極簡(jiǎn)版的pms(項(xiàng)目管理系統(tǒng))。
Groovy和Grails簡(jiǎn)介
Groovy簡(jiǎn)介
Groovy 是一種動(dòng)態(tài)語(yǔ)言,它在 JVM 上運(yùn)行,并且與 Java 語(yǔ)言無(wú)縫集成。
Groovy 可以大大減少 Java 代碼的數(shù)量。在 Groovy 中,不再需要為字段編寫 getter 和 setter 方法,因?yàn)?Groovy 會(huì)自動(dòng)提供它們。不再需要編寫 for Iterator i = list.iterator() 來(lái)循環(huán)遍歷一系列的項(xiàng);list.each 可以做相同的事情,而且看上去更簡(jiǎn)潔,表達(dá)更清晰。簡(jiǎn)言之,Groovy 就是 21 世紀(jì)的 Java 語(yǔ)言。[2]
Groovy 不會(huì)替代 Java 語(yǔ)言 — 它只是提供了增強(qiáng)。您可以很快地掌握 Groovy,因?yàn)檎f(shuō)到底,Groovy 代碼就是 Java 代碼。這兩種語(yǔ)言是如此兼容,甚至可以將一個(gè) .java 文件重命名為一個(gè) .groovy 文件 — 例如,將 Person.java 改為 Person.groovy — 從而得到一個(gè)有效的(可執(zhí)行的)Groovy 文件(雖然這個(gè) Groovy 文件并沒有用到 Groovy 提供的任何語(yǔ)法)。
Grails簡(jiǎn)介
Grails是一套用于快速Web應(yīng)用開發(fā)的開源框架,它基于Groovy編程語(yǔ)言,并構(gòu)建于Spring、Hibernate等開源框架之上,是一個(gè)高生產(chǎn)力一站式框架。
Grails這個(gè)獨(dú)特的框架被視為是提升工程師生產(chǎn)效率的動(dòng)態(tài)工具,因?yàn)槠涓纱嗟腁PI設(shè)計(jì),合理的默認(rèn)值以及約定架構(gòu)。與java的無(wú)縫集成使得這個(gè)框架成為世界上眾多框架中的首選。一系列強(qiáng)大的特性,如基于sping的依賴注入和各式各樣的插件,可以提供創(chuàng)建現(xiàn)代基于web的app的所有需要的東西。
我們使用Grails框架。就像 Rails 與 Ruby 編程語(yǔ)言聯(lián)系非常緊密一樣,Grails 也離不開 Groovy。
DRY(Don't Repeat Yourself,不要重復(fù)自己)
約定優(yōu)于配置(Convention over Configuration)
DRY和約定優(yōu)先于配置的思想,是由Rails興起并迅速被廣泛接收和欣賞的Web框架新思路。Grails作為JEE世界的Rails,把這些最前沿的設(shè)計(jì)理念帶入已顯得陳舊的JEE社區(qū),擁有鮮明突出的特點(diǎn),以及由此帶來(lái)的優(yōu)秀的開發(fā)效率。
對(duì)Grails來(lái)說(shuō),Groovy是其能夠?qū)崿F(xiàn)靈活多變的快速開發(fā),區(qū)別于其他運(yùn)行于JVM之上的Web框架的核心技術(shù)。
Groovy的動(dòng)態(tài)特性是其最大亮點(diǎn),在這方面幾乎不輸于Ruby等其他熱門的動(dòng)態(tài)語(yǔ)言。[3]
Grails實(shí)現(xiàn)原理
基于Spring MVC的控制器層
構(gòu)建于Gant 上的命令行腳本運(yùn)行環(huán)境,內(nèi)置Tomcat服務(wù)器,不用重新啟動(dòng)服務(wù)器就可以進(jìn)行重新加載
基于Spring的MessageSource核心概念,提供了對(duì)國(guó)際化(i18n)的支持
基于Spring事務(wù)抽象概念,實(shí)現(xiàn)事務(wù)服務(wù)層[1]
Github:https://github.com/grails
官網(wǎng):https://grails.org/
數(shù)據(jù)庫(kù)的對(duì)象關(guān)系映射層使用GORM
我們使用 Grail 對(duì)象關(guān)系映射(Grails Object Relational Mapping,GORM)API 進(jìn)行數(shù)據(jù)庫(kù)層的持久化工作。
安裝Grails 3 開發(fā)環(huán)境
瀏覽器訪問 http://www.grails.org/Download,下載,解壓,設(shè)置環(huán)境變量即可。具體步驟如下:
1.下載并解壓 grails.zip。
2.創(chuàng)建一個(gè) GRAILS_HOME 環(huán)境變量。
3.將 $GRAILS_HOME/bin 添加到 PATH中。
如果你的電腦上有SDKMAN! (The Software Development Kit Manager),可以直接命令行自動(dòng)安裝Grails最新穩(wěn)定版本:
$ sdk install grails
安裝完畢,驗(yàn)證一下:
$ grails -v
| Grails Version: 3.2.8
| Groovy Version: 2.4.10
| JVM Version: 1.8.0_40
OK, grails開發(fā)環(huán)境搞定。我們可以看到,grails依賴的Groovy,JVM環(huán)境版本。
創(chuàng)建Grails項(xiàng)目
讓我們來(lái)體驗(yàn)JVM上的Ruby on rails式的命令行自動(dòng)工程生成的快感吧!
命令行直接運(yùn)行:
$ grails create-app pms
Resolving dependencies..
| Application created at /Users/jack/book/pms
我們就生成了一個(gè)grails工程demo,目錄如下:
.
├── build.gradle
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── grails-app
│ ├── assets
│ │ ├── images
│ │ │ ├── apple-touch-icon-retina.png
│ │ │ ├── apple-touch-icon.png
│ │ │ ├── favicon.ico
│ │ │ ├── grails-cupsonly-logo-white.svg
│ │ │ ├── skin
│ │ │ │ ├── database_add.png
│ │ │ │ ├── database_delete.png
│ │ │ │ ├── database_edit.png
│ │ │ │ ├── database_save.png
│ │ │ │ ├── database_table.png
│ │ │ │ ├── exclamation.png
│ │ │ │ ├── house.png
│ │ │ │ ├── information.png
│ │ │ │ ├── shadow.jpg
│ │ │ │ ├── sorted_asc.gif
│ │ │ │ └── sorted_desc.gif
│ │ │ └── spinner.gif
│ │ ├── javascripts
│ │ │ ├── application.js
│ │ │ ├── bootstrap.js
│ │ │ └── jquery-2.2.0.min.js
│ │ └── stylesheets
│ │ ├── application.css
│ │ ├── bootstrap.css
│ │ ├── errors.css
│ │ ├── grails.css
│ │ ├── main.css
│ │ └── mobile.css
│ ├── conf
│ │ ├── application.yml
│ │ ├── logback.groovy
│ │ └── spring
│ │ └── resources.groovy
│ ├── controllers
│ │ └── pms
│ │ └── UrlMappings.groovy
│ ├── domain
│ ├── i18n
│ │ ├── messages.properties
│ │ ├── messages_cs_CZ.properties
│ │ ├── messages_da.properties
│ │ ├── messages_de.properties
│ │ ├── messages_es.properties
│ │ ├── messages_fr.properties
│ │ ├── messages_it.properties
│ │ ├── messages_ja.properties
│ │ ├── messages_nb.properties
│ │ ├── messages_nl.properties
│ │ ├── messages_pl.properties
│ │ ├── messages_pt_BR.properties
│ │ ├── messages_pt_PT.properties
│ │ ├── messages_ru.properties
│ │ ├── messages_sv.properties
│ │ ├── messages_th.properties
│ │ └── messages_zh_CN.properties
│ ├── init
│ │ └── pms
│ │ ├── Application.groovy
│ │ └── BootStrap.groovy
│ ├── services
│ ├── taglib
│ ├── utils
│ └── views
│ ├── error.gsp
│ ├── index.gsp
│ ├── layouts
│ │ └── main.gsp
│ └── notFound.gsp
├── grails-wrapper.jar
├── grailsw
├── grailsw.bat
├── settings.gradle
└── src
├── integration-test
│ └── groovy
├── main
│ ├── groovy
│ └── webapp
└── test
└── groovy
29 directories, 62 files
這真的是一鍵生成。
我們可以直接使用下面的命令運(yùn)行這個(gè)工程:
$ grails run-app
它會(huì)自動(dòng)下載gradle-3.4.1-bin.zip(通常會(huì)很慢):
| Resolving Dependencies. Please wait...
Downloading https://services.gradle.org/distributions/gradle-3.4.1-bin.zip
如果我們本地有g(shù)radle環(huán)境,我們也可將此工程導(dǎo)入idea,配置一下本地的gradle環(huán)境。如下圖所示:
首次構(gòu)建,gradle需要下載工程依賴的jar包。
我們可以看到build.gradle里面已經(jīng)配置好一切:
buildscript {
repositories {
mavenLocal()
maven { url "https://repo.grails.org/grails/core" }
}
dependencies {
classpath "org.grails:grails-gradle-plugin:$grailsVersion"
classpath "com.bertramlabs.plugins:asset-pipeline-gradle:2.14.1"
classpath "org.grails.plugins:hibernate5:${gormVersion-".RELEASE"}"
}
}
version "0.1"
group "pms"
apply plugin:"eclipse"
apply plugin:"idea"
apply plugin:"war"
apply plugin:"org.grails.grails-web"
apply plugin:"org.grails.grails-gsp"
apply plugin:"asset-pipeline"
repositories {
mavenLocal()
maven { url "https://repo.grails.org/grails/core" }
}
dependencies {
compile "org.springframework.boot:spring-boot-starter-logging"
compile "org.springframework.boot:spring-boot-autoconfigure"
compile "org.grails:grails-core"
compile "org.springframework.boot:spring-boot-starter-actuator"
compile "org.springframework.boot:spring-boot-starter-tomcat"
compile "org.grails:grails-dependencies"
compile "org.grails:grails-web-boot"
compile "org.grails.plugins:cache"
compile "org.grails.plugins:scaffolding"
compile "org.grails.plugins:hibernate5"
compile "org.hibernate:hibernate-core:5.1.3.Final"
compile "org.hibernate:hibernate-ehcache:5.1.3.Final"
console "org.grails:grails-console"
profile "org.grails.profiles:web"
runtime "com.bertramlabs.plugins:asset-pipeline-grails:2.14.1"
runtime "com.h2database:h2"
testCompile "org.grails:grails-plugin-testing"
testCompile "org.grails.plugins:geb"
testRuntime "org.seleniumhq.selenium:selenium-htmlunit-driver:2.47.1"
testRuntime "net.sourceforge.htmlunit:htmlunit:2.18"
}
bootRun {
jvmArgs('-Dspring.output.ansi.enabled=always')
addResources = true
}
assets {
minifyJs = true
minifyCss = true
}
我們?cè)赼pplication.yml里面配置一下server.port (默認(rèn)8080):
server:
port: 8008
命令行執(zhí)行(我們也可以使用grails run-app運(yùn)行工程,區(qū)別是grails會(huì)下載外部gradle包,配置的gradle環(huán)境不是本地機(jī)器):
gradle bootRun
你將看到類似如下啟動(dòng)日志:
02:18:02: Executing external task 'bootRun'...
:compileJava NO-SOURCE
:compileGroovy UP-TO-DATE
:buildProperties UP-TO-DATE
:processResources
:classes
:findMainClass
objc[27257]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/bin/java (0x1001e44c0) and /Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/libinstrument.dylib (0x101a4e4e0). One of the two will be used. Which one is undefined.
:bootRun
Grails application running at http://localhost:8008 in environment: development
啟動(dòng)完畢,訪問http://localhost:8008/,你將看到如下頁(yè)面:
為了演示上的簡(jiǎn)易性,數(shù)據(jù)庫(kù)我們直接用的是H2,在application.yml配置如下:
hibernate:
cache:
queries: false
use_second_level_cache: true
use_query_cache: false
region.factory_class: org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory
dataSource:
pooled: true
jmxExport: true
driverClassName: org.h2.Driver
username: sa
password:
environments:
development:
dataSource:
dbCreate: create-drop
url: jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
test:
dataSource:
dbCreate: update
url: jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
production:
dataSource:
dbCreate: none
url: jdbc:h2:./prodDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
properties:
jmxEnabled: true
initialSize: 5
maxActive: 50
minIdle: 5
maxIdle: 25
maxWait: 10000
maxAge: 600000
timeBetweenEvictionRunsMillis: 5000
minEvictableIdleTimeMillis: 60000
validationQuery: SELECT 1
validationQueryTimeout: 3
validationInterval: 15000
testOnBorrow: true
testWhileIdle: true
testOnReturn: false
jdbcInterceptors: ConnectionState
defaultTransactionIsolation: 2 # TRANSACTION_READ_COMMITTED
建立領(lǐng)域模型Model層
我們繼續(xù)在當(dāng)前工程根目錄下。使用grails的create-domain-class命令創(chuàng)建領(lǐng)域類:
$grails create-domain-class Project
執(zhí)行這個(gè)命令,grails也會(huì)下gradle包。下載完,grails程序自動(dòng)解壓,放到約定的目錄,日志如下:
| Resolving Dependencies. Please wait...
Downloading https://services.gradle.org/distributions/gradle-3.4.1-bin.zip
.
.
.
Unzipping /Users/jack/.gradle/wrapper/dists/gradle-3.4.1-bin/71zneekfcxxu7l9p7nr2sc65s/gradle-3.4.1-bin.zip to /Users/jack/.gradle/wrapper/dists/gradle-3.4.1-bin/71zneekfcxxu7l9p7nr2sc65s
Set executable permissions for: /Users/jack/.gradle/wrapper/dists/gradle-3.4.1-bin/71zneekfcxxu7l9p7nr2sc65s/gradle-3.4.1/bin/gradle
Cleaned up directory '/Users/jack/book/pms/build/classes/main'
Cleaned up directory '/Users/jack/book/pms/build/resources/main'
CONFIGURE SUCCESSFUL
Total time: 2 mins 43.589 secs
| Created grails-app/domain/pms/Project.groovy
| Created src/test/groovy/pms/ProjectSpec.groovy
我們繼續(xù)創(chuàng)建項(xiàng)目Project的里程碑Milestone領(lǐng)域類:
$ grails create-domain-class Milestone
| Created grails-app/domain/pms/Milestone.groovy
| Created src/test/groovy/pms/MilestoneSpec.groovy
我們可以看到這兩個(gè)類的代碼如下:
package pms
class Project {
static constraints = {
}
}
package pms
class Milestone {
static constraints = {
}
}
一開始,沒有什么內(nèi)容。其中,static constraints變量里面主要定義對(duì)應(yīng)的實(shí)體類的約束條件。
下面我們來(lái)設(shè)計(jì)領(lǐng)域?qū)ο蟮膶傩浴?/p>
一個(gè)項(xiàng)目Project,我們極簡(jiǎn)化處理,取幾個(gè)代表的屬性,比如:名稱,負(fù)責(zé)人,開始時(shí)間,結(jié)束時(shí)間,狀態(tài)等。
package pms
class Project {
static constraints = {
}
Integer id
String name
String owner
Date startDate
Date endDate
String status
}
通常,一個(gè)項(xiàng)目,會(huì)有多個(gè)里程碑,所以我們這里的里程碑表,多條記錄對(duì)應(yīng)一個(gè)Project。里程碑屬性我們就取: 關(guān)聯(lián)的項(xiàng)目id,名稱,負(fù)責(zé)人,計(jì)劃時(shí)間,實(shí)際時(shí)間,狀態(tài)。
package pms
class Milestone {
static constraints = {
}
Integer id
Integer projectId
String name
String owner
Date expectDate
Date actualDate
String status
}
使用grails腳手架自動(dòng)生成Controller層,視圖View層代碼
grails的腳手架控制值相當(dāng)簡(jiǎn)易,簡(jiǎn)單易用。我們可以使用
grails create-controller $DomainName : 創(chuàng)建DomainName對(duì)應(yīng)的空Controller
grails generate-controller $DomainName :創(chuàng)建DomainName對(duì)應(yīng)的包含CRUD的Controller
grails generate-all $DomainName: 創(chuàng)建DomainName對(duì)應(yīng)的包含CRUD的Controller,以及對(duì)應(yīng)的視圖view模板代碼
下面我們就使用grails generate-all來(lái)創(chuàng)建Project,Milestone的Controller,以及視圖。
$ grails generate-all Project
| Rendered template Controller.groovy to destination grails-app/controllers/pms/ProjectController.groovy
| Rendered template Spec.groovy to destination src/test/groovy/pms/ProjectControllerSpec.groovy
| Scaffolding completed for grails-app/domain/pms/Project.groovy
| Rendered template create.gsp to destination grails-app/views/project/create.gsp
| Rendered template edit.gsp to destination grails-app/views/project/edit.gsp
| Rendered template index.gsp to destination grails-app/views/project/index.gsp
| Rendered template show.gsp to destination grails-app/views/project/show.gsp
| Views generated for grails-app/domain/pms/Project.groovy
$ grails generate-all Milestone
| Rendered template Controller.groovy to destination grails-app/controllers/pms/MilestoneController.groovy
| Rendered template Spec.groovy to destination src/test/groovy/pms/MilestoneControllerSpec.groovy
| Scaffolding completed for grails-app/domain/pms/Milestone.groovy
| Rendered template create.gsp to destination grails-app/views/milestone/create.gsp
| Rendered template edit.gsp to destination grails-app/views/milestone/edit.gsp
| Rendered template index.gsp to destination grails-app/views/milestone/index.gsp
| Rendered template show.gsp to destination grails-app/views/milestone/show.gsp
| Views generated for grails-app/domain/pms/Milestone.groovy
下面是創(chuàng)建之后的工程目錄:
我們可以看出,通過(guò)統(tǒng)一的約定,我們得到規(guī)整的目錄結(jié)構(gòu)。很好的體現(xiàn)了“約定優(yōu)于配置(Convention over Configuration)”的方法論思想。
對(duì)控制器的理解可以歸結(jié)為三個(gè) R:return、redirect 和 render。有些動(dòng)作利用隱式的 return 語(yǔ)句將數(shù)據(jù)返回到具有相同名稱的 GSP 頁(yè)面。有些動(dòng)作進(jìn)行重定向。
我們看一下ProjectController的index方法:
def index(Integer max) {
params.max = Math.min(max ?: 10, 100)
respond Project.list(params), model:[projectCount: Project.count()]
}
我們沒有寫list,count,hasErros等方法,GROM都幫我們打理好一切了。具體的實(shí)現(xiàn)源碼在org.grails.datastore.gorm里面。這個(gè)處理方案跟Spring-jpa的思想基本是一樣的。都是通過(guò)注解元編程,動(dòng)態(tài)生成相應(yīng)的方法代碼。
部署測(cè)試
完成上述步驟,我們就已經(jīng)有了包含CRUD基本功能的Web應(yīng)用了,使用
gradle bootRun
命令運(yùn)行工程,使用瀏覽器訪問:http://localhost:8008/
你將看到如下頁(yè)面:
我們可以看到,“Available Controllers”列表,這個(gè)功能模塊是通過(guò)如下一段gsp代碼實(shí)現(xiàn)的:
<div id="controllers" role="navigation">
<h2>Available Controllers:</h2>
<ul>
<g:each var="c" in="${grailsApplication.controllerClasses.sort { it.fullName } }">
<li class="controller">
<g:link controller="${c.logicalPropertyName}">${c.fullName}</g:link>
</li>
</g:each>
</ul>
</div>
新建一個(gè)Project,保存,如下圖:
點(diǎn)擊Project列表頁(yè):
編輯該項(xiàng)目:
Grails通過(guò)UrlMappings統(tǒng)一Url映射,簡(jiǎn)化了Controller到View的映射路徑的代碼。只要我們按照“約定”的目錄結(jié)構(gòu)組織我們的代碼即可。
package pms
class UrlMappings {
static mappings = {
"/$controller/$action?/$id?(.$format)?"{
constraints {
// apply constraints here
}
}
"/"(view:"/index")
"500"(view:'/error')
"404"(view:'/notFound')
}
}
Grails框架里面充滿了大量“約定規(guī)則”,按照“約定規(guī)則”編程,我們看到了,代碼是如此之“極簡(jiǎn)”。
我們簡(jiǎn)單看一個(gè)例子。如下圖:
這里的“New Milestone”,是怎么實(shí)現(xiàn)的呢?我們來(lái)看一下milestone/index.gsp里面的一段代碼:
<g:message code="default.list.label" args="[entityName]" />
這里的default.list.label值配置在i18n/messages.properties里面。
default.home.label=Home
default.list.label={0} List
default.add.label=Add {0}
default.new.label=New {0}
default.create.label=Create {0}
default.show.label=Show {0}
default.edit.label=Edit {0}
default.button.create.label=Create
default.button.edit.label=Edit
default.button.update.label=Update
default.button.delete.label=Delete
default.button.delete.confirm.message=Are you sure?
不過(guò),在這種.properties配置文件中,中文可讀性比較差。類似這樣子:
default.blank.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u4E0D\u80FD\u4E3A\u7A7A
gsp代碼中,以 g: 為前綴的就是 GroovyTag。
本章pms項(xiàng)目工程源碼:
https://github.com/EasySpringBoot/pms
小結(jié)
參考資料
1.http://baike.baidu.com/item/grails
2.https://www.ibm.com/developerworks/cn/java/j-grails01158/
3.http://www.infoq.com/cn/articles/case-study-grails-partii/