2018年3月15日 星期四

五大類型指令 (Redis 二)

五種型態為:string、list、set、zset、hash,上一篇已經有寫 string了,這篇寫另外四種,可用type key名稱查是什麼型態


※list

可重覆的隊列


增、改:

lpush(left push):從左邊開始塞值,越先塞的會被擠到右邊,重覆下指令會 append
rpush(right push):和 lpush 一樣,差別是從右邊往左邊塞值
lpushx(exists)、rpushx:只有 key 存在才會 push 成功,也就是 key 至少要有一個元素

linsert:在指定的 value 之前或之後插入想加入的值
    linsert key名稱 [before|after] 已有的值 想插入的值:在已有的值前或後增加想插入的值,但      如果有多個已有的值,只會插入第一個
    linsert key before xxx ooo:在 xxx 前面插入 ooo

lset :覆蓋指定的index
假設有個 list 內容是 a b c d e,下 lset list 3 xxx,那 list 內容會變成 a b c xxx e



lpush示例:


rpush示例:



刪:

lrem(left remove):刪除元素
    格式:lrem key名稱 count 刪除內容
    .count > 0 從左到右刪除幾個最右邊參數的內容
    .count < 0 從右到左刪除幾個最右邊參數的內容
    .count = 0 將最右邊參數的內容全部刪除
ltrim:保留一個連續範圍,其他全部刪除,由0開始,-1表示倒數第一個,-2表示倒數第二個,依此類推


查:

llen(left length):查有幾個元素
lindex(left index):查第幾個元素的值
lrange:由0開始,-1表示倒數第一個,-2表示倒數第二個,依此類推
rlen、rrem、rindex、rrange、rtrim:哈哈哈!唬弄你的,並沒有這些命令


刪+查:

lpop:取得最左邊的值後刪除
rpop:取得最右邊的值後刪除

blpop:和 lpop 很像,當沒有值可 pop 時,會阻塞住,直到有值返回或 timeout
    blpop key… timeout 時間(秒)
brpop:blpop 的相反


刪+增:

rpoplpush:將最右邊的值彈出插入到另一個 list(自己也行),然後刪除最右邊的值








※set

裡面的成員不可重覆


增、改:

sadd:增加、附加成員


刪:

srem:刪除一到多個成員


查:

smembers:查看所有成員
sismember:給一個 value,回傳是否是裡面的成員
scard:查看有多少成員

srandmember:隨機出現一個元素
    但如果有加參數,就會顯示多少個,但不會重覆
    又如果數字超過成員的數,還是只會顯示成員的總數

差集、交集、併集

    sadd set1 a b c d e
    sadd set2 c d e f g
    sadd set3 b c d e f

.sdiff:差集,返回的是第一個 key 剩下的部分
    sdiff set1 set2,返回隨機的 a b
    sdiff set2 set1,返回隨機的 f g
    sdiff set1 set2 set3,返回 a
.sinter:交集。返回相同的部分
    sinter set1 set2,返回隨機的 c d e
    sinter set1 set2 set3,還是返回隨機的 c d e
.sunion:併集,全部成員都有,但會去掉重覆的部分
    sunion set1 set2,返回隨機的 a b c d e f g

sdiffstore、sinterstore、sunionstore,這三個都是在最後加上「store」,前面沒加 store 只是返回,加上 store 後,可以儲存在新的 set,此 key 可以不存在,存在就覆蓋
sdiffstore xxx set1 set2,將 set1 set2 的差集結果,存到 xxx 這個 key


刪+查:

spop:返回並刪除隨機一到多個數


刪+增:

smove 將 key1 裡的成類數數移動到 key2 裡的成員數值
    smove key1 key2 成員數值



※hash

Key、Value 鍵值對


增、改:

hset:塞一個鍵值對
hmset:塞多個鍵值對
hsetnx:key沒有才能塞進去
hincrby:增加整數,可為負數
hincrbyfloat:增加整數、小數,可為負數


刪:

hdel:刪除一到多個鍵


查:

hget:取一個鍵值對
hmget:取多個鍵值對
hgetall:取得 key 裡的所有鍵值對
hkeys:取得 key 裡的所有鍵
hvals:取得 key 裡的所有值
hlen:取得 key 裡有多少鍵值對
hstrlen:取得 key 裡的鍵,對應鍵的值有多長
hexists:判斷 key 裡有沒有給定的鍵


