Outil pour lire et afficher les versions Java .class

115

Connaissez-vous un outil qui recherchera les fichiers .class, puis affichera leurs versions compilées?

Je sais que vous pouvez les regarder individuellement dans un éditeur hexadécimal, mais j'ai beaucoup de fichiers de classe à examiner (quelque chose dans mon application géante est en train de compiler vers Java6 pour une raison quelconque).

SCdF
la source
1
Plus populaire en double stackoverflow.com/questions/1096148/... a en réponse quelques outils pratiques non mentionnés ici.
Vadzim le

Réponses:

142

Utilisez l' outil javap fourni avec le JDK. L' -verboseoption imprimera le numéro de version du fichier de classe.

> javap -verbose MyClass
Compiled from "MyClass.java"
public class MyClass
  SourceFile: "MyClass.java"
  minor version: 0
  major version: 46
...

Pour afficher uniquement la version:

WINDOWS> javap -verbose MyClass | find "version"
LINUX  > javap -verbose MyClass | grep version
staffan
la source
2
Version major.minor = JDK / JavaSE; 45,3 = JDK1.1; 46,0 = JDK1,2; 47,0 = JDK1,3; 48,0 = JDK1,4; 49,0 = JavaSE5 (1,5); 51,0 = JavaSE7 (1,7); 50,0 = JavaSE6 (1,6); 52,0 = JavaSE8 (1,8); 53,0 = JavaSE9; 54,0 = JavaSE10; 55,0 = JavaSE11; 56,0 = JavaSE12; 57,0 = JavaSE13; 58,0 = JavaSE14;
nephewtom
45

Il est assez facile de lire la signature du fichier de classe et d'obtenir ces valeurs sans API tierce. Tout ce que vous avez à faire est de lire les 8 premiers octets.

