2018年7月31日 星期二

this、owner、delegate (Groovy 2.x 四)


class MyGroovy {
    class InnerGroovy {
        static def staticClouser = {
            println "a ${this}" // 閉包定義的類
            println "b ${owner}" // 閉包定義的類或物件
            println "c ${delegate}" // 預設和 owner 一樣
        }
    
        def static methodStaticClouser() {
            def xxx = {
                println "d ${this}"
                println "e ${owner}"
                println "f ${delegate}"
            }
            xxx.call()
        }
    }
    
    static void main(def args) {
        MyGroovy.InnerGroovy.staticClouser.call()
        MyGroovy.InnerGroovy.methodStaticClouser()
    }
}

※結果:
a class MyGroovy
b class MyGroovy$InnerGroovy
c class MyGroovy$InnerGroovy
d class MyGroovy
e class MyGroovy$InnerGroovy
f class MyGroovy$InnerGroovy



class Animal{}
    
class MyGroovy {
    static def staticClouser = {
        println "a ${this}" // 閉包定義的類
        println "b ${owner}" // 閉包定義的類或物件
        println "c ${delegate}" // 預設和 owner 一樣
    }
    
    static def methodStaticClouser() {
        def xxx = {
            println "d ${this}"
            println "e ${owner}"
            println "f ${delegate}"
        }
        xxx.delegate = new Animal()
        xxx.call()
    }
    
    static void main(def args) {
        MyGroovy.staticClouser.call()
        MyGroovy.methodStaticClouser()
    }
}

※結果:
a class MyGroovy
b class MyGroovy
c class MyGroovy
d class MyGroovy
e class MyGroovy
f Animal@3745e5c6



class Animal {
    String name
}
    
class MyGroovy {
    Integer id
    String name
    Double price
    
    def show = {
        "編號${id},名稱${name},價錢為${price}"
    }
    
    String display() {
        println show.call()
    }
    
    static void main(String[] args) {
        MyGroovy m1 = new MyGroovy(price: 50.2, name: '西洋棋', id: 1)
        MyGroovy m2 = new MyGroovy(name: '象棋', id: 2)
        Animal a = new Animal(name: '老虎')
        m1.display()
        m1.show.delegate = a
        m1.show.resolveStrategy = Closure.DELEGATE_FIRST // 預設為 0,也就是 OWNER_FIRST
        m1.display()
        m2.display()
    }
}

※有點像 javascript 的 call、apply、bind

2018年7月30日 星期一

閉包與相關方法 (Groovy 2.x 三)

※數字與閉包的相關方法

※upto、downto

package script
    
def up(int n) {
    def sum = 0
    // 1.upto(n) { num -> println num }
    // 1.upto(n, {num -> rtn += num})
    1.upto(n) { num ->
        sum += num
    }
    sum
}
println up(10)
    
    
def down(int n) {
    // 10.downto(n) { num -> println num }
    // 10.downto(n, {num -> n += num})
    10.downto(n) { num -> n += num }
    n
}
println down(0)

※因為在參數使用 def 會有 cannot infer argument type ...,所以改成了 int

※閉包在最後一個參數,可以將閉包獨立拿到 () 後面


※times

def add(n) {
    def rtn = 0
    n = ++n // 因為底層的 size 是 <,不是 <=
    // n.times({ num -> rtn += num })
    n.times { num -> rtn += num }
    rtn
}
println add(10)





※字串與閉包的相關方法

※each、eachLine

// each
def s = 'abc'
def rtn = s.each {
    st -> print st.multiply(2) // aabbcc
}
println '\r\n' + rtn // abc
    
    
// eachLine
def s2 = '''abc
xyz
OGC
'''
def rt = s2.eachLine(0, {
    st, i -> println "${i} = ${st}"
})
println rt // null

※eachLine 第一個參數預設為0,index的意思,可以改變起始數字


※find、findAll、any、every、collect、collectReplacements

final def s = 'a1b2c3'
println s.find {
    st -> !st.isNumber() // a,找到第一個不是數字的,沒有是 null
}
    
println s.findAll {
    st -> !st.isNumber() // [a, b, c],找到全部都不是數字的,沒有是 null
}
    
println s.any {
    st -> st.isNumber() // true,其中一個是數字就是 true
}
    
println s.every {
    st -> st.isNumber() // false,全部是數字才是 true
}
    
println s.collect { // 循環每一個 index
    st -> st.center(3, "&") // [&a&, &1&, &b&, &2&, &c&, &3&]
}
    
println s.collectReplacements { // 循環每一個 index 後,判斷是否取代
    it == 'a' ? 'A' : it
}
    
    
// public static Object find(Object self, Closure closure) {...}

※最後一行註解的部分是原碼,第一個參數是自己去點的內容,可以假裝沒看到



※集合與閉包的相關方法


def list = ['xxx', 'x', 'xxxxx', 'xx', 'xxxx']
    
list.sort {
    it.size() // it -$gt; return it.size()
}
println list // [x, xx, xxx, xxxx, xxxxx]
    
def list2 = [9, -5, 2, 7]
println list2.min() // -5
println list2.max { // 9
    Math.abs(it)
}
    
println list2.count {
    it % 2 == 0 // 偶數有幾個
}




