Google Code Prettify

2014年6月15日 星期日

即時更新程式設定

有一些交易程式,在營業時間並不容許隨易的停止服務,如果遇到很小的 bug 或問題,最好是以不停止服務,僅更改設定的方式來解決,這裡提供一個小小的程式,可以不停止服務的情況下,更新程式的設定。
 1 package idv.steven.annotation;
 2 
 3 import java.lang.annotation.ElementType;
 4 import java.lang.annotation.Retention;
 5 import java.lang.annotation.RetentionPolicy;
 6 import java.lang.annotation.Target;
 7 
 8 @Retention(RetentionPolicy.RUNTIME)
 9 @Target(ElementType.FIELD)
10 public @interface AutoUpdate {
11     public String key();
12 }
首先定義一個 annotation,這個 annotation 是用在標注要更新的欄位,所以第 9 行設定僅能用在欄位上。第 8 行也要特別注意,RetentionPolicy 有三個值,如下:
  • SOURCE: Annotations 只存於程式碼檔,編譯器產生二進位表時捨棄之。
  • CLASS: Annotations 保留於 class 二進位表內,但於執行期無法取用。
  • RUNTIME: Annotations 保留於 class 二進位表內,且執行期可經由 reflection 機制取用。
RetentionPolicy 的預設值是 CLASS。
1 package idv.steven.annotation;
2 
3 import java.util.Map;
4 
5 public interface WatchedObject {
6     public void onUpdating(Map<String, String> map);
7 }
要被即時更新的類別必須實作這個介面,之後會以 Observer pattern 的方式更新物件的值。
  1 package idv.steven.annotation;
  2 
  3 import java.io.FileInputStream;
  4 import java.io.FileNotFoundException;
  5 import java.io.IOException;
  6 import java.lang.reflect.Field;
  7 import java.nio.file.FileSystems;
  8 import java.nio.file.Path;
  9 import java.nio.file.Paths;
 10 import java.nio.file.StandardWatchEventKinds;
 11 import java.nio.file.WatchEvent;
 12 import java.nio.file.WatchKey;
 13 import java.nio.file.WatchService;
 14 import java.util.ArrayList;
 15 import java.util.Map;
 16 import java.util.Properties;
 17 import java.util.TreeMap;
 18 
 19 public class WatchProperties implements Runnable {
 20     private ArrayList<WatchedObject> watchedObj = new ArrayList<WatchedObject>();
 21     private boolean isContinue = true;
 22 
 23     private String fullFilename;
 24     private String pathname = "";
 25     private String filename = "";
 26     
 27     public WatchProperties(String fullFilename) throws FileNotFoundException {
 28         this.fullFilename = fullFilename;
 29         splitPathAndFile(fullFilename);
 30     }
 31 
 32     public void run() {
 33         final Path path = Paths.get(pathname);
 34         WatchService watchService;
 35         try {
 36             watchService = FileSystems.getDefault().newWatchService();
 37         
 38             path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
 39 
 40             while (isContinue) {
 41                 final WatchKey key = watchService.take();
 42                 
 43                 for (WatchEvent<?> watchEvent : key.pollEvents()) {
 44                     final WatchEvent<Path> watchEventPath = (WatchEvent<Path>) watchEvent;
 45                     final Path currentFilename = watchEventPath.context();
 46                     
 47                     if (currentFilename.toString().equalsIgnoreCase(filename)) {
 48                         triggerUpdating();
 49                         break;
 50                     }
 51                 }
 52                 
 53                 boolean valid = key.reset();
 54                 if (!valid) {
 55                     System.out.println("reset fail");
 56                     break;
 57                 }
 58                 else {
 59                     System.out.println("reset success");
 60                 }
 61             }
 62 
 63             watchService.close();
 64         } catch (IOException | InterruptedException e) {
 65             e.printStackTrace();
 66         }
 67     }
 68 
 69     private void splitPathAndFile(String fullFilename) throws FileNotFoundException {
 70         fullFilename.replace('\\', '/');
 71         String[] tokens = fullFilename.split("/");
 72         if (tokens != null) {
 73             for(int i=0; i<tokens.length-2; i++) {
 74                 pathname = pathname + tokens[i] + "/";
 75             }
 76             pathname = pathname + tokens[tokens.length-2];
 77             filename = tokens[tokens.length-1];
 78         }
 79         else {
 80             throw new FileNotFoundException();
 81         }
 82     }
 83     
 84     private void triggerUpdating() {
 85         Properties properties = new Properties();
 86         
 87         try {
 88             properties.load(new FileInputStream(fullFilename));
 89             
 90             for(WatchedObject obj:watchedObj) {
 91                 Map<String, String> map = new TreeMap<String, String>();
 92                 
 93                 Class<? extends WatchedObject> aClass = obj.getClass().asSubclass(WatchedObject.class);
 94                 Field[] fields = aClass.getDeclaredFields();
 95                 for(Field field:fields) {
 96                     AutoUpdate autoUpdate = field.getAnnotation(AutoUpdate.class);
 97                     if (autoUpdate != null) {
 98                         String key = autoUpdate.key();
 99                         if (properties.getProperty(key) != null) {
100                             map.put(key, properties.getProperty(key));
101                         }
102                     }
103                 }
104                 
105                 if (map.size() > 0) {
106                     obj.onUpdating(map);
107                 }
108             }
109         } catch (FileNotFoundException ex) {
110             ex.printStackTrace();
111             return;
112         } catch (IOException ex) {
113             ex.printStackTrace();
114             return;
115         }
116     }
117     
118     public void register(WatchedObject obj) {
119         watchedObj.add(obj);
120     }
121     
122     public void unregister(WatchedObject obj) {
123         watchedObj.remove(obj);
124     }
125 }
這個類別使用 Watch Service API 監看設定檔,當設定檔改變,透過 Observer pattern 的方式,通知相關物件。這裡就程式簡略的說明:
  • Line 94: 一定要呼叫 getDeclaredFields() 才能取得所有的 field,如果呼叫 getFields() 僅能取得 public 的 field。
  • register(): 要被即時更新的物件透過這個 method 註冊。
  • Line 96: 我們僅在意有 @AutoUpdate 這個 annotation 的欄位。
 1 package idv.steven.annotation;
 2 
 3 import java.io.FileNotFoundException;
 4 import java.util.Map;
 5 import java.util.Set;
 6 
 7 public class Main implements WatchedObject {
 8     @AutoUpdate(key="bufLen")
 9     private Integer bufLen;
10     @AutoUpdate(key="msgCode")
11     private String msgCode;
12     
13     private void run() {
14         try {
15             WatchProperties watchProperties = new WatchProperties("D:/temp/main.properties");
16             watchProperties.register(this);
17             Thread watchThread = new Thread(watchProperties);
18             watchThread.start();
19             
20         } catch (FileNotFoundException e) {
21             e.printStackTrace();
22         }
23     }
24     
25     @Override
26     public void onUpdating(Map<String, String> map) {
27         Set<String> keys = map.keySet();
28         for(String key:keys) {
29             if (key.equals("bufLen")) {
30                 bufLen = Integer.parseInt(map.get(key).toString());
31                 System.out.println("bufLen = " + bufLen);
32             }
33             else if (key.equals("msgCode")) {
34                 msgCode = map.get(key).toString();
35                 System.out.println("msgCode = " + msgCode);
36             }
37         }
38     }
39     
40     public static void main(String[] args) {
41         new Main().run();
42     }
43 }
Main 是要被即時更新的類別,所以要實作 WatchedObject 這個介面,並在 16 行將自己註冊到監看的程式上。當設定檔有異動,會有事件透過 onUpdating() 傳過來,這時候即可更新相關欄位的值。
bufLen=1024
msgCode=MSG001
Organization=OTC
設定檔的內容如上,當然會用到的只有上面兩行,這是個 property 檔,key 的值要和 Main 類別程式中的 annotation 標示的 key 值一樣。




