以编程方式确定Java类的JDK编译版本

当需要确定使用哪个JDK版本来编译特定的Java .class文件时, 通常使用的方法是使用javap并在javap输出中查找列出的“主要版本”。 我在博客文章Autoboxing,Unboxing和NoSuchMethodError中引用了这种方法,但是在继续以编程方式实现此方法之前,请先在此进行详细说明。

以下代码段演示了如何对commons-configuration-1.10.jar包含的Apache Commons Configuration类ServletFilterCommunication运行javap -verbose

javapVerboseMajorVersion49CommonsConfiguration

我在上面显示的屏幕快照中圈了“主要版本”。 在“主要版本:”之后列出的数字(在本例中为49)表示用于编译此类的JDK版本是J2SE 5 。 Java类文件的Wikipedia页面列出了与每个JDK版本相对应的“主要版本”数字:

主要版本 JDK版本
52 Java SE 8
51 Java SE 7
50 Java SE 6
49 J2SE 5
48 JDK 1.4
47 JDK 1.3
46 JDK 1.2
45 JDK 1.1

这是确定用于编译.class文件的JDK版本的简便方法,但是对目录或JAR文件中的多个类执行此操作可能会变得很乏味。 如果我们可以通过编程方式检查此主要版本,以便可以编写脚本,则会更容易。 幸运的是,Java确实支持这一点。 Matthias Ernst发布了“ 代码段:以编程方式调用javap ”,其中他演示了如何使用JDK工具JAR中的JavapEnvironment以编程方式执行javap功能,但是有一种更简便的方法来标识.class文件中指示字节的特定字节。用于编译的JDK版本。

博客文章“ 从类格式主要/次要版本信息中识别Java编译器版本 ”和StackOverflow线程“ Java API来找出要为其编译类文件的JDK版本? ”演示了如何使用DataInputStream从Java .class文件中读取相关的两个字节。

对用于编译.class文件的JDK版本的基本访问

下一个代码清单演示了访问.class文件的JDK编译版本的简约方法。

final DataInputStream input = new DataInputStream(new FileInputStream(pathFileName));
input.skipBytes(4);
final int minorVersion = input.readUnsignedShort();
final int majorVersion = input.readUnsignedShort();

该代码在感兴趣的(假定的) .class文件上实例化FileInputStream ,并且该FileInputStream用于实例化DataInputStream 。 有效.class文件的前四个字节包含数字,指示该数字是有效的Java编译类,因此被跳过。 接下来的两个字节被读取为无符号的short,代表次要版本。 在那之后是最重要的两个字节。 它们也以未签名的缩写形式读入,代表主要版本。 这个主要版本与JDK的特定版本直接相关。 Java虚拟机规范的 第4章 (“类文件格式”)中描述了这些有效字节(magic,minor_version和major_version)。

在上面的代码清单中,为方便理解,仅跳过了“魔术” 4个字节。 但是,我更喜欢检查这四个字节,以确保它们是.class文件所期望的。 JVM规范解释了这前四个字节应该是什么,“魔术项目提供了标识类文件格式的魔术数字; 它的值为0xCAFEBABE。” 下一个代码清单将修改前面的代码清单,并添加检查以确保所讨论的文件在Java编译的.class文件中。 请注意,该检查专门使用十六进制表示形式CAFEBABE以提高可读性。

final DataInputStream input = new DataInputStream(new FileInputStream(pathFileName));
// The first 4 bytes of a .class file are 0xCAFEBABE and are "used to
// identify file as conforming to the class file format."
// Use those to ensure the file being processed is a Java .class file.
final String firstFourBytes =Integer.toHexString(input.readUnsignedShort())+ Integer.toHexString(input.readUnsignedShort());
if (!firstFourBytes.equalsIgnoreCase("cafebabe"))
{throw new IllegalArgumentException(pathFileName + " is NOT a Java .class file.");
}
final int minorVersion = input.readUnsignedShort();
final int majorVersion = input.readUnsignedShort();

在检查了其中最重要的部分之后,下面的代码清单提供了我称为ClassVersion.java的Java类的完整清单。 它具有main(String[])函数,因此可以从命令行轻松使用其功能。

