Статьи

Лучшая оболочка Java Script Wrapper

Во многих Java-проектах вы часто видите скрипт оболочки-обертки, который вызывает команду java с ее пользовательскими параметрами приложения. Например, $ ANT_HOME / bin / ant, $ GROOVY_HOME / bin / groovy или даже в нашем планировщике TimeMachine вы увидите $ TIMEMACHINE_HOME / bin / scheduler.sh.

Написание этих скриптов-обёрток скучно и подвержено ошибкам. Большинство проблем связано с настройкой правильного пути к классу для приложения. Если вы работаете над внутренним проектом для компании, то вы можете избежать жестких кодов и изменений вашей среды. Но для проектов с открытым исходным кодом люди должны сделать оболочку более гибкой и универсальной. Большинство из них даже предоставляют .bat-версию. Windows DOS — это действительно жестокий и ограниченный терминал, позволяющий избавиться от потребностей вашего проекта. По этой причине я часто призываю других использовать Cygwin настолько, насколько они могут. По крайней мере, у него есть настоящая оболочка bash для работы. Еще одна распространенная проблема с этими обертками — она ​​может быстро выйти из-под контроля и иметь слишком много дубликатов одинаковых скриптов в каждом месте вашего проекта.

В этой статье я покажу вам сценарий оболочки Java, который я написал. Он прост в использовании и очень гибок для запуска практически любой Java-программы. Давайте сначала посмотрим, как он используется, а затем я распечатаю его содержимое внизу поста.

Представляем скрипт оболочки 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! Более того, вы можете указать эти параметры даже после основного класса! Это делает run-java повторно переносимым другим сценарием, и при этом он может добавлять дополнительные системные свойства и путь к классам.

Например, TimeMachine поставляется с библиотекой Groovy, поэтому вместо того, чтобы снова загружать ее полный дистрибутив, вы можете просто вызвать Groovy следующим образом.

$TIMEMACHINE_HOME/bin/run-java groovy.ui.GroovyMain test.groovy

Вы можете использовать run-java в любом каталоге, в котором вы находитесь, так что это удобно. Он разрешит свой собственный каталог и автоматически загрузит все jar-файлы в каталог lib. Теперь, если вы хотите, чтобы Groovy запускался с дополнительными jar-файлами, вы можете использовать опцию -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 со всеми опциями и аргументами для вас, чтобы проверить.

В сценарии есть много других опций, которые вы можете узнать больше, прочитав комментарии к нему. Текущий скрипт будет работать на любом Linux bash или на терминале Windows Cygwin.

Использование run-java во время разработки с Maven

Приведенные выше примеры предполагают, что вы находитесь в освобожденной структуре проекта, такой как эта

$TIMEMACHINE_HOME
  +- bin/run-java
  +- lib/*.jar

 

Но как насчет разработки? Частым случаем использования является то, что вы хотите иметь возможность запускать ваши последние скомпилированные классы в target / classes без необходимости упаковки или выпуска всего проекта. Вы можете использовать нашу run-java и в этом сценарии. Сначала просто добавьте bin / run-java в свой проект, затем запустите mvn compile dependency: copy-dependencies, который сгенерирует все jar-файлы в target / зависимость. Вот и все. Run-java автоматически обнаружит эти каталоги и создаст правильный путь к классу для запуска вашего основного класса.

Если вы используете Eclipse IDE для разработки, то ваша цель / классы всегда будут обновлены, а 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 parameters
JAVA_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 $CLASSPATH
RUN_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 classpath
RUN_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 is
JAVA_CMD=java
if [ -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
	fi
fi

# 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[@]}"
	fi
else
	# 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" "$@"
	fi
fi