【日劇 - Miss PILOT】
2013/10/15 開播的日劇,堀北真希主演,是一個不小心被錄取為儲備機師後努力取得機師資格的人,當然這是勵志片。戲的重點是什麼呢? 美女吧?! 主角手塚晴 (堀北真希) 不用說是個大美女。
劇中另一個儲備女機師小田千里 (相武紗季) 外表比較不亮眼,所以讓大家看一下非本劇的另一張照片。
第三位阿倍野鈴 (櫻庭奈奈美) 是地勤,是一個缺乏安全感的機師女友,因為她只是戲份不多的配角,很難找到她在 Miss PILOT 中的獨照,就姑且看一下底下這張照片吧!
鈴木倫子 (菜菜緒) 是地勤,自認是教官國木田孝之助 (齋藤工) 的女友,才色兼具。
不知道為什麼,航空公司裡,連廚師都很漂亮,三枝佳乃子 (藤澤惠麻) 是飛行員宿舍的管理員兼主廚,戲份也不多,很難找到劇中的獨照 …

2014年6月8日 星期日

使用 Watch Service API 觀察檔案系統

在 Java 7 以前,如果要觀察一個目錄下的檔案是否有異動 (新增、修改、刪除),唯一的辦法就是開啟一個 thread,每隔一段時間去檢查一下,當發現有異動,發出異動的訊息。Java 7 提供了 Watch Service API 解決了這個問題,現在不需要再這麼麻煩且浪費系統資源了,透過這組 API 程式可以很優雅、簡單的監控檔案系統的異動狀況。 如下的程式,會監控 D:\temp 這個目錄,如果這個目錄有任何檔案新增、修改、刪除,就會產生 event。
 1 package idv.steven.nio2;
 2 
 3 import java.io.IOException;
 4 import java.nio.file.FileSystems;
 5 import java.nio.file.Path;
 6 import java.nio.file.Paths;
 7 import java.nio.file.StandardWatchEventKinds;
 8 import java.nio.file.WatchEvent;
 9 import java.nio.file.WatchKey;
