Strumento per leggere e visualizzare le versioni .class di Java


115

Qualcuno di voi conosce uno strumento che cercherà i file .class e quindi visualizzerà le loro versioni compilate?

So che puoi guardarli singolarmente in un editor esadecimale ma ho molti file di classe da controllare (qualcosa nella mia applicazione gigante si sta compilando in Java6 per qualche motivo).


1
Il più popolare duplicato stackoverflow.com/questions/1096148/… ha in risposta alcuni strumenti utili non menzionati qui.
Vadzim

Risposte:


142

Usa lo strumento javap fornito con JDK. L' -verboseopzione stamperà il numero di versione del file di classe.

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

Per mostrare solo la versione:

WINDOWS> javap -verbose MyClass | find "version"
LINUX  > javap -verbose MyClass | grep version

2
Versione 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;
nipote

45

È abbastanza facile leggere la firma del file di classe e ottenere questi valori senza un'API di terze parti. Tutto quello che devi fare è leggere i primi 8 byte.

ClassFile {
    u4 magic;
    u2 minor_version;
    u2 major_version;

Per la versione del file di classe 51.0 (Java 7), i byte di apertura sono:

CA FE BA BE 00 00 00 33

... dove 0xCAFEBABE sono i byte magici, 0x0000 è la versione secondaria e 0x0033 è la versione 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);
    }
  }
}

Scorrere directory ( File ) e archivi ( JarFile ) alla ricerca di file di classe è banale.

Il blog di Oracle Joe Darcy elenca la versione della classe per i mapping della versione JDK fino a 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

Ricorda inoltre che assert viene eseguito solo se è abilitato all'avvio di java, quindi potresti leggere file spazzatura se non stai utilizzando IllegalArgumentException (ad esempio)
jontejj

21

Su Unix-like

file /path/to/Thing.class

Fornirà anche il tipo di file e la versione. Ecco come appare l'output:

dati di classe Java compilati, versione 49.0


(semplificato dalla risposta di WMR)
phunehehe

questo è molto più semplice delle altre soluzioni
mmuller

9

Se sei su un sistema unix potresti semplicemente fare un file

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

(la mia versione del file dice "dati compilati della classe Java, versione 50.0" per le classi java6).


Su macOS (10.12.6 almeno), l'output è ancora più utile: file *.class produce: ClassName.class: compiled Java class data, version 50.0 (Java 1.6)
Gary

5

Ancora un altro controllo della versione di Java

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

5

In eclipse se non hai fonti allegate. Fai attenzione alla prima riga dopo il pulsante di collegamento della sorgente.

// Compilato da CDestinoLog.java ( versione 1.5: 49.0, super bit )

inserisci qui la descrizione dell'immagine


2

Forse anche questo aiuta qualcuno. Sembra che ci sia un modo più semplice per ottenere la versione JAVA utilizzata per compilare / costruire .class. In questo modo è utile per l'autoverifica dell'applicazione / classe sulla versione JAVA.

Ho esaminato la libreria JDK e ho trovato questa utile costante: com.sun.deploy.config.BuiltInProperties.CURRENT_VERSION . Non lo so da quando è in JAVA JDK.

Provando questo pezzo di codice per diverse costanti di versione ottengo il risultato di seguito:

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() );

produzione:

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

Nel bytecode di classe c'è davvero una costante memorizzata - vedi la parte contrassegnata in rosso di Main.call - costante memorizzata in .class bytecode

La costante è nella classe utilizzata per verificare se la versione JAVA non è aggiornata (vedere Come Java verifica che non sia aggiornata ) ...


1

Una soluzione basata su Java che utilizza numeri magici di versione . Di seguito viene utilizzato dal programma stesso per rilevare la sua versione del 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));
    }
}

0

Questa classe Java analizza il contenuto di tutti i contenuti WAR e JAR trovati nell'elenco delle directory e stampa il riepilogo delle versioni dei file di classe Java per ogni componente, incluso ogni JAR all'interno di 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);
            }
        };
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.