我是一名初学Java的开发人员,想了解Java中库是如何工作的。
外部库和内部库是静态链接还是动态链接?
我问这个问题是因为我见过一些项目有依赖项,但只构建了一个.jar文件作为输出。这意味着依赖项是静态加载的,但我读到了一些相互矛盾的信息,这些信息指出外部库是由JVM使用名为“ClassPath”的东西加载的。
我用maven shade插件编译了一个小的基本程序,得到了一个原始版本和一个阴影版本。我的理解是,阴影版本是一个所有依赖项都包含在文件本身中的版本,所以它应该是唯一一个可以独立运行的版本。原始版本应该需要依赖项在其预期的位置才能运行,但它在任何地方都运行得很好。我已经研究了几个小时了,还没有发现任何关于这个问题的东西,如果我能得到一个清晰的解释,说明在构建项目时库是如何链接的,以及JVM是如何处理这些依赖关系的,我会欣喜若狂的。
1条答案
按热度按时间brgchamk1#
“链接”不是java生态系统使用的术语,无论如何,java中的所有内容都是动态链接的。
但是,我有一个jar,它运行得很好!
当然;它仍然是动态链接的,只是需要加载的所有内容都在那个jar文件中。
那么它是如何工作的呢?
一个类是由ClassLoader加载的,你可以编写自己的类。他们可以从任何地方获取它,包括“动态生成”,“实时下载”,或者“从数据库获取”。
标准类加载器(因为
java
本身需要加载你的主类,它不能使用一些自定义类加载器,因为我们如何加载那个类加载器呢?鸡和蛋的问题)是大多数应用程序使用的一个,而没有引入自己的类加载器。标准的加载器知道java自己的东西在哪里。在现代的JVM上,它直接在java安装中的所有jmod文件中。Java只知道它们在哪里,你不需要把这些目录放在你的路径中,把它们传递给java可执行文件,或者担心它们。
剩下的就是你自己的类了。标准系统启动时会有一个“类路径”,它由以下三个部分之一组成:
*
结尾的路径(不要让bash替换它,所以在linux上用单引号引用它),前面有一个目录,例如/com/foo/libs/*
-这将导致java扩展到包括classpath上该目录中的每个jar文件(但不是递归的-不是子目录)。Java不对这些做任何事情,也根本不会在启动时加载一堆类。
相反,它会尝试运行在bootclasspath中找不到的主类,所以我们需要加载一个类(只有一个类)。这是通过重写完全限定的类名来完成的,例如
com.foo.MyApp
为路径形式(/com/foo/MyApp.class
)。然后,它会在每个类路径中查找该资源。对于文件路径,它会被追加,对于jar,该资源会在jar中查找。第一个命中的类被加载。运行这个类通常需要加载更多的类,这些类都是以同样的方式解析的。
...那么这个类路径是在哪里定义的呢?
这取决于你如何加载java。有4种方法可以运行
java.exe
:java com.foo.Bar
。这意味着使用了环境变量CLASSPATH
。-cp
参数:java -cp 'libs/*':myapp.jar com.foo.Bar
-好吧,你传递了类路径,这就是所使用的。-jar
参数:java -jar myapp.jar
-那么**-cp
和CLASSPATH将被完全忽略!-而不是打开jar中的文件META-INF/MANIFEST.MF
,读取其中的键/值对Class-Path
。(如果不存在,则作为空字符串)获取传递给它的jar,这就是类路径。Class-Path
条目是空格分隔的,并且相对于你正在运行的jar所在的目录,所以当你java -jar
它时,你在哪个工作目录并不重要。如何部署jar?
java生态系统倾向于阴影化。他们都错了。阴影化很少是正确的。它很慢,需要传输的数据比需要的多得多。
相反,添加
Class-Path
(所有构建工具都可以这样做)。例如,有:并且在清单中有
Class-Path: libs/lib1.jar libs/lib2.jar
(这也是所有构建系统都可以为您做的事情)。然后,该jar将“正常工作”。即使您在/home/foo
中运行java -jar /myapp/myapp.jar
。只有您有新版本要部署时才替换lib
jar,否则不需要替换它们。当部署到你无法控制的系统时,着色是很好的,除了,这也不起作用:你不能/不应该要求最终用户安装java,你必须用一个安装程序来安装,比如jlink + jpackage。JRE作为一个概念已经死了十年了。