最近 Java subreddit 出現了一篇”在沒有IDE的情況下編譯Java包” 的帖子,這個帖子拋出了這麼一個問題,“是否存在一個命令可以編譯一組處於同一文件夾下獨立包內的java文件的方法(這稱之為bin),同時怎樣運行新的類文件呢?” 它的提出者 kylolink解釋說,“當我開始依賴Eclipse來編寫代碼時就開始擔心沒有Eclipse時自己該怎麼寫代碼了。” 我看過很多次這類問題,事實上,這促使我(目前已經四年了)發了一篇文章: GPS系統和IDE:究竟是好還是壞? 我喜歡強大的現代化的Java的集成開發環境(IDE),因為它使得我的編程更加輕松,但知道如何構建和運行簡單的Java示例也是有必要的,這篇文章主要就是著重於如何做到這些的。
我博客中一篇文章 通過簡單的測試學習Java,其中寫了我喜歡用一個簡單的文本編輯器和命令行工具來編寫並且運行簡單的程序。現在我有個很棒的想法,是關於我最喜歡的Java IDE的,即早期決定使用IDE的好處是為了保證“開銷”。在大多數實際應用程序中,毫無疑問IDE的開銷是值得的。然而,對於最簡單的示例應用程序並非總是如此。這篇文章的剩余部分展示了在沒有遇到這些情況的前提下是如何構建和運行Java代碼的。
建立和運行Java代碼
為了對這篇文章進行更具體的討論,我將使用一些非常簡單的Java類,在同一個包中通過彼此相關的組合或繼承(不在 未命名的包中)來調用dustin.examples。這兩個在第三個類之前是沒有 main 函數的,直到 Main.java 才有 main 函數為了在沒有IDE的情況下運行示例。三個類的代碼清單如下。
Parent.java
package dustin.examples; public class Parent { @Override public String toString() { return "I'm the Parent."; } }
Child.java
package dustin.examples; public class Child extends Parent { @Override public String toString() { return "I'm the Child."; } }
Main.java
package dustin.examples; import static java.lang.System.out; public class Main { private final Parent parent = new Parent(); private final Child child = new Child(); public static void main(final String[] arguments) { final Main instance = new Main(); out.println(instance.parent); out.println(instance.child); } }
接下來顯示了目錄結構與這些類的 .java 源文件。截圖顯示源文件的目錄層次結構代表了包名(dustin/examples 源自包 dustin.examples)和該子目錄下被稱為package-reflecting的目錄層次結構 src。我還創建了 classes 子目錄(當前為空)用於存放編譯後文件 .class 文件,因為 javac 在目錄不存在的情況下不會創建該目錄。
用javac構建和運行java
無論使用哪種方法正常地構建Java代碼(Ant, Maven, Gradle, 或者 IDE),我相信謹慎的做法是,至少了解如何使用 javac來構建Java代碼。Oracle/Sun的支持者們使用 javac 命令行工具的基本項來運行, javac -help 也可以運行 javac -help -X來查看其它的擴展選項。如何應用這些選項的更多細節可以在 Windows 或者 Unix/Linux的javac的文檔工具查看。
當進入 javac 文檔 時, -sourcepath 選項可以被用來表示源文件存在的路徑。在上面所呈現的目錄結構中,假設我在運行 C:\java\examples\javacAndJava\ 目錄的 javac 命令,這將意味著會需要這樣的命令: javac -sourcepath src src\dustin\examples\*.java。下一張截圖顯示了結果。
因為我們沒有指定 .class 文件的目標目錄,在默認情況下它們被放置在同一個目錄下被編譯的 .java 源文件。我們可以使用 -d 選項來糾正這種情況。我們現在可以運行的命令,例如 javac -sourcepath src -d classes src\dustin\examples\*.java。如前所述,指定目標目錄(classes)必須是存在的。這樣,命令將會在下面的截圖指定的目錄下定位.class文件。
用Java源文件編譯成適當的 .class 文件在指定的目錄中,我們現在可以運行Java應用程序啟動命令行工具 java。這僅僅是通過by java -help 所示的指令或者是 java工具頁 和.class 文件的 -classpath 所指定的地方(或 -cp)選項。使用兩種方法來指定 classes目錄以便用於找到 .class 文件,接下來的截圖印證了這點。最後一個參數是完全合格的(整個Java包)類名,它有一個 main 函數來執行。下面的截圖顯示了java -cp classes dustin.examples.Main 和java -classpath classes dustin.examples.Main的命令。
構建和運行Ant
對於最簡單的Java應用程序, javac and java 使用起來非常簡單,它們用於構建並執行應用程序就分別證明了這一點。應用程序會稍微復雜一點(如代碼中存在多個包/目錄或更復雜的依賴於第三方庫和框架的類路徑),但這種方法非常難用。 Apache Ant 是最古老的“三巨頭”,它是被用於成千上萬的應用程序部署的Java構建工具。正如我討論過 以前的一篇博客,一個基礎的Ant構建文件很容易被創建,特別是如果都始於一個模板就像我在 這篇文章中介紹的一樣。
接下來的代碼是Ant的 build.xml 文件的,它將 .java 文件 編譯成 .class文件然後運行 dustin.examples.Main 類就像上面的 javac 和 java一樣。
build.xml
<?xml version="1.0" encoding="UTF-8"?> <project name="BuildingSansIDE" default="run" basedir="."> <description>Building Simple Java Applications Without An IDE</description> <target name="compile" description="Compile the Java code."> <javac srcdir="src" destdir="classes" debug="true" includeantruntime="false" /> </target> <target name="run" depends="compile" description="Run the Java application."> <java classname="dustin.examples.Main" fork="true"> <classpath> <pathelement path="classes"/> </classpath> </java> </target> </project>
我沒有使用Ant也沒有包括一般所用的方式(例如 “clean” 和 “javadoc”),我使用的是 javac和 java來使例子盡可能的簡單。請注意我使用了”debug”來給javac Ant 任務設置”true”;因為這不是Ant的默認方式而是javac的默認方式。的確,Ant的 javac task 和 java task與 javac and java的命令工具非常相似。
因為我希望使用默認名稱Ant來構建文件的時候不顯式指定(build.xml),因為我提供“運行”的目標,構建文件的“默認形式”,因為“編譯”作為一個依賴包括“運行”的目標,此外Ant是我的環境路徑,我所需要做的是在命令行上讓Ant來編譯運行目錄下的“ant”類型的 build.xml文件示例,下圖就印證了這一點。
雖然我演示了用Ant編譯和運行一個簡單的Java應用程序,通常我只用Ant進行編譯同時用java來運行(或者如果classpath非常復雜時就使用 java腳本來執行)。
用Maven來搭建和運行
雖然Ant是第一個主流的Java構建工具, Apache Maven 最終獲得了成功在很大程度上要感謝它采用的配置是按照慣例同時也支持常見的庫。當代碼和生成的 標准目錄布局對象一致時,Maven很容易使用。很遺憾,我的例子不遵循這個目錄結構,但Maven允許我 覆蓋默認的目錄結構。下面的Maven POM文件覆蓋了源代碼和目標目錄以及提供了一個Maven構建所需的最小元素,此時Maven的版本是Maven 3.2.1。
pom.xml
<project> <modelVersion>4.0.0</modelVersion> <groupId>dustin.examples</groupId> <artifactId>CompilingAndRunningWithoutIDE</artifactId> <version>1</version> <build> <defaultGoal>compile</defaultGoal> <sourceDirectory>src</sourceDirectory> <outputDirectory>classes</outputDirectory> <finalName>${project.artifactId}-${project.version}</finalName> </build> </project>
因為上面的 pom.xml 文件指定了一個“compile”的“defaultGoal”, pom.xml 是默認定義的POM文件,用執行器(mvn)來搜索,因為Maven安裝的 bin 文件夾在我的path環境變量中,我只需要運行“mvn”來編譯 .class 文件,這在下張截圖中將會顯示。
我也可以用Maven的 mvn exec:java -Dexec.mainClass=dustin.examples.Main命令來運行編譯後的應用程序,下圖得以展示。
與Ant一樣,我通常不使用Maven運行簡單的Java應用程序,而是用 java 運行編譯後的代碼(或者使用腳本直接調用 java 的classpath路徑)。
用Gradle構建和運行
Gradle 是最新,最流行和最時尚的三大主流Java構建工具之一。我有時會懷疑時髦東西的本質,但是我發現了有不少東西 例如Gradle (用Groovy編寫的XML, 內置Ant支持和Ivy支持,配置按照慣例很容易被覆蓋,Maven存儲庫支持等)。下一個例子顯示了一個Gradle構建文件,它可用於編譯和運行一個簡單的應用程序,這裡主要展示一下示例代碼。它改編自我在博客 簡單Gradle Java插件定義的例子。
build.gradle
apply plugin: 'java' apply plugin: 'application' // Redefine where Gradle should expect Java source files (*.java) sourceSets { main { java { srcDirs 'src' } } } // Redefine where .class files are written sourceSets.main.output.classesDir = file("classes") // Specify main class to be executed mainClassName = "dustin.examples.Main" defaultTasks 'compileJava', 'run'
前兩行 build.gradle 文件指定 Java plugin 和 Application plugin的應用程序,它將許多功能自動構建。“sourceSets” 和 “sourceSets.main.output.classesDir”的定義允許覆蓋Gradle’s Java 插件各自的Java源代碼和編譯文件類的默認目錄。“mainClassName”明確了規范類應該作為應用程序的一部分插件運行。“defaultTasks”指定要運行的任務,只需在命令行鍵入:‘compileJava’是一個標准的提供任務的Java插件,‘run’是一個標准的提供任務的應用程序的插件。因為我稱構建文件為 build.gradle ,因為我指定默認的‘compileJava’任務和‘run’ 方式,因為我有Gradle的 bin文件夾安裝目錄,我需要做的就是構建和運行示例來 鍵入“gradle”命令,接下來將得到證實。
甚至最大的懷疑者都承認Gradle構建對於這個簡單示例都非常方便。它的某些約定和假設結合了簡潔的依賴,很容易根據需要重寫選擇違約的機制。這一事實,這在Groovy而不是XML中也非常吸引人!
Ant和Maven一樣,我傾向於只用這些工具,通過 java 或者腳本調用 java來直接構建和運行編譯好的 .class文件。順便說一下,我通常也保存這些 .class 文件為JAR來運行,但這超出了本文的范圍。
總結
IDE通常沒有必要構建簡單的應用程序和例子,它的開銷甚至比最簡單的例子都要多,這時直接使用 javac 和 java 來構建和運行實例就顯得非常方便。為了使Ant,Maven或者Gradle的構建工具變得更加有吸引力,許多IDE支持著這些構件工具,這意味著開發者要將構件工具轉移到IDE上,如果已經確定了先前創建的過程,那麼IDE支持的簡單應用就已經成長為了一個成熟的項目。