ClassFile {
    u4 magic;
    u2 minor_version;
    u2 major_version;

Pour la version de fichier de classe 51.0 (Java 7), les octets d'ouverture sont:

CA FE BA BE 00 00 00 33

... où 0xCAFEBABE sont les octets magiques, 0x0000 est la version mineure et 0x0033 est la version principale.

import java.io.*;

public class Demo {
  public static void main(String[] args) throws IOException {
    ClassLoader loader = Demo.class.getClassLoader();
    try (InputStream in = loader.getResourceAsStream("Demo.class");
        DataInputStream data = new DataInputStream(in)) {
      if (0xCAFEBABE != data.readInt()) {
        throw new IOException("invalid header");
      }
      int minor = data.readUnsignedShort();
      int major = data.readUnsignedShort();
      System.out.println(major + "." + minor);
    }
  }
}

Parcourir les répertoires ( File ) et les archives ( JarFile ) à la recherche de fichiers de classe est trivial.

Le blog d' Oracle Joe Darcy répertorie la version de classe avec les mappages de version JDK jusqu'à Java 7:

Target   Major.minor Hex
1.1      45.3        0x2D
1.2      46.0        0x2E
1.3      47.0        0x2F
1.4      48.0        0x30
5 (1.5)  49.0        0x31
6 (1.6)  50.0        0x32
7 (1.7)  51.0        0x33
8 (1.8)  52.0        0x34
9        53.0        0x35
McDowell
la source
Rappelez-vous également que assert n'est exécuté que s'il est activé lors du lancement de java afin que vous puissiez lire des fichiers indésirables si vous n'utilisez pas IllegalArgumentException (par exemple)
jontejj
21

Sur le type Unix

fichier /path/to/Thing.class

Donnera également le type et la version du fichier. Voici à quoi ressemble la sortie:

données de classe Java compilées, version 49.0

Phunehehe
la source
(simplifié à partir de la réponse de WMR)
phunehehe
c'est beaucoup plus simple que les autres solutions
mmuller
9

Si vous êtes sur un système Unix, vous pouvez simplement faire un

find /target-folder -name \*.class | xargs file | grep "version 50\.0"

(ma version du fichier dit "données de classe Java compilées, version 50.0" pour les classes java6).

WMR
la source
Sur macOS (10.12.6 au moins), la sortie est encore plus utile: file *.class produit: ClassName.class: compiled Java class data, version 50.0 (Java 1.6)
Gary
5

Encore une autre vérification de la version Java

od -t d -j 7 -N 1 ApplicationContextProvider.class | head -1 | awk '{print "Java", $2 - 44}'
je vais
la source
5

Dans eclipse si vous n'avez pas de sources attachées. Faites attention à la première ligne après le bouton Attacher la source.

// Compilé à partir de CDestinoLog.java ( version 1.5: 49.0, super bit )

entrez la description de l'image ici

PbxMan
la source
2

Peut-être que cela aide quelqu'un aussi. Il semble qu'il existe un moyen plus simple d'obtenir la version JAVA utilisée pour compiler / construire .class. Cette méthode est utile pour l'auto-vérification de l'application / classe sur la version JAVA.

J'ai parcouru la bibliothèque JDK et j'ai trouvé cette constante utile: com.sun.deploy.config.BuiltInProperties.CURRENT_VERSION . Je ne sais pas depuis quand il est en JAVA JDK.

En essayant ce morceau de code pour plusieurs constantes de version, j'obtiens le résultat ci-dessous:

src:

System.out.println("JAVA DEV       ver.: " + com.sun.deploy.config.BuiltInProperties.CURRENT_VERSION);
System.out.println("JAVA RUN     v. X.Y: " + System.getProperty("java.specification.version") );
System.out.println("JAVA RUN v. W.X.Y.Z: " + com.sun.deploy.config.Config.getJavaVersion() ); //_javaVersionProperty
System.out.println("JAVA RUN  full ver.: " + System.getProperty("java.runtime.version")  + " (may return unknown)" );
System.out.println("JAVA RUN       type: " + com.sun.deploy.config.Config.getJavaRuntimeNameProperty() );

production:

JAVA DEV       ver.: 1.8.0_77
JAVA RUN     v. X.Y: 1.8
JAVA RUN v. W.X.Y.Z: 1.8.0_91
JAVA RUN  full ver.: 1.8.0_91-b14 (may return unknown)
JAVA RUN       type: Java(TM) SE Runtime Environment

Dans la classe bytecode, il y a vraiment une constante stockée - voir la partie marquée en rouge de Main.call - constante stockée dans .class bytecode

La constante est dans la classe utilisée pour vérifier si la version JAVA est obsolète (voir Comment Java vérifie qu'elle est obsolète ) ...

Radoslav Kastiel
la source
1

Une solution Java utilisant des numéros magiques de version . En dessous, il est utilisé par le programme lui-même pour détecter sa version de bytecode.

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.io.IOUtils;
public class Main {
    public static void main(String[] args) throws DecoderException, IOException {
        Class clazz = Main.class;
        Map<String,String> versionMapping = new HashMap();
        versionMapping.put("002D","1.1");
        versionMapping.put("002E","1.2");
        versionMapping.put("002F","1.3");
        versionMapping.put("0030","1.4");
        versionMapping.put("0031","5.0");
        versionMapping.put("0032","6.0");
        versionMapping.put("0033","7");
        versionMapping.put("0034","8");
        versionMapping.put("0035","9");
        versionMapping.put("0036","10");
        versionMapping.put("0037","11");
        versionMapping.put("0038","12");
        versionMapping.put("0039","13");
        versionMapping.put("003A","14");

        InputStream stream = clazz.getClassLoader()
            .getResourceAsStream(clazz.getName().replace(".", "/") + ".class");
        byte[] classBytes = IOUtils.toByteArray(stream);
        String versionInHexString = 
            Hex.encodeHexString(new byte[]{classBytes[6],classBytes[7]});
        System.out.println("bytecode version: "+versionMapping.get(versionInHexString));
    }
}
Marinos An
la source
0

Cette classe Java analyse le contenu de tous les contenus WAR et JAR trouvés sous la liste des répertoires, et imprime un résumé des versions de fichier de classe Java pour chaque composant, y compris chaque JAR dans les WAR:

public class ShowClassVersions {
    private static final byte[] CLASS_MAGIC = new byte[] {(byte)0xca, (byte)0xfe, (byte)0xba, (byte)0xbe};
    private final byte[] bytes = new byte[8];
    private TreeMap<String,ArrayList<String>> vers = new TreeMap<>();

    private void scan(Path f) throws IOException {
        if (Files.isDirectory(f)) {
            Pattern pattern = Pattern.compile("\\.[wj]ar$"); // or |\\.class
            try(var stream = Files.find(f, Integer.MAX_VALUE, (p,a) -> a.isRegularFile() && pattern.matcher(p.toString()).find())) {
                stream.forEach(this::scanFile);
            }
            return;
        }
        scanFile(f);
    }
    private void scanFile(Path f) {
        String fn = f.getFileName().toString();
        try {
            if (fn.endsWith(".jar"))
                scanArchive(f);
            else if (fn.endsWith(".war"))
                scanArchive(f);
            else if (fn.endsWith(".class"))
                record(f, versionOfClass(f));
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }
    private void scanArchive(Path p) throws IOException {
        try(InputStream in = Files.newInputStream(p))  {
            scanArchive(p.toAbsolutePath().toString(), in);
        }
    }
    private String scanArchive(String desc, InputStream in) throws IOException {
        String version = null;
        ZipInputStream zip = new ZipInputStream(in);
        ZipEntry entry = null;
        while ((entry = zip.getNextEntry()) != null) {
            String name = entry.getName();
            if (version == null && name.endsWith(".class"))  {
                version = versionOfClass(zip);
            }
            else if (name.endsWith(".jar"))  {
                scanArchive(desc+" ==>> "+name, zip);
            }
        }
        if (version != null)
            record(desc, version);
        return version;
    }

    private String versionOfClass(Path p) throws IOException {
        String version = null;
        try(InputStream in = Files.newInputStream(p)) {
            version = versionOfClass(in);
        }
        return version;
    }

    private String versionOfClass(InputStream in) throws IOException {
        String version = null;
        int c = in.read(bytes);
        if (c == bytes.length && Arrays.mismatch(bytes, CLASS_MAGIC) == CLASS_MAGIC.length) {
            int minorVersion = (bytes[4] << 8) + (bytes[4] << 0);
            int majorVersion = (bytes[6] << 8) + (bytes[7] << 0);
            version = ""+majorVersion + "." + minorVersion;
        }
        return version;
    }
    private void record(String p, String v) {
        vers.computeIfAbsent(String.valueOf(v), k -> new ArrayList<String>()).add(p);
    }
    private void record(Path p, String v) {
        record(p.toAbsolutePath().toString(), v);
    }
    public static void main(String[] args) throws IOException {
        ShowClassVersions v = new ShowClassVersions();
        var files = Arrays.stream(args).map(Path::of).collect(Collectors.toList());
        for (var f : files) {
            v.scan(f);
        }
        for (var ver : v.vers.keySet()) {
            System.out.println("Version: "+ver);
            for (var p : v.vers.get(ver)) {
                System.out.println("   "+p);
            }
        };
    }
}
DuncG
la source