考虑下面的伪代码
项目A
class A
public static void main(String[] args){
println("hello from version" + Version.number)
}
class Version {
public static final long number = 1;
}
项目B
class B
public static void main(String[] args){
println("hello from version" + Version.number)
}
class Version {
public static final long number = 2;
}
我总共造了四个罐子。
Jar A:使用javac,包含Class A +版本 Jar B:使用javac,包含Class B +版本 Jar C:使用mvn,包含A +版本 Jar D:使用mvn,包含B +版本 然后,我将Jars A和B添加到classpath并执行以下命令:
A.main()
B.main()
产生以下结果(两种情况下请注意相同的版本)
hello from version 1
hello from version 1
我对mvn编译的jar(C和D)做了同样的操作,结果如下
hello from version 1
hello from version 2
看起来不错。
我假设类加载器发生了一些奇怪的事情,似乎对于javac编译的项目,它正在加载Version类遇到的第一个实例,并为class A使用相同的实例B。对于mvn编译的项目,这不会发生。
我读过他们不使用Java,但实际上javax.tools.JavaCompiler-是什么原因造成的?值得一提的是,我已经检查了用于解析Version类的类加载器,在所有情况下,它都是URLClassLoader
版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。
这个现象实际上与编译工具(javac vs. Maven)本身关系不大,而是更多地涉及到Java类加载机制和构建工件时的类路径处理方式。
当你直接使用 javac
编译项目A和项目B,并将这两个项目的输出(即包含各自Version
类的jar包A和B)添加到同一个类路径中时,Java运行时环境在加载类时遵循“先找到者优先”的原则。由于两个jar都包含了名为Version
的类,但类路径上没有明确区分它们,JVM会加载它遇到的第一个Version
类定义。因此,在执行A.main()
和B.main()
时,无论哪个类被先加载,之后的所有对Version
类的引用都会指向已加载的那个版本,导致你看到的都是"hello from version 1"。
Maven在构建过程中,如果两个模块(对应你的项目A和项目B)有相同的包名和类名(如Version
),默认情况下它们应该是在不同的命名空间下,因为通常每个Maven模块会被构建成独立的jar,且Maven鼓励良好的模块化实践,避免这种直接的类冲突。然而,如果你的确设法让Maven构建了两个包含不同版本Version
类的jar,并且在执行时能够区分它们(比如通过不同的类路径或使用shading来重命名内部类),那么Maven构建的jar可能是因为在执行时通过不同的类加载上下文或者特定的类路径配置,使得JVM能够识别并加载到不同的Version
类实例,从而产生了预期中的不同结果。
类加载器的工作原理是:当请求加载一个类时,它首先检查自己是否已经加载过该类,如果没有,它会委托给其父加载器去尝试加载;如果父加载器也未能加载,则当前加载器会尝试从其类路径中查找并加载该类。在这个过程中,如果类路径中有多个同名类,加载器的行为依赖于如何组织这些jar以及加载器的具体实现。
提到的javax.tools.JavaCompiler
是Java提供的一种API,允许程序以编程方式调用Java编译器进行源代码编译。这与你的问题直接关联不大,但它说明了即使不通过命令行直接使用javac,也可以在程序中动态编译Java源码。Maven等构建工具在后台可能也会利用类似的API来完成编译任务,但关键差异在于它们如何管理输出和解决依赖,而不是编译过程本身的不同。
总之,你观察到的现象主要是由类路径管理和类加载器的工作方式决定的,而非编译工具的直接差异。正确的做法是确保每个项目或模块的输出在类路径中是唯一可辨识的,避免类名冲突。