因爲項目須要用到 Groovy 語言,這兩天對其進行了粗略的學習,本文是對學習作的一個簡單總結,主要內容參考於官方文檔(Groovy 的官方文檔仍是很是不錯的,強烈推薦閱讀),但願本文對準備學習使用或者對 Groovy 感興趣的同窗有所幫助,若有不對之處還望指出哈,對這門語言的理解仍是比較膚淺的。php
Groovy 是 Apache 旗下的一門基於 JVM 平臺的動態/敏捷編程語言,在語言的設計上它吸納了 Python、Ruby 和 Smalltalk 語言的優秀特性,語法很是簡練和優美,開發效率也很是高(編程語言的開發效率和性能是相互矛盾的,越高級的編程語言性能越差,由於意味着更多底層的封裝,不過開發效率會更高,需結合使用場景作取捨)。而且,Groovy 能夠與 Java 語言無縫對接,在寫 Groovy 的時候若是忘記了語法能夠直接按Java的語法繼續寫,也能夠在 Java 中調用 Groovy 腳本,均可以很好的工做,這有效的下降了 Java 開發者學習 Groovy 的成本。Groovy 也並不會替代 Java,而是相輔相成、互補的關係,具體使用哪門語言這取決於要解決的問題和使用的場景。html
.groovy
文件用命令行直接運行。public class Hello {
public static void main(String[] args) {
System.out.println("hello world");
}
}複製代碼
在Groovy中,這些均可以省略,下面這4種方式均可以輸出「hello world」。System.out.println("hello world");
System.out.println "hello world";
println("hello world")
println 'hello world'複製代碼
固然,也能夠像Java同樣運行在類的main方法中。class Hello {
static void main(args) {
println 'hello world'
}
}複製代碼
若是 Groovy 腳本文件裏只有執行代碼,沒有類的定義,則 Groovy 編譯器會生成一個 Script 的子類,類名和腳本文件的文件名同樣,而腳本中的代碼會被包含在一個名爲run
的方法中,同時還會生成一個main方法,做爲整個腳本的入口。因此,做爲 JVM 平臺語言,與 Java 本質上仍是同樣的。Groovy 會默認導入下面這些包、類,不須要使用import
語句顯式導入。java
java.io.*
java.lang.*
java.math.BigDecimal
java.math.BigInteger
java.net.*
java.util.*
groovy.lang.*
groovy.util.*複製代碼
在 Groovy 中,調用的方法將在運行時被選擇。這種機制被稱爲運行時分派或多重方法(multi-methods),是根據運行時實參(argument)的類型來選擇方法。Java 採用的是相反的策略:編譯時根據聲明的類型來選擇方法。mysql
下面是一個例子,一樣的 Java 代碼在 Java 和 Groovy 環境中運行結果是不一樣的.web
int method(String arg) {
return 1;
}
int method(Object arg) {
return 2;
}
Object o = "Object";
int result = method(o);
// In Java
assertEquals(2, result);
// In Groovy
assertEquals(1, result);複製代碼
產生差別的緣由在於,Java 使用靜態數據類型,o 被聲明爲 Object 對象,而 Groovy 會在運行時實際調用方法時進行選擇。由於調用的是 String 類型的對象,因此天然調用 String 版本的方法。spring
在 Groovy 中,{...} 語句塊是留給閉包(Closure)使用的,因此不能像 Java 中同樣使用下面這種方式初始化數組sql
int[] array = { 1, 2, 3}複製代碼
而應該是下面這樣shell
int[] array = [1,2,3]複製代碼
Groovy 默認會隱式的建立getter、setter方法,而且會提供帶參的構造器,下面二者是等價的。apache
// In Java
public class Person {
private String name;
Person(String name) {
this.name = name
}
public String getName() {
return name
}
public void setName(String name) {
this.name = name
}
}
// In Groovy
class Person {
String name
}
def person = new Person(name: '張三')
assert '張三' == person.name
person.name = '李四'
//person.setName('李四')
assert '李四' == person.getName()複製代碼
在 Java 中若是沒有顯式的指定訪問修飾符(public、protected、private
)那麼默認是包訪問權限,但在 Groovy 中默認是public
的,因此須要使用@PackageScope
註解。編程
class Person {
@PackageScope String name
}複製代碼
ARM(Automatic Resource Management,自動資源管理)語句塊(或者叫TWR語法)從 Java 7 開始引入,用於下降IO操做代碼的複雜度,但 Groovy 並不支持。相反,Groovy 提供多種基於閉包的方法,不但能夠達到一樣的效果而且會更加簡潔優美。
//In Groovy
Path file = Paths.get("/User/lihengming/test.txt");
Charset charset = Charset.forName("UTF-8");
try (BufferedReader reader = Files.newBufferedReader(file, charset)) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
//In Groovy
new File('/User/lihengming/test.txt').eachLine('UTF-8') {
println it
}
//或者這樣,更接近於Java的方式
new File('/User/lihengming/test.txt').withReader('UTF-8') { reader ->
reader.eachLine {
println it
}
}
//若是隻是爲了讀取並打印出文本的內容的話,下面是最簡潔的方式
print new File('/User/lihengming/test.txt').text複製代碼
Groovy 一樣支持內部類而且實現跟 Java 是同樣的,但不該拿 Java 語言規範來考量它,應對差別狀況保持冷靜與寬容(keep shaking the head about things that are different)。在Groovy中內部類看起來有點相似 groovy.lang.Closure 類的實現。
//靜態內部類
class A {
static class B {}
}
new A.B()
//匿名內部類
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
CountDownLatch called = new CountDownLatch(1)
Timer timer = new Timer()
timer.schedule(new TimerTask() {
void run() {
called.countDown()
}
}, 0)
assert called.await(10, TimeUnit.SECONDS)複製代碼
Java 8 支持 Lambda 表達式和方法引用
Runnable run = () -> System.out.println("Run");
list.forEach(System.out::println);複製代碼
Java 8 的 lambda 幾乎能夠認爲是匿名內部類。Groovy 並無採用這種語法,而採用閉包來實現。
Runnable run = { println 'run' }
list.each { println it } // or list.each(this.&println)複製代碼
因爲雙引號所包括起來的字符串字面量會被解釋爲 GString 值(即 「Groovy 字符串」的簡稱),因此若是當某個類中的 String 字面量含有美圓字符($)時,那麼利用 Groovy 和 Java 編譯器進行編譯時,Groovy 極可能就會編譯失敗,或者產生與 Java 編譯所不一樣的結果。
一般,若是某個 API 聲明瞭形參的類型,Groovy 會自動轉換 GString 和 String。要當心那些形參爲 Object 的 Java API,須要檢查它們的實際類型。
在 Groovy 中,由單引號所建立的字面量屬於 String 類型對象,而雙引號建立的字面量則多是 String 或 GString 對象,具體分類由字面量中是否有插值來決定。
assert 'c'.getClass()==String
assert "c".getClass()==String
assert "c${1}".getClass() in GString複製代碼
;
)結尾,固然加上也不會報錯,畢竟徹底兼容 Java 的語法。==
等價於 Java 中的equals
方法。註釋和 Java 同樣,支持單行(//
)、多行(/* */
)和文檔註釋(/** */
)
除此以外還支持 Shebang line(UNIX系統支持一種特殊的單行註釋叫做 Shebang line,用於指明腳本的運行環境,便於在終端中能夠直接運行)#號必須是文件的第一個字符。
#!/usr/bin/env groovy
println "Hello from the shebang line"複製代碼
Groovy 中定義變量默認訪問修飾符是public
,變量定義時遵循 Java 變量命名規範,變量名以字母、下劃線或美圓符號$開始,能夠包含字母、數字、下劃線和美圓符號$,但關鍵字除外。除了這些規則以外,Groovy 定義變量時若是是一行定義一個類型,末尾的分號能夠省略,可是若是多個變量佔一行,變量之間必須以分號分割。
Groovy 定義變量的方式和 Java 是相似的,區別在於 Groovy 提供了def
關鍵字供使用,它能夠省略變量類型的定義,根據變量的值進行類型推導。
def a = 123
def b = 'b'
def c = true
boolean d = false
int e = 123複製代碼
若是定義變量的字面量值爲數字時,類型會根據數字的大小自動調整
def a = 1
assert a instanceof Integer
// Integer.MAX_VALUE
def b = 2147483647
assert b instanceof Integer
// Integer.MAX_VALUE + 1
def c = 2147483648
assert c instanceof Long
// Long.MAX_VALUE
def d = 9223372036854775807
assert d instanceof Long
// Long.MAX_VALUE + 1
def e = 9223372036854775808
assert e instanceof BigInteger複製代碼
對於浮點型字面量爲了精度 Groovy 默認使用的類型爲 BigDecimal
def decimal = 123.456
println decimal.getClass() // class java.math.BigDecimal複製代碼
Groovy爲 數字類型提供一種更簡單的聲明類型的方式:類型後綴
- Integer 使用I或i
- Long 使用L或l
- BigInteger 使用G或g
- BigDecimal 使用G或g
- Double 使用D或d
- Float 使用F或f複製代碼
使用例子
def a = 123I
assert a instanceof Integer
def b= 123L
assert b instanceof Long複製代碼
在Groovy種有兩種字符串類型,普通字符串(java.lang.String
)和插值字符串(groovy.lang.GString
)。
普通字符串使用單引號
println 'hello'複製代碼
插值字符串使用雙引號
def name = '張三'
println "hello $name"複製代碼
除此以外,還支持三單引號的寫法,能夠保留文本的換行及縮進格式
def strippedFirstNewline = '''line one
line two
line three
'''
println strippedFirstNewline
// 能夠寫成下面這種形式,可讀性更好
def strippedFirstNewline2 = '''\
line one
line two
line three
'''
println strippedFirstNewline2複製代碼
在 Groovy 中並無明確的字符字面量表示形式,須要顯示的指定,有三種方式
char c1 = 'A' // 聲明類型
assert c1 instanceof Character
def c2 = 'B' as char // 用as關鍵字
assert c2 instanceof Character
def c3 = (char) 'C' // 強制類型轉換
assert c3 instanceof Character複製代碼
Groovy 方法的默認訪問修飾符是public
,方法的返回類型能夠不須要聲明,但需添加def
關鍵字。有返回值的方法return
能夠被省略,默認返回最後一行代碼的運行結果,若是使用了return
關鍵字則返回指定的返回值。
String method1() {
return 'hello'
}
assert method1() == 'hello';
def method2() {
return 'hello'
}
assert method2() == 'hello';
def method3() {
'hello'
}
assert method3() == 'hello';複製代碼
Groovy 方法的參數類型能夠被省略,默認爲Object類型。
def add(int a, int b) {
return a + b
}
//與上面的等價
def add(a, b) {
a + b
}複製代碼
Groovy 方法的其餘特性與Java同樣,好比支持重載、不定長參數(...)等。
Groovy 提供了閉包的支持,語法和 Lambda 表達式有些相似,簡單來講就是一段可執行的代碼塊或函數指針。閉包在 Groovy 中是groovy.lang.Closure
類的實例,這使得閉包能夠賦值給變量,或者做爲參數傳遞。Groovy 定義閉包的語法很簡單,就像下面這樣。
//閉包的參數爲可選項
def closure = { [closureParameters -> ] statements }複製代碼
閉包能夠訪問外部變量,而方法(函數)則不能。
def str = 'hello'
def closure={
println str
}
closure()//hello複製代碼
閉包調用的方式有兩種,閉包.call(參數)或者閉包(參數),在調用的時候能夠省略圓括號。
def closure = {
param -> println param
}
closure('hello')
closure.call('hello')
closure 'hello'複製代碼
閉包的參數是可選的,若是沒有參數的話能夠省略->
操做符。
def closure = {println 'hello'}
closure()複製代碼
多個參數以逗號分隔,參數類型和方法同樣能夠顯式聲明也可省略。
def closure = { String x, int y ->
println "hey ${x} the value is ${y}"
}複製代碼
若是隻有一個參數的話,也可省略參數的定義,Groovy提供了一個隱式的參數it
來替代它。
def closure = { it -> println it }
//和上面是等價的
def closure = { println it }
closure('hello')複製代碼
閉包能夠做爲參數傳入,閉包做爲方法的惟一參數或最後一個參數時可省略括號。
def eachLine(lines, closure) {
for (String line : lines) {
closure(line)
}
}
eachLine('a'..'z',{ println it })
//可省略括號,與上面等價
eachLine('a'..'z') { println it }複製代碼
Groovy 定義 List 的方式很是簡潔,使用中括號([]
),以逗號(,
)分隔元素便可。Groovy中的 List 其實就是java.util.List
,實現類默認使用的是java.util.ArrayList
。
def numbers = [1, 2, 3]
assert numbers instanceof List
assert numbers.class == java.util.ArrayList
assert numbers.size() == 3複製代碼
Groovy 定義數組的語法和 List 很是相似,區別在於數組的定義必須指定類型爲數組類型,能夠直接定義類型或者使用def定義而後經過as關鍵字來指定其類型。
String[] arrStr = ['Ananas', 'Banana', 'Kiwi'] //直接聲明類型爲數組類型 String[]
assert arrStr instanceof String[]
assert !(arrStr instanceof List)
def numArr = [1, 2, 3] as int[] //痛過as關鍵字指定類型爲數組類型 int[]
assert numArr instanceof int[]
assert numArr.size() == 3複製代碼
Groovy 定義 Map 的方式很是簡潔,經過中括號包含key、val的形式,key和value以冒號分隔([key:value]
)。Groovy中的Map其實就是java.util.Map
,實現類默認使用的是java.util.LinkedHashMap
。
// key雖然沒有加引號,不過Groovy會默認將其轉換爲字符串
def colors = [red: '#FF0000', green: '#00FF00', blue: '#0000FF']
assert colors['red'] == '#FF0000' // 使用中括號訪問
assert colors.green == '#00FF00' // 使用點表達式訪問
colors['pink'] = '#FF00FF' // 使用中括號添加元素,至關於Java Map 的 put(key,value)方法
colors.yellow = '#FFFF00'// 使用點表達式添加元素
assert colors.pink == '#FF00FF'
assert colors['yellow'] == '#FFFF00'
assert colors instanceof java.util.LinkedHashMap // 默認使用LinkedHashMap類型
// Groovy Map的key默認語法不支持變量,這裏的key時間上是字符串'keyVal'而不是keyVal變量的值'name'
def keyVal = 'name'
def persons = [keyVal: 'Guillaume']
assert !persons.containsKey('name')
assert persons.containsKey('keyVal')
//要使用變量做爲key,須要使用括號
def keyVal = 'name'
def persons = [(keyVal): 'Guillaume']
assert persons.containsKey('name')
assert !persons.containsKey('keyVal')複製代碼
在 Groovy 中可使用..
操做符來定義一個區間對象,簡化範圍操做的代碼。
def range = 0..5
assert (0..5).collect() == [0, 1, 2, 3, 4, 5]
assert (0..<5).collect() == [0, 1, 2, 3, 4] // 至關於左閉右開區間
assert (0..5) instanceof List // Range其實是List接口的實現
assert (0..5).size() == 6
assert ('a'..'d').collect() == ['a','b','c','d']//也能夠是字符類型
//常見使用場景
for (x in 1..10) {
println x
}
('a'..'z').each {
println it
}
def age = 25;
switch (age) {
case 0..17:
println '未成年'
break
case 18..30:
println '青年'
break
case 31..50:
println '中年'
break
default:
println '老年'
}複製代碼
Grails 是一個基於 Groovy 語言,構建於 Spring/Spring Boot、Hibernate 之上的高生產力、一站式 Web 開發框架。特別適合小型團隊進行敏捷開發,效率很是高。因爲性能和學習成本的緣由,普及率比較低,大分部公司仍是更傾向於選擇 Spring Boot 做爲開發框架。
Gradle 是一個基於 Apache Ant 和 Apache Maven 概念的項目自動化構建工具。它使用一種基於 Groovy 的特定領域語言(DSL)來進行構建配置,拋棄了基於XML的各類繁瑣配置,主要以面向Java應用爲主,支持從 Maven 倉庫下載依賴。如今愈來愈多的項目(Android項目居多)使用Gradle 來做爲項目的構建工具,相信將來 Gradle 也會逐漸代替 Maven,就像 Maven 代替 Ant 那樣。
使用Maven構建項目
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.vs</groupId>
<artifactId>com.vs.maven.gradle</artifactId>
<version>1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.6.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa
</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.25</version>
</dependency>
</dependencies>
<properties>
<java.version>1.8</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>複製代碼
使用Gradle構建項目
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:1.2.6.RELEASE")
}
}
dependencies {
compile("org.springframework.boot:spring-boot-starter-web") {
exclude module: "spring-boot-starter-tomcat"
}
compile("org.springframework.boot:spring-boot-starter-security")
compile("org.springframework.boot:spring-boot-starter-data-jpa")
testCompile("mysql:mysql-connector-java:5.1.25")
}複製代碼
從 Spring 2.0 版本開始對動態腳本語言進行了支持(Spring 官方文檔該部分地址),其中便包括 Groovy ,Spring 提供了<lang:groovy/>
標籤來定義 Groovy Bean 。Groovy Bean 能夠經過script-source
屬性加載腳本文件,腳本源文件能夠來自本地或者網絡,而且能夠經過refresh-check-delay
屬性監聽腳本內代碼變動從新裝載 Bean 實現動態 Bean 。
// from the file '/java/Calculator.java'
public interface Calculator {
int add(int x, int y);
}
// from the file '/resources/CalculatorGroovyImpl.groovy'
class CalculatorGroovyImpl implements Calculator {
int add(int x, int y) {
x + y
}
}複製代碼
<-- from the file 'beans.xml' 省略 xmlns -->
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<lang:groovy id="calculator" script-source="classpath:CalculatorGroovyImpl.groovy" refresh-check-delay="1000"/>
</beans>複製代碼
public class Tester {
public static void main(String[] args) throws InterruptedException {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Calculator calculator = (Calculator)context.getBean("calculator");
//嘗試修改CalculatorGroovyImpl.groovy,將 x + y,修改成x * y。
while (true){
System.out.println(calculator.add(1, 1));
TimeUnit.SECONDS.sleep(1);
}
}
}複製代碼
從 Spring 4.0 版本開始 Spring 支持使用 Groovy DSL 來定義 Bean 的配置,詳見 Spring 官方文檔該部分 。
beans {
//beanName(type)
dataSource(BasicDataSource) {
//注入屬性
driverClassName = "org.hsqldb.jdbcDriver"
url = "jdbc:hsqldb:mem:grailsDB"
username = "sa"
password = ""
settings = [mynew:"setting"]
}
sessionFactory(SessionFactory) {
//注入屬性,引用其餘Bean
dataSource = dataSource
}
myService(MyService) {
//使用閉包定義嵌套的Bean
nestedBean = { AnotherBean bean ->
dataSource = dataSource
}
}
}複製代碼
ApplicationContext context = new GenericGroovyApplicationContext("beans.groovy");
MyService myService = context.getBean(MyService.class);複製代碼
Groovy很是容易集成在Java環境中,利用其動態性來作規則引擎、流程引擎、動態腳本環境,很是適合那些不不須要常常發佈但又常常變動的場景下使用。在Java中集成(調用)Groovy 代碼有下面幾種方式。
groovy.util.Eval
類是最簡單的用來在運行時動態執行 Groovy 代碼的類,提供了幾個靜態工廠方法供使用,內部其實就是對GroovyShell的封裝。
//執行Groovy代碼
Eval.me("println 'hello world'");
//綁定自定義參數
Object result = Eval.me("age", 22, "if(age < 18){'未成年'}else{'成年'}");
assertEquals(result, "成年");
//綁定一個名爲 x 的參數的簡單計算
assertEquals(Eval.x(4, "2*x"), 8);
//帶有兩個名爲 x 與 y 的綁定參數的簡單計算
assertEquals(Eval.xy(4, 5, "x*y"), 20);
//帶有三個綁定參數(x、y 和 z)的簡單計算
assertEquals(Eval.xyz(4, 5, 6, "x*y+z"), 26);複製代碼
groovy.lang.GroovyShell
除了能夠執行 Groovy 代碼外,提供了更豐富的功能,好比能夠綁定更多的變量,從文件系統、網絡加載代碼等。
GroovyShell shell = new GroovyShell();
//能夠綁定更多變量
shell.setVariable("age",22);
//直接求值
shell.evaluate("if(age < 18){'未成年'}else{'成年'}");
//解析爲腳本,延遲執行或者緩存起來
Script script = shell.parse("if(age < 18){'未成年'}else{'成年'}");
assertEquals(script.run(), "成年");
//能夠從更多位置加載/執行腳本
//shell.evaluate(new File("script.groovy"));
//shell.evaluate(new URI("http://wwww.a.com/script.groovy"));複製代碼
groovy.lang.GroovyClassLoader
是一個定製的類加載器,能夠在運行時加載 Groovy 代碼,生成 Class 對象。
GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
String scriptText = "class Hello { void hello() { println 'hello' } }";
//將Groovy腳本解析爲Class對象
Class clazz = groovyClassLoader.parseClass(scriptText);
//Class clazz = groovyClassLoader.parseClass(new File("script.groovy"));
assertEquals(clazz.getName(),"Hello");
clazz.getMethod("hello").invoke(clazz.newInstance());複製代碼
groovy.util.GroovyScriptEngine
可以處理任何 Groovy 代碼的動態編譯與加載,能夠從統一的位置加載腳本,而且可以監聽腳本的變化,當腳本發生變化時會從新加載。
//script/groovy/hello.groovy
println "hello $name"複製代碼
GroovyScriptEngine scriptEngine = new GroovyScriptEngine("script/groovy");
Binding binding = new Binding();
binding.setVariable("name", "groovy");
while (true){
scriptEngine.run("hello.groovy", binding);
TimeUnit.SECONDS.sleep(1);
}複製代碼
//輸出
hello groovy
hello groovy
....
//將hello.groovy內代碼修改成println "hi $name", GroovyScriptEngine會從新進行加載
hi groovy
hi groovy複製代碼
JSR-223 是 Java 中調用腳本語言的標準 API。從 Java 6 開始引入進來,主要目的是用來提供一種統一的框架,以便在 Java 中調用多種腳本語言。JSR-223 支持大部分流行的腳本語言,好比JavaScript、Scala、JRuby、Jython和Groovy等。
ScriptEngine engine = new ScriptEngineManager().getEngineByName("groovy");
Bindings bindings = new SimpleBindings();
bindings.put("age", 22);
Object value = engine.eval("if(age < 18){'未成年'}else{'成年'}",bindings);
assertEquals(value,"成年");
//script/groovy/hello.groovy
//println "hello world"
engine.eval(new FileReader("script/groovy/hello.groovy"));
//hello world複製代碼
因爲 Groovy 自身已經提供了更豐富的集成機制,若是在 Java 應用中只是使用 Groovy 一種腳本語言的話,使用 Groovy 提供的集成機制可能會更合適一點。
與Java集成大體就以上幾種方式,在使用前推薦閱讀:Groovy與Java集成常見的坑