在討論這個問題前,先簡單的介紹一下雙重解析器的工作原理:顧名思義,雙重解析是雙重的:它由一個ivyResolver和一個artifactResolver組成,其中ivyResolver負責解析ivy的模塊描述符,而artifactResolver則用于解析制品。換言之,ivyResolver用來指明需要什么,而artifactResolver則負責獲取具體的制品文件。
舉例說,下面的ivy配置:
<ivysettings>
<properties file="ivysettings-public.properties" />
<settings defaultResolver="public" />
<resolvers>
<dual name="public" descriptor="required">
<filesystem name="public-ivy">
<ivy pattern="${path.public.depository.dir}/[organisation]/[module]/ivy-[revision].xml" />
</filesystem>
<ibiblio name="public-ibiblio" m2compatible="true"
root="http://192.168.0.30:8081/nexus/content/groups/public/"
pattern="[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]"
usepoms="false" >
</ibiblio>
</dual>
</resolvers>
</ivysettings>
其中的public-ivy作為ivyResolver,指向本地文件系統,這里有我們需要使用的依賴的模塊描述符。public-ibiblio作為artifactResolver,實際是指向一個基于nexus的本地maven 倉庫(maven術語叫做企業倉庫,類似私服的概念,proxy + cache),所有的制品比如最常見的jar包都是從這里獲取的。
個人感覺ivy設計使用dual resolver的初衷是:通過控制ivyResolver的內容來達到控制整個系統中所有第三方依賴,即只使用ivyResolver中提供好的依賴(包括版本)。比如說只使用spring2.5.6版本,不容許不同開發人員和不同的project使用spring的其他版本,再比如都使用easymock,不容許個別人因為個人喜好而是用jmock......同時有利于日后的版本維護和版本升級,方便解決版本沖突。
開發過大型java系統的朋友應該都會遇到上面的這些問題,比如我曾經參與的產品,有超過200個的java project,無數依賴,管理起來非常痛苦。
第一次在學習ivy的過程中看到ivy中的雙重解析器,就感覺設計非常的不錯,可以比較好的解決這方面的問題。只要維護好ivyResolver中的依賴,則整個系統中的依賴都被限制在這個范圍中。比如如果有人想用spring2.5.6之外的版本,哼哼,ivyResolver解析器會不工作的......
但是,在實際的使用過程中發現,雙重解析器的工作模式有點問題:如果目標依賴在ivyResolver中可以找到則情況正常,但是如果目標依賴在ivyResolver中沒有定義,ivy居然會在artifactResolver的繼續查找!然后報告說依賴解析成功已下載云云,而不是我期望的無法解析,暈倒。如果是這樣的話完全達不到預期的目標嘛。
[ivy:resolve] :: resolving dependencies :: net.runafter.skymaildemo#Pop3Resource;working@EV001A4B84D85E
[ivy:resolve] confs: [default, master, compile, runtime, test]
[ivy:resolve] found commons-lang#commons-lang;2.4 in public-ibiblio
[ivy:resolve] :: resolution report :: resolve 218ms :: artifacts dl 0ms
郁悶中開始翻dual resolver的文檔,結果一無所獲,基本沒有任何相關的說明,參考文檔中dual resolver根本沒有任何屬性可以用來設置上述約束。上google大法,無果,暈,我到底要怎么樣做才能讓這中解析失敗呢?
中午吃了個飯回來,恩,中間因為想這個事情走神被同事誤解為感冒了身體不好......想想還有最后一招,反正手頭有源代碼,萬一真不行我改改總可以吧?打開org.apache.ivy.plugins.resolver.DualResolver,意外發現有這么一段:
public ResolvedModuleRevision getDependency(DependencyDescriptor dd, ResolveData data)
throws ParseException {
if (ivyResolver == null || artifactResolver == null) {
throw new IllegalStateException(
"exactly two resolvers must be added: ivy(1) and artifact(2) one");
}
ResolvedModuleRevision resolved = data.getCurrentResolvedModuleRevision();
data = new ResolveData(data, doValidate(data));
final ResolvedModuleRevision mr = ivyResolver.getDependency(dd, data);
if (mr == null) {
checkInterrupted();
if (isAllownomd()) {
Message.verbose("ivy resolver didn't find " + dd
+ ": trying with artifact resolver");
return artifactResolver.getDependency(dd, data);
} else {
return null;
}
} else {
if (mr == resolved) {
// nothing has actually been resolved here, we don't need to touch the returned rmr
return mr;
}
return new ResolvedModuleRevision(
mr.getResolver(), this, mr.getDescriptor(), mr.getReport(), mr.isForce());
}
}
OK,就是這個了,allownomd屬性看來就是用來干這個的,omd應該是optional module descriptor。如果容許可選的模塊描述符,則ivy會在ivyResolver查找不到的情況下使用artifactResolver來繼續解析。這個屬性的相關設置代碼如下:
public static final String DESCRIPTOR_OPTIONAL = "optional";
public static final String DESCRIPTOR_REQUIRED = "required";
private boolean allownomd = true;
public boolean isAllownomd() {
return allownomd;
}
public void setAllownomd(boolean allownomd) {
Message.deprecated(
"allownomd is deprecated, please use descriptor=\""
+ (allownomd ? DESCRIPTOR_OPTIONAL : DESCRIPTOR_REQUIRED) + "\" instead");
this.allownomd = allownomd;
}
/**
* Sets the module descriptor presence rule.
* Should be one of {@link #DESCRIPTOR_REQUIRED} or {@link #DESCRIPTOR_OPTIONAL}.
*
* @param descriptorRule the descriptor rule to use with this resolver.
*/
public void setDescriptor(String descriptorRule) {
if (DESCRIPTOR_REQUIRED.equals(descriptorRule)) {
allownomd = false;
} else if (DESCRIPTOR_OPTIONAL.equals(descriptorRule)) {
allownomd = true;
} else {
throw new IllegalArgumentException(
"unknown descriptor rule '" + descriptorRule
+ "'. Allowed rules are: "
+ Arrays.asList(new String[] {DESCRIPTOR_REQUIRED, DESCRIPTOR_OPTIONAL}));
}
}
試著在ivy配置文件的dual上加上descriptor屬性
<dual name="public" descriptor="required">
再試就ok了:
[ivy:resolve]
[ivy:resolve] :: problems summary ::
[ivy:resolve] :::: WARNINGS
[ivy:resolve] module not found: commons-lang#commons-lang;2.4
[ivy:resolve] ==== public-ivy: tried
[ivy:resolve] C:/aoxj/workspace/peasonal/study/java/tools/ivy/skyMailDemo/skyMail/ivy/depositories/public/commons-lang/commons-lang/ivy-2.4.xml
[ivy:resolve] ==== public-ibiblio: tried
[ivy:resolve] ::::::::::::::::::::::::::::::::::::::::::::::
[ivy:resolve] :: UNRESOLVED DEPENDENCIES ::
[ivy:resolve] ::::::::::::::::::::::::::::::::::::::::::::::
[ivy:resolve] :: commons-lang#commons-lang;2.4: not found
[ivy:resolve] ::::::::::::::::::::::::::::::::::::::::::::::
[ivy:resolve]
[ivy:resolve]
[ivy:resolve] :: USE VERBOSE OR DEBUG MESSAGE LEVEL FOR MORE DETAILS
問題解決,雙重解析器現在和預期的一樣工作。
小小的抱怨一下,ivy的文檔雖然在開源項目中已經算很不錯了,但是在這些小的細節上還是有疏漏,比如這里的descriptor屬性,如果沒有它雙重解析器基本上是無法達到設計目標的。看來多翻翻源代碼是有必要的^0^