Chame “java -jar MyFile.jar” com a opção de classpath adicional

91

Eu criei um arquivo jar contendo todas as minhas coisas compiladas. Além disso, meu script ant build copia as bibliotecas necessárias para uma subpasta "libs". A estrutura é semelhante a esta:

MyProgram.jar
libs/

Portanto, quando tento executar meu programa agora, recebo o seguinte erro:

java -cp ".:/home/user/java/MyProgram/jar/libs" -jar MyProgram.jar
java.lang.ClassNotFoundException: org.postgresql.Driver
    at java.net.URLClassLoader$1.run(URLClassLoader.java:217)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:205)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:321)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:294)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:266)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:186)
    at database.PostgresQL.getConnection(PostgresQL.java:38)
    at recommender.dao.Creative2IdxDAO.createCreatives2Idx(Creative2IdxDAO.java:19)
    at main.Main.calculateCorrelationMatrix(Main.java:51)
    at main.Main.main(Main.java:28)
java.lang.NullPointerException
    at recommender.dao.Creative2IdxDAO.createCreatives2Idx(Creative2IdxDAO.java:25)
    at main.Main.calculateCorrelationMatrix(Main.java:51)
    at main.Main.main(Main.java:28)

Por que isso acontece?

toom
fonte

Respostas:

151

Você pode usar tanto -jar ou -cp , você não pode combinar os dois. Se você deseja colocar JARs adicionais no caminho de classe, você deve colocá-los no manifesto do JAR principal e então usar java -jarou colocar o caminho de classe completo (incluindo o JAR principal e suas dependências) -cpe nomear a classe principal explicitamente na linha de comando

java -cp 'MyProgram.jar:libs/*' main.Main

