Java Web 系統(tǒng)總結(jié)之二 值對象

各種值對象辨析

貧血或失血模型的 Java Web 系統(tǒng), 數(shù)據(jù)與操作是彼此分離的. 操作主要體現(xiàn)在服務(wù)層的接口方法以及實現(xiàn), 而所謂數(shù)據(jù)基本上就是常見的各種 O 了. 如:

  • BO Business Object
  • PO Persistent Object
  • VO View Object
  • DTO Data Transfer Object
  • POJO Plan Old Java Object

BO 即業(yè)務(wù)對象, 也稱為 Model, 即數(shù)據(jù)模型. PO 為持久化對象, 即 Entity 實體, 是模型在數(shù)據(jù)訪問層的化身, 用于存取關(guān)系或非關(guān)系數(shù)據(jù)庫. VO 是模型在控制層的化身, 通常用于向前端接口或圖形界面?zhèn)鬟f數(shù)據(jù). DTO 是模型在數(shù)據(jù)傳遞時的化身, 主要用于數(shù)據(jù)在不同服務(wù)間的轉(zhuǎn)換和傳遞. POJO 即普通 Java 對象, 原來是針對 J2EE 規(guī)范中各種 Bean, 如 EntityBean, Session Bean 等作區(qū)分, 用來指代非 Bean 的簡單對象. 后來 J2EE 沒落, 在很多文章中也就用來泛指這些值對象了. 除了上述帶 O 的, 還有一些不帶 O 的值對象變種, 例如 xxxQuery, 可能就是加了分頁信息的 VO; 又如 xxxResult, 也許即是封了調(diào)用結(jié)果的 DTO 等等. 凡此種種, 不一而足.

為什么不能只使用 BO, 而非要在不同的層使用不同的化身呢? 一, 是因為雖然大致類似, 但不同的 O 關(guān)注點不同, 其中屬性多少會有些區(qū)別. 數(shù)據(jù)庫中保存的, 控制層可能不需要展示; 控制層展示的, 業(yè)務(wù)邏輯層計算可能又用不到. 二, 是為了架構(gòu)清晰, 便于各層獨立封裝. 如單獨提取控制層, 可以做到不依賴底層 PO, 兩層可獨立變化. 所以開發(fā)時要注意不要一個 Entity 一路從前用到后, 貫穿所有分層. 同理, 盡管屬性相似或雷同, VO 也不應(yīng)繼承 PO, 而是應(yīng)該彼此分開. 實際開發(fā)中, 根據(jù)項目具體需要, 各種值對象一般也不會全部使用, 不過 PO 和 VO 基本上是必不可少的.

值對象比較簡單, 算是基礎(chǔ)中的基礎(chǔ), 會寫 Java 的應(yīng)該沒有人不會寫值對象, 然而要寫得規(guī)范統(tǒng)一則需要自覺和約束. 比如控制層代碼都是 VO, 就別突然冒出個什么 Query 來; 還有 dto 的包名下, 就別突然有弄了個 BO 出來. 這種鶴立雞群, 駝立羊群指明了代碼是未做修改就從別的系統(tǒng)粘了過來, 顯得十分不專業(yè). 關(guān)于值對象的規(guī)范, 阿里巴巴的 Java 開發(fā)手冊寫得不錯. 如果所在團(tuán)隊有約定規(guī)范, 服從團(tuán)隊規(guī)范; 若有些部分團(tuán)隊沒有明確規(guī)約, 則可以參考阿里的 Java 開發(fā)手冊. 具體但不限于: 值對象類命名的大小寫, 屬性名的大小寫, Boolean 類型的字段的命名不要加 "is", 屬性類型推薦使用包裝類型等等, 在此不再贅述. 阿里的 Java 開發(fā)手冊也提供了 IDEA 插件, 可以在開發(fā)中對不符合規(guī)范的代碼進(jìn)行提示. 如有條件, 也可接入 Sonar, 配置規(guī)則對代碼進(jìn)行靜態(tài)掃描, 強制執(zhí)行規(guī)范.

實際工程中, 一般都會使用 ORM 框架, 那么 PO 可通過 mybatis-generator 或是 hibernate-tools 逆向工程, 根據(jù)數(shù)據(jù)庫表直接生成. 如果有維護(hù)良好的代碼生成器, 各層值對象均可通過數(shù)據(jù)庫或填寫字段自動生成.

值對象方法