※sotedset

set 加上一個數字,可以利用此數字由小到大排序,這個數字的名字叫 score


增、改:

zadd:新增、附加一到多個成員,score 可以是負數
    xx:只能修改,注意回傳值,預設是新增成功的數量,加 ch 才看的出來
    nx:只能新增
    incr:增加 score,可以是負數
    ch:變化在返回值,不加時回傳的是新增的數量;加了是更改的數量
zincrby:增加、減少某個 key 的 score,可用小數點,但有可能不夠精確
    如果沒有 key 會自動增加 key
    如果 key 裡沒有對應的值,會新增


刪:

zrem:移除一到多個成員
zremrangebyscore:移除 score 的範圍
zremrangebyrank:移除 index 的範圍


查:

zrange[withscores]:查看元素,加上 withscores 會顯示 score
zrevrange:zrange 的倒序
zrank:給某個成員值,取得它的 index
zrevrank:zrank 的倒序
zscore:查看某個成員的 score
zcard:查看有多少成員
zcount:查看某 score 範圍的成員個數
zrangebyscore:依 score 由小到大排序
    zrangebyscore key -10 5,表示 score 從 -10 到 5
    zrangebyscore key (-10 5,表示 score 從 -10 到 5,但不包括 -10,「(」有不等於的意思
    zrangebyscore key -inf +inf,因為有時想從小到大全部列出來,不想寫範圍,可以用這個

上面的結果有一個全新的資料後,還可以再截取,使用 limit
    zrangebyscore key -10 5 limit 2 4,表示 score 從 -10 到 5 出來的結果,然後再從新的結果取index 為 2 開始的數字開始取 4 個

zrevrangebyscore:反向的 zrangebyscore

2018年3月14日 星期三

安裝、簡易設定、常用的基本指令 (Redis 一)

官網好心人翻譯官網的網站

Linux下載與安裝:


$ wget http://download.redis.io/releases/redis-4.0.8.tar.gz
$ tar -zxvf redis-4.0.8.tar.gz
$ cd redis-4.0.8
$ make

# yum install gcc --這行官網沒有,有可能在安裝時會出錯,假設是 gcc 這個程式找不到,可以用類似這樣的方法取得,gcc 是 C 語言的諞譯器

$ make distclean --此行是之前 make 失敗後,再一次 make 有可能會失敗,可加此行再 make
$ make
$ cd src
$ ./redis-server [設定檔路徑,預設為redis目錄下的 redis.conf]
$ ./redis-cli,redis用戶端
出現redis>表示成功

shutdown可關閉redis-server
exit 或 quit 可關閉redis-cli
help 查格式、簡單的說明
  .help 指令名稱,如 help set
  .help @群組名稱,如 help @list
  .打上「help空格」這五個鍵後按 tab,會有 以上兩個用法的提示,先是群組,這個版本有 14個,最後是命令和其他,有214個
ps -e |grep redis 可查看是否啟動



※Windows 下載









懶的進官網,就點我吧!
一樣有以上兩支程式,但設定檔有兩個很像,但我設背景執行都沒用,使用 windows 內鍵的 start redis-server 也只是會多出一個 cmd 而已



※不想裝環境

其實官網有提供,到下載頁的最下方,如下操作



懶的連官網,就點我吧!
可在紅框輸入指令



設定檔:


### GENERAL ###
daemonize 改為 yes 可以在背景執行,改完後,會在/var/run/redis.pid 這個檔案寫 pid
也可用 linux 內鍵的「&」,但不曉得有沒有 pid

logfile 預設沒有寫,所以是空的,加檔案名稱就會將伺服器啟動的資訊寫在裡面,當然還得看層級


dump.rdb

操作的記錄會在這裡,假設在路徑 redis-4.0.8 資料夾執行./src/redis-server,那會存在 redis-4.0.8下;又假設是在 src 資料夾執行 ./redis-server,那會存在 src 下,下次重啟 redis 時,會找當前目錄下的 dump.rdb ,如果沒有,那之前的東西就沒有保存下來了,就會是預設什麼都沒有的情形

在 redis.conf 的 ###SNAPSHOTTING###
預設有三行
save 900 1
save 300 10
save 60 10000
分別表示 900 秒(15分鐘)之內有執行1次操作以上
300秒(5分鐘)之內執行10次操作以上
60秒內執行1萬次操作以上
只要符合這樣的條件,就會自動存檔

在離開時 shutdown 也會存檔
如果在伺服器開啟的狀態下刪這個檔,用戶還是取的到,重啟伺服器才會沒有,但因為離開下 shutdown 會存檔,可以用 shutdown nosave 指令,這樣就沒有了


執行:

到src下,執行 ./redis-server ../redis.conf 啟動伺服器
./redis-cli 啟動用戶端,出現「>」提示符號,表示可以下指令了



常用的基本指令:

clear:清除螢幕
ping:測試有沒有連到伺服器,回傳 PONG
get:取得一個 key
set:設定一個 key
mget:取得多個key
mset:設定多個key
del:刪除某個 key
keys *:列出全部的key,可用「?、*」,如 a*,表示顯示 a 開頭的
dbsize:查詢當前資料庫有幾個 key
select index:選第幾個資料庫,預設是0,預設有16 個資料庫(0~15),在### GENERAL ### 有
    想知道目前第幾個庫,可看「>」前面的方括號,如果沒有方括號就是第 0 號庫
databases 可設定
flushall:清除全部資料庫的 key
flushdb:清除當前資料庫的 key
exists:回傳 1 表示存存;0 不存在
type:回傳值有 6 種,none、string、list、set、zset、hash,數字屬於 string
append:附加到原本的key後面,如果此 key 沒有,那就和 set 一樣
strlen:返回字串長度
getset:在 set 之前會先回傳舊的值,沒有舊值回傳 nil,set 還是會成功
    getset key1 xxx,假設 key1 還沒有創建,那會回傳 nil
    此時 get key1 會回傳 xxx
    getset key1 ooo,回傳 xxx,並塞新值 ooo
    再次 get key1 會回傳 ooo
setrange:將字串的第幾個字取代
    假設 key 的內容為 abcde
    setrange key 2 z
    get key 返回的是 abzde
    setrange key 2 xxx
    get key 返回的是 abxxx,並不會延長,而是將原本的 index 覆蓋
getrange:取得子字串,不會影響原來的字串
shutdown:關閉 redis 伺服器
exit:關閉redis 用戶端


※注意 key 如果已經有了會被覆蓋

※可以設定空格,但必需用成對的「"」或「'」包起來,這兩個符號不會被寫進去,可用 strlen 指令查看,但如果要輸入「"」或「'」,感覺上跳脫字元是「\」,但就是失敗
但使用 java 的 jedis 都可以寫入,但「"」使用 redis 看時是「\"」,長度是 1

中文使用 UTF-8 編碼,中文的一就是 E4B880,每兩個前面會用\x,變成 \xe4\xb8\x80





※加減

加減都只能設定成數字時才能使用
INCR:加 1
INCRBY:INCR只能加 1,這個指令可以加很多,但只能是整數;負數也行,負數變減
INCRBYFLOAT:加減整數、小數都可以
DECR:減1
DECRBY:DECR只能減 1,這個指令可以減很多,但只能是整數;負數也行,負數變加
.沒有DECRBYFLOAT,使用 INCRBYFLOAT 就無敵了



※SET參數

SET 有參數 [EX|PX] [NX|XX]
EX(expire):過了幾秒即失效,也可用 SETEX 指令
PX:過了幾毫秒即失效,也可用 PSETEX 指令
NX(not exist):如果 key 不存在才能設定,也可用 SETNX 指令
XX(exist):如果 key 存在才能設定
.NX 和 XX只能選其一
.官網的 EX 和 PX 沒有說只能選其一,但我試的結果就是會出錯,可能是版本問題吧,這裡是用4.0.8
但 EX 和 PX 可設定多個,結果是後者蓋前者
.沒有SETXX指令
.SETNX、SETEX、PSETEX 這三個命令,因為 SET 能做到相同的功能,所以未來的版本有可能會將其廢棄,最後移除


EXPIRE、PEXPIRE、PERSIST

.假如 SET 時忘記加失效時間,可用這個指令
.EXPIRE 設定的是秒;PEXPIRE 設定的是毫秒
.假設有了失效時間,但想讓它變成永久有效,可用 PERSIST 指令


TTL(time to live)、PTTL

key 的生存時間
.設定了 EX PX,可以使用這個指令看剩多少時間
.如果這個key是無時間限制的(或者說沒設定失效時間),會回傳-1,也就是永不失效
.如果沒有這個key,會回傳-2
.所以假設 set k1 v1 ex 5
get k1,隨著時間減一秒,會出現 5 4 3 2 1 0 -2
而假如有個key已經有了且沒有設定失效時間,那會回傳-1
.TTL回傳秒;PTTL 回傳的是毫秒

2018年2月4日 星期日

Struts 檢查 HTTP 方法 (Struts 四)

※ActionMapping、RequestProcessor

public class MyActionMapping extends ActionMapping {}
    
    
    
public class MyRequestProcessor extends RequestProcessor {
    @Override
    protected ActionMapping processMapping(HttpServletRequest req, HttpServletResponse res, String action)
            throws IOException {
        ActionMapping mapping = super.processMapping(req, res, action);
        if (mapping instanceof MyActionMapping) {
            if (!req.getMethod().equals("POST")) {
                mapping = null;
            }
        }
        return mapping;
    }
}

※mapping = null 網頁會變成空的,我使用 findForward 沒效

※struts-config

<form-beans>
    <form-bean name="ooo" type="controller.LoginActionForm">
        <form-property name="username" type="java.lang.String" />
        <form-property name="password" type="java.lang.String" />
    </form-bean>
</form-beans>
    
<action-mappings>
    <action path="/xxx" type="controller.LoginAction" name="ooo"
        scope="request" validate="true" input="/index.jsp" className="mapping.MyActionMapping">
        <forward name="success" path="/s.jsp" />
        <forward name="fail" path="/f.jsp" />
    </action>
</action-mappings>
    
<controller processorClass="listener.MyRequestProcessor"
contentType="text/html" nocache="true" locale="false" />

※增加 controller 標籤,和在 action 標籤增加 className 屬性


2018年2月3日 星期六

泛型的extends和super

※賦值

class Animal {}
class Bird extends Animal {}
class Chicken extends Bird {}


List<Animal> listA = new ArrayList<>();
List<Bird> listB = new ArrayList<>();
List<Chicken> listC = new ArrayList<>();

// ------------- 等號右邊沒有 super extends 賦值時 --------------
// 方法的參數有以下左邊的物件時,意思是一樣的
List<? super Animal> a1 = listA;
List<? super Bird> b1 = listA;
List<? super Chicken> c1 = listA;

List<? super Animal> a1s = listB; // x
List<? super Bird> b1s = listB;
List<? super Chicken> c1s = listB;

List<? super Animal> a1ss = listC; // x
List<? super Bird> b1ss = listC; // x
List<? super Chicken> c1ss = listC;
// -------------------------------
List<? extends Animal> a2 = listA;
List<? extends Bird> b2 = listA; // x
List<? extends Chicken> c2 = listA; // x

List<? extends Animal> a2e = listB;
List<? extends Bird> b2e = listB;
List<? extends Chicken> c2e = listB; // x

List<? extends Animal> a2ee = listC;
List<? extends Bird> b2ee = listC;
List<? extends Chicken> c2ee = listC;

// ------------- 等號右邊有 super extends 賦值時 --------------
// 不管是 super 或 extends,都把它當作右邊的物件來看
// super 只能賦值 super;extends 只能賦值 extends
// 被賦值的(等號右邊)沒有 super 或 extends 都會編譯錯誤,但相反可以,如上面的例子
List<? super Animal> qs1 = a1;
List<? super Animal> qs2 = b1; // x
List<? super Animal> qs3 = c1; // x
List<? super Bird> qs4 = a1;
List<? super Bird> qs5 = b1;
List<? super Bird> qs6 = c1; // x
List<? super Chicken> qs7 = a1;
List<? super Chicken> qs8 = b1;
List<? super Chicken> qs9 = c1;

// -------------------------------
List<? extends Animal> qe1 = a2;
List<? extends Animal> qe2 = b2;
List<? extends Animal> qe3 = c2;
List<? extends Bird> qe4 = a2; // x
List<? extends Bird> qe5 = b2;
List<? extends Bird> qe6 = c2;
List<? extends Chicken> qe7 = a2; // x
List<? extends Chicken> qe8 = b2; // x
List<? extends Chicken> qe9 = c2;

※增加元素

Animal a = new Animal();
Bird b = new Bird();
Chicken c = new Chicken();
    
List<? super Animal> listA = new ArrayList<>();
List<? super Bird> listB = new ArrayList<>();
List<? super Chicken> listC = new ArrayList<>();
listA.add(a);
listA.add(b);
listA.add(c);
    
// listB.add(a);
listB.add(b);
listB.add(c);
    
// listC.add(a);
// listC.add(b);
listC.add(c);

※super 可增加元素,取得元素要用 Object;extends 只能取得元素,不能增加元素

※增加元素時,不要看 super 和 extends 關鍵字,會搞混,那是給賦值的時候用的

※如果改用 ? 是無法增加元素的;? 等同 ? extends Object,因為有 extends 就不可以


※注意

1.super 只有傳參時,才表示此類和此類的父類;賦值時最多只表示此類
2.super 轉換後,可以存也可以取
3.extends 只能取得父類的方法,無法存


※super

Animal a = new Animal();
a.setEat("Animal吃");
a.setJump("Animal跳");
    
Bird b = new Bird();
b.setEat("Bird吃");
b.setJump("Bird跳");
b.setFly("Bird飛");
    
Chicken c = new Chicken();
c.setMorning("Chicken說早安");
    
List<? super Animal> list = new ArrayList<>();
list.add(a);
list.add(b);
list.add(c);
    
for (Object o : list) {
    if (o instanceof Chicken) {
        Chicken chicken = (Chicken) o;
        System.out.println(chicken.getMorning());
        continue;
    }
    
    if (o instanceof Bird) {
        Bird bird = (Bird) o;
        System.out.println(bird.getFly());
        continue;
    }
    
    if (o instanceof Animal) {
        Animal animal = (Animal) o;
        System.out.println(animal.getEat());
        continue;
    }
}

※注意順序和 continue,子類在上;父類在下


※extends

public class ExtendsAndSuper {
    public static void main(String[] args) {
        List al= new ArrayList<>();
        Animal a = new Animal();
        a.setEat("Animal吃");
        a.setJump("Animal跳");
        al.add(a);
    
        List bl= new ArrayList<>();
        Bird b = new Bird();
        b.setEat("Bird吃");
        b.setJump("Bird跳");
        b.setFly("Bird飛");
        bl.add(b);
    
        List cl= new ArrayList<>();
        Chicken c = new Chicken();
        c.setEat("Chicken吃");
        c.setMorning("Chicken說早安");
        cl.add(c);
    
        // new ExtendsAndSuper().xxx(al);
        new ExtendsAndSuper().xxx(bl);
        new ExtendsAndSuper().xxx(cl);
    }
    
    private void xxx(List<? extends Bird> list) {
        for (Animal a : list) {
            System.out.println(a.getEat());
            a.setEat("好吃");
            System.out.println(a.getEat());
        }
    }
}

※注意迴圈裡轉換的是 Bird 的父類,還是可以的;但如果迴圈用的是 Bird,還可以用 Bird 的方法

2018年1月25日 星期四

基本的 Annotation (JUnit4 一)

官網連結,左下有很多連結,JavaDocs 是 API

※設定

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

※會依賴 hamcrest-core 1.3,此包可以使用 Matcher,這樣子 assertThat 方法就可以使用了

※JUnit 不可以用在多執行緒,main 執行緒一跑完,其他的執行緒也沒了,解決方法如下:
1.可以在主執行緒寫 sleep 睡久一點,超過其他執行緒的時間就可以了

2.使用 join,讓其他執行緒先跑完,如「執行緒名稱」.join(),就會先執行完該執行緒才會執行其他執行緒,如果有 5 個執行緒,那5個都得這樣做

3.使用 CountDownLatch 類別, 裡面有 await 和 countDown 方法

可看 JUnitCore 裡的 main 方法,使用的是 System.exit



※@BeforeClass、@Before、@AfterClass、@After、@Test、@Ignore

@BeforeClass
public static void start1() {
    System.err.println("class之前執行1");
}
    
@BeforeClass
public static void start2() {
    System.err.println("class之前執行2");
}
    
@Before
public void begin1() {
    System.out.println("方法之前執行1");
}
    
@Before
public void begin2() {
    System.out.println("方法之前執行2");
}
    
@After
public void end1() {
    System.out.println("方法之後執行1");
}
    
@After
public void end2() {
    System.out.println("方法之後執行2\r\n");
}
    
@AfterClass
public static void theEnd1() {
    System.err.println("class之後執行1");
}
    
@AfterClass
public static void theEnd2() {
    System.err.println("class之後執行2");
}
    
@Test
public void bruceTest1() {
    System.out.println("測試1");
}
    
@Test
public void bruceTest2() {
    System.out.println("測試2");
}
    
@Ignore
@Test
public void bruceTest3() {
    System.out.println("測試3");
}

※結果(注意執行順序):
class之前執行2
class之前執行1
方法之前執行2
方法之前執行1
測試1
方法之後執行1
方法之後執行2

方法之前執行2
方法之前執行1
測試2
方法之後執行1
方法之後執行2

class之後執行1
class之後執行2

※@BeforeClass、@AfterClass 一定要 static,錯誤會提示

※@BeforeClass、@AfterClass、@Before、@After、@Test,都只能寫在方法上,而且都沒有參數,也沒有回傳值,也都只能是 public

※@Test 才會執行,但如果又加上 @Ignore 就會忽略

※寫@Ignore 和不寫@Test 的差別如下圖:
紅框會發現有 skipped,此行表示執行了 3 個@Test 的方法,但有一個忽略了,第二個紅框有可以發現圖示是白色的;如果不寫@Test,那就什麼都沒有



※@Test、@Ignore 的參數

@Test(expected = ArithmeticException.class)
public void bruceTest1() {
    System.out.println(1 / 0);
}
    
@Test(timeout = 1000)
public void bruceTest2() {
    try {
        Thread.sleep(1100);
        System.out.println("哈哈哈");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
    
@Ignore("忽略理由")
@Test
public void bruceTest3() {
    System.out.println("測試3");
}

※@Ignore 可以寫在方法或類別上,忽略方法或忽略整個類別,參數可以寫忽略的理由

※@Test 的屬性 timeout 的單位是毫秒,如果超過這個時間就會報錯

※@Test 的屬性 expected 是預期會出現的 Exception,如果不出現就會報錯

2018年1月6日 星期六

Spring、Spring MVC 設定檔整合 (Spring3.x MVC 十)

雖然可以只用 spring MVC 的設定檔就可以了,但要整合也是 OK 的,但要注意整合的問題

※整合時會出現注入兩次

spring 可以有很多設定檔,可用 web.xml,或者在設定檔使用 import 標籤將其他的設定檔抓進來,但好幾個 spring 設定檔都掃瞄一樣的包,就算設了很多次,不管使用 web.xml 或 import 標籤,都只會注入一次,以上的情形,spring MVC也是一樣

但這兩個設定檔是分開的,兩個都掃瞄一樣的包,會出現兩次


※java bean

package controller;
    
@Controller
@RequestMapping("/ooo/xxx/")
public class XxxAction {
    public XxxAction() {
        System.out.println("XxxAction");
    }
}




package service;
    
@Service
public class XxxServiceImpl {
    public XxxServiceImpl(){
        System.out.println("XxxServiceImpl");
    }
}




※設定檔

※spring 設定檔
<context:component-scan base-package="controller, service" />



※spring MVC 設定檔
<import resource="init.TestDispatcherServlet-servlet2.xml" />
<context:component-scan base-package="controller, service" />




※web.xml

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/applicationContext*.xml</param-value>
</context-param>
    
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
    
<servlet>
    <servlet-name>testServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/init.TestDispatcherServlet-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
    
<servlet-mapping>
    <servlet-name>testServlet</servlet-name>
    <url-pattern>*.mvc</url-pattern>
</servlet-mapping>

※啟動伺服器後,會看到 console 會印出兩次 java bean 建構子的內容


※解決方式

可以使用掃瞄不同包來解決,如 spring 掃瞄 controller、spring MVC 掃瞄 service
也可以使用如下的方式:

<context:component-scan base-package="controller, service" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
    <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" />
</context:component-scan>




<context:component-scan base-package="controller, service" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>

※use-default-filters 預設是 true,表示可掃瞄 @Controller、@Service…等的包,將它變成 false 就沒辦法掃了,然後子標籤在和它說可以掃瞄什麼類型的注解

※如果使用 annotation,可以用 @ComponentScan(useDefaultFilters = false, includeFilters = { @Filter(type = FilterType.ANNOTATION, value = App.class) })
.FilterType.ASSIGNABLE_TYPE 將指定的 class 加入 bean id
.FilterType.CUSTOM 寫個類別繼承 TypeFilter 後,即可使用




※spring 抓 spring MVC 注入的變數

spring 就像個全域變數, spring MVC 像個區域變數
區域變數知道有全域變數,但全域變數不知有區域變數
所以 spring MVC 可以抓 spring 的注入屬性;相反則不能

以上個例子來說,假設 controller 是 spring 設定檔設定的;而 service 是 spring MVC 設定的
那 service 使用 @Autowire 抓 XxxAction,伺服器啟動成功;
controller 使用 @Autowire 抓 XxxServiceImpl,伺服器啟動失敗

2018年1月2日 星期二

攔截器、例外處理 (Spring3.x MVC 九)

※攔截器

※HandlerInterceptor

public class HelloInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        System.out.println("A1");
        return true;
    }
    
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        System.out.println("A2");
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        System.out.println("A3");
    }
}

※寫四支一模一樣的,分別是 ABCD 的 1~3

※preHandle 執行在目標方法之前
postHandle 執行在目標方法之後、view 之前
afterCompletion view 之後

※preHandle 回傳 false,那之後的都不會執行


※spring 設定


mapping 是映射什麼路徑
exclude-mapping 是排除路徑

<mvc:interceptors>
    <bean class="init.HelloInterceptor" />
    
    <mvc:interceptor>
        <mvc:mapping path="/**" />
        <bean class="init.HelloInterceptor2" />
    </mvc:interceptor>
    
    <mvc:interceptor>
        <mvc:mapping path="/ooo/xxx/a/b/*.mvc" />
        <bean class="init.HelloInterceptor3" />
    </mvc:interceptor>
    
    <mvc:interceptor>
        <mvc:mapping path="/**" />
        <mvc:exclude-mapping path="/ooo/xxx/a/*/*.mvc" />
        <bean class="init.HelloInterceptor4" />
    </mvc:interceptor>
</mvc:interceptors>
    
<mvc:view-controller path="ooo/xxx/b/a/c.mvc" view-name="jump1" />
<mvc:view-controller path="ooo/xxx/a/b/c1.mvc" view-name="jump1" />
<mvc:view-controller path="ooo/xxx/a/b/c2.mvc" view-name="jump1" />

※可以不加 <mvc:annotation-driven />

※HelloInterceptor、HelloInterceptor2 可以說是一樣的

※注意 exclude-mapping 最上面一定要有 mapping,否則會報錯,也就是先指定一個大範圍,然後排除,排除可以寫好幾個


※測試

<a href="ooo/xxx/b/a/c.mvc">c</a>
<a href="ooo/xxx/a/b/c1.mvc">c1</a>
<a href="ooo/xxx/a/b/c2.mvc">c2</a>

※第一個執行 ABD,其他兩個都是 ABC


※執行順序

preHandle:寫在設定檔的前面最先執行,全部的 preHandle執行完才執行 postHandle
postHandle:寫在設定檔的前面最後執行,全部的 postHandle 執行完才執行 afterCompletion
afterCompletion :寫在設定檔的前面最後執行

preHandle 是正序;postHandle、afterCompletion 執行順序是倒序



※例外處理

使用 @ExceptionHandler

※controller

@RequestMapping("exception")
public String testException(Integer i) {
    System.out.println(2/i);
    return "hello";
}
    
@ExceptionHandler(ArithmeticException.class)
public String exceptionMethod() {
    System.out.println("我錯了");
    return "errPage";
}

※回傳的字串是配合 spring 設定檔的 InternalResourceViewResolver

※測試

<a href="ooo/xxx/exception.mvc?i=0">exception</a>

※網址是不變的



※不能使用 Map

@ExceptionHandler(ArithmeticException.class)
public ModelAndView exceptionMethod(Exception ex /*, Model model*/) {
    System.out.println("我錯了" + ex);
    ModelAndView mav = new ModelAndView("errPage");
    mav.addObject("excep", ex);
    return mav;
}

※當然包括子類 Model,只是放在參數裡,都還沒有 put,就 500了,想塞值到前端,要用 ModelAndView


※多個 @ExceptionHandler

@ExceptionHandler(ArithmeticException.class)
public String exceptionMethod(Exception ex) {
    System.out.println("A");
    return "errPage";
}
    
@ExceptionHandler(Exception.class)
public String exceptionMethod2(Exception ex) {
    System.out.println("B");
    return "errPage";
}

※會以完全匹配為主,沒有完全匹配才會退而求其次


※全域的例外處理 @ControllerAdvice

@ControllerAdvice
public class GlobalException {
    @ExceptionHandler(ArithmeticException.class)
    public String exceptionMethod(Exception ex) {
        System.out.println("C");
        return "errPage";
    }
    
    @ExceptionHandler(Exception.class)
    public String exceptionMethod2(Exception ex) {
        System.out.println("D");
        return "errPage";
    }
}

※區域例外處理全部都沒有 @ExceptionHandler 才會執行全域的  @ControllerAdvice

※全域必需設定
1. <context:component-scan base-package="controller" />
    屬性 use-default-filters 不能是 false,預設是 true
2. <mvc:annotation-driven />
否則有寫也不會執行



※預設的例外處理

看原碼 DefaultHandlerExceptionResolver.doResolveException,Eclipse 可使用 Ctrl + Shift + T
以 3.2.13 版為例,會發現有 13 個預設的例外處理
NoSuchRequestHandlingMethodException
HttpRequestMethodNotSupportedException
HttpMediaTypeNotSupportedException
HttpMediaTypeNotAcceptableException
MissingServletRequestParameterException
ServletRequestBindingException
ConversionNotSupportedException
TypeMismatchException
HttpMessageNotReadableException
HttpMessageNotWritableException
MethodArgumentNotValidException
MissingServletRequestPartException
BindException

※上圖為預設的,下圖為修改過的,想修改就必需如下設定


※自訂例外

@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR, reason = "我錯了")
public class DefinedSelfException extends RuntimeException {}