※排序

def list = [1, -7, 2, 3, -6, 8, -4, 5]
/*
Collections.sort(list) { a, b ->
    if (a == b) {
        0
    } else {
        Math.abs(a) < Math.abs(b) ? -1 : 1
    }
}
*/
    
list.sort() { a, b ->
    if (a == b) {
        0
    } else {
        Math.abs(a) < Math.abs(b) ? -1 : 1
    }
}
println list



String 方法、邏輯控制 (Groovy 2.x 二)

※String 方法

package script
    
String x = 'xyz';
String r1 = x.charAt(1)
String r2 = x[1..2]
println x // xyz
println r1 // y
println r2 // yz
    
String c = x.center(6, '?') // 沒有第二個參數,預設是空格
println x // xyz
println c // ?xyz??
    
String p = x.padLeft(6, '?')
println x // xyz
println p // ???xyz
    
println 'abc'
println 'abc' > 'abd' // 前二個一樣,第三個 ascii 99 > 100 嗎? false
    
println 'axbxc'.minus('x') // abxc
println 'axbxc' - 'x' // abxc
    
println 'axbxc'.plus('x') // axbxcx
println 'axbxc' + 'x' // axbxcx
    
println 'abc'.reverse() // 從後往前讀,cba
println 'abc'.capitalize() // 首字母大寫,Abc
println 'ABC'.uncapitalize() // 首字母小寫,aBC
    
println ''.isBlank() // true
println ' '.isBlank() // true
println ''.isEmpty() // true
println ' '.isEmpty() // false
    
println Integer.MAX_VALUE // 2147483647
println '2147483648'.isInteger() // false
println '2147483648'.isLong() // true
println '2147483648'.isFloat() // true
println '2147483648'.isDouble() // true
println '2147483648'.isBigInteger() // true
println '2147483648'.isNumber() // 會呼叫 isBigDecimal(),true
println '2147483648'.isBigDecimal() // true
// 沒有 isByte isShort isCharacter isBoolean
    
println '2147483647'.toInteger() // 超過會報錯
// 沒有 toByte





※if

if(new ArrayList<>()) {
    println 'true'
} else { // 0、0.0、null、''、new空物件、
    println 'false'
}

※java 一定要是 boolean 才可以


※不支援範圍操作

int i = 59
if(i == 1..59) {
    println '不及格'
} else {
    println '及格' // 永遠都是及格
}




※switch

Integer a = 7
switch (a){
    case 7:
        println 'a'
        break
    
    case '7':
        println 'b'
        break
    
    case Number.class:
        println 'c'
        break
    
    case Integer.class:
        println 'd'
        break
    
    default:
        println 'e'
}

※java 的 switch 型別要全部一樣才可以,且只能是 char 和 int,java 7 又多個 String

※a b c d 都會被呼叫到,看誰寫前面就先印



def list = [1,2,3]
switch (list){
    case ArrayList:
        println 'a'
        break
    
    case [1,2,3]: // 相當於是 new,不可能進來
        println 'b'
        break
    
    case List.class:
        println 'c'
        break
    
    default:
        println 'd'
}

※switch 可以放其他東西,可以有類似 instanceof 的寫法


※支援範圍操作

def i = 61
switch (i){
    case 0..59:
        println '不及格'
        break
    
    case 60..100:
        println '及格'
        break
    
    default:
        println '超過範圍'
}




※迴圈

def sum = 0
for (def i in 1..10) { // in 寫「:」也行
    sum += i
}
println sum





for (def m : [a: 1, b: 2, c: 3]) { // in 寫「:」也行
    print "${m.key} "
    println m.value
}

※List 、Set 就直接拿來用,而 Map,使用內鍵的 key 和 value 即可

2018年7月25日 星期三

基本概念 (Groovy 2.x 一)

主要寫的是 Java 和 Groovy 的差異,和 Java 一樣就不寫了

※Eclipse 安裝環境

Help -> About Eclipse 查自己的版本
然後到這裡,標題為Releases,複製對應自己版本的 Release Update Site
然後回到 Eclipse,選 Help - > Install New Software,然後如下操作:


※選左上角的 Add 後,會跳出一個視窗, Name 隨便打,Location 將剛剛複製的貼進去


※詳細差在哪我不知道,反正我是全選了,安裝要一段時間,安裝好後會提示重啟 Eclipse



※IntelliJ、Android Studio

安裝好 IntelliJ 或 Android Studio 就有環境可測試了(較舊的沒有),如下圖:


※但是這只是測試用而已,如果想使用 Groovy 的專案,還要去官網下載 groovy 的 SDK 來用,如果安裝好了,可以 New -> Groovy Script,如 xxx.Ooo,這將會創建一個 package 為 xxx,而 Ooo.groovy 為檔名

※要注意 jvm 版本和安裝時預設雖然有勾環境變數,但會有「~」的路徑,我沒試過可不可以,反正我是改成正確的路徑,最後 New -> Project 如下設定即可(但 Android Studio 我不知道):

如果忘記選 Groovy library,還可以如下設定:
※有成功才會在左邊的 External Libraries 有 groovy-xxx的東西,沒有就要在右邊的 Add Framework Support 裡面去勾選了,右邊的是按 TestGroovy 按右鍵出來的



