Comment exécuter une classe à partir de Jar qui n'est pas la classe principale dans son fichier Manifest

167

J'ai un JAR avec 4 classes, chacune a la méthode principale. Je veux être en mesure d'exécuter chacun de ceux-ci selon les besoins. J'essaye de l'exécuter à partir de la ligne de commande sur la boîte Linux.

E.g. The name of my JAR is MyJar.jar

Il a la structure de répertoires pour les classes principales comme suit:

com/mycomp/myproj/dir1/MainClass1.class
com/mycomp/myproj/dir2/MainClass2.class
com/mycomp/myproj/dir3/MainClass3.class
com/mycomp/myproj/dir4/MainClass4.class

Je sais que je peux spécifier une classe comme principale dans mon fichier Manifest. Mais y a-t-il un moyen par lequel je peux spécifier un argument sur la ligne de commande pour exécuter la classe que je souhaite exécuter?

J'ai essayé ceci:

jar cfe MyJar.jar com.mycomp.myproj.dir2.MainClass2 com/mycomp/myproj/dir2/MainClass2.class /home/myhome/datasource.properties /home/myhome/input.txt

Et j'ai eu cette erreur:

com/mycomp/myproj/dir2/MainClass2.class : no such file or directory

(Dans la commande ci-dessus, «/home/myhome/datasource.properties» et «/home/myhome/input.txt» sont les arguments de ligne de commande).

Bhushan
la source
Il suffit de les emballer dans différents jars, en utilisant un autre jar pour contenir les dépendances?
Nick
11
Pourquoi ne pas avoir une seule classe principale qui appelle la méthode spécifique (sur les 4) basée sur des arguments de ligne de commande?
Can't Tell

Réponses:

216

Vous pouvez créer votre fichier JAR sans Main-Class dans son fichier Manifest. Ensuite :

java -cp MyJar.jar com.mycomp.myproj.dir2.MainClass2 /home/myhome/datasource.properties /home/myhome/input.txt
lxu4net
la source
2
Selon le document, cela ne fonctionnera pas: "Pour que cette option fonctionne, le manifeste du fichier JAR doit contenir une ligne de la forme Main-Class: classname."
Thomas
"Échec du chargement de l'attribut manifeste de la classe principale à partir de MyJar.jar"
Bhushan
10
Thomas a raison. vous devez remplacer "-jar" par "-cp". Voir mon code mis à jour
lxu4net
9
La réponse fournie par @chrisdew fonctionne sans avoir à changer le fichier Jar en modifiant / supprimant l'attribut Main-Class.
Ed Griebel le
157

Vous pouvez exécuter une classe qui a une public final static mainméthode à partir d' un fichier JAR, même si le fichier jar a un Main-Classdéfini .

Exécuter la classe principale:

java -jar MyJar.jar  // will execute the Main-Class

Exécutez une autre classe avec une public static void mainméthode:

java -cp MyJar.jar com.mycomp.myproj.AnotherClassWithMainMethod

Remarque: le premier utilise -jar, le second utilise -cp.