※HttpStatus 有很多,隨便選即可

※controller

// @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR, reason = "我錯了2")
@RequestMapping("definedSelfExp")
public String definedSelfExp(Integer i) {
    if (i == 0) throw new DefinedSelfException();
    
    System.out.println(2 / i);
    return "hello";
}

※@ResponseStatus 若寫在方法上,i == 0 時不變;但若不是 0,不會跳頁,會產生例外,但寫在這裡的中文會亂碼,寫在另外一支 class 不會,這個我沒解

※測試

<a href="ooo/xxx/definedSelfExp.mvc?i=0">definedSelfExp</a>





※SimpleMappingExceptionResolver


※spring 設定檔

<bean id="simpleMappingExceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="exceptionMappings">
        <props>    
            <prop key="java.lang.ArithmeticException">arithmeticPage</prop>
            <prop key="java.lang.ArrayIndexOutOfBoundsException">arrayIndexPage</prop>
        </props>
    </property>
    <property name="exceptionAttribute" value="xxx" />
</bean>
    
<mvc:default-servlet-handler />
    
<mvc:annotation-driven />

※prop 包起來的字串是頁面名稱,配合 InternalResourceViewResolver

※exceptionAttribute 如果不寫,預設是 exception,可以在 JSP 用 EL


※controller

@RequestMapping("simpleExp")
// @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR, reason = "我錯了2")
public String simpleExp(Integer i) {
    // if (i == 0) throw new DefinedSelfException();
    System.out.println(2 / i);
    return "hello";
}

※方法上的 @ResponseStatus 是有用的


※測試

<a href="ooo/xxx/simpleExp.mvc?i=0">simpleExp</a>