這會為包括OSGi和JPMS的模塊化系統(tǒng)帶來問題。理想情況下,domain或bean類應該隱藏在一個模塊內(nèi)部:如果它被導出,就會成為公共API,這樣會對依賴于它的消費者造成破壞,但是我們希望能夠靈活地隨意改變我們的內(nèi)部類。另一方面,如所述的,通過反射訪問非導出類型支持框架是非常有效的。
由于OSGi的類加載器是基于設(shè)計的,模塊可以獲得其他模塊非導出包和類型的可見性——只要他們知道該類型的全限定名以及知道是哪個模塊發(fā)出的請求(請記住幾個模塊可以包含任何給定的類型名稱)。Java長期使用反射的精神有效地減少了隔離,在這里甚至所謂的私有字段都可以通過調(diào)用setAccessible方法被公開。
在OSGi中使用此功能是常見的做法,用來提供根本沒有導出模塊的實現(xiàn)!相反,它們可能包含引用內(nèi)部類型的聲明,這些內(nèi)部類型可以通過框架加載。例如,使用JPA做持久化的模塊可以引用persistence.xml文件的domain類型,并且在需要時JPA實現(xiàn)模塊將會加載引用類型。
最大的用例是實施服務組件。OSGi規(guī)范包含一章節(jié)叫聲明式服務(DS),定義了一個模塊如何聲明組件:類的生命周期是由框架管理的。組件可以綁定到OSGi服務注冊表中的服務,并且可以自選地為自己提供服務。例如:
@Component
public class CartMgrComponent implements CartManager {
@Reference
UserAdmin users;
@Override
public Cart getOrCreateCart(String user) {
// ...
}
}
在這個例子中,CartMgrComponent是一個提供CartManager服務的組件。它引用了一個服務——UserAdmin,類的生命周期由DS框架管理。當UserAdmin服務可用,CartMgrComponent就會被創(chuàng)建,并且它會發(fā)布CartManager服務,該服務同樣可以在其他模塊的其他組件中引用。
這個框架可以工作是因為它加載了CartMgrComponent類,該類已經(jīng)被@Component注解標記為組件。定義組件和服務是OSGi應用設(shè)計和編寫的主要方式。
在JPMS,只有導出包的類型可以被訪問,即使是反射。雖然在非導出包中類型是可見的(你可以調(diào)用Class.forName獲取一個類對象),但在模塊外它們是不可訪問的。當一個框架試圖調(diào)用newInstance實例化一個對象,會拋出IllegalAccessException異常。這似乎切斷了框架的許多可能性,但是也有一些解決方法。
一種方法是提供個別類型作為服務,可以通過java.util.serviceloader加載。自Java 6開始serviceloader就是標準平臺的一部分,在Java 9它已被更新支持跨模塊工作。serviceloader可以訪問非導出包的類型,只要提供包含provides聲明的模塊。不幸的是,serviceloader是古老的,不能為現(xiàn)代化框架,如DS或spring,提供所需的靈活性。
第二種可能是使用“qualified”導出包。這種導出,只允許指定模塊訪問,而不是所有模塊都通用。例如,你可以導出bean包到Spring Framework模塊。但是這可能無法用于其他方面像JPA,因為JPA是一個規(guī)范而不是一個指定的模塊,并且它可以由不同的模塊實現(xiàn),如Hibernate、EclipseLink等。
第三種可能是“dynamic”導出,這種包任何人都可以訪問,但只能自己使用反射,而且不是在編譯時。這是JPMS一個非常新的特性,在郵件列表上它仍然是有爭議的。它最接近OSGi的permissive方法,但它仍然需要模塊作者為某些包明確添加dynamic導出,這些包中可能包含需要反射地加載的類型。作為一個OSGi用戶感覺它是不必要的復雜性。