※預設import

java.io.*
java.lang.*
java.math.BigDecimal
java.math.BigInteger
java.net.*
java.util.*
groovy.lang.*
groovy.util.*
    
import static Calendar.getInstance as now

※import static 比 Java 多個 as 的別名功能


※class 的省略

class Person {
    String name
    
    String greet(String otherPerson) {
        Integer i = 88;
        def j = 11;
        println i + j
        "Hello ${otherPerson}"
    }
    
    static void main(String... args){
        println new Person().greet("xxxxxxxxxxxxxxxxxx")
    }
}

※最後可不加「;」
1.回傳值可不用寫 return 關鍵字
2.宣告多一個 def,如 def i = 0;、def s = 'xxx',轉換後為 Object

/* class */
3.如果屬性前面沒有修飾子(同package),轉換後的 class 其實會幫我們加上 private,並給 setter/getter,但如果自己加 private,就不會提供 setter/getter,不管是不是 static 都會這樣
4.方法、class、建構子,修飾子不寫和寫 public 一樣,而方法不管是不是 static 也是一樣,所以 main 方法可以不寫 public
5.雖然已安裝 java8,還是不能寫 lambda 表達式和 reference method(::)

/* interface */
6.只能定義 public 和不寫,但轉換後都是不寫的
7.雖然已安裝 java8,也是不能在裡面定義 static 方法和 default 關鍵字,我試的是 2.5.1 版本
如果一定要使用 java8 的 default,groovy 有一種很像 interface 的 Type,叫做 trait,轉換後也是 interface,可參考官網的教學,或者這篇簡而有力的教學

8.System.out.println 只寫 println 即可,() 可省略(在 groovy 中,只要是有參數的都可省略),但 println 是 groovy 的,可按 ctrl + 滑鼠左鍵進去看
9.預設改 private 後,轉換後也確實是 private,但還是抓的到
10.看 groovy 幫我們的代碼轉成什麼樣子
※在 out 資料夾裡有轉變後的 class,注意如果有修改,要執行完才會重新生成
可以看到都會實作一個叫 GroovyObject 的介面


※六種字串

// 1.單引號
println 'ab' == 'a' + 'b' // true,這個才是和java的 「"」一樣
    
// 2.雙引號
def x = 'xxx'
println "x is ${x}" /* x is xxx,多了打變數的功能,「{}」可省略
類似ES6的「`」,想打「$」,用跳脫字元「\」*/
    
// 3.三個單引號
def o = '''a
b
c
d'''
println o // 有換行效果,類似HTML的 <pre>
    
// 4.三個雙引號
def o = """a
b
${x}
d""" // 三個「'」加上抓變數的功能
    
// 5.正斜線
/.../
用「/」包起來,可以有以上的所有功能
    
// 6.錢符號正斜線
$/.../$
和/../很像,差在跳脫字元的不同,這個跳脫字元是「$」,其他都是「\」
    
println /\//; // /
println $///$; // /
    
println /$/; // $
println $/$$/$; // $

※使用雙引號,而且有使用到變數,會自動轉為 org.codehaus.groovy.runtime.GStringImpl,可以印出 .class 查看

※如果有一個方法定義參數為 String,GString 可以傳的進來,但相反不行


※方法

package script
    
final def me = "me!"
final def test = "look ${me}"
    
println "1 + 1 = ${1+1}" // 1 + 1 = 2
println echo('xxx') // a xxx
println echo("xxx") // a xxx
println echo(test) // b look me!
    
String echo(m) {
    "a ${m}"
}
    
String echo(GString m) {
    "b ${m}"
}
    
String echo(String m) {
    "c ${m}"
}

※a 方法省略了 def 關鍵字,而這三個方法都省略了 return 關鍵字

※a 方法在沒有明確的型態對應時才會呼叫的到,如將 c 刪除就呼叫的到

※如果 test 變數沒有使用到變數(${me}),那會是 String,有用到變數才是 GString

※groovy 就算寫 void,還是會有回傳值 null,但 void 關鍵字不能省略



※方法的括號可省略

echo 'xxx'
    
def echo(String m) {
    println "${m}"
}
    
getList([1, 2, 3])
getList Arrays.asList(1, 2, 3)
getList Stream.of(1, 2, 3).collect(Collectors.toList())
    
getArray(['a', 'b', 'c'] as String[])
getArray 'a', 'b', 'c'
String[] array = new String[3]
array[0] = 'a'
array[1] = 'b'
array[2] = 'c'
getArray array
    
getMap([a: 1, b: 2, c: 3])
getMap(a: 1, b: 2, c: 3)
getMap a: 1, b: 2, c: 3
    
def getList(List<Integer> list) {
    println "a ${list}"
}
    
def getArray(String[] array) {
    println "b ${array}"
}
    
def getMap(Map<String, Integer> map) {
    println "c ${map}"
}

※只有完全沒參數才一定要(),否則都可省略,但要注意閉包可以提出去的問題,假設有個方法有兩個參數,最後一個是閉包,提出去時不能省略(),想省略只能想在()裡面

※集合使用到 groovy 的寫法時,只有 Map 可省略() 和 []

