在進行系統整合時,系統間不管是透過網路、檔案傳送訊息資料,常會遇到訊息字串的各個欄位是固定長度的,這種情況下程式怎麼寫會比較簡潔易懂? 這裡是我提供的一個方法,使用 annotation 將訊息字串欄位的設定值直接寫在程式上,而不是寫在設定檔裡,這樣應該會比較直覺。
假設有個程式,要顯示中華民國總統的任期和歷史評價,其訊息格式如下:
首先,我們定義一個 annotation,可以設定欄位的開始位置和長度,如下:
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 MsgField { 11 public int start(); 12 public int length(); 13 }
接著定義一個介面,所有訊息的 POJO 都要實作這個介面,這個介面的作用只是為了統一各個訊息的類型。
1 package idv.steven.msgspec; 2 3 public interface FixedFieldMsg { 4 5 }
接著依訊息的需要定義 POJO,這裡當然就是依上方的訊息規格來定義,並且於類別裡的各個欄位上設定開始位置、長度,因為訊息有四個欄位,當然這個類別也有相對應的四個欄位。
1 package idv.steven.pojo; 2 3 import idv.steven.MsgField; 4 5 public class President implements FixedFieldMsg { 6 @MsgField(start=0,length=4) 7 private String startYear; 8 @MsgField(start=4,length=4) 9 private String endYear; 10 @MsgField(start=8,length=10) 11 private String name; 12 @MsgField(start=18,length=30) 13 private String evaluate; 14 15 /** 16 * @return 任期開始的年份 17 */ 18 public String getStartYear() { 19 return startYear; 20 } 21 public void setStartYear(String startYear) { 22 this.startYear = startYear; 23 } 24 25 /** 26 * @return 任期結束的年份 27 */ 28 public String getEndYear() { 29 return endYear; 30 } 31 public void setEndYear(String endYear) { 32 this.endYear = endYear; 33 } 34 35 /** 36 * @return 姓名 37 */ 38 public String getName() { 39 return name; 40 } 41 public void setName(String name) { 42 this.name = name; 43 } 44 45 /** 46 * @return 評價 47 */ 48 public String getEvaluate() { 49 return evaluate; 50 } 51 public void setEvaluate(String evaluate) { 52 this.evaluate = evaluate; 53 } 54 }
下面就是剖析訊息的程式了…
1 package idv.steven; 2 3 import java.lang.reflect.Field; 4 import java.lang.reflect.InvocationTargetException; 5 import java.lang.reflect.Method; 6 import java.util.Arrays; 7 8 import idv.steven.annotation.MsgField; 9 import idv.steven.msgspec.FixedFieldMsg; 10 import idv.steven.msgspec.President; 11 12 /** 13 * 剖析固定欄位長度的訊息字串成為一個物件 14 * @author Steven 15 */ 16 public class Parser { 17 /** 18 * 將訊息字串轉換為訊息物件 19 * @param msgText 訊息字串 20 * @param msgObject 訊息物件 21 * @return 訊息物件 22 * @throws IllegalArgumentException 23 * @throws IllegalAccessException 24 * @throws NoSuchMethodException 25 * @throws SecurityException 26 * @throws InvocationTargetException 27 * @throws ClassNotFoundException 28 */ 29 public FixedFieldMsg parsing(byte[] msgText, FixedFieldMsg msgObject) 30 throws IllegalArgumentException, IllegalAccessException, 31 NoSuchMethodException, SecurityException, 32 InvocationTargetException, ClassNotFoundException { 33 34 Class<?> aClass = Class.forName(msgObject.getClass().getName()); 35 Field[] fields = aClass.getDeclaredFields(); 36 for(Field field:fields) { 37 MsgField msgField = field.getAnnotation(MsgField.class); 38 if (msgField != null) { 39 byte[] subMsg = Arrays.copyOfRange(msgText, msgField.start(), msgField.start() + msgField.length()); 40 41 Method method = aClass.getMethod(getSetterName(field.getName()), String.class); 42 method.invoke(msgObject, new String(subMsg)); 43 //field.set(message, new String(subMsg)); 44 } 45 } 46 47 return msgObject; 48 } 49 50 private String getSetterName(String fieldName) { 51 return String.format("set%s%s", fieldName.substring(0, 1).toUpperCase(), fieldName.substring(1)); 52 } 53 54 public static void main(String[] args) { 55 String msgText = "20082016馬英九 一個自私的笨蛋、禍國殃民! "; 56 57 try { 58 Parser parser = new Parser(); 59 President president = (President) parser.parsing(msgText.getBytes(), new President()); 60 61 System.out.println("任期: " + president.getStartYear() + " ~ " + president.getEndYear()); 62 System.out.println("總統姓名: " + president.getName()); 63 System.out.println("歷史評價: " + president.getEvaluate()); 64 } catch (Exception e) { 65 e.printStackTrace(); 66 } 67 } 68 }
這個程式如果要實際應用,當然還要加上一些錯誤處理,以避免傳入的字串不合規定,在這裡為使程式邏輯更清楚,就先省略了,只談我們真正在意的部份。程式說明如下:
- parsing method 無疑是程式的重點,這裡將利用 Java 的反射 (reflect) 機制,簡潔的將訊息剖析及訊息轉換成物件合在一起,注意一下 Line 59,呼叫 parsing 時,第二個參數就傳入實際要轉換成的物件。
- Line 34: 透過這個方法,可以取得類別的相關訊息,要特別注意的是,這裡取得的不是物件 (object) 而是類別 (class)。
- Line 35: 取得類別中所有的欄位,不管它是 public、protected、private 或無修飾子。
- Line 37: 取得欄位上的 annotation 相關的訊息。
- Line 39: 以 annotation 設定的開始位置、長度,從訊息字串中取出子字串。
- Line 41: 依 Java 的習慣,POJO 的欄位會設定成 private,再透過 getter、setter method 來存取,我們依這樣的習慣寫這個程式,所以要取得 setter method 才能給欄位設定字串中取得的值。getMethod 一子第一個參數是 setter method 的名稱,第二個參數是傳給 setter method 的參數的類別種類。
- Line 42: 呼叫 setter method 將值設定給欄位。
- Line 43: 如果 Message 類別的欄位修飾子改成 public,我們也可以用這個方式給各個欄位值,這只是給各位參考,但不建議這麼做。
執行結果如下:
任期: 2008 ~ 2016
總統姓名: 馬英九
歷史評價: 一個自私的笨蛋、禍國殃民!
see also: Spring Batch: FixedLengthTokenizer
【日劇 - 倒數第二次戀愛】
以中年人為主角的日劇好像還不少,除了這一部戲外,天海祐希主演的「偽裝夫妻」,篠原涼子主演的「熟女正青春」也都蠻好看的。但是,要說選角嘛~ 「倒數第二次戀愛」選的小泉今日子 (飾演 吉野千明)、中井貴一 (飾演 長倉和平) 真的最讓人覺得完全沒得挑剔,劇中兩人的鬥嘴真的就是活生生的中年人,非常合拍。
除了主角生動的演出外,配角也相當不錯,三井 (久保田磨希 飾) 是吉野千明的得力助手,出現的時間固然不多,表現似乎也沒有什麼特別,但是她的存在總能突顯吉野的一些人格特質,是不可或缺的人物。
女主角吉野的兩個閏密長得如三井一樣不亮眼,可是這兩個閏密的存在很能顯示出吉野中年生活的空虛,而且那看似平淡無奇的演出,就是生活中三個歐巴桑聚在一起的樣子。
這部戲沒有什麼特別的高潮,從頭到尾卻完全不會讓人覺得無聊,蠻神奇的編劇、蠻神奇的演出,要說有什麼讓人不滿意的,雙胞胎兄妹長倉萬理子 (內田有紀 飾)、長倉真平 (坂口憲二 飾) 的角色設定比較讓人覺得怪,一方面是兩個角色怪異的個性和這平淡的劇情有點格格不入,另一方面是造型看起來有那麼一點不舒服 ...
沒有留言:
張貼留言