ClassVersion.java

import static java.lang.System.out;import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;/*** Prints out the JDK version used to compile .class files. */
public class ClassVersion
{private static final Map<Integer, String> majorVersionToJdkVersion;static{final Map<Integer, String> tempMajorVersionToJdkVersion = new HashMap<>();tempMajorVersionToJdkVersion.put(45, "JDK 1.1");tempMajorVersionToJdkVersion.put(46, "JDK 1.2");tempMajorVersionToJdkVersion.put(47, "JDK 1.3");tempMajorVersionToJdkVersion.put(48, "JDK 1.4");tempMajorVersionToJdkVersion.put(49, "J2SE 5");tempMajorVersionToJdkVersion.put(50, "Java SE 6");tempMajorVersionToJdkVersion.put(51, "Java SE 7");tempMajorVersionToJdkVersion.put(52, "Java SE 8");majorVersionToJdkVersion = Collections.unmodifiableMap(tempMajorVersionToJdkVersion);}/*** Print (to standard output) the major and minor versions of JDK that the* provided .class file was compiled with.** @param pathFileName Name of (presumably) .class file from which the major* and minor versions of the JDK used to compile that class are to be* extracted and printed to standard output.*/public static void printCompiledMajorMinorVersions(final String pathFileName){try{final DataInputStream input = new DataInputStream(new FileInputStream(pathFileName));printCompiledMajorMinorVersions(input, pathFileName);}catch (FileNotFoundException fnfEx){out.println("ERROR: Unable to find file " + pathFileName);}}/*** Print (to standard output) the major and minor versions of JDK that the* provided .class file was compiled with.** @param input DataInputStream instance assumed to represent a .class file*    from which the major and minor versions of the JDK used to compile*    that class are to be extracted and printed to standard output.* @param dataSourceName Name of source of data from which the provided*    DataInputStream came.*/public static void printCompiledMajorMinorVersions(final DataInputStream input, final String dataSourceName){  try{// The first 4 bytes of a .class file are 0xCAFEBABE and are "used to// identify file as conforming to the class file format."// Use those to ensure the file being processed is a Java .class file.final String firstFourBytes =Integer.toHexString(input.readUnsignedShort())+ Integer.toHexString(input.readUnsignedShort());if (!firstFourBytes.equalsIgnoreCase("cafebabe")){throw new IllegalArgumentException(dataSourceName + " is NOT a Java .class file.");}final int minorVersion = input.readUnsignedShort();final int majorVersion = input.readUnsignedShort();out.println(dataSourceName + " was compiled with "+ convertMajorVersionToJdkVersion(majorVersion)+ " (" + majorVersion + "/" + minorVersion + ")");}catch (IOException exception){out.println("ERROR: Unable to process file " + dataSourceName+ " to determine JDK compiled version - " + exception);}}/*** Accepts a "major version" and provides the associated name of the JDK* version corresponding to that "major version" if one exists.** @param majorVersion Two-digit major version used in .class file.* @return Name of JDK version associated with provided "major version."*/public static String convertMajorVersionToJdkVersion(final int majorVersion){return  majorVersionToJdkVersion.get(majorVersion) != null? majorVersionToJdkVersion.get(majorVersion): "Unknown JDK version for 'major version' of " + majorVersion;}public static void main(final String[] arguments){if (arguments.length < 1){out.println("USAGE: java ClassVersion <nameOfClassFile.class>");System.exit(-1);}printCompiledMajorMinorVersions(arguments[0]);}
}

下一个屏幕快照演示了如何针对自己的.class文件运行此类。

ClassVersionShowingItsOwnCompilationVersion

如PowerShell控制台的最后一个屏幕快照所示,该类的版本是使用JDK 8编译的。

有了这个ClassVersion ,我们就可以使用Java告诉我们何时编译特定的.class文件。 但是,这比简单地使用javap并手动寻找“主要版本”要容易得多。 使它更强大和更易于使用的是在脚本中使用它。 考虑到这一点,我现在将重点放在利用此类的Groovy脚本中,以识别用于编译JAR或目录中的多个.class文件的JDK版本。

下一个代码清单是可以使用ClassVersion类的Groovy脚本的示例。 该脚本演示了用于编译指定目录及其子目录中的所有.class文件的JDK版本。

displayCompiledJdkVersionsOfClassFilesInDirectory.groovy

#!/usr/bin/env groovy// displayCompiledJdkVersionsOfClassFilesInDirectory.groovy
//
// Displays the version of JDK used to compile Java .class files in a provided
// directory and in its subdirectories.
//if (args.length < 1)
{println "USAGE: displayCompiledJdkVersionsOfClassFilesInDirectory.groovy <directory_name>"System.exit(-1)
}File directory = new File(args[0])
String directoryName = directory.canonicalPath
if (!directory.isDirectory())
{println "ERROR: ${directoryName} is not a directory."System.exit(-2)
}print "\nJDK USED FOR .class COMPILATION IN DIRECTORIES UNDER "
println "${directoryName}\n"
directory.eachFileRecurse
{ file ->String fileName = file.canonicalPathif (fileName.endsWith(".class")){ClassVersion.printCompiledMajorMinorVersions(fileName)}
}
println "\n"

接下来显示的是刚刚列出的脚本生成的输出示例。

带有已编译JdkVersion的allClassesInDirectoryShown

接下来显示另一个Groovy脚本,该脚本可用于标识用于编译指定目录或其子目录之一中的任何JAR文件中的.class文件的JDK版本。

displayCompiledJdkVersionsOfClassFilesInJar.groovy

#!/usr/bin/env groovy// displayCompiledJdkVersionsOfClassFilesInJar.groovy
//
// Displays the version of JDK used to compile Java .class files in JARs in the
// specified directory or its subdirectories.
//if (args.length < 1)
{println "USAGE: displayCompiledJdkVersionsOfClassFilesInJar.groovy <jar_name>"System.exit(-1)
}import java.util.zip.ZipFile
import java.util.zip.ZipExceptionString rootDir = args ? args[0] : "."
File directory = new File(rootDir)
directory.eachFileRecurse
{ file->if (file.isFile() && file.name.endsWith("jar")){try{zip = new ZipFile(file)entries = zip.entries()entries.each{ entry->if (entry.name.endsWith(".class")){println "${file}"print "\t"ClassVersion.printCompiledMajorMinorVersions(new DataInputStream(zip.getInputStream(entry)), entry.name)}}}catch (ZipException zipEx){println "Unable to open file ${file.name}"}}
}
println "\n"

接下来显示针对本文前面部分使用的JAR运行此脚本的输出的早期部分。 JAR中包含的所有.class文件都具有JDK的版本,它们针对打印到标准输出进行编译。

allClassesInJarCompiledVersionsScriptOutput

其他想法

刚刚显示的脚本演示了一些实用程序,这些实用程序是通过能够以编程方式访问用于编译Java类的JDK版本而实现的。 这里是增强这些脚本的其他一些想法。 在某些情况下,我使用了这些增强功能,但此处未显示它们以保持更好的清晰度并避免使发布时间更长。

  • ClassVersion.java可能是用Groovy编写的。
  • ClassVersion.java返回单个信息而不是将其打印到标准输出,则其功能将更加灵活。 同样,即使返回返回的字符串,也比假设调用者希望将输出写入标准输出更为灵活。
  • 这将是容易巩固上述脚本指示用于编译个体JDK版本.class以及在目录中直接访问文件.class包含在JAR文件的文件从相同的脚本。
  • 演示脚本的一种有用的变体是返回在特定版本的JDK之前或之后,使用特定版本的JDK编译的所有.class文件的脚本。

结论

这篇文章的目的是演示以编程方式确定用于将Java源代码编译为.class文件的JDK版本。 文章演示了基于JVM类文件结构的“主要版本”字节确定用于编译的JDK版本,然后演示了如何使用Java API读取和处理.class文件,以及识别用于编译它们的JDK版本。 最后,用Groovy编写的几个示例脚本演示了以编程方式访问此信息的价值。

翻译自: https://www.javacodegeeks.com/2015/02/programmatically-determining-java-classs-jdk-compilation-version.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/360645.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何使用Spring Security和Basic身份验证保护Jersey REST服务

在我之前的博客文章“ 检查REST API是否有效的快速方法–从清单文件中获取GET详细信息”中 &#xff0c;我展示了如何开发REST资源以轻松检查开发的REST API是否可用。 在本文中&#xff0c;我将介绍如何使用Spring Security和基本身份验证来保护此资源的安全性– “在HTTP事务…

python提取数据段_python提取数据段 python数据分析

如何在python中用slice分段取数据&#xff1f;执行以下操作&#xff1a;&gt&gt arange(6)&gt&gt a[0&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5]&gt&gt a[0:3]&#xff0c;a[5][[2&#xff0c;3&#xff0c;4&#xff0c;4&a…

一个JAXB Nuance:字符串与枚举(受限制的XSD字符串)

尽管用于XML绑定的Java体系结构 &#xff08; JAXB &#xff09;在名义情况下&#xff08;尤其是自Java SE 6以来&#xff09; 相当容易使用&#xff0c;但它也存在许多细微差别。 一些常见的细微差别是由于无法将 XML模式定义 &#xff08;XSD&#xff09;类型与Java 类型精确…

休眠锁定模式– OPTIMISTIC_FORCE_INCREMENT锁定模式如何工作

介绍 在我以前的文章中 &#xff0c;我解释了OPTIMISTIC锁定模式是如何工作的&#xff0c;以及它如何帮助我们同步外部实体状态更改。 在本文中&#xff0c;我们将介绍OPTIMISTIC_FORCE_INCREMENT锁定模式的使用模式。 使用LockModeType.OPTIMISTIC &#xff0c;将在当前正在运…

设置本地Nexus存储库并从Maven部署WAR文件

Maven Central充当中央存储库管理器&#xff0c;其中二进制工件由不同的团队/公司/个人上载并与世界其他地方共享。 就像github和其他对源代码控制非常有效的源代码存储库一样&#xff0c;这些存储库管理器还充当您自己生成的二进制工件的部署目标。 设置本地存储库管理器具有…

mac solr mysql 配置文件_Solr配置文件浅析

接上一篇Linux下安装solr7.4&#xff0c;来谈谈solr的配置文件schema.xml和db-data-config.xml首先看schema.xml&#xff1a;idfield标签用来定义solr core中的字段。这里列出的三个字段如果没有特殊原因尽量保留。字段id被声明为uniqueKey,是让id来唯一标明一个solrdocument。…

JSF:在正确的阶段进行验证(了解生命周期)

嗨&#xff0c;大家好&#xff01; 尽管标题强调验证一词&#xff0c;但本文实际上是关于JSF生命周期的。 那是因为我相信&#xff0c;真正了解生命周期的最简单方法之一就是通过做出我们一直在做的事情&#xff1a;验证用户输入。 总的来说&#xff0c;理解所谓的JSF生命周期…

OpenShift v3:使用WildFly和MySQL的Java EE 7入门

OpenShift是Red Hat的开源PaaS平台。 OpenShift v3 &#xff08;将于今年发布&#xff09;将提供使用Docker和Kubernetes运行微服务的整体体验。 以经典的Red Hat方式&#xff0c;所有工作都在OpenShift Origin的开源中完成。 这也将推动OpenShift Online和OpenShift Enterpris…

mySQL日期函数并运行_mysql日期相关的函数

1、获取当前时间&#xff1a;/**获得当前日期时间(date time)函数&#xff1a;now(), 常用**/select now() fromdual;/**获取当前时间戳&#xff0c;current_timestamp或者current_timestamp()**/select current_timestamp, current_timestamp() fromdual;/**获得当前日期时间…

序列化对象C++对象的JSON序列化与反序列化探索

新手发帖&#xff0c;很多方面都是刚入门&#xff0c;有错误的地方请大家见谅&#xff0c;欢迎批评指正 一&#xff1a;背景 作为一名C开发人员&#xff0c;我始终很期待能够像C#与JAVA那样&#xff0c;可以省力的进行对象的序列化与反序列化&#xff0c;但到现在为止&#xff…

python socket udp并发_Python进阶----UDP协议使用socket通信,socketserver模块实现并发

Python进阶----UDP协议使用socket通信,socketserver模块实现并发一丶基于UDP协议的socket实现UDP协议传输数据代码如下:&#x1f447;### 客户端# -*-coding:utf-8-*-# Author:Dsimport socket# 实例化UDP协议的socket对象 ,配置参数, socket.SOCK_DGRAM(数据报)udp_clisocket.…

Java IO基准测试:Quasar与异步ForkJoinPool与ManagedBlock

“ Arien看到了我们运行的parallelStreams和ForkJoin基准测试的结果后&#xff0c;在Twitter上与我们联系。 这激起了他的兴趣&#xff0c;因此他进行了一些自己的测试&#xff0c;将Quasar纤维加入了混合物。 这是他的结果和结论。” –塔基皮&#xff08;Takipi&#xff09;A…

WP8开发札记(一)WP8应用生命周期管理

在介绍生命周期前&#xff0c;我们先了解两个相关的概念。 1、墓碑机制&#xff1a;WP8与Android采用的真后台机制不同&#xff0c;WP8采用的是墓碑机制。一旦从当前应用程序离开&#xff08;非退出&#xff09;&#xff0c;该应用会被墓碑化&#xff0c;这样可以更好的管理&am…

python类继承中构造方法_第8.3节 Python类的__init__方法深入剖析:构造方法与继承详解...

第8.3节Python类的__init__方法深入剖析&#xff1a;构造方法与继承详解一、 引言上两节介绍了构造方法的语法及参数&#xff0c;说明了构造方法是Python的类创建实例后首先执行的方法&#xff0c;并说明如果类没有重写构造方法&#xff0c;Python将会给出默认的__init__方法…

OpenShift DIY:使用Gradle构建Spring Boot / Undertow应用程序

由于此bug&#xff0c; Gradle 1.6是在OpenShift上运行的最后一个受支持的Gradle版本。 但是从Gradle 2.2开始&#xff0c;这不再是问题&#xff0c;因此使用自己动手做墨盒在OpenShift上运行最新的Gradle不再是问题。 DIY墨盒是一种实验性墨盒&#xff0c;它提供了一种在OpenS…

使用JAX-RS和Jetty创建Web服务和Rest Server

用Java创建WebService非常容易。 将其添加到ServletContainer并将其部署到嵌入式WebServer仅需要几行代码。 让我们创建一个具有两个函数的简单计算器&#xff0c;作为WebService的示例。 计算器将计算任何数量的squareRoot和平方。 它将返回一个简单的JSON响应&#xff0c;其…

maya 中使用节点连接来求余数:

绑个东西要用到求余&#xff0c;不喜欢用表达式&#xff0c;就想用节点连出来&#xff0c;找了下网上只有 镀金铆钉 在火星时代上的教程&#xff0c;不过不能下载了&#xff0c;就自己想了下&#xff0c;终于搞出来了&#xff0c;做下笔记&#xff0c;不要忘了。 求余的思路&a…

java web 登录界面案例_【JavaWeb】74:写一个登录案例

今天是刘小爱自学Java的第74天。感谢你的观看&#xff0c;谢谢你。话不多说&#xff0c;开始今天的学习&#xff1a;Java又常被称之为后台开发。什么叫后台呢&#xff1f;除了后台还有什么前台、前端后端……这些概念一大堆&#xff0c;还容易弄混。以一个三层架构的知识点来引…

7种JIRA集成可优化Java开发流程

有哪些最佳集成可以用来优化JIRA工作流程&#xff1f; 我喜欢寻找在工作流程中提高效率的方法。 看着那些小的自动化和流畅的流程&#xff0c;使我的脸上露出笑容。 我知道我并不孤单&#xff0c;偶尔花更多的时间来获得一点点提升以使其正常工作&#xff0c;而不是最终节省了我…

hive处理日志,自定义inputformat

开放环境&#xff0c;hadoop-0.20.2&#xff0c;hive-0.6 1.日志分隔符 Xml代码 2010-05-31 10:50:17|||61.132.4.82|||http://www.360buy.com/product/201185.html 2010-05-31 10:50:17|||61.132.4.82|||http://www.360buy.com/product/201185.html分隔符是“ ||| ”&#xf…