※給陣列傳參數時,不能寫 new String[] {xxx, xxx} 的方式,因為 groovy 會以為是閉包

※集合

//   List、Set、Array 
def list = [1, 2, 3]
println list
println list instanceof List
println list instanceof LinkedList
println list.size()
list << 4 // 除了add方法外,用這個符號也能增加元素
    
def linkList = [1, 2, 3] as LinkedList
println linkList instanceof List
println linkList instanceof LinkedList
    
def set = [3, 2, 3] as Set
println set instanceof Set
    
String[] sArray =['Ananas', 'Banana', 'Kiwi']
def sArray =['Ananas', 'Banana', 'Kiwi'] as String[]    
def intArray = [1, 2, 3] as int[]
    
    
//   Map 
def map = [:] // 宣告空的 map
println map // [:]
println map.isEmpty() // true
map."a" = "aaa" // key 有沒有用「"」包起來都是可以的
map << [c:"ccc"]
map.put("d", "ddd") // 這個和之前一樣,key還是要用「"」包起來
println map // [a:aaa, c:ccc, d:ddd]
println map.a // aaa
println map['a'] // aaa
println map["a"] // aaa
    
def m = [b:"bbb"] // 非空的 map
println m // [b:bbb]
println m.b // bbb
m.xxx = [b:"bbb"]
println m.toMapString() // [b:bbb, xxx:[b:bbb]],Map 中的 Map
println m.xxx.b // bbb
println m.class // null,因為沒有class這個key
println m.getClass() // class java.util.LinkedHashMap

※增加元素都可以用「<<」


※運算符、三元運算子

println 2 ** 10 // 1024,2的10次方
    
def f = 2
println f ** 5 // 32,2的5次方
println f **= 5 // 32,f = f ** 5
    
def x = 1;
println x == 1 ?: "sucess" // 「?:」不能分開,?:之間沒寫預設是 true

※?:最後的 else 一定要寫


※基本型態的後綴

Java 只有 L、F、D 來代表 Long、Float、Double,大小寫都可以
Groovy 多兩個 I 和 G,一樣大小寫都可以,i 表示 Integer;G 代表有兩個 BigInteger、BigDecimal
例:
def long = 999L
def xxx = 555I
def ooo = 548G

※最好用大寫,避免和數字混淆

※groovy 中沒有基本型態,雖然可以宣告,但用 class 去查看,會發現都是 Wrapper 類別

※使用 def 時,預設宣告整數為 Integer,有小數點的為 BigDecimal(就算是1.0也是一樣)


※?.

String result = null
println result?.toUpperCase()

※可防止 NullPointerException,回傳 null


※@field

class User {
    String name = 'xxx'
    String getName() {
        println 'ooooooooooooo'
        this.name
    }
}
println new User().@name

※可以直接專取屬性,不加 @ 會呼叫 getter

※&method

def str = 'xxx'
def result = str.&toUpperCase //一定要用def接
println result()
    
def xxx(String str) { str.toLowerCase()}
def xxx(Integer x) { 2 * x }
def ref = this.&xxx
println ref('xOx') // xox
println ref(123) // 246

※使用「.&」宣告完,可以當方法呼叫,但要注意 &後面的方法沒有 ()

class Ooo {
    int count = 0
    def xxx() {
        return ++this.count
    }
}
    
def ref = new Ooo().&xxx
println ref() // 1
println ref() // 2

※這個有點像 javascript 的閉包功能

※*.

class Car {
    String make
    String model
}
def cars = [
    new Car(make: 'Peugeot', model: '508'),
    new Car(make: 'Renault', model: 'Clio')
]
    
def makes = cars*.make
println makes == ['Peugeot', 'Renault'] // true
println makes instanceof List // true

※只取集合的某個屬性


※延伸方法參數

int function(int x, int y, int z) {
    x*y+z
}
    
def args = [4,5,6]
println function(*args) // 26




※延伸操作-集合

def items = [4,5,2]
def list = [1,2,3,*items,6]
println list == [1,2,3,4,5,2,6] // true
    
def set = [1,2,3,*items,6] as Set
println set == [1,2,3,4,5,6] as Set // true
    
def m1 = [c:3, d:4]
def map = [a:1, b:2, *:m1]
println map == [a:1, b:2, c:3, d:4]




※範圍操作

def range = 0..3
println range // [0, 1, 2, 3]
println (1..3).collect() //  [1, 2, 3]
println (1..<3).collect() // [1, 2],只有小於,也只能寫在後面
println ('a'..'d').collect() //[a, b, c, d]
println range.contains(3) // true,包括3這個元素嗎?
println range.contains('3') // false,包括3這個元素嗎?
println range.contains(4) // false
println range.from // 0,第一個元素
println range.to // 3,最後一個元素
    
    
def r = 0..30
def n = r.by(3)
n.each {
    print "${it} " // 0 3 6 9 12 15 18 21 24 27 30
}
    
/* 簡化
(0..30).by(3).each {
    print "${it} "
}
*/

※<和3中間可以空格

※Range 為 List 的子類,都是 interface,Range 有四個實作類 EmptyRange, IntRange, NumberRange, ObjectRange


※太空船操作

