首页 > 代码库 > 即時更新程式設定

即時更新程式設定

有一些交易程式,在營業時間並不容許隨易的停止服務,如果遇到很小的 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     @Override26     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 值一樣。