编译并运行其他目录中具有许多依赖类的java项目
我的所有目录都有这样的结构:
我正在尝试编译并执行 ServerMain 和 ClientMain。以下编译有效:
src>javac -cp .;../lib/gson-2.8.2.jar client/gui/*.java client/net/*.java client/*.java server/*.java server/net/*.java server/exceptions/*.java server/controller/*.java server/data/*.java common/*.java
我必须不在 src 中执行程序,而是在上一个目录中执行程序,因为代码中有一个从 cfg 目录中的配置文件读取的函数。
我尝试执行此命令,但有很多错误:ClassesNotFound 或 cfg 文件路径错误(代码中为 /cfg/server.cfg)或未找到 ServerMain。
下面的命令是在src的上一级目录(图中的PROJECT)中执行的,
java -cp lib/gson-2.8.2.jar src/server/WinsomeServerMain
如何正确执行?
I have this structure of all my directories:
I am trying to compile and execute ServerMain and ClientMain. The following compilation worked:
src>javac -cp .;../lib/gson-2.8.2.jar client/gui/*.java client/net/*.java client/*.java server/*.java server/net/*.java server/exceptions/*.java server/controller/*.java server/data/*.java common/*.java
I have to execute the program not in src, but in the previous directory, because in the code there is a function which read from configuration file in the cfg directory.
I tried this command to execute but I have a lot of errors: ClassesNotFound or Errors in the path of cfg file (which is /cfg/server.cfg in the code) or the ServerMain is not found.
The following command is executed in the previous directory of src which is PROJECT in the images
java -cp lib/gson-2.8.2.jar src/server/WinsomeServerMain
How do I execute properly?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
唯一真正正确的答案是:把所有这些东西扔进垃圾桶;为自己建立一个构建系统,例如 maven 或 gradle.
为什么?
javac
并不是特别擅长编译不同目录中的许多互连的源文件,但是,将所有代码集中到一个巨大的包中并不是一个好主意。这些工具是。你只需..告诉他们编译。他们“知道”java 源代码位于src/main/java
中,并且可以轻松调整以考虑 2 个独立的“项目”(src/client/java
和>src/server/java
,在你的情况下),甚至为这些提供单独的 jar。还有依赖管理的问题:使用这些工具,您只需编写所谓的 GAV 字符串(Group/Artifact/Version),例如
com.google.gson::gson::2.9.0
在配置文件中,maven/gradle 会处理它。它知道在哪里可以在线找到 gson 并为您下载它,并确保它自动位于所有相关的类路径上。这很好:将所有这些 dep 放在源代码控制系统中意味着源代码控制大小会猛增,这很烦人,所以如果不在源代码控制中,如何在计算机/开发人员之间传输 gson?答案是:你不需要 - 每个需要它的系统都会自动下载它。 maven 和 gradle 可以为你做什么。最后,您可以免费获得“只需编译所有文件”,无需像现在一样列出 6 个目录。那很好。
但我真的想使用 javac
你错了。但是,如果您必须:
代码片段中的错误 1:
src
滥用您的
javac
命令会编译所有不同的文件就地,这意味着您现在拥有.class
文件位于包含src
的文件夹结构中,现在您的项目结构在欺骗您。这没有用。如果您确实想让 java 和类文件位于同一目录中,那么只需删除层次结构中的 src 即可。它应该是yourMainProjectDir/client/net/MyClient.java
以及它旁边的MyClient.class
。或者,使用-d
开关告诉 javac 输出到特定目录。例如,bin
:错误 2 - 文件名而不是完全限定的类名
java
不运行文件。它运行类。顺序参数(在您的示例中为 src/server/WinsomeServerMain)是一个完全限定的类名。类似java.lang.String
的东西,而不是类似java/lang/String.class
的东西。然后,java
将扫描类路径中列出的每个目录(它们是文件/目录,并通过-cp
传递)以查找该资源。在您的情况下,类路径上需要 2 个独立的东西:gson 和您刚刚编译的自己的类文件。
因此:
这里发生了一些事情:
我实际上不知道你的包裹是什么,从你的问题中不清楚。您的
WinsomeServerMain.java
文件顶部应该有一个package xyz;
语句。如果是服务器
,则上面的内容是“正确的”。如果没有 package 声明,你需要一个,无包对除了其自己包中的其他内容之外的所有内容都是不可见的,并且应该在引入任何类型的任何层次结构时放弃。Java 可以处理类路径中的 *,但您必须让 java 进行解包。在除了普通的 jane windows 之外的几乎所有 shell 环境中,shell 都会看到 * 并为您解压它,您不希望这样,因此,您必须使用单引号告诉 shell 不要对其进行操作。另外,java非常简单,只理解
*
。它不理解*.jar
,所以不要输入它。类路径根包含类路径。因此,bin
是根(因为类文件直接位于其中),但lib
不是(因为类文件不在lib
中) > - 它们位于 jar 文件中,jar 文件位于lib
中)。因此,这不是一个拼写错误:bin
是单独列出的,但它是lib/*
。这意味着您可以将任何您想要的库放入lib
中,而lib/*
将获取所有这些库。如何处理配置文件
如果这些配置文件是只读的(“随应用程序一起提供” - 永远不会改变并且在编译时知道的静态数据),则应该将它们粘贴在 jar 文件中并使用
getResourceAsStream
。如果它们旨在供用户配置,则它们不应该与您的类文件“一起” - 类文件是代码,并且代码最好放置在只读位置(至少就代码而言) 。这就是为什么将可编辑文件放在它们旁边是一个坏主意。一个好的解决方案是在 jar 内发送模板化版本(使用
YourClass.class.getResourceAsStream("/configtemplate.txt")
读取它们 - 如果该文件位于 jar 的根目录中,它将找到该文件。开始时,读取 Paths.get(System.getProperty("user.home"), "myapp.config") ,如果缺少,则创建它,用模板填充它并告诉用户去编辑它。就是为此目的而设计的,并且绝对是可写的
这会写入用户的主目录(Windows 上的 C:\Users\YourUserName、Linux 上的 /home/youracct、Mac 上的 /Users/youracct 等),该目录
。构建系统的优点是它们使创建良好的部署变得更容易:您确实希望您的文件位于指定其自己的类路径的 jar 文件中,您最好希望“发布”您的应用程序,以便有人结束。补充:
运行应用程序所需的唯一操作是键入 java -jar YourApp.jar 或只需在资源管理器中双击 YourApp.jar - 所有这些都与平台无关( Windows 和 Mac 上的交易相同)。您可以这样做 - jar 可以指定自己的类路径,该类路径是相对于 jar 进行解析的。这涉及到包含一个清单,其中包含关键的类路径:lib/gson.jar。您可以手动完成此操作(
jar cvmf MyManifest.mf MyApp.jar client/ui/*.class client/*.class etcetera
- 类似的操作,您可以在其中创建 MyManifest.mf 文本文件并列出其中的密钥),但是这里有很多步骤,而 gradle/maven 可以自动执行这一切。The only really correct answer is: Toss all this stuff in the bin; go get yourself a build system, such as maven or gradle.
Why?
javac
isn't particularly good at compiling many interconnected source files in different directories, and yet, just lumping all your code into one gigantic package isn't a good idea. These tools are. You just.. tell them to compile. They 'know' that java sources are insrc/main/java
, and can be easily adjusted to consider 2 separate 'projects' (src/client/java
andsrc/server/java
, in your case), and even deliver separate jars for these.There's also the issue of dependency management: With these tools you just write a so-called GAV string (Group/Artifact/Version), such as
com.google.gson::gson::2.9.0
in a config file and maven/gradle just takes care of it. It knows where to find gson online and downloads it for you, and ensures it's on all the relevant classpaths automatically. This is nice: Sticking all those deps in your source control system means the source control size skyrockets which is annoying, so if not in source control, how do you transfer gson between computers / developers? The answer is: You don't - every system that needs it just downloads it automatically. Which maven and gradle can do for you.Finally, you get 'just compile ALL the files' for free, you don't need to list 6 directories, as you are now. That's nice.
But I really want to use javac
You'd be wrong. But if you must:
Error 1 in your snippets:
src
abuseYour
javac
command compiles all the various files in place, meaning, you now have.class
files in a folder structure that includessrc
and now your project structure is lying to you. This isn't useful. If you really intend for java and class files to live in the same dir, then just get rid ofsrc
in the hierarchy. It shuold then just beyourMainProjectDir/client/net/MyClient.java
and next to it,MyClient.class
. Alternatively, use the-d
switch to tell javac to output to a specific directory. For example,bin
:Error 2 - filename instead of a fully qualified class name
java
does not run files. It runs classes. The sequential argument (which in your example wassrc/server/WinsomeServerMain
) is a fully qualified classname. Something likejava.lang.String
, not something likejava/lang/String.class
.java
will then scan each directory listed in the classpath (which ARE files/directories, and passed with-cp
) for that resource.In your case, you need 2 separate things on the classpath: gson, and your own class files, which you just compiled.
Thus:
A few things are happening here:
I don't actually know what your packages are, it's not clear from your question. You should have a
package xyz;
statement at the top of yourWinsomeServerMain.java
file. If it'sserver
, the above is 'correct'. If there is no package statement, you need one, packageless is invisible to everything except other stuff in its own package and should be abandoned the moment you introduce any hierarchy of any sort.Java can handle * in classpaths, but you must let java do the unpacking. On just about every shell environment except plain jane windows, the shell will see that * and unpack it for you, you don't want that, hence, you MUST use single quotes to tell the shell not to act on it. Also, java is very simplistic and just understands
*
. It does not understand*.jar
, so don't type that. A classpath root contains classpaths. Hence,bin
is a root (as the class files are directly in it), butlib
is not (because the classfiles aren't inlib
- they are in a jar file, and the jar file is inlib
). Hence, that isn't a typo:bin
is listed on its own, but it'slib/*
. This means you can put whatever libraries you want inlib
andlib/*
will get them all.How to deal with config files
If those config files are intended to be read only ('shipped with your app' - static data that never changes and that you know at compile time), you should stick them in the jar file and read them with
getResourceAsStream
. If they are intended to be user configurable, they shouldn't be 'with' your class files - class files are code, and code should optimally be placed in a read-only location (at least, as far as the code is concerned). That is why sticking editable files next to them is a bad idea.A good solution is to ship templated versions inside the jar (read them with
YourClass.class.getResourceAsStream("/configtemplate.txt")
- which will find that file if it's in the root of the jar. On start, readPaths.get(System.getProperty("user.home"), "myapp.config")
and if it is missing, create it, filling it with the template and telling the user to go edit it.That writes to a user's home dir (C:\Users\YourUserName on windows, /home/youracct on linux, /Users/youracct on mac, etc), which is intended for this purpose, and is definitely writable.
Better deployment
Yet another advantage of build systems is that they make creation of nice deploys easier: You really want your files in a jar file that specifies its own classpath. build tools can do that. You optimally want to 'ship' your app such that someone ends up with:
and that the only thing that is needed to run the app is to type
java -jar YourApp.jar
or just doubleclick YourApp.jar in the explorer - and for all that to be platform agnostic (same deal on windows and macs). You can do that - jars can specify their own classpath which is resolved relative to the jar. This involves including a manifest that here would have keyClass-Path: lib/gson.jar
inside it. You can do it by hand (jar cvmf MyManifest.mf MyApp.jar client/ui/*.class client/*.class etcetera
- something like that, where you create the MyManifest.mf text file and list that key inside it), but there are so many steps here, and gradle/maven automate it all.