println 1 <=> 1 //  0,相等
println 1 <=> 3 //  -1,左邊小於右邊
println 3 <=> 1  // 1,左邊大於右邊
println 'a' <=> 'z'//  -1
    
println 1.compareTo(1) // 0
println 1.compareTo(3) // -1
println 3.compareTo(1) // 1
println 'a'.compareTo('z') // -25

※可能 <==> 長得像太空船吧!

※1就是 true;-1就是 false


※正則

package script
    
import java.util.regex.Matcher
import java.util.regex.Pattern
    
    
def p1 = ~/^foo.*/ // 以 foo 開頭的
def p2 = ~'.*foo$' // 以 foo 結尾的
def p3 = ~"(foo){2}" // 剛好是兩個 foo 的
def p4 = ~$/foo/$ // 只能是 foo 的
println p1 instanceof Pattern
println p1.matcher('foo123').matches()
println p2.matcher('123foo').matches()
println p3.matcher('foofoo').matches()
println p4.matcher('foo').matches()
    
    
def text = "xxx"
def m = text =~ /xxx/
println m instanceof Matcher
println m.matches()
if (m.matches()) { // 官網直接判斷m而已,說會呼叫find(),但我試的結果永遠為true
    println '有匹配到'
} else {
    println '沒匹配到'
}
println m.matches() ? 'o' : 'x'
    
    
def t = 'ooo'
def mat = t ==~ /ooo/
println mat instanceof Boolean
println mat
if (mat) {
    println '有匹配到'
} else {
    println '沒匹配到'
}
println mat ?:false

※主要就是 ~/.../ 為 Pattern; =~ 為 Matcher; ==~ 為 Boolean

※/.../ 後面和 javascript 不一樣,沒有什麼 gim 的


※範圍操作-集合

def list = [0,1,2,3,4]
println list[2] == 2 // true
list[2] = 4
println list[0..2] == [0,1,4] // true
list[0..2] = [6,6,6]
println list == [6,6,6,3,4] // true




※閉包

def closure1 = {
    -> println "xxx" // xxx
}
closure1.call() // call 可以省略
    
def closure2 = { i, p ->
    println "${i}, ${p}" // 5, ooo
}
closure2(5, "ooo")
    
def closure3 = {
    println it // 是我, it為內鍵變數
}
closure3("是我")

※一定要用 def 宣告

※使用 -> 將參數和實作隔開,但如果沒有參數,可以省略 ->

※和 java8 的 lambda 不太一樣,參數永遠沒有「()」

※如果只有一個參數,可以使用 it 這個內鍵變數

※參數的型態和 lambda 一樣,通通可以省略,相當於是 def,但如果有寫,如 String,那就要乖乖傳 String 進去

※一定有回傳值,沒有就是 null,像 println 回傳 void,所以是 null,return 關鍵字可以不寫,所以假設最後一行寫 'xxx',那回傳的就是 xxx

※return 可以不寫在最後一行,但 return 之後的程式碼不會執行


※閉包的循環

[1,2,3,4,5].each { print "${it}"} // 12345
println ''
['a':1,'b':2,'c':3].each { print "${it}-"} // a=1-b=2-c=3-
println ''
['a':1,'b':2,'c':3].each { print it.key+":"+it.value} // a:1b:2c:3
println ''




2018年7月21日 星期六

整合 Mybatis (SpringBoot 2.x 五)

※Annotation 設定


※build.gradle

compile 'org.mybatis.spring.boot:mybatis-spring-boot-starter:1.3.2'
compile 'com.alibaba:druid:1.1.10'

※druid 就是類似 c3p0 連線池的東西


※application.properties

spring.datasource.driverClassName=oracle.jdbc.driver.OracleDriver
spring.datasource.url=jdbc:oracle:thin:@172.26.103.1:1521:lottery
spring.datasource.username=lott_new_a3d1
spring.datasource.password=Lottery2011
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

※可參考官網,搜尋 DATASOURCE


※controller

@Controller
@RequestMapping("/chess")
public class ChessAction {
    @Autowired
    private IChessService service;
    
    @RequestMapping(path = "/addChess")
    public String addChess(Chess chess) {
        return service.addChess(chess) == 1 ? "chess/success" : "chess/fail";
    }
}

※在 classpath 下templates 增加 chess 資料夾,增加成功和失敗頁的 html


※service & serviceImpl

public interface IChessService {
    int addChess(Chess chess);
}
    
    
@Service
@Transactional
public class ChessServiceImpl implements IChessService {
    @Autowired
    private IChessDAO chessDao;
    
    @Override
    public int addChess(Chess chess) {
        return chessDao.insertChess(chess);
    }
}




※dao

@Mapper
public interface IChessDAO {
    @Insert("INSERT INTO CHESS(ID, NAME, PRICE) VALUES(#{id}, #{name}, #{price})")
    public int insertChess(Chess chess);
}

※在測試類增加 @MapperScan("ooo.xxx.dao") 和這裡的 @Mapper 是一樣的意思,兩者取其一即可,但都寫也是 OK 的


※addChess.html

<form action="chess/addChess">
    編號:<input name="id" /><br />
    棋名:<input name="name" /><br />
    價錢:<input name="price" /><br />
    <input type="submit" value="送出" />