(Estou usando a dir/*sintaxe que diz ao javacomando para adicionar todos os .jararquivos de um diretório específico ao classpath. Observe que o *deve ser protegido contra expansão pelo shell, por isso usei aspas simples.)

Você mencionou que está usando o Ant, portanto, para a abordagem de manifesto alternativo, você pode usar a <manifestclasspath>tarefa do ant depois de copiar as dependências, mas antes de construir o JAR.

<manifestclasspath property="myprogram.manifest.classpath" jarfile="MyProgram.jar">
  <classpath>
    <fileset dir="libs" includes="*.jar" />
  </classpath>
</manifestclasspath>

<jar destfile="MyProgram.jar" basedir="classes">
  <manifest>
    <attribute name="Main-Class" value="main.Main" />
    <attribute name="Class-Path" value="${myprogram.manifest.classpath}" />
  </manifest>
</jar>

Com isso instalado, java -jar MyProgram.jarfuncionará corretamente e incluirá todos os libsarquivos JAR no caminho de classe também.

Ian Roberts
fonte
Adicionando acima, ou pense em adicionar as entradas jar necessárias no arquivo MANIFEST.MF.
Himanshu Bhardwaj
@HimanshuBhardwaj, na verdade, adicionei um exemplo de como fazer isso usando<manifestclasspath>
Ian Roberts,
O que se passa com o ':' no 'MyProgram.jar: libs / *' é um separador?
Gobliins
@Gobliins dois pontos são o separador entre os itens em um caminho nos sistemas operacionais Linux e Mac. No Windows, você usaria ponto e vírgula ( ;) em vez de dois pontos no Windows para letras de unidade. O OP usou dois pontos e barras normais em sua pergunta, sugerindo que eles estão no Linux ou Mac.
Ian Roberts,
5
Se as opções forem mutuamente exclusivas, a linha de comando deve imprimir um aviso quando ambas forem usadas. Quem tem tempo para essas curiosidades ?!
JJS de
22

Quando a -jaropção é usada, a -cpopção é ignorada. A única maneira de definir o caminho de classe é usando o arquivo de manifesto no jar.

É mais fácil apenas usar a -cpopção, adicionar seu arquivo jar a ela e chamar explicitamente a classe principal.

Além disso, presumindo que a /home/user/java/MyProgram/jar/libspasta contenha arquivos jar (em oposição aos arquivos de classe), isso não funcionará. Você não pode especificar uma pasta de arquivo jar, mas deve especificar cada arquivo jar individualmente no caminho de classe (vale a pena escrever um script de shell simples para fazer isso para você se houver um número significativo de jars).

Jonathan
fonte
0

É um pouco complicado. O script a seguir é uma tentativa de obter o classpath do manifesto do jar e permitir a adição de entradas de classpath extras. Tive resultados mistos com isso, mas quero compartilhar o script, para que possa ser totalmente funcional aqui.

O script tem dois nomes

  • showmanifest
  • Calljar

vinculando os dois arquivos juntos com

ln calljar showmanifest

com calljar -h você pode ver o uso.

#!/bin/bash
#set -x
# show the manifest of a jar file
# 2012-07-18
# author WF

#
# show usage
#
usage() {
 echo "usage: showmanifest (jarfile | directory jarfile) " 1>&2
 echo "usage: calljar directory jarfile classpath pattern arguments" 1>&2
 echo "             -h|--help " 1>&2
 echo "               show this help and exit" 1>&2
 echo "             -m|--mainclass javaclass" 1>&2
 echo "               mainclass to use (otherwise manifest is inspected)" 1>&2
 exit 1
}

#
# show the manifest of the given jar file
#
show() {
  dir="$1"
  jar="$2"
    fulljar=`find "$dir" -name "$jar"`
    cd /tmp
    mkdir show$$
    cd show$$
    jar xvf $fulljar META-INF/MANIFEST.MF
    cat META-INF/MANIFEST.MF
    cd /tmp
    rm -rf show$$
}

#
# show the classpath of the manifest
#
calljar() {
  dir="$1"
    jar="$2"
    classpath="$3"
    pattern="$4"
    arguments="$5"
    cmd=`show "$dir" "$jar"   | awk -v extracp="$classpath" -v dir="$dir" -v pattern="$pattern" -v jar="$jar" -v mainclass="$mainclass" -v args="$arguments" '
/Main-Class: / { if (mainclass=="") mainclass=$2 }
/^Class-Path:/ { 
  incp=1; 
    cp=$0; 
    gsub("Class-Path: ","",cp) 
    next
}
/^ .*$/ && incp { 
    line=substr($0,2)
  # remove carriage return (if any)
  cp=cp line
}
END { 
  # we do not like carriage returns
  gsub("\\r","",cp)
  gsub("\\r","",mainclass)
    # we do not like blanks ...
  gsub(" ","",cp)
    gsub(pattern,":"dir"/"pattern,cp)
  print "java -cp " extracp cp ":"dir"/"jar " " mainclass " " args
}
    '`
  #echo $cmd
    $cmd
}


# echo $# arguments found: $*
# parse command line options
while true; do
# echo "option $1"
  case "$1" in
    # options without arguments
    -h|--help) usage;;
         # for options with required arguments, an additional shift is required
        -m|--mainclass) mainclass=$2; shift;;
      (--) shift; break;;
      (-*) echo "$0: error - unrecognized option $1" 1>&2; usage;;
    (*) dir=$1;shift;break;;
  esac
  shift
done

#echo "argcount=$#"
case  $# in
  0) dir=`dirname "$dir"`
       jar=`basename "$dir"`
         show "$dir" "$jar";;
  1) jar="$1"
         show "$dir" "$jar";;
  2) usage;;
    3) usage;;
  *) jar="$1"; shift;
         classpath="$1"; shift;
         pattern="$1"; shift;
         arguments="$@";
    #echo "mainclass=${mainclass}"
    #echo "classpath=${classpath}"

  #echo calljar "${dir}" "${jar}" "${classpath}" "$pattern" "$arguments"
    calljar "$dir" "$jar" "$classpath" "$pattern" "$arguments"
    ;;
esac
Wolfgang Fahl
fonte
-1

Para testes rápidos e únicos de um aplicativo, você pode simplesmente vincular simbolicamente os arquivos JAR de dependência necessários ao diretório que contém o arquivo JAR do aplicativo principal.

Exemplo (para um aplicativo app.jarque usa a biblioteca Eclipse SWT, que no meu caso foi instalada /usr/share/java):

$ ln -s /usr/share/java/swt.jar .
$ java -jar app.jar
ack
fonte