Ferramenta para ler e exibir versões Java .class

115

Algum de vocês conhece uma ferramenta que pesquisa por arquivos .class e exibe suas versões compiladas?

Eu sei que você pode olhar para eles individualmente em um editor hexadecimal, mas eu tenho muitos arquivos de classe para examinar (algo em meu aplicativo gigante está compilando para Java6 por algum motivo).

SCdF
fonte
1
A duplicata mais popular stackoverflow.com/questions/1096148/… tem em resposta algumas ferramentas úteis não mencionadas aqui.
Vadzim

Respostas:

142

Use a ferramenta javap que vem com o JDK. A -verboseopção imprimirá o número da versão do arquivo de classe.

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

Para mostrar apenas a versão:

WINDOWS> javap -verbose MyClass | find "version"
LINUX  > javap -verbose MyClass | grep version
staffan
fonte
2
Versão major.minor = JDK / JavaSE; 45,3 = JDK1,1; 46,0 = JDK1,2; 47,0 = JDK 1,3; 48,0 = JDK 1,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

É fácil ler a assinatura do arquivo de classe e obter esses valores sem uma API de terceiros. Tudo que você precisa fazer é ler os primeiros 8 bytes.

ClassFile {
    u4 magic;
    u2 minor_version;
    u2 major_version;

Para a versão do arquivo de classe 51.0 (Java 7), os bytes de abertura são:

CA FE BA BE 00 00 00 33

... onde 0xCAFEBABE são os bytes mágicos, 0x0000 é a versão secundária e 0x0033 é a versão 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);
    }
  }
}

Andar por diretórios ( Arquivo ) e arquivos ( JarFile ) procurando por arquivos de classe é trivial.

O blog de Joe Darcy da Oracle lista a versão da classe para os mapeamentos da versão JDK até 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
fonte
Lembre-se também de que assert só é executado se estiver habilitado ao iniciar java, então você pode ler arquivos
inúteis
21

No tipo Unix

arquivo /path/to/Thing.class

Fornece também o tipo e a versão do arquivo. Esta é a aparência da saída:

dados compilados de classe Java, versão 49.0

Phunehehe
fonte
(simplificado da resposta do WMR)
phunehehe
isso é muito mais simples do que as outras soluções
mmuller
9

Se você estiver em um sistema Unix, você pode simplesmente fazer um

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

(minha versão do arquivo diz "dados de classe Java compilados, versão 50.0" para classes java6).

WMR
fonte
No macOS (10.12.6 pelo menos), a saída é ainda mais útil: file *.class produz: ClassName.class: compiled Java class data, version 50.0 (Java 1.6)
Gary
5

Ainda outra verificação de versão java

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

Em eclipse, se você não tiver fontes anexadas. Cuidado com a primeira linha após o botão de anexar fonte.

// Compilado de CDestinoLog.java ( versão 1.5: 49.0, super bit )

insira a descrição da imagem aqui

PbxMan
fonte
2

Talvez isso ajude alguém também. Parece que há uma maneira mais fácil de usar a versão JAVA para compilar / construir .class. Esta forma é útil para a autoverificação do aplicativo / classe na versão JAVA.

Eu examinei a biblioteca JDK e encontrei esta constante útil: com.sun.deploy.config.BuiltInProperties.CURRENT_VERSION . Não sei desde quando está em JAVA JDK.

Tentando este pedaço de código para várias constantes de versão, obtenho o resultado abaixo:

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

resultado:

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

No bytecode da classe, há realmente uma constante armazenada - veja a parte marcada em vermelho de Main.call - constante armazenada no bytecode .class

Constante está na classe usada para verificar se a versão JAVA está desatualizada (veja Como o Java verifica se está desatualizada ) ...

Radoslav Kastiel
fonte
1

Uma solução baseada em java usando números mágicos de versão . Abaixo, ele é usado pelo próprio programa para detectar sua versão 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
fonte
0

Esta classe Java verifica o conteúdo de todos os conteúdos WAR e JARs encontrados na lista de diretórios e imprime um resumo das versões do arquivo de classe java para cada componente, incluindo cada JAR dentro dos 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
fonte