如何才能使用戶可以訪問您的應用程序數據,又不會將您的實體 bean 直接暴露給 Web 層,從而不會使您的應用程序面臨安全性威脅?Brett McLaughlin 提供了一個解決方案,它可使您的實體 bean 很安全,並且使您的整個應用程序有效率地運行。
Enterprise JavaBeans 技術一般分成三種核心類型的 bean:會話 bean、消息驅動 bean 和實體 bean。bean 還可以分成充當業務對象的 bean 和充當數據對象的 bean。會話 bean 和消息驅動 bean 是業務對象;實體 bean 是數據對象。大多數情況下,只需要將業務對象暴露給 Web 層(有時稱為應用層),因為業務對象可以使用數據對象來處理數據存儲。但在有些情況下,有必要允許用戶直接訪問和操作數據對象,這意味著要將實體 bean 暴露給 Web 層。這會使您的應用程序面臨安全性威脅,而且隨著您的應用程序的發展,還會導致雜亂無章的代碼。
在這篇EJB最佳實踐的文章中,我們將討論為什麼絕對不可以將實體 bean 暴露給應用程序的 Web 層,以及為什麼這是難以避免的。接著我將向您演示一個變通方法,它既能使您的實體 bean 更安全,又能使您的整個應用程序更有效率。
暴露的風險
實體bean揭示了大量有關數據庫底層結構的信息。因為每個bean都包含用於數據庫中的各個字段的取值(Accessor)和賦值(mutator)方法(即,getxxx() 和 setxxx()),而且因為方法名稱通常與字段名稱連在一起(User bean 中的 getFirstName() 通常訪問 Users表中的firstName字段),所以從應用程序的實體bean推斷這個應用程序的整個數據庫結構是可能的。盡管在單個應用程序中這可能不是什麼大問題,但您的bean常常會暴露給其它網絡上另外的應用程序。在 Web 服務系統中尤其會發生這種情況,其中應用程序都是跨多個網絡鏈接的。
暴露的 bean:一個工作示例
請考慮稱為MovIE的實體bean。該bean的方法是getTitle()、getDirector()和getActors()。因為您已暴露了該 bean 的遠程接口,所以您的應用程序和幾個客戶機應用程序都編寫了合並 getDirector() 方法的代碼。但是,隨著應用程序的發展,它應該能接受由多位導演指導的電影,這一點很清楚。數據庫更改了,您的 getDirector() 方法變成了 getDirectors()。現在該方法是復數形式並被編碼為返回一個列表,而不是單一對象。其結果是,所有使用舊方法的代碼都將破壞,從而不得不重寫您的應用程序代碼(以及您的客戶機應用程序代碼)。
除了存在使您數據庫結構暴露給未知應用程序的明顯危險之外,您還應該考慮當數據結構更改時會發生什麼。因為實體 bean 與您的數據結構緊密地結合在一起,所以隨著數據庫表中列的更改或新列的添加,實體 bean 常常要隨之更改。通過將您的實體 bean 暴露給應用層,可以使它們的方法名稱可用,而且這些方法名稱幾乎總是被您的應用程序和外部應用程序合並到新的應用程序代碼中。隨著您的實體 bean 的更改以及發展,就會出現嚴重的代碼混亂。
會話虛包模式
幸運的是,有一個設計模式讓您允許用戶訪問數據對象,同時又不會將實體 bean 暴露給 Web 層。會話虛包(Session Facade)模式在實體 bean 和應用程序客戶機之間放置了一個會話 bean。當我通過這種方法使用會話 bean 時,我喜歡把它當作管理器,因為它的任務是管理對其它實體的訪問。
會話bean旨在充當業務邏輯和應用邏輯之間的接口。在我們的工作示例的例子中,邏輯極其簡單,而且它只調出一個底層實體 bean。然而在實際情況中,您可能會選擇讓會話 bean 管理數據驗證、安全性或任何其它特定於業務的功能。通過將實體 bean 封裝在會話 bean 中,您可以訪問所有所需的業務功能,同時不會“污染”您的實體 bean 代碼。
為了解決工作示例中的問題,您可以創建稱為 MovieManager 的會話 bean。MovIEManager bean 將包含舊方法 getDirector() 和新方法 getDirectors()。當添加了新方法時,它只是被“代理”到您的實體 bean。因為第一個方法在實體 bean 中不再可用,所以會話 bean 使用助手方法來隱藏它,如清單 1 所示:
清單 1. 隱藏已不使用的方法
public Person getDirector() {
// We can't call getDirector() any more on the entity bean
// Call the new method, through this manager
List directors = getDirectors();
// Return the first one in the list
return (Person)directors.item(0);
}
清單 1 的適用性不是特別健壯,但它獲得了成功。因為隱藏了舊方法(而不是刪除),所以所有與它相關的應用程序代碼都一直存在。通過將助手方法放置在它所屬的業務對象中,您還將它排斥在實體 bean 之外。解決了這個緊急的問題後,您可以選擇將 bean 的客戶機手工移植到新方法,或讓助手方法一直處理移植工作。不管是哪種方法,都不會影響您的應用程序代碼和您的客戶機代碼。
隱藏數據結構
盡管上面的解決方案確實解決了更改管理的問題,但它不能消除安全性問題。您仍需要保護實體bean(並由此保護您的數據結構),以免暴露給Web層。通過向會話bean添加一些簡單的業務邏輯和數據操作功能,您不僅可以隱藏應用程序的數據結構,還可以提供對它所包含的信息的更復雜訪問。
例如,隨著應用程序的發展,您可能發現讓用戶依次訪問各種數據對象(例如,導演、制片人、演員)的效率很低。因為這類信息幾乎總是被放在一起,並一起使用,所以您可能發現重新確定會話 bean 的用途會很有幫助。您的會話 bean 將不包含 getDirector() 或 getDirectors() 方法,而是包含了如清單 2 所示的新的業務邏輯:
清單 2. 用會話 bean 掩蓋數據結構
public List getCrew(String movIEName)
throws NamingException, RemoteException {
List crew = new LinkedList();
EJBHomeFactory f = EJBHomeFactory.getInstance();
MovieHome movIEHome =
(MovIEHome)f.lookup("Java:comp/env/ejb/Movie", MovIEHome.class);
Movie movie = movieHome.findByName(movIEName);
crew.add(movIE.getDirectors());
crew.add(movIE.getProducers());
crew.add(movIE.getExecutiveProducers());
// and so on...
return crew;
}
public List getCast(String movIEName)
throws NamingException, RemoteException {
List cast = new LinkedList();
EJBHomeFactory f = EJBHomeFactory.getInstance();
MovieHome movIEHome =
(MovIEHome)f.lookup("Java:comp/env/ejb/Movie", MovIEHome.class);
Movie movie = movieHome.findByName(movIEName);
crew.add(movIE.getActors());
crew.add(movIE.getStandIns());
// and so on...
return cast;
}
通過分離不同的應用程序層,以及使用業務邏輯來處理數據操作,您既阻止了對實體 bean 直接而且可能不安全的訪問,又為您的 Web 層創建了更有意義的方法集。本例中,會話虛包充當了實體 bean 的封裝器以及真正的業務邏輯單元,從而將原始數據轉變成有意義的信息。
會話虛包模式是許多其它設計模式的基本構件,其優點遠遠不止這裡所討論的。在 EJB 最佳實踐的下一篇專欄文章中,我們將使用本系列的第一篇技巧文章中研究的業務接口(Business Interface)模式,以及您在這裡了解到的某些訣竅來進一步抽象出使用您應用程序中所有類型 EJB 組件的過程。
要避免的解決方案
解決所描述的這類問題的一個常見解決方案是簡單地將助手方法添加到實體bean本身。但這樣做的後果可能是災難性的。即使您實體bean中的每個方法每年只更改一次,但bean會在那年結束時將擁有雙倍數量的方法。當您考慮方法不只是每年更改一次,而是以頻繁得多的頻率更改時,為什麼這個“邦迪式(Band-Aid)”方法不起作用就很明顯了。當實體bean看上去不再象數據對象而開始象助手類時,您知道您已經有問題了。另一方面,會話 bean 常常看上去十分象助手類,因為那是會話 bean 旨在實現的功能之一。