值對象的方法比較簡單, 無外就是 getter, setter, toString, hashCodeequals. 其中 gettersetter 的建議只作存取, 不要加特殊邏輯, 否則很容易自尋煩惱. 同時也要注意方法名與屬性名保持一致, 大部分框架都是基于此存取屬性值的. 有些找不到對應(yīng)屬性直接報錯還好, 如果會忽略, 則這種不一致會導(dǎo)致錯誤發(fā)現(xiàn)時間后延, 從而造成不必要的損失. toString, hashCodeequals 源自 Object, 可根據(jù)實際需要決定是否需要覆寫. 如果覆寫, 那么就別忘了一個面試中問濫了的問題: equalshashCode 為什么要一起覆寫? 具體而言, 這些方法要怎么來寫, 總結(jié)起來, 大致有五種方式:

  1. 代碼生成器
    一般使用代碼生成器生成的值對象類, 會同時附帶這些方法.

  2. IDE
    現(xiàn)代開發(fā)工具都會提供相關(guān)功能, 可以根據(jù)不同選項快速生成上述方法.

  3. Apache Commons Lang
    Apache Commons Lang 提供的一系列 Builder 工具, 常用的如 EqualsBuilder, HashCodeBuilder, ToStringBuilder, CompareToBuilder 用于輔助實現(xiàn)一些常用方法. 其原理是利用反射獲得類的屬性, 從而進(jìn)行比較, 拼接或計算.

  4. lombok
    使用 lombok 提供的注解: @Getter, @Setter, @ToString, @EqualsAndHashCode@Data 來標(biāo)注值對象. lombok 的原理是在編譯時修改字節(jié)碼, 故只能在 class 文件中看到這些生成的方法, java 的源碼中則眼不見心不煩, 顯得比較整潔. 也是因為如此, lombok 對泛型的支持不是很好, 而且 IDE 也需要安裝插件才能不提示編譯錯誤.

  5. 手寫
    手工編寫這些方法的代碼.

上述五種方式中, 個人推薦用第 5 種, 手工編寫的方式, 雖然費時而且還容易出錯, 但這能讓你在 Boss 面前顯得很忙碌, 工作量很飽和.

在實際工程中, 如果代碼生成器及模板如果良好可用, 則最好使用代碼生成器, 省時省力, 還可以與部門其它項目保持一致. 如無代碼生成器或模板疏于維護(hù), 個人傾向于使用 lombok, 一是步驟少打字少, 提高效率; 二是因為字節(jié)碼技術(shù), 所以源碼看上去清爽干凈. 如果直接使用 @Data 需要注意, 值對象若有繼承關(guān)系, @ToString@EqualsAndHashCode 注解的 callSuper 屬性需改為 true, 才會使非 Object 的父類屬性參與到 toString, hashCodeequals 的生成之中, 該屬性默認(rèn)為 false.

值對象轉(zhuǎn)換

除了內(nèi)部方法, 值對象之間的主要方法就是來回來去互相轉(zhuǎn)換了. 這些轉(zhuǎn)換總結(jié)下來, 大致也有五種方式:

  1. Apache BeanUtils / PropertyUtils
    Apache 提供的類轉(zhuǎn)換類, BeanUtils 在對 Bean 賦值時會進(jìn)行類型轉(zhuǎn)化, 而 PropertyUtils 不會.

  2. Spring BeanUtils
    Spring 提供的類轉(zhuǎn)換類, 與 Apache 提供的工具類同名, 但是實際使用和特性均有不同.

  3. BeanCopier
    由 cglib 提供的工具類, 由于采用字節(jié)碼技術(shù), 轉(zhuǎn)換效率比前兩種基于反射的 BeanUtils 都要高.

  4. dozer
    開源工具包, 如用在 spring 項目中, 需要整合. dozer 比較靈活, 可以配置 xml 文件, 為值對象中的屬性轉(zhuǎn)化添加自己的邏輯.

  5. 手寫
    手工編寫代碼轉(zhuǎn)換.

之前說 settergetter 手寫只是笑談, 現(xiàn)實中真沒見過有誰手敲的, 不過值對象轉(zhuǎn)換手寫的就普遍的多了, 而且不是不同名不同類型的屬性手敲, 而是從頭敲到尾, 大概是要這樣才能夠獲得一種安全感吧. 手寫轉(zhuǎn)換的相比反射轉(zhuǎn)換的唯一優(yōu)勢, 可能就在于執(zhí)行效率高了. 然而從系統(tǒng)整體運行考慮, 相對于數(shù)據(jù)庫和網(wǎng)絡(luò)傳輸, 這一點執(zhí)行效率幾乎可以忽略不計, 更何況如果真在乎這點效率, 還有基于字節(jié)碼的 BeanCopier 呢.

