在讨论这个问题前,先简单的介绍一下双重解析器的工作原理:顾名思义,双重解析是双重的:它由一个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^