10 import java.nio.file.WatchService;
11 
12 public class WatchFile {
13     
14     public void run() {
15         final Path path = Paths.get("D:/temp");
16         WatchService watchService;
17         try {
18             watchService = FileSystems.getDefault().newWatchService();
19             
20             path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE,
21                     StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE);
22 
23             while (true) {
24                 final WatchKey key = watchService.take();
25                 
26                 for (WatchEvent<?> watchEvent : key.pollEvents()) {
27                     final WatchEvent<Path> watchEventPath = (WatchEvent<Path>) watchEvent;
28                     final Path filename = watchEventPath.context();
29                     
30                     System.out.println("kind: " + watchEvent.kind().toString());
31                     System.out.println("filename: " + filename.toString());
32                 }
33                 key.reset();
34                 break;
35             }
36 
37             watchService.close();
38             System.out.println("exit");
39         } catch (IOException | InterruptedException e) {
40             e.printStackTrace();
41         }
42     }
43 
44     public static void main(String[] args) {
45         new WatchFile().run();
46     }
47 }
 現在解釋一下程式:
  • Line 18: 建立一個檔案系統的監視服務。
  • Line 19: 把監視服務註冊到我們要監視的路徑 (D:\temp),並指定要監視的事件類別,這裡指定「新增」、「修改」、「刪除」都要監視。
  • Line 24: 程式會停在這一行,開始監視。
  • Line 26~34: 監視的目錄有異動,產生的事件在這裡處理,我們很簡單的將事件類別和檔案名稱印出來。
  • Line 37: 關閉監視服務。
Line 24 要特別說明,上述程式呼叫 take() 是方法之一,完整的說明如下:
  • poll(): If no key is available, it returns immediately a null value.
  • poll(long, TimeUnit): If no key is available, it waits the specified time and tries again. If still no key is available, then it returns null. The time period is indicated as a long number, while the TimeUnit argument determines whether the specified time is minutes, seconds, milliseconds, or some other unit of time.
  • take(): If no key is available, it waits until a key is queued or the infinite loop is stopped for any of several different reasons.






【日劇 - 東京機場管制保安部】
日劇的很喜歡拍這種以特定職業為主題的影集,雖然透過戲劇要了解那個行業,肯定會有很多的失真,但是還是可以對該工作有更多了解。這部戲由深田恭子主演,她由地勤調任管制保安部,裡面的人工作似乎多半時間是非常繁忙,而且又因為指揮不當的話可能會造成空難事件,或即使沒有造成空難事件,也可能因為一些原因而被認為失職,看起來是個壓力非常大的工作。

上圖是深田恭子執勤的樣子,要一直看雷達螢幕、窗外的狀況,也要了解各跑道是否處於可以使用的狀態,還要了解目前的天氣,尤其是風向與風速,然後透過自己的經驗判斷,安排天上的飛機降落及地方的飛機起飛,這怎麼想都知道不會太輕鬆。
佐佐木希在劇中是個開心果,這個角色的安排應該是希望讓嚴肅的職場環境產生的凝重氣氛可以淡一些。

公務員的工作常常有個特性,即使他目前的職務是很重要、很專業的,但是當他們離開這個環境,想到私人企業上班的話,這些經驗多半是派不上用場! 機場管制員的工作大概就是屬於這一類。這也是公務員的流動性通常不高的原因之一! 但是,事實上不該是如此! 政府針對離職公務員應該提供轉職的輔導,讓他們可以學到別的專長,以便能順利轉職到私人企業。