</form>




※測試類

@SpringBootApplication
// @MapperScan("ooo.xxx.dao")
@ComponentScan({ "ooo.xxx.serviceImpl", "ooo.xxx.controller", "ooo.xxx.dao" })
public class Test {
    public static void main(String[] args) {
        SpringApplication.run(Test.class, args);
    }
}

※啟動後,打上「http://localhost:9000/addChess.html」

※如果啟動出現錯誤 Consider defining a bean of type 'ooo.xxx.service.IChessService' in your configuration,那就要加上 @ComponentScan,第一章有說過,必需放在啟動類或啟動類的子類,但是我並沒有,所以使用這個 annotation 是個變通的方式

※要心心,這裡有一點錯,測試頁都會 Whitelabel Error Page


※XML 設定


※application.properties

mybatis.mapper-locations=ooo/xxx/mapper/*.xml
mybatis.type-aliases-package=ooo.xxx.javabean

※annotation 設定再加上這兩行,參考 mybatis 官網,注意範例是有加 mybatis. 開頭的

※注意mapper-locations 的路徑是用「/」分開,不是用「.」

※第二個是在 xml 的 parameterType 會自動加上這裡設定的前綴,如果不設定,那 xml 就只好全打出來了


※mapper

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
<mapper namespace="ooo.xxx.dao.IChessDAO">
    <insert id="insertChess" parameterType="Chess">
        INSERT INTO CHESS(ID, NAME, PRICE) VALUES(#{id}, #{name}, #{price})
    </insert>
</mapper>

※有了xml,dao 的 @Insert 就不需要了

※!DOCTYPE等的,是複製官網

2018年7月16日 星期一

整合 JSP、Freemarker、Thymeleaf (SpringBoot 2.x 四)

※JSP

※build.gradle

dependencies {
    compile 'org.springframework.boot:spring-boot-starter'
    testCompile 'org.springframework.boot:spring-boot-starter-test'
    compile 'org.springframework.boot:spring-boot-starter-web'
    compile 'javax.servlet:jstl'
    compile 'org.apache.tomcat.embed:tomcat-embed-jasper'
}

※增加下面兩個


※java bean

public class Animal {
    private Integer id;
    private String name;
    
    public Animal(Integer id, String name) {
        super();
        this.id = id;
        this.name = name;
    }
    
    public Integer getId() {
        return id;
    }
    
    public String getName() {
        return name;
    }
}




※controller

@Controller
@RequestMapping("/zoo")
public class AnimalAction {
    @RequestMapping("/animalInfo")
    public String displayAnimal(Model model) {
        List<Animal> list = new ArrayList<>();
        list.add(new Animal(1, "tiger"));
        list.add(new Animal(2, "horse"));
        list.add(new Animal(3, "lion"));
    
        model.addAttribute("animalList", list);
        return "zoo"; // "/WEB-INF/jsp/zoo.jsp"
    }
}




※zoo.jsp

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<table border="1" align="center" width="30%">
    <tr>
        <th>id</th>
        <th>name</th>
    </tr>
    
    <c:forEach items="${animalList}" var="a">
        <tr>
            <td align="center"><c:out value="${a.id}" /></td>
            <td align="center">${a.name}</td>
        </tr>
    </c:forEach>
</table>

※注意 taglib 的uri 有 jsp 的才是 jsp 2.x的,才有支援 EL

※c:forEach 屬性不能空格,items 必需用 EL


※application.properties

server.port=9000
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

※增加兩個 viewResolver,如果不加,那 controller 回傳可用註解的部分


※測試

@SpringBootApplication
public class Test {
    public static void main(String[] args) {
        SpringApplication.run(Test.class, args);
    }
}





※Freemarker

和 整合 JSP 差不多,官網手冊

※build.gradle

compile 'org.springframework.boot:spring-boot-starter-freemarker'




※zoo.ftl

<html>
    <body>
        <table border="1" align="center" width="50%">
            <tr>
                <th>id</th>
                <th>name</th>
            </tr>
    
            <#list animalList as a>
                <tr>
                    <td>${a.id}</td>
                    <td>${a.name}</td>
                </tr>
            </#list>    
        </table>
    </body>
</html>




※application.properties

spring.freemarker.prefix=
spring.freemarker.suffix=.ftl

※測試類和 controller 和 整合 JSP 一樣

官網有說明,在倒數第二個圓點,ftl 檔必需放在 classpath:/templates/下,所以上面兩個都是預設值,還有很多 freemarker 的設定,看官網,下面有 FREEMARKER



※Thymeleaf

※thyme 和 time 發音相同,百里香的意思,thymeleaf 為百里香的葉子
語法可參考官網

※build.gradle

buildscript {
    ext {
        springBootVersion = '2.0.3.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}
    
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
    
group = 'sbt'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
    
repositories {
    mavenCentral()
}
    
dependencies {
    compile 'org.springframework.boot:spring-boot-starter'
    testCompile 'org.springframework.boot:spring-boot-starter-test'
    compile 'org.springframework.boot:spring-boot-starter-web'
    // compile 'javax.servlet:jstl'
    // compile 'org.apache.tomcat.embed:tomcat-embed-jasper'
    // compile 'org.springframework.boot:spring-boot-starter-freemarker'
    compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
}




※java bean 和 controller

public class Animal {
    private Integer id;
    private String name;
    // getter...
    
    public Animal(Integer id, String name) {
        super();
        this.id = id;
        this.name = name;
    }
}
    
    
@Controller
@RequestMapping("/zoo")
public class AnimalAction {
    @RequestMapping("/animalInfo")
    public String displayAnimal(Model model) {
        List<Animal> list = new ArrayList<>();
        list.add(new Animal(1, "tiger"));
        list.add(new Animal(2, "horse"));
        list.add(new Animal(3, "lion"));
        model.addAttribute("animalList", list);
    
        model.addAttribute("name", "leopard");
        return "zoo";
    }
}




※zoo.html

<div th:text="可以寫死"></div>
<div th:text="${name}"></div>
<input th:value="${name}" />
    
<table border="1" align="center" width="50%">
    <tr>
        <th>id</th>
        <th>name</th>
    </tr>
    
    <tr th:each="a : ${animalList}">
        <td th:text="${a.id}" />
        <td th:text="${a.name}" />
    </tr>
</table>

※th 會有警告,不想看到可在 html 標籤加屬性,如 <html xmlns:th="http://www.thymeleaf.org">


※測試類

@SpringBootApplication
public class Test {
    public static void main(String[] args) {
        SpringApplication.run(Test.class, args);
    }
}

※可參考上面的 PDF 連結,3~7章為基礎語法,公用方法在19章

※還有取得 request、session…等作用域的,要看這裡

2018年7月14日 星期六

靜態資源、上傳 (SpringBoot 2.x 三)

※靜態資源


官網說明中,可以在 classpath 和 ServletContext 下增加檔案或資料夾,裡面都算是靜態

1.專案下有個 .classpath 檔, kind="src" 的 path 就是 classpath 路徑,也可自己增加
classpath/static
classpath/public
classpath/resources
classpath/META-INF/resources

2.ServletContext
src/main/webapp
如果包的是jar檔,大多數的構建工具可能會默默的忽略

以上5個地方都可以

假設在 public 下放一個圖片檔 xxx.jpg
網址打上 http://localhost:9000/xxx.jpg 即可訪問靜態資源



※上傳


<form action="upload" method="post" enctype="multipart/form-data">
    <input type="file" name="fileName"><br /> 
    <input type="submit" />
</form>




@RestController
public class FileUpload {
    @RequestMapping("/upload")
    public Map<String, String> processFileUpload(@RequestParam("fileName") MultipartFile fName)
            throws IllegalStateException, IOException {
        final File path = new File("D:/" + fName.getOriginalFilename());
        fName.transferTo(path);
        Map<String, String> map = new HashMap<>();
        map.put("msg", "success");
        map.put("path", path.getPath());
        return map;
    }
}
    
    
@SpringBootApplication
public class Test {
    public static void main(String[] args) {
        SpringApplication.run(Test.class, args);
    }
}

※@RestController 就是 @ResponseBody 和 @Controller 的組合,點進去可以看到,也就是整個 class 都是回傳 JASON 的,如果整個 class 都要回傳 JASON,就不用每一個方法都設 @ResponseBody 了

※打上 http://localhost:9000/index.html 後可上傳

※@RequestParam 必需對應前端的 name 名稱,如果不寫,那 MultipartFile 的變數必需和前端的 name 一樣

※預設不能超過 1048576 b 的檔案,也就是 1MB
會出「The field fileName exceeds its maximum permitted size of 1048576 bytes.」的訊息

官網說在 classpath 下增加 application.properties 可設定一些有的沒的,搜尋 MULTIPART 可設定上傳相關的屬性,有需要在複製下來改,以下是預設值和說明

spring.servlet.multipart.enabled=true #是否啟用多檔上傳
spring.servlet.multipart.file-size-threshold=0 # 檔案超過多少時緩存,可參考這篇的說明
spring.servlet.multipart.location= #檔案上傳時的臨時資料夾,可參考這篇的說明
spring.servlet.multipart.max-file-size=1MB #一個檔案的最大值
spring.servlet.multipart.max-request-size=10MB #全部檔案的最大值
spring.servlet.multipart.resolve-lazily=false #是否啟用lazy

2018年7月13日 星期五

Servlet (SpringBoot 2.x 二)

有 Servle、Filter、Listener

※Servlet

※使用 annotation

@WebServlet(urlPatterns = "*.do")
public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("呼叫成功!");
    }
}
    
    
@SpringBootApplication
@ServletComponentScan
public class Test {
    public static void main(String[] args) {
        SpringApplication.run(Test.class, args);
    }
}

※使用 @ServletComponentScan 可以掃瞄到 @WebServlet、@Filter、@WebListener


※使用 XxxRegistrationBean

public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("呼叫成功!");
    }
}
    
    
@SpringBootApplication
public class Test {
    public static void main(String[] args) {
        SpringApplication.run(Test.class, args);
    }
    
    @Bean
    public ServletRegistrationBean<MyServlet> getServletBean() {
        return new ServletRegistrationBean<>(new MyServlet(), "*.do");
    }
}

※方法取什麼無所謂,主要是回傳值



※Filter 

※使用 annotation

※針對副檔名過濾

@WebServlet(urlPatterns = "*.do")
public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("呼叫成功!");
    }
}
    
    
@WebFilter(urlPatterns = "*.do")
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("過濾前");
        chain.doFilter(request, response);
        System.out.println("過濾後");
    }
    
    @Override
    public void destroy() {}
}
    
    
@SpringBootApplication
@ServletComponentScan
public class Test {
    public static void main(String[] args) {
        SpringApplication.run(Test.class, args);
    }
}



※針對 servlet 名稱過濾

@WebServlet(name="xxx", urlPatterns = "*.do")
    
    
@WebFilter(servletNames="xxx")

※和針對副檔名差不多,只有 annotation 不一樣而已,name 和 servletNames 對應好即可


※使用 XxxRegistrationBean 過濾


※針對副檔名過濾

@SpringBootApplication
public class Test {
    public static void main(String[] args) {
        SpringApplication.run(Test.class, args);
    }
    
    @Bean
    public ServletRegistrationBean<MyServlet> getServletBean() {
        return new ServletRegistrationBean<>(new MyServlet(), "*.do");
    }
    
    @Bean
    public FilterRegistrationBean<MyFilter> getFilterBean() {
        FilterRegistrationBean<MyFilter> bean = new FilterRegistrationBean<>();
        bean.setFilter(new MyFilter());
        bean.addUrlPatterns("*.do");
        return bean;
    }
}



※針對 servlet 名稱過濾

public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("過濾前");
        chain.doFilter(request, response);
        System.out.println("過濾後");
    }
    
    @Override
    public void destroy() {}
}
    
    
@SpringBootApplication
public class Test {
    public static void main(String[] args) {
        SpringApplication.run(Test.class, args);
    }
    
    private ServletRegistrationBean<MyServlet> servletBean = new ServletRegistrationBean<>(new MyServlet(), "*.do");
    
    @Bean
    public ServletRegistrationBean<MyServlet> getServletBean() {
        return servletBean;
    }
    
    @Bean
    public FilterRegistrationBean<MyFilter> getFilterBean() {
        return new FilterRegistrationBean<MyFilter>(new MyFilter(), servletBean);
    }
}





※Listener

※使用 annotation

@WebListener
public class MyListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("contextInitialized方法被呼叫了");
    }
    
    @Override
    public void contextDestroyed(ServletContextEvent sce) {}
}
    
    
@SpringBootApplication
@ServletComponentScan
public class Test {
    public static void main(String[] args) {
        SpringApplication.run(Test.class, args);
    }
}

※啟動時就會看到 contextInitialized 的內容了


※使用 XxxRegistrationBean 監聽

@SpringBootApplication
public class Test {
    public static void main(String[] args) {
        SpringApplication.run(Test.class, args);
    }
    
    @Bean
    public ServletListenerRegistrationBean<MyListener> getListenerBean() {
        return new ServletListenerRegistrationBean<>(new MyListener());
    }
}

※這種方式就不需要 MyListener 的 @WebListener 了


2018年7月12日 星期四

HelloWorld (SpringBoot 2.x 一)

系統要求:
tomcat 8.5+
java8+
Maven3.2+
Gradle4+
官網說明


使用Maven只要使用簡單的專案即可


※Maven 設定

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.3.RELEASE</version>
</parent>
    
<properties>
    <java.version>1.8</java.version>
</properties>
    
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>
    
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>



※Gradle 設定

buildscript {
    ext {
        springBootVersion = '2.0.3.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}
    
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
    
group = 'sbt'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
    
repositories {
    mavenCentral()
}
    
dependencies {
    compile 'org.springframework.boot:spring-boot-starter'
    testCompile 'org.springframework.boot:spring-boot-starter-test'
    compile 'org.springframework.boot:spring-boot-starter-web'
}



※Controller

package ooo.xxx;
    
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
    
@Component
@RequestMapping("/testSpringBoot")
public class HelloSpringBoot {
    
    @RequestMapping("/helloWorld")
    @ResponseBody
    public Map<String, String> displayHelloWorld() {
        Map<String, String> map = new HashMap<>();
        map.put("a", "aaa");
        map.put("b", "bbb");
        return map;
    }
}



※測試

package ooo.xxx;
    
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
    
@SpringBootApplication
public class Test {
    public static void main(String[] args) {
        SpringApplication.run(Test.class, args);
    }
}

※小心 @SpringBootApplication 裡有個 @ComponentScan,預設只會掃瞄啟動類和他的子類,否則運行起來時不報錯,但網頁會報錯,如下有四個 package:
ooo
ooo.zzz
ooo.xxx
ooo.xxx.zzz
測試類只有在 ooo 和 ooo.xxx 才會執行成功
如果一定要不同的 package,那就再加個 @ComponentScan

※網址打上 http://localhost:8080/testSpringBoot/helloWorld 可以看到 JSON

※如果不喜歡用 8080 可以在 classpath 增加一個檔案叫 application.properties 或application.yml,內容寫 server.port=9000 即可,但 yml 是玩空格玩得很厲害的檔案,可參考官網,但如果兩個檔案都寫是 properties 贏了