基于工具的轉(zhuǎn)換, 盡管方便, 但是也有問題需要注意. 比如同名不同類型屬性的轉(zhuǎn)換, settergetter 是否匹配, DateBigDecimal 等特殊類型以及 null 值的處理等等. Apache 的工具類在這些工具中是轉(zhuǎn)換效率最低的, 對于日期類型的處理很容易出錯, 而且還需要處理異常, 故此實際項目中很少使用. 不過因為與 spring 的工具同名, 代碼中經(jīng)郴呵海看到不做區(qū)分, 亂用一氣的, 估計是引包自動提示先引到哪個就用了哪個. 這兩個工具類參數(shù) source 和 target 位置相反, 混淆不分比較容易出現(xiàn)復(fù)制方向搞反的低級錯誤. 不論使用哪一個, 出現(xiàn)對象屬性時都要警覺, 因為其拷貝均為淺拷貝. BeanCopier 是基于字節(jié)碼技術(shù), 生成動態(tài)代理來進(jìn)行對象裝換, 因此效率比基于反射的 BeanUtils 要高不少. 不過代碼中也逞烟玻看到不做封裝, 每轉(zhuǎn)換一次都新生成一個代理類的, 也不知道是不是為了日后優(yōu)化故意留的一個口子. dozer 只是了解, 實際生產(chǎn)中還沒有使用過. 其配置最為靈活, 可通過 xml, 添加特定的映射轉(zhuǎn)換規(guī)則. 個人認(rèn)為, 對于屬性差異性很大的對象來說轉(zhuǎn)換方式統(tǒng)一便捷, 但是對于值對象這種大部分屬性名稱和類型均相同, 只有小部分差異的對象, 使用 dozer 以及其它可擴展的 converter 有些過重, 反而增加了代碼復(fù)雜度.

因此, 實際項目當(dāng)中最普及的用法, 還是在值對象屬性的命名和類型做到盡量統(tǒng)一的前提下, 先使用 spring 的 BeanUtils 拷貝一致的屬性, 再手工寫上幾行代碼, 轉(zhuǎn)換不一致的屬性.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末伞梯,一起剝皮案震驚了整個濱河市衣式,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌渤愁,老刑警劉巖牵祟,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異抖格,居然都是意外死亡诺苹,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門雹拄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來收奔,“玉大人,你說我怎么就攤上這事滓玖∑汉澹” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵势篡,是天一觀的道長翩肌。 經(jīng)常有香客問我,道長禁悠,這世上最難降的妖魔是什么念祭? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮碍侦,結(jié)果婚禮上粱坤,老公的妹妹穿的比我還像新娘隶糕。我一直安慰自己,他們只是感情好站玄,可當(dāng)我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布若厚。 她就那樣靜靜地躺著,像睡著了一般蜒什。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上疤估,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天灾常,我揣著相機與錄音,去河邊找鬼铃拇。 笑死钞瀑,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的慷荔。 我是一名探鬼主播雕什,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼显晶!你這毒婦竟也來了贷岸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤磷雇,失蹤者是張志新(化名)和其女友劉穎偿警,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體唯笙,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡螟蒸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了崩掘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片七嫌。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖苞慢,靈堂內(nèi)的尸體忽然破棺而出诵原,到底是詐尸還是另有隱情,我是刑警寧澤枉疼,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布皮假,位于F島的核電站,受9級特大地震影響骂维,放射性物質(zhì)發(fā)生泄漏惹资。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一航闺、第九天 我趴在偏房一處隱蔽的房頂上張望褪测。 院中可真熱鬧猴誊,春花似錦、人聲如沸侮措。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽分扎。三九已至澄成,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間畏吓,已是汗流浹背墨状。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留菲饼,地道東北人肾砂。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像宏悦,于是被迫代替她去往敵國和親镐确。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理饼煞,服務(wù)發(fā)現(xiàn)源葫,斷路器,智...
    卡卡羅2017閱讀 134,672評論 18 139
  • 國家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 10,990評論 6 13
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法砖瞧,類相關(guān)的語法臼氨,內(nèi)部類的語法,繼承相關(guān)的語法芭届,異常的語法储矩,線程的語...
    子非魚_t_閱讀 31,644評論 18 399
  • web組件 演示分支:gh-pages 代碼發(fā)布分支:master 項目演示(DEMO)地址:http://www...
    深沉的簡單閱讀 318評論 0 0
  • 見過了皇家寺廟南無世的輝煌逃片,看過了色達(dá)佛學(xué)院的震感屡拨,還是最喜歡這個小小的達(dá)倫寺的斑駁滄桑,一條幽暗的轉(zhuǎn)經(jīng)通道歷史的...
    小小七的一切閱讀 341評論 0 1