Herramienta para leer y mostrar versiones de Java .class

115

¿Alguno de ustedes conoce alguna herramienta que busque archivos .class y luego muestre sus versiones compiladas?

Sé que puede verlos individualmente en un editor hexadecimal, pero tengo muchos archivos de clase para revisar (algo en mi aplicación gigante se está compilando en Java6 por alguna razón).

SCdF
fuente
1
El duplicado más popular stackoverflow.com/questions/1096148/… tiene en respuesta algunas herramientas útiles que no se mencionan aquí.
Vadzim

Respuestas:

142

Utilice la herramienta javap que viene con el JDK. La -verboseopción imprimirá el número de versión del archivo de clase.

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

Para mostrar solo la versión:

WINDOWS> javap -verbose MyClass | find "version"
LINUX  > javap -verbose MyClass | grep version
Staffan
fuente
2
Versión 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;
sobrino
45

Es bastante fácil leer la firma del archivo de clase y obtener estos valores sin una API de terceros. Todo lo que necesita hacer es leer los primeros 8 bytes.

ClassFile {
    u4 magic;
    u2 minor_version;
    u2 major_version;

Para la versión de archivo de clase 51.0 (Java 7), los bytes de apertura son:

CA FE BA BE 00 00 00 33

... donde 0xCAFEBABE son los bytes mágicos, 0x0000 es la versión secundaria y 0x0033 es la versión principal.

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

Caminar directorios ( Archivo ) y archivos ( JarFile ) buscando archivos de clase es trivial.

El blog de Joe Darcy de Oracle enumera la versión de la clase a las asignaciones de la versión JDK hasta 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
fuente
También recuerde que la aserción solo se ejecuta si está habilitada al iniciar java, por lo que puede leer archivos basura si no está usando IllegalArgumentException (por ejemplo)
jontejj
21

En tipo Unix

archivo /path/to/Thing.class

También le dará el tipo de archivo y la versión. Así es como se ve la salida:

datos de clase Java compilados, versión 49.0

phunehehe
fuente
(simplificado de la respuesta de WMR)
phunehehe
esto es mucho más simple que las otras soluciones
mmuller
9

Si está en un sistema Unix, puede hacer un

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

(mi versión del archivo dice "datos de clase Java compilados, versión 50.0" para clases java6).

WMR
fuente
En macOS (10.12.6 al menos), la salida es aún más útil: file *.class produce: ClassName.class: compiled Java class data, version 50.0 (Java 1.6)
Gary
5

Otra comprobación de la versión de Java

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

En eclipse si no tiene fuentes adjuntas. Tenga en cuenta la primera línea después del botón adjuntar fuente.

// Compilado de CDestinoLog.java ( versión 1.5: 49.0, super bit )

ingrese la descripción de la imagen aquí

PbxMan
fuente
2

Quizás esto también ayude a alguien. Parece que hay una manera más fácil de usar la versión JAVA para compilar / construir .class. De esta manera es útil para la autocomprobación de la aplicación / clase en la versión JAVA.

Revisé la biblioteca JDK y encontré esta constante útil: com.sun.deploy.config.BuiltInProperties.CURRENT_VERSION . No sé desde cuándo está en JAVA JDK.

Al probar este fragmento de código para varias constantes de versión, obtengo el resultado a continuación:

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

salida:

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

En el código de bytes de la clase, realmente hay una constante almacenada; vea la parte marcada en rojo de Main.call: constante almacenada en el código de bytes .class

La constante está en la clase utilizada para verificar si la versión de JAVA está desactualizada (consulte Cómo Java verifica que está desactualizada ) ...

Radoslav Kastiel
fuente
1

Una solución basada en Java que utiliza números mágicos de versión . A continuación, el propio programa lo utiliza para detectar su versión de código de bytes.

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

Esta clase de Java escanea el contenido de todos los contenidos de WAR y JAR que se encuentran en la lista de directorios e imprime un resumen de las versiones de archivo de la clase de Java para cada componente, incluido cada JAR dentro de los 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
fuente