英文:
Library does not find its own "sublibrary"
问题
我正试图在Java中创建一个地址列表,将其内容保存在一个Sqlite数据库中。
为此(以及为了将来的其他用途),我尝试创建了一个用于各种数据库连接的自己的库("PentagonsDatabaseConnector-1.0.jar")。它目前支持Sqlite和MySql。
它引用了其他库,以便它们提供JDBC驱动程序("mysql-connector-java-8.0.16.jar"和"sqlite-jdbc-3.30.1.jar")。
问题:如果我从自己的项目文件夹访问它,我的库运行得很好,但是一旦我编译它并将其添加到"Adressliste"项目中,它就无法再找到JDBC驱动程序了(尽管我仍然可以访问我自己编写的库的其余部分)。另外,正如屏幕截图所示,"PentagonsDatabaseConnector-1.0.jar"会在"lib"文件夹中带上JDBC库。
你们有什么想法是什么出了问题吗?
谢谢你们的帮助!
附言:抱歉我的英语不太好,我是德国人
英文:
I'm trying to make an addresslist in Java, which saves its contents in a Sqlite database.
Therefor (and for other future uses), I tried to create my own library for all kinds of database connections ("PentagonsDatabaseConnector-1.0.jar"). It currently supports Sqlite and MySql.
It references other libraries for them to provide the JDBC-drivers ("mysql-connector-java-8.0.16.jar" and "sqlite-jdbc-3.30.1.jar").
Problem: My Library works just fine if I'm accessing it from its own project folder, but as soon as I compile it and add it to the "Adressliste"-project, it isn't able to find the JDBC-drivers anymore (I can access the rest of my self-written library without problems though). Also, as shown in the screenshot, "PentagonsDatabaseConnector-1.0.jar" brings the JDBC-libraries with itself in "lib"-folder.
Do you guys have an idea whats wrong?
Thank you for your help!
Ps: Sorry for bad English, I'm German
答案1
得分: 1
Java无法读取嵌套的jar文件。
依赖项有几种不同的类型。在这种情况下,PentagonsDC是一个普通的依赖项;它在运行时必须存在,也必须在编译时存在。
JDBC库有点特殊;它们只是运行时依赖关系。你不需要在编译时保留它们。这是有道理的,因为JDBC库在概念上是可插拔的。
那么,我该怎么做呢?
使用构建系统来管理你的依赖关系是超过90%的Java程序员会选择的答案,也是我建议你在这里做的。特别是对于刚开始的人,我建议使用Maven。在这里,你只需要在一个文本文件中写入你的依赖项名称,Maven会在编译时自动处理。
对于运行时方面,你有几个选项,这取决于你的Java应用程序是如何运行的。
以下是一些示例:
基于清单的类路径
你将你的Java应用程序“独立”运行,就像你编写了启动应用程序的psv main(String[])
方法,然后将其分发到需要运行的地方。在这种情况下,通常的策略是使用安装程序(你需要在客户端上运行你的应用程序,而Oracle和任何操作系统供应商都不再支持在最终用户系统上维护一个正常运行的JVM;现在这是你的工作——但很不幸,这并不是一件简单的事情)。鉴于你已经有了这个安装程序,你应该部署你的jar文件,使它们在清单中包含(jar文件实际上是zip文件,清单位于META-INF/MANIFEST.MF):
Main-Class: com.of.yourproj.Main
Class-Path: lib/sqlite-jdbc.jar lib/mysql-jdbc.jar lib/guava.jar
然后按以下目录结构组织:
C:\Program Files\yourapp\yourapp.jar
C:\Program Files\yourapp\lib\sqlite-jdbc.jar
C:\Program Files\yourapp\lib\mysql-jdbc.jar
或者在其他操作系统上相应的目录结构。清单中的类路径条目以空格分隔,并且相对于包含'yourapp.jar'的目录进行解析。以这种方式完成,你可以从任何位置运行yourapp.jar,它以及在Class-Path中列出的所有条目都对它可用。
构建工具可以为你创建这个清单。
Shading / Uberjars
Shading是将所有内容打包到一个巨大的单一jar文件中的概念;不是将jar包放入jar包中,而是将依赖jar包的内容解压缩到主应用程序jar包中。这在构建过程中可能会相当慢(如果你有几百MB的依赖项,这些依赖项需要打包,并且所有类文件需要进行阴影重写的分析,这是一项耗时的工作)。Shading的一般思想是部署“只需传输一个jar文件”,但实际上并不实际,因为你不能再假设最终用户已经安装了JVM,即使他们安装了,你也不能保证它是适当的最新版本。我在这里提到它,因为你可能会从其他人那里听到这个,但我不建议这样做。
如果你真的想使用这种方法,唯一的选择是构建系统:它们有一个插件可以做到这一点;Java本身没有附带任何命令行工具可以执行此操作。还有关于所谓的“签名jar”的注意事项,不能将其解压缩到单个uberjar中。
应用程序容器
并非所有的Java应用程序都是独立的,你提供主入口点。例如,如果你正在编写一个Web服务,你根本没有主入口点;框架有。对于每个URL,你希望做出响应,Web服务都有大量的入口点。框架会负责调用它们,通常这些框架都有自己的文档和规范,用于加载依赖项。通常情况下,你只需要将一个jar文件放在一个地方,将其依赖项放在一个名为'lib'的子目录中,或者构建一个所谓的war文件,但是实际上,有很多Web框架以及它们如何执行这一操作的选项。好消息是,通常很简单,框架的教程会涵盖这一点。
这些建议适用于任何“应用程序容器”系统;通常这些是Web框架,但也有一些非与Web相关的框架可以启动你的应用程序。
不要这样做
不要强迫用户手动提供-classpath
选项或者修改CLASSPATH
环境变量。
不要尝试编写一个加载嵌套jar的自定义类加载器。
注意:对于Java来说,Sqlite2相对复杂;它并不能为你带来“轻量级”的许多好处,因为它是一个本地依赖项。在Java领域中,一个简单的、无处不在的解决方案是“h2”,它完全由Java编写,因此可以将整个h2引擎作为Java应用程序的一部分进行分发,而不需要任何本地组
英文:
Java cannot read jars-in-jars.
Dependencies come in a few flavours. In this case, PentagonsDC is a normal dependency; it must be there at runtime, and also be there at compile time.
The JDBC libraries are a bit special; they are runtime-only deps. You don't need them to be around at compile time. You want this, because JDBC libraries are, as a concept, pluggable.
Okay, so what do I do?
Use a build system to manage your dependencies is the answer 90%+ of java programmers go to, and what I recommend you do here. Particularly for someone starting out, I advise Maven. Here you'd just put in a text file the names of your dependencies and maven just takes care of it, at least at compile time.
For the runtime aspect, you have a few options. It depends on how your java app runs.
Some examples:
Manifest-based classpaths
You run your java application 'stand alone', as in, you wrote the psv main(String[])
method that starts the app and you distribute it everywhere it needs to run. In this case, the usual strategy is to have an installer (you need a JVM on the client to run your application and neither oracle nor any OS vendor supports maintaining a functioning JVM on end-user's systems anymore; it is now your job – this is unfortunately non-trivial), and given that you have that, you should deploy your jars such that they contain in the manifest (jars are zips, the manifest ends up at META-INF/MANIFEST.MF):
Main-Class: com.of.yourproj.Main
Class-Path: lib/sqlite-jdbc.jar lib/mysql-jdbc.jar lib/guava.jar
And then have a directory stucture like so:
C:\Program Files\yourapp\yourapp.jar
C:\Program Files\yourapp\lib\sqlite-jdbc.jar
C:\Program Files\yourapp\lib\mysql-jdbc.jar
Or the equivalent on any other OS. The classpath entries in the manifest are space separated and resolved relative to the dir that 'yourapp.jar' is in. Done this way, you can run yourapp.jar from anywhere and it along with all entries listed in Class-Path are now available to it.
Build tools can make this manifest for you.
Shading / Uberjars
Shading is the notion of packing everything into a single giant jar; not jars-in-jars, but unpack the contents of your dependency jars into the main app jar. This can be quite slow in the build (if you have a few hundred MB worth of deps, those need to be packed in and all class files need analysis for the shade rewrite, that's a lot of bits to process, so it always takes some time). The general idea behind shading is that deployment 'is as simple as transferring one jar file', but this is not actually practical, given that you can no longer assume that end users have a JVM installed, and even if they do, you cannot rely on it being properly up to date. I mention it here because you may hear this from others, but I wouldn't recommend it.
If you really do want to go for this, the only option is build systems: They have a plugin to do it; there is no command line tool that ships with java itself that can do this. There are also caveats about so-called 'signed jars' which cannot just be unpacked into a single uberjar.
App container
Not all java apps are standalone where you provide the main. If you're writing a web service, for example, you have no main at all; the framework does. Instead of a single entrypoint ('main' - the place where your code initially begins execution), web services have tons of entrypoints: One for every URL you want to respond to. The framework takes care of invoking them, and usually these frameworks have their own documentation and specs for how dependencies are loaded. Usually it is a matter of putting a jar in one place and its dependencies in a subdir named 'lib', or you build a so-called war file, but, really, so many web frameworks and so many options on how they do this. The good news is, usually its simple and the tutorial of said framework will cover it.
This advice applies to any 'app container' system; those are usually web frameworks, but there are non-web related frameworks that take care of launching your app.
Don't do these
Don't force your users to manually supply the -classpath
option or mess with the CLASSPATH
environment variable.
Don't try to write a custom classloader that loads jars-in-jars.
NB: Sqlite2 is rather complicated for java; it's not getting you many of the benefits that the 'lite' is supposed to bring you, as it is a native dependency. The simple, works everywhere solution in the java sphere is 'h2', which is written in all java, thus shipping the entire h2 engine as part of the java app is possible with zero native components.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论