在許多Java工程中,經常會看到帶有程式自訂參數調用Java命令的封裝shell指令碼。例如,
$ANT_HOME/bin/ant, $GROOVY_HOME/bin/groovy
,甚至在我們的TimeMachine Scheduler程式中也能見到
$TIMEMACHINE_HOME/bin/scheduler.sh
編寫這些封裝指令碼很無聊而且容易出錯。大多數的問題來自為程式設定正確的classpath。如果你正在為一個公司開發內部項目的話,那麼你有可能遠離糾結的路徑以及環境變數問題。但是對於開源項目,人們需要使封裝更加靈活和通用。大多數甚至提供了.bat版本。Windows DOS確實是個野蠻且被限制的終端而不能很好的滿足你項目指令碼需求。因此,我常鼓勵別人盡量還是多使用Cygwi。至少它具備一個真實的bash shell。其他常見的問題就是這些封裝很快就會失去控制而且在你的項目各處都會出現很多冗餘指令碼。
run-java封裝指令碼介紹
如果你看到 $TIMEMACHINE_HOME/bin/scheduler.sh 的代碼,你會看到它其實是在同目錄下迴圈調用run-java指令碼。
DIR=$(dirname $0)SCHEDULER_HOME=$DIR/..$DIR/run-java -Dscheduler.home="$SCHEDULER_HOME" timemachine.scheduler.tool.SchedulerServer "$@"
正如你看到的,我們的 run-java 可以使用 -D 選項,不僅這樣,它同樣也能使用 -cp 選項!而且,你還能在main class後面指定這些選項!這樣能夠使得run-java被其他的指令碼封裝,並且仍舊能夠添加額外的系統屬性以及classpath。
例如,TimeMachine 附帶了 Groovy 庫,所以你可以簡單的像這樣調用
groovy:$TIMEMACHINE_HOME/bin/run-java groovy.ui.GroovyMain test.groovy
,而不用再次下載整個分支。
你可以很方便地在任何目錄下使用,它確認自己的目錄然後可以自動載入lib目錄下的任何jar包。現在如果你想要附加更多的jar包來運行Groovy的話,可以如下使用 -cp 選項:
$TIMEMACHINE_HOME/bin/run-java -cp "$HOME/apps/my-app/lib/*" groovy.ui.GroovyMain test.groovy
通常如果你設定java classpath不夠小心時會經常導致錯誤,但是使用 run-java 可以預先運行一次:
RUN_JAVA_DRY=1 $TIMEMACHINE_HOME/bin/run-java -cp "$HOME/apps/my-app/lib/*" groovy.ui.GroovyMain test.groovy
你只需在命令提示行中運行上面一整行代碼即可。它將輸出完整的附帶所有選項和參數的java命令。
run-script還包含很多其它的選項,你可以通過查看其注釋瞭解。當前的指令碼能夠在任何的Linux bash和Windows Cygwin中運行。
在開發中通過Maven使用 run-java
根據上面提到的樣本,假設項目發布結構如下:
$TIMEMACHINE_HOME +- bin/run-java +- lib/*.jar
但是在開發過程中目錄會是怎樣呢?一個常見的用例便是:你希望能夠運行target/classes下最新編譯的代碼而不是將整個項目打包或者發布。你同樣可以在此種情況下使用 run-java 。首先,簡單的將 bin/run-java 添加進你的項目,然後運行
mvn compile dependency:copy-dependencies
將會在target/dependency下產生所有的jar檔案。就只需要做這些。run-java將自動的檢測這些目錄,並為你的main class建立正確的classpath。
如果你使用Eclipse來開發,那麼你的target/classes目錄將總是在更新的,run-java便能成為你項目開發中的瑰寶。
擷取 run-java 封裝指令碼
#!/usr/bin/env bash## Copyright 2012 Zemian Deng## Licensed under the Apache License, Version 2.0 (the "License");# you may not use this file except in compliance with the License.# You may obtain a copy of the License at## http://www.apache.org/licenses/LICENSE-2.0## Unless required by applicable law or agreed to in writing, software# distributed under the License is distributed on an "AS IS" BASIS,# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.# See the License for the specific language governing permissions and# limitations under the License. # A wrapper script that run any Java6 application in unix/cygwin env.## This script is assumed to be located in an application's "bin" directory. It will# auto resolve any symbolic link and always run in relative to this application# directory (which is one parent up from the script.) Therefore, this script can be# run any where in the file system and it will still reference this application# directory.## This script will by default auto setup a Java classpath that picks up any "config"# and "lib" directories under the application directory. It also will also add a# any typical Maven project output directories such as "target/test-classes",# "target/classes", and "target/dependency" into classpath. This can be disable by# setting RUN_JAVA_NO_PARSE=1.## If the "Default parameters" section bellow doesn't match to user's env, then user# may override these variables in their terminal session or preset them in shell's# profile startup script. The values of all path should be in cygwin/unix path,# and this script will auto convert them into Windows path where is needed.## User may customize the Java classpath by setting RUN_JAVA_CP, which will prefix to existing# classpath, or use the "-cp" option, which will postfix to existing classpath.## Usage:# run-java [java_opts] <java_main_class> [-cp /more/classpath] [-Dsysprop=value]## Example:# run-java example.Hello# run-java example.Hello -Dname=World# run-java org.junit.runner.JUnitCore example.HelloTest -cp "C:\apps\lib\junit4.8.2\*"## Created by: Zemian Deng 03/09/2012 # This run script dir (resolve to absolute path)SCRIPT_DIR=$(cd $(dirname $0) && pwd) # This dir is where this script live.APP_DIR=$(cd $SCRIPT_DIR/.. && pwd) # Assume the application dir is one level up from script dir. # Default parametersJAVA_HOME=${JAVA_HOME:=/apps/jdk} # This is the home directory of Java development kit.RUN_JAVA_CP=${RUN_JAVA_CP:=$CLASSPATH} # A classpath prefix before -classpath option, default to $CLASSPATHRUN_JAVA_OPTS=${RUN_JAVA_OPTS:=} # Java options (-Xmx512m -XX:MaxPermSize=128m etc)RUN_JAVA_DEBUG=${RUN_JAVA_DEBUG:=} # If not empty, print the full java command line before executing it.RUN_JAVA_NO_PARSE=${RUN_JAVA_NO_PARSE:=} # If not empty, skip the auto parsing of -D and -cp options from script arguments.RUN_JAVA_NO_AUTOCP=${RUN_JAVA_NO_AUTOCP:=} # If not empty, do not auto setup Java classpathRUN_JAVA_DRY=${RUN_JAVA_DRY:=} # If not empty, do not exec Java command, but just print # OS specific support. $var _must_ be set to either true or false.CYGWIN=false;case "`uname`" in CYGWIN*) CYGWIN=true ;;esac # Define where is the java executable isJAVA_CMD=javaif [ -d "$JAVA_HOME" ]; then JAVA_CMD="$JAVA_HOME/bin/java"fi # Auto setup applciation's Java Classpath (only if they exists)if [ -z "$RUN_JAVA_NO_AUTOCP" ]; then if $CYGWIN; then # Provide Windows directory conversion JAVA_HOME_WIN=$(cygpath -aw "$JAVA_HOME") APP_DIR_WIN=$(cygpath -aw "$APP_DIR") if [ -d "$APP_DIR_WIN\config" ]; then RUN_JAVA_CP="$RUN_JAVA_CP;$APP_DIR_WIN\config" ; fi if [ -d "$APP_DIR_WIN\target\test-classes" ]; then RUN_JAVA_CP="$RUN_JAVA_CP;$APP_DIR_WIN\target\test-classes" ; fi if [ -d "$APP_DIR_WIN\target\classes" ]; then RUN_JAVA_CP="$RUN_JAVA_CP;$APP_DIR_WIN\target\classes" ; fi if [ -d "$APP_DIR_WIN\target\dependency" ]; then RUN_JAVA_CP="$RUN_JAVA_CP;$APP_DIR_WIN\target\dependency\*" ; fi if [ -d "$APP_DIR_WIN\lib" ]; then RUN_JAVA_CP="$RUN_JAVA_CP;$APP_DIR_WIN\lib\*" ; fi else if [ -d "$APP_DIR/config" ]; then RUN_JAVA_CP="$RUN_JAVA_CP:$APP_DIR/config" ; fi if [ -d "$APP_DIR/target/test-classes" ]; then RUN_JAVA_CP="$RUN_JAVA_CP:$APP_DIR/target/test-classes" ; fi if [ -d "$APP_DIR/target/classes" ]; then RUN_JAVA_CP="$RUN_JAVA_CP:$APP_DIR/target/classes" ; fi if [ -d "$APP_DIR/target/dependency" ]; then RUN_JAVA_CP="$RUN_JAVA_CP:$APP_DIR/target/dependency/*" ; fi if [ -d "$APP_DIR/lib" ]; then RUN_JAVA_CP="$RUN_JAVA_CP:$APP_DIR/lib/*" ; fi fifi # Parse addition "-cp" and "-D" after the Java main class from script arguments# This is done for convenient sake so users do not have to export RUN_JAVA_CP and RUN_JAVA_OPTS# saparately, but now they can pass into end of this run-java script instead.# This can be disable by setting RUN_JAVA_NO_PARSE=1.if [ -z "$RUN_JAVA_NO_PARSE" ]; then # Prepare variables for parsing FOUND_CP= declare -a NEW_ARGS IDX=0 # Parse all arguments and look for "-cp" and "-D" for ARG in "$@"; do if [[ -n $FOUND_CP ]]; then if [ "$OS" = "Windows_NT" ]; then # Can't use cygpath here, because cygpath will auto expand "*", which we do not # want. User will just have to use OS path when specifying "-cp" option. #ARG=$(cygpath -w -a $ARG) RUN_JAVA_CP="$RUN_JAVA_CP;$ARG" else RUN_JAVA_CP="$RUN_JAVA_CP:$ARG" fi FOUND_CP= else case $ARG in '-cp') FOUND_CP=1 ;; '-D'*) RUN_JAVA_OPTS="$RUN_JAVA_OPTS $ARG" ;; *) NEW_ARGS[$IDX]="$ARG" let IDX=$IDX+1 ;; esac fi done # Display full Java command. if [ -n "$RUN_JAVA_DEBUG" ] || [ -n "$RUN_JAVA_DRY" ]; then echo "$JAVA_CMD" $RUN_JAVA_OPTS -cp "$RUN_JAVA_CP" "${NEW_ARGS[@]}" fi # Run Java Main class using parsed variables if [ -z "$RUN_JAVA_DRY" ]; then "$JAVA_CMD" $RUN_JAVA_OPTS -cp "$RUN_JAVA_CP" "${NEW_ARGS[@]}" fielse # Display full Java command. if [ -n "$RUN_JAVA_DEBUG" ] || [ -n "$RUN_JAVA_DRY" ]; then echo "$JAVA_CMD" $RUN_JAVA_OPTS -cp "$RUN_JAVA_CP" "$@" fi # Run Java Main class if [ -z "$RUN_JAVA_DRY" ]; then "$JAVA_CMD" $RUN_JAVA_OPTS -cp "$RUN_JAVA_CP" "$@" fifi