abeille fanée
la source
10
Et si vous voulez que tous les fichiers jars dans le répertoire courant soient également sur le chemin de classe (par exemple, exécuter la classe Main à partir du répertoire lib), vous pouvez utiliser java -cp "./*" com.mycomp.myproj.AnotherClassWithMainMethodNotez les guillemets doubles pour ./*empêcher les machines Unix de l'étendre. Notez également que "./*.jar"cela ne fonctionnera pas.
Petr Újezdský
19

Outre l'appel java -jar myjar.jar com.mycompany.Myclass, vous pouvez également faire de la classe principale de votre Manifest une classe Dispatcher.

Exemple:

public class Dispatcher{

    private static final Map<String, Class<?>> ENTRY_POINTS =
        new HashMap<String, Class<?>>();
    static{
        ENTRY_POINTS.put("foo", Foo.class);
        ENTRY_POINTS.put("bar", Bar.class);
        ENTRY_POINTS.put("baz", Baz.class);
    }

    public static void main(final String[] args) throws Exception{

        if(args.length < 1){
            // throw exception, not enough args
        }
        final Class<?> entryPoint = ENTRY_POINTS.get(args[0]);
        if(entryPoint==null){
            // throw exception, entry point doesn't exist
        }
        final String[] argsCopy =
            args.length > 1
                ? Arrays.copyOfRange(args, 1, args.length)
                : new String[0];
        entryPoint.getMethod("main", String[].class).invoke(null,
            (Object) argsCopy);

    }
}
Sean Patrick Floyd
la source
1
C'est une bonne alternative pour le faire! Mon jar a été créé en utilisant "spring-boot: repackage", donc je ne peux pas exécuter d'autres classes d'entrée par la méthode "-cp". Je ne sais pas ce que le plugin spring-boot a fait au pot. Dans ce cas, une classe de répartition est utile.
Zhou
6

Cette réponse est pour les utilisateurs de Spring-boot:

Si votre JAR provient d'un Spring-bootprojet et a été créé à l'aide de la commande mvn package spring-boot:repackage, la méthode "-cp" ci-dessus ne fonctionnera pas. Tu auras:

Erreur: impossible de trouver ou de charger la classe principale your.alternative.class.path

même si vous pouvez voir la classe dans le JAR par jar tvf yours.jar.

Dans ce cas, exécutez votre classe alternative par la commande suivante:

java -cp yours.jar -Dloader.main=your.alternative.class.path org.springframework.boot.loader.PropertiesLauncher

Comme je l'ai compris, la org.springframework.boot.loader.PropertiesLauncherclasse de Spring-boot sert de classe d'entrée de répartition, et le -Dloader.mainparamètre lui dit quoi exécuter.

Référence: https://github.com/spring-projects/spring-boot/issues/20404

Zhou
la source
Et si votre fichier jar est exécutable. c'est-à-dire que lorsque vous décompressez le fichier jar, toutes les classes sont précédées de "/ BOOT-INF / classes /"
slashdottir
2

Tout d'abord jarcrée un pot et ne l'exécute pas. Essayez à la java -jarplace.

Deuxièmement, pourquoi passez-vous la classe deux fois, en tant que FQCN ( com.mycomp.myproj.dir2.MainClass2) et en tant que fichier ( com/mycomp/myproj/dir2/MainClass2.class)?

Éditer:

Il semble que cela java -jarnécessite la spécification d'une classe principale. Vous pouvez essayer à la java -cp your.jar com.mycomp.myproj.dir2.MainClass2 ...place. -cpdéfinit le fichier jar sur le chemin de classe et permet à java d'y rechercher la classe principale.

Thomas
la source
J'ai fait comme mentionné ici: download.oracle.com/javase/tutorial/deployment/jar/appman.html
Bhushan
1
À partir de cette page: "Il peut être utilisé lors de la création ou de la mise à jour d'un fichier jar." - Ceci est donc utilisé pour définir l'attribut de la classe principale, pas pour exécuter le fichier jar.
Thomas
1

Une autre option similaire à laquelle je pense que Nick a brièvement fait allusion dans les commentaires est de créer plusieurs pots wrapper. Je ne l'ai pas essayé, mais je pense qu'ils pourraient être complètement vides à part le fichier manifeste, qui devrait spécifier la classe principale à charger ainsi que l'inclusion de MyJar.jar dans le classpath.

MyJar 1 .jar \ META-INF \ MANIFEST.MF

Manifest-Version: 1.0
Main-Class: com.mycomp.myproj.dir1.MainClass1
Class-Path: MyJar.jar

MyJar 2 .jar \ META-INF \ MANIFEST.MF

Manifest-Version: 1.0
Main-Class: com.mycomp.myproj.dir2.MainClass2
Class-Path: MyJar.jar

etc. Ensuite, lancez-le simplement avec java -jar MyJar2.jar

JSub
la source
-4

Ajoutez le plugin ci-dessous dans votre fichier pom.xml et spécifiez la classe principale avec le nom complet dans l'élément.

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <version>1.2.5.RELEASE</version>
    <configuration>
        <mainClass>**main Class name with full qualified name**</mainClass>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>repackage</goal>
            </goals>
        </execution>
    </executions>
</plugin>
Ajay Kumar
la source