Alat untuk membaca dan menampilkan versi Java .class

115

Apakah ada di antara Anda yang mengetahui alat yang akan mencari file .class dan kemudian menampilkan versi kompilasi mereka?

Saya tahu Anda dapat melihatnya satu per satu di editor hex tetapi saya memiliki banyak file kelas untuk dilihat (sesuatu di aplikasi raksasa saya sedang mengkompilasi ke Java6 untuk beberapa alasan).

SCdF
sumber
1
Duplikat stackoverflow.com/questions/1096148/… yang lebih populer telah menjawab beberapa alat praktis yang tidak disebutkan di sini.
Vadzim

Jawaban:

142

Gunakan alat javap yang disertakan dengan JDK. The -verbosepilihan akan mencetak nomor versi file kelas.

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

Untuk hanya menampilkan versinya:

WINDOWS> javap -verbose MyClass | find "version"
LINUX  > javap -verbose MyClass | grep version
staffan
sumber
2
Versi 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;
keponakan
45

Cukup mudah untuk membaca tanda tangan file kelas dan mendapatkan nilai ini tanpa API pihak ketiga. Yang perlu Anda lakukan hanyalah membaca 8 byte pertama.

ClassFile {
    u4 magic;
    u2 minor_version;
    u2 major_version;

Untuk file kelas versi 51.0 (Java 7), byte pembukanya adalah:

CA FE BA BE 00 00 00 33

... di mana 0xCAFEBABE adalah byte ajaib, 0x0000 adalah versi minor dan 0x0033 adalah versi mayor.

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

Berjalan direktori ( File ) dan arsip ( JarFile ) mencari file kelas itu sepele.

Blog Oracle Joe Darcy mencantumkan versi kelas untuk pemetaan versi JDK hingga 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
sumber
Ingat juga bahwa assert hanya dijalankan jika diaktifkan saat meluncurkan java sehingga Anda dapat membaca file sampah jika Anda tidak menggunakan IllegalArgumentException (misalnya)
jontejj
21

Pada Unix-like

file /path/to/Thing.class

Akan memberikan tipe file dan versinya juga. Seperti inilah hasilnya:

mengkompilasi data kelas Java, versi 49.0

phunehehe
sumber
(disederhanakan dari jawaban WMR)
phunehehe
ini jauh lebih sederhana daripada solusi lain
mmuller
9

Jika Anda menggunakan sistem unix, Anda bisa melakukan

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

(Versi file saya mengatakan "data kelas Java yang dikompilasi, versi 50.0" untuk kelas java6).

WMR
sumber
Di macOS (setidaknya 10.12.6), outputnya bahkan lebih berguna: file *.class menghasilkan: ClassName.class: compiled Java class data, version 50.0 (Java 1.6)
Gary
5

Namun pemeriksaan versi java lainnya

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

Dalam gerhana jika Anda tidak memiliki sumber terlampir. Perhatikan baris pertama setelah tombol lampirkan sumber.

// Dikompilasi dari CDestinoLog.java ( versi 1.5: 49.0, super bit )

masukkan deskripsi gambar di sini

PbxMan
sumber
2

Mungkin ini membantu seseorang juga. Sepertinya ada cara yang lebih mudah untuk mendapatkan versi JAVA yang digunakan untuk kompilasi / build .class. Cara ini berguna untuk pemeriksaan mandiri aplikasi / kelas pada versi JAVA.

Saya telah melalui perpustakaan JDK dan menemukan konstanta yang berguna ini: com.sun.deploy.config.BuiltInProperties.CURRENT_VERSION . Entah sejak kapan sudah di JAVA JDK.

Mencoba potongan kode ini untuk beberapa konstanta versi, saya mendapatkan hasil di bawah ini:

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

keluaran:

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

Dalam bytecode kelas ada konstanta yang benar-benar tersimpan - lihat bagian bertanda merah dari Main.call - konstanta yang disimpan dalam bytecode .class

Konstanta ada di kelas yang digunakan untuk memeriksa apakah versi JAVA sudah kedaluwarsa (lihat Cara memeriksa Java yang sudah kedaluwarsa ) ...

Radoslav Kastiel
sumber
1

Solusi berbasis java menggunakan nomor ajaib versi . Di bawah ini digunakan oleh program itu sendiri untuk mendeteksi versi bytecode-nya.

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
sumber
0

Kelas Java ini memindai konten dari semua konten WAR dan JAR yang ditemukan di bawah daftar direktori, dan mencetak ringkasan versi file kelas java untuk setiap komponen termasuk setiap JAR dalam WARs:

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
sumber