Java 中文官方教程 2022 版(四十三)
课程:使扩展安全
现在您已经了解如何使用扩展,您可能想知道扩展具有哪些安全权限。例如,如果您正在开发一个涉及文件 I/O 的扩展,您需要了解如何为读写文件授予适当的权限。相反,如果您考虑使用他人开发的扩展,您将希望清楚地了解扩展具有哪些安全权限,以及如何在需要时更改这些权限。
本课程向您展示了 Java™ 平台的安全架构如何处理扩展。您将了解到如何查看授予扩展软件的权限,并学会通过一些简单的步骤修改扩展权限。此外,您还将学习如何封装扩展中的包,以限制对代码指定部分的访问。
本课程分为两个部分:
为扩展设置权限
本节包含一些示例,展示了扩展必须满足哪些条件才能被授予权限执行安全敏感操作。
封装扩展中的包
您可以选择在扩展 JAR 文件中封装包作为额外的安全措施。如果一个包被封装,这意味着该包中定义的所有类必须来自一个单独的 JAR 文件。本节将向您展示如何修改扩展清单以封装扩展包。
附加文档
您将在本课程的适当位置找到与安全相关的链接和参考文档。
要获取有关安全的完整信息,您可以参考以下内容:
-
Java SE 中的安全功能教程(在本教程中)
为扩展设置权限
如果有安全管理器生效,那么必须满足以下条件,以使任何软件,包括扩展软件,能够执行安全敏感操作:
-
扩展中的安全敏感代码必须包装在
PrivilegedAction对象中。 -
安全管理器实施的安全策略必须授予扩展适当的权限。默认情况下,安装的扩展被授予所有安全权限,就好像它们是核心平台 API 的一部分。安全策略授予的权限仅适用于包装在
PrivilegedAction实例中的代码。
让我们更详细地看一下这些条件,附带一些示例。
使用PrivilegedAction类
假设你想要修改上一课程示例中扩展示例中的RectangleArea类,将矩形面积写入文件而不是标准输出。然而,向文件写入是一个涉及安全的操作,因此如果你的软件将在安全管理器下运行,你需要将你的代码标记为特权代码。你需要执行两个步骤来实现这一点:
-
你需要将执行安全敏感操作的代码放在类型为
java.security.PrivilegedAction的对象的run方法中。 -
你必须将该
PrivilegedAction对象作为参数传递给java.security.AccessController的doPrivileged方法。
如果我们将这些准则应用于RectangleArea类,我们的类定义将如下所示:
import java.io.*;
import java.security.*;
public final class RectangleArea {
public static void
writeArea(final java.awt.Rectangle r) {
AccessController.
doPrivileged(new PrivilegedAction() {
public Object run() {
try {
int area = r.width * r.height;
String userHome = System.getProperty("user.home");
FileWriter fw = new FileWriter( userHome + File.separator
+ "test" + File.separator + "area.txt");
fw.write("The rectangle's area is " + area);
fw.flush();
fw.close();
} catch(IOException ioe) {
System.err.println(ioe);
}
return null;
}
});
}
}
这个类中的唯一方法writeArea计算矩形的面积,并将面积写入名为area.txt的文件中,该文件位于用户主目录下的test目录中。
处理输出文件的安全敏感语句必须放在新的PrivilegedAction实例的run方法中。(注意,run要求返回一个Object实例。返回的对象可以是null。)然后将新的PrivilegedAction实例作为参数传递给AccessController.doPrivileged的调用。
有关使用doPrivileged的更多信息,请参阅JDK™文档中的特权块 API。
以这种方式将安全敏感代码包装在PrivilegedAction对象中是使扩展能够执行安全敏感操作的第一个要求。第二个要求是:让安全管理器授予特权代码适当的权限。
使用安全策略指定权限
运行时生效的安全策略由策略文件指定。默认策略由 JRE 软件中的lib/security/java.policy文件设置。
策略文件通过grant条目为软件分配安全权限。策略文件可以包含任意数量的 grant 条目。默认策略文件为安装的扩展程序具有以下 grant 条目:
grant codeBase "file:${{java.ext.dirs}}/*" {
permission java.security.AllPermission;
};
此条目指定由file:${{java.ext.dirs}}/*指定的目录中的文件将被授予名为java.security.AllPermission的权限。 (请注意,从 Java 6 开始,java.ext.dirs指的是类似classpath的目录路径,每个目录都可以容纳安装的扩展。)不难猜到java.security.AllPermission授予安装的扩展程序可以授予的所有安全权限。
默认情况下,安装的扩展程序没有安全限制。只要安全敏感代码包含在作为doPrivileged调用参数传递的PrivilegedAction实例中,扩展软件就可以执行安全敏感操作,就好像没有安装安全管理器一样。
要限制授予扩展程序的权限,您需要修改策略文件。要拒绝所有扩展程序的所有权限,您可以简单地删除上述 grant 条目。
并非所有权限都像默认授予的java.security.AllPermission那样全面。删除默认 grant 条目后,您可以为特定权限输入新的 grant 条目,包括:
-
java.awt.**AWTPermission** -
java.io.**FilePermission** -
java.net.**NetPermission** -
java.util.**PropertyPermission** -
java.lang.reflect.**ReflectPermission** -
java.lang.**RuntimePermission** -
java.security.**SecurityPermission** -
java.io.**SerializablePermission** -
java.net.**SocketPermission**
JDK 中的权限文档提供了关于每个权限的详细信息。让我们看看使用 RectangleArea 作为扩展所需的权限。
RectangleArea.writeArea方法需要两个权限:一个用于确定用户主目录的路径,另一个用于写入文件。假设RectangleArea类打包在文件area.jar中,您可以通过向策略文件添加以下条目来授予写入权限:
grant codeBase "file:${java.home}/lib/ext/area.jar" {
permission java.io.PropertyPermission "user.home",
"read";
permission java.io.FilePermission
"${user.home}${/}test${/}*", "write";
};
此条目的codeBase "file:${java.home}/lib/ext/area.jar"部分保证此条目指定的权限仅适用于area.jar。 java.io.PropertyPermission允许访问属性。 第一个参数"user.home"指定属性的名称,第二个参数"read"表示可以读取该属性。(另一个选择是"write"。)
java.io.FilePermission允许访问文件。第一个参数"${user.home}${/}test${/}*"表示area.jar被授予访问用户主目录中test目录中所有文件的权限。(请注意${/}是一个平台无关的文件分隔符。)第二个参数表示被授予的文件访问权限仅限于写入。(第二个参数的其他选择是"read"、"delete"和"execute"。)
签署扩展程序
你可以使用策略文件对扩展程序授予的权限施加额外限制,要求它们必须由受信任的实体签名。(有关签署和验证 JAR 文件的审查,请参阅本教程中的签署 JAR 文件课程。)
为了允许在授予权限时与扩展程序或其他软件一起进行签名验证,策略文件必须包含一个keystore 条目。密钥库条目指定用于验证的密钥库。密钥库条目的形式为
keystore "*keystore_url*";
URL keystore_url可以是绝对或相对的。如果是相对的,URL 是相对于策略文件的位置。例如,要使用keytool使用的默认密钥库,将此条目添加到java.policy中
keystore "file://${user.home}/.keystore";
要指示必须签署扩展程序才能被授予安全权限,您可以使用signedBy字段。例如,以下条目指示只有当扩展程序area.jar由密钥库中的别名为 Robert 和 Rita 的用户签名时,才授予列出的权限:
grant signedBy "Robert,Rita",
codeBase "file:${java.home}/lib/ext/area.jar" {
permission java.io.PropertyPermission
"user.home", "read";
permission java.io.FilePermission
"${user.home}${/}test${/}*", "write";
};
如果codeBase字段被省略,如下所示的"grant",则权限将授予任何由"Robert"或"Rita"签名的软件,包括已安装或下载的扩展程序:
grant signedBy "Robert,Rita" {
permission java.io.FilePermission "*", "write";
};
有关策略文件格式的更多详细信息,请参阅 JDK 文档中的安全体系结构规范第 3.3.1 节。
在扩展中封存包
原文:
docs.oracle.com/javase/tutorial/ext/security/sealing.html
您可以选择性地封存扩展 JAR 文件中的包作为额外的安全措施。如果一个包被封存,那么该包中定义的所有类必须来自一个单独的 JAR 文件。
没有封存,一个“敌对”的程序可以创建一个类并将其定义为您的扩展包的成员。然后,敌对软件将自由访问您的扩展包的包保护成员。
在扩展中封存包与封存任何 JAR 打包的类没有区别。要封存您的扩展包,您必须向包含您的扩展的 JAR 文件的清单中添加Sealed头部。您可以通过将Sealed头部与包的Name头部关联来封存单个包。在存档中未与单个包关联的Sealed头部表示所有包都被封存。这样的“全局”Sealed头部将被与单个包关联的任何Sealed头部覆盖。与Sealed头部关联的值要么是true,要么是false。
例子
让我们看几个示例清单文件。在这些示例中,假设 JAR 文件包含这些包:
com/myCompany/package_1/
com/myCompany/package_2/
com/myCompany/package_3/
com/myCompany/package_4/
假设您想要封存所有包。您可以通过简单地向清单中添加一个存档级别的Sealed头部来实现:
Manifest-Version: 1.0
Sealed: true
任何具有这个清单的 JAR 文件中的所有包都将被封存。
如果您只想封存com.myCompany.package_3,您可以通过这个清单来实现:
Manifest-Version: 1.0
Name: com/myCompany/package_3/
Sealed: true
在这个例子中,唯一的Sealed头部是与包com.myCompany.package_3的Name头部相关联的,因此只有该包被封存。(Sealed头部与Name头部相关联,因为它们之间没有空行。)
最后一个例子,假设您想要封存所有包除了com.myCompany.package_2。您可以通过这样的清单来实现:
Manifest-Version: 1.0
Sealed: true
Name: com/myCompany/package_2/
Sealed: false
在这个例子中,存档级别的Sealed: true头部表示 JAR 文件中的所有包都将被封存。然而,清单中还有一个与包com.myCompany.package_2相关联的Sealed: false头部,该头部覆盖了该包的存档级别封存。因此,这个清单将导致所有包都被封存,除了com.myCompany.package_2。
路径:反射 API
反射的用途
反射通常被需要能够检查或修改在 Java 虚拟机中运行的应用程序的运行时行为的程序所使用。这是一个相对高级的特性,应该只由对语言基础知识有很好掌握的开发人员使用。在这种情况下,反射是一种强大的技术,可以使应用程序执行原本不可能的操作。
可扩展性特性
应用程序可以通过使用完全限定名称创建可扩展性对象的实例来利用外部、用户定义的类。
类浏览器和可视化开发环境
类浏览器需要能够枚举类的成员。可视化开发环境可以从反射中可用的类型信息中受益,帮助开发人员编写正确的代码。
调试器和测试工具
调试器需要能够检查类的私有成员。测试工具可以利用反射系统地调用类中定义的可发现的一组 API,以确保测试套件中的代码覆盖率较高。
反射的缺点
反射很强大,但不应该被滥用。如果可以在不使用反射的情况下执行操作,则最好避免使用它。在通过反射访问代码时应牢记以下问题。
性能开销
因为反射涉及动态解析的类型,某些 Java 虚拟机优化无法执行。因此,反射操作比非反射操作性能较慢,并且应该避免在性能敏感应用程序中频繁调用的代码段中使用。
安全限制
反射需要运行时权限,当在安全管理器下运行时可能不存在。这是一个重要的考虑因素,对于必须在受限制的安全上下文中运行的代码,比如在 Applet 中。
内部信息的暴露
由于反射允许代码执行在非反射代码中非法的操作,比如访问private字段和方法,使用反射可能导致意想不到的副作用,可能使代码失效并破坏可移植性。反射代码打破了抽象,因此可能会随着平台升级而改变行为。
路径课程
本路径涵盖了通过反射访问和操作类、字段、方法和构造函数的常见用法。每个课程包含代码示例、提示和故障排除信息。
本课程展示了获取Class对象的各种方法,并使用它来检查类的属性,包括其声明和内容。
本课程描述了如何使用 Reflection API 查找类的字段、方法和构造函数。提供了设置和获取字段值、调用方法以及使用特定构造函数创建对象实例的示例。
这节课介绍了两种特殊类型的类:在运行时生成的数组和定义唯一命名对象实例的enum类型。示例代码展示了如何检索数组的组件类型以及如何使用数组或enum类型设置和获取字段。
注意:
本教程中的示例旨在用于实验 Reflection API。因此,异常处理与在生产代码中使用的方式不同。特别是,在生产代码中,不建议将对用户可见的堆栈跟踪信息输出。
课程:类
每种类型都是引用类型或基本类型。类、枚举和数组(它们都继承自java.lang.Object)以及接口都是引用类型。引用类型的示例包括java.lang.String、所有基本类型的包装类,如java.lang.Double、接口java.io.Serializable和枚举javax.swing.SortOrder。基本类型有一组固定的类型:boolean、byte、short、int、long、char、float和double。
对于每种类型的对象,Java 虚拟机实例化一个不可变的java.lang.Class实例,该实例提供了用于检查对象的运行时属性的方法,包括其成员和类型信息。Class还提供了创建新类和对象的能力。最重要的是,它是所有反射 API 的入口点。本课程涵盖了涉及类的最常用的反射操作:
检索类对象
原文:
docs.oracle.com/javase/tutorial/reflect/class/classNew.html
所有反射操作的入口点是java.lang.Class。除了java.lang.reflect.ReflectPermission之外,java.lang.reflect中的类都没有公共构造函数。要访问这些类,需要在Class上调用适当的方法。有几种方法可以获取Class,具体取决于代码是否可以访问对象、类的名称、类型或现有的Class。
Object.getClass()
如果对象的实例可用,则获取其Class的最简单方法是调用Object.getClass()。当然,这仅适用于所有继承自Object的引用类型。以下是一些示例。
Class c = "foo".getClass();
Class c = System.console().getClass();
与虚拟机关联的唯一控制台由static方法System.console()返回。getClass()返回的值是对应于java.io.Console的Class。
enum E { A, B }
Class c = A.getClass();
A是枚举E的一个实例;因此getClass()返回对应于枚举类型E的Class。
byte[] bytes = new byte[1024];
Class c = bytes.getClass();
由于数组是Objects,因此也可以在数组实例上调用getClass()。返回的Class对应于具有组件类型byte的数组。
import java.util.HashSet;
import java.util.Set;
Set<String> s = new HashSet<String>();
Class c = s.getClass();
在这种情况下,java.util.Set是一个指向类型为java.util.HashSet的对象的接口。getClass()返回的值是与java.util.HashSet对应的类。
.class 语法
如果类型可用但没有实例,则可以通过在类型名称后附加".class"来获得一个Class。这也是获取原始类型对应的Class的最简单方式。
boolean b;
Class c = b.getClass(); // compile-time error
Class c = boolean.class; // correct
请注意,语句boolean.getClass()会产生编译时错误,因为boolean是原始类型,不能被解引用。.class语法返回与类型boolean对应的Class。
Class c = java.io.PrintStream.class;
变量c将是与类型java.io.PrintStream对应的Class。
Class c = int[][][].class;
.class语法可用于检索与给定类型的多维数组对应的Class。
Class.forName()
如果类的完全限定名称可用,则可以使用静态方法Class.forName()获取相应的Class。这不能用于原始类型。数组类名称的语法由Class.getName()描述。此语法适用于引用和原始类型。
Class c = Class.forName("com.duke.MyLocaleServiceProvider");
此语句将根据给定的完全限定名称创建一个类。
Class cDoubleArray = Class.forName("[D");
Class cStringArray = Class.forName("[[Ljava.lang.String;");
变量cDoubleArray将包含与原始类型double的数组对应的Class(即与double[].class相同)。变量cStringArray将包含与String的二维数组对应的Class(即与String[][].class相同)。
原始类型包装器的 TYPE 字段
.class 语法是获取原始类型的 Class 更方便且更常用的方式;然而还有另一种获取 Class 的方法。每种原始类型和 void 在 java.lang 中都有一个包装类,用于将原始类型装箱为引用类型。每个包装类都包含一个名为 TYPE 的字段,该字段等于被包装的原始类型的 Class。
Class c = Double.TYPE;
存在一个类 java.lang.Double 用于包装原始类型 double,每当需要一个 Object 时。Double.TYPE 的值与 double.class 相同。
Class c = Void.TYPE;
Void.TYPE 与 void.class 相同。
返回类的方法
有几个反射 API 返回类,但只有在已经直接或间接地获取了 Class 后才能访问这些类。
返回给定类的超类。
Class c = javax.swing.JButton.class.getSuperclass();
javax.swing.JButton 的超类是 javax.swing.AbstractButton。
返回类的所有公共类、接口和枚举,包括继承的成员。
Class<?>[] c = Character.class.getClasses();
Character 包含两个成员类 Character.Subset 和 Character.UnicodeBlock。
返回在此类中显式声明的所有类、接口和枚举。
Class<?>[] c = Character.class.getDeclaredClasses();
Character 包含两个公共成员类 Character.Subset 和 Character.UnicodeBlock 以及一个私有类 Character.CharacterCache。
java.lang.reflect.Field.getDeclaringClass()。
java.lang.reflect.Method.getDeclaringClass()。
java.lang.reflect.Constructor.getDeclaringClass()。
返回声明这些成员的Class。匿名类声明不会有声明类,但会有封闭类。
import java.lang.reflect.Field;
Field f = System.class.getField("out");
Class c = f.getDeclaringClass();
public class MyClass {
static Object o = new Object() {
public void m() {}
};
static Class<c> = o.getClass().getEnclosingClass();
}
由o定义的匿名类的声明类为null。
返回类的直接封闭类。
Class c = Thread.State.class().getEnclosingClass();
枚举Thread.State的封闭类为Thread。
public class MyClass {
static Object o = new Object() {
public void m() {}
};
static Class<c> = o.getClass().getEnclosingClass();
}
由o定义的匿名类由MyClass封闭。
检查类的修饰符和类型
原文:
docs.oracle.com/javase/tutorial/reflect/class/classModifiers.html
一个类可以用一个或多个修饰符声明,这些修饰符会影响其运行时行为:
-
访问修饰符:
public,protected和private -
要求覆盖的修饰符:
abstract -
限制为一个实例的修饰符:
static -
禁止值修改的修饰符:
final -
强制严格浮点行为的修饰符:
strictfp -
注解
并非所有修饰符都允许用于所有类,例如接口不能是final,枚举不能是abstract。java.lang.reflect.Modifier包含了所有可能的修饰符声明。它还包含可用于解码由Class.getModifiers()返回的修饰符集合的方法。
ClassDeclarationSpy示例展示了如何获取类的声明组件,包括修饰符、泛型类型参数、实现的接口和继承路径。由于Class实现了java.lang.reflect.AnnotatedElement接口,因此也可以查询运行时注解。
import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import static java.lang.System.out;
public class ClassDeclarationSpy {
public static void main(String... args) {
try {
Class<?> c = Class.forName(args[0]);
out.format("Class:%n %s%n%n", c.getCanonicalName());
out.format("Modifiers:%n %s%n%n",
Modifier.toString(c.getModifiers()));
out.format("Type Parameters:%n");
TypeVariable[] tv = c.getTypeParameters();
if (tv.length != 0) {
out.format(" ");
for (TypeVariable t : tv)
out.format("%s ", t.getName());
out.format("%n%n");
} else {
out.format(" -- No Type Parameters --%n%n");
}
out.format("Implemented Interfaces:%n");
Type[] intfs = c.getGenericInterfaces();
if (intfs.length != 0) {
for (Type intf : intfs)
out.format(" %s%n", intf.toString());
out.format("%n");
} else {
out.format(" -- No Implemented Interfaces --%n%n");
}
out.format("Inheritance Path:%n");
List<Class> l = new ArrayList<Class>();
printAncestor(c, l);
if (l.size() != 0) {
for (Class<?> cl : l)
out.format(" %s%n", cl.getCanonicalName());
out.format("%n");
} else {
out.format(" -- No Super Classes --%n%n");
}
out.format("Annotations:%n");
Annotation[] ann = c.getAnnotations();
if (ann.length != 0) {
for (Annotation a : ann)
out.format(" %s%n", a.toString());
out.format("%n");
} else {
out.format(" -- No Annotations --%n%n");
}
// production code should handle this exception more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}
private static void printAncestor(Class<?> c, List<Class> l) {
Class<?> ancestor = c.getSuperclass();
if (ancestor != null) {
l.add(ancestor);
printAncestor(ancestor, l);
}
}
}
以下是一些输出示例。用户输入用斜体表示。
$ *java ClassDeclarationSpy java.util.concurrent.ConcurrentNavigableMap*
Class:
java.util.concurrent.ConcurrentNavigableMap
Modifiers:
public abstract interface
Type Parameters:
K V
Implemented Interfaces:
java.util.concurrent.ConcurrentMap<K, V>
java.util.NavigableMap<K, V>
Inheritance Path:
-- No Super Classes --
Annotations:
-- No Annotations --
这是源代码中java.util.concurrent.ConcurrentNavigableMap的实际声明:
public interface ConcurrentNavigableMap<K,V>
extends ConcurrentMap<K,V>, NavigableMap<K,V>
请注意,由于这是一个接口,它隐式地是abstract的。编译器为每个接口添加这个修饰符。此外,此声明包含两个泛型类型参数,K和V。示例代码仅打印这些参数的名称,但可以使用java.lang.reflect.TypeVariable中的方法检索有关它们的其他信息。接口也可以像上面显示的那样实现其他接口。
$ *java ClassDeclarationSpy "[Ljava.lang.String;"*
Class:
java.lang.String[]
Modifiers:
public abstract final
Type Parameters:
-- No Type Parameters --
Implemented Interfaces:
interface java.lang.Cloneable
interface java.io.Serializable
Inheritance Path:
java.lang.Object
Annotations:
-- No Annotations --
由于数组是运行时对象,所有类型信息都由 Java 虚拟机定义。特别是,数组实现了Cloneable和java.io.Serializable,它们的直接超类始终是Object。
$ *java ClassDeclarationSpy java.io.InterruptedIOException*
Class:
java.io.InterruptedIOException
Modifiers:
public
Type Parameters:
-- No Type Parameters --
Implemented Interfaces:
-- No Implemented Interfaces --
Inheritance Path:
java.io.IOException
java.lang.Exception
java.lang.Throwable
java.lang.Object
Annotations:
-- No Annotations --
从继承路径可以推断出java.io.InterruptedIOException是一个受检异常,因为RuntimeException不存在。
$ *java ClassDeclarationSpy java.security.Identity*
Class:
java.security.Identity
Modifiers:
public abstract
Type Parameters:
-- No Type Parameters --
Implemented Interfaces:
interface java.security.Principal
interface java.io.Serializable
Inheritance Path:
java.lang.Object
Annotations:
@java.lang.Deprecated()
此输出显示java.security.Identity,一个已弃用的 API,具有注解java.lang.Deprecated。这可能被反射代码用于检测已弃用的 API。
注意: 并非所有注解都可以通过反射获得。只有那些具有java.lang.annotation.RetentionPolicy为RUNTIME的注解是可访问的。在语言中预定义的三个注解@Deprecated,@Override,和@SuppressWarnings中,只有@Deprecated在运行时可用。
发现类成员
原文:
docs.oracle.com/javase/tutorial/reflect/class/classMembers.html
Class 提供了两类方法来访问字段、方法和构造函数:列举这些成员的方法和搜索特定成员的方法。此外,还有用于访问直接在类上声明的成员的方法,以及搜索超接口和超类以查找继承成员的方法。以下表格总结了所有定位成员的方法及其特性。
定位字段的类方法
Class API |
成员列表? | 继承成员? | 私有成员? |
|---|---|---|---|
getDeclaredField() |
否 | 否 | 是 |
getField() |
否 | 是 | 否 |
getDeclaredFields() |
是 | 否 | 是 |
getFields() |
是 | 是 | 否 |
定位方法的类方法
Class API |
成员列表? | 继承成员? | 私有成员? |
|---|---|---|---|
getDeclaredMethod() |
否 | 否 | 是 |
getMethod() |
否 | 是 | 否 |
getDeclaredMethods() |
是 | 否 | 是 |
getMethods() |
是 | 是 | 否 |
定位构造函数的类方法
Class API |
成员列表? | 继承成员? | 私有成员? |
|---|---|---|---|
getDeclaredConstructor() |
否 | N/A¹ | 是 |
getConstructor() |
否 | N/A¹ | 否 |
getDeclaredConstructors() |
是 | N/A¹ | 是 |
getConstructors() |
是 | N/A¹ | 否 |
¹ 构造函数不会被继承。
给定一个类名和感兴趣的成员指示,ClassSpy示例使用get*s()方法来确定所有公共元素的列表,包括任何继承的元素。
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Member;
import static java.lang.System.out;
enum ClassMember { CONSTRUCTOR, FIELD, METHOD, CLASS, ALL }
public class ClassSpy {
public static void main(String... args) {
try {
Class<?> c = Class.forName(args[0]);
out.format("Class:%n %s%n%n", c.getCanonicalName());
Package p = c.getPackage();
out.format("Package:%n %s%n%n",
(p != null ? p.getName() : "-- No Package --"));
for (int i = 1; i < args.length; i++) {
switch (ClassMember.valueOf(args[i])) {
case CONSTRUCTOR:
printMembers(c.getConstructors(), "Constructor");
break;
case FIELD:
printMembers(c.getFields(), "Fields");
break;
case METHOD:
printMembers(c.getMethods(), "Methods");
break;
case CLASS:
printClasses(c);
break;
case ALL:
printMembers(c.getConstructors(), "Constuctors");
printMembers(c.getFields(), "Fields");
printMembers(c.getMethods(), "Methods");
printClasses(c);
break;
default:
assert false;
}
}
// production code should handle these exceptions more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}
private static void printMembers(Member[] mbrs, String s) {
out.format("%s:%n", s);
for (Member mbr : mbrs) {
if (mbr instanceof Field)
out.format(" %s%n", ((Field)mbr).toGenericString());
else if (mbr instanceof Constructor)
out.format(" %s%n", ((Constructor)mbr).toGenericString());
else if (mbr instanceof Method)
out.format(" %s%n", ((Method)mbr).toGenericString());
}
if (mbrs.length == 0)
out.format(" -- No %s --%n", s);
out.format("%n");
}
private static void printClasses(Class<?> c) {
out.format("Classes:%n");
Class<?>[] clss = c.getClasses();
for (Class<?> cls : clss)
out.format(" %s%n", cls.getCanonicalName());
if (clss.length == 0)
out.format(" -- No member interfaces, classes, or enums --%n");
out.format("%n");
}
}
这个例子相对紧凑;然而,printMembers()方法略显笨拙,因为java.lang.reflect.Member接口自反射最早的实现以来就存在,当引入泛型时,它无法被修改以包含更有用的getGenericString()方法。唯一的替代方法是像所示那样进行测试和转换,用printConstructors()、printFields()和printMethods()替换此方法,或者满足于相对简洁的Member.getName()的结果。
输出示例及其解释如下。用户输入用斜体表示。
$ *java ClassSpy java.lang.ClassCastException CONSTRUCTOR*
Class:
java.lang.ClassCastException
Package:
java.lang
Constructor:
public java.lang.ClassCastException()
public java.lang.ClassCastException(java.lang.String)
由于构造函数不会被继承,因此在直接超类RuntimeException和其他超类中定义的异常链接机制构造函数(具有Throwable参数)将不会被找到。
$ *java ClassSpy java.nio.channels.ReadableByteChannel METHOD*
Class:
java.nio.channels.ReadableByteChannel
Package:
java.nio.channels
Methods:
public abstract int java.nio.channels.ReadableByteChannel.read
(java.nio.ByteBuffer) throws java.io.IOException
public abstract void java.nio.channels.Channel.close() throws
java.io.IOException
public abstract boolean java.nio.channels.Channel.isOpen()
接口java.nio.channels.ReadableByteChannel定义了read()。其余方法是从超级接口继承的。可以通过将get*s()替换为getDeclared*s()来轻松修改此代码,仅列出实际在类中声明的方法。
$ *java ClassSpy ClassMember FIELD METHOD*
Class:
ClassMember
Package:
-- No Package --
Fields:
public static final ClassMember ClassMember.CONSTRUCTOR
public static final ClassMember ClassMember.FIELD
public static final ClassMember ClassMember.METHOD
public static final ClassMember ClassMember.CLASS
public static final ClassMember ClassMember.ALL
Methods:
public static ClassMember ClassMember.valueOf(java.lang.String)
public static ClassMember[] ClassMember.values()
public final int java.lang.Enum.hashCode()
public final int java.lang.Enum.compareTo(E)
public int java.lang.Enum.compareTo(java.lang.Object)
public final java.lang.String java.lang.Enum.name()
public final boolean java.lang.Enum.equals(java.lang.Object)
public java.lang.String java.lang.Enum.toString()
public static <T> T java.lang.Enum.valueOf
(java.lang.Class<T>,java.lang.String)
public final java.lang.Class<E> java.lang.Enum.getDeclaringClass()
public final int java.lang.Enum.ordinal()
public final native java.lang.Class<?> java.lang.Object.getClass()
public final native void java.lang.Object.wait(long) throws
java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws
java.lang.InterruptedException
public final void java.lang.Object.wait() hrows java.lang.InterruptedException
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
在这些结果的字段部分,枚举常量被列出。虽然这些在技术上是字段,但将它们与其他字段区分开可能是有用的。这个例子可以修改为使用java.lang.reflect.Field.isEnumConstant()来实现这一目的。在本教程的后续部分检查枚举中的EnumSpy示例包含了一个可能的实现。
在输出的方法部分中,观察到方法名称包含声明类的名称。因此,toString()方法是由Enum实现的,而不是从Object继承的。可以通过使用Field.getDeclaringClass()来修改代码,使这一点更明显。以下片段展示了潜在解决方案的一部分。
if (mbr instanceof Field) {
Field f = (Field)mbr;
out.format(" %s%n", f.toGenericString());
out.format(" -- declared in: %s%n", f.getDeclaringClass());
}
故障排除
原文:
docs.oracle.com/javase/tutorial/reflect/class/classTrouble.html
以下示例展示了在反射类时可能遇到的典型错误。
编译器警告:"注意:...使用了未经检查或不安全的操作"
当调用方法时,会检查参数值的类型并可能进行转换。ClassWarning调用getMethod()会导致典型的未经检查的转换警告:
import java.lang.reflect.Method;
public class ClassWarning {
void m() {
try {
Class c = ClassWarning.class;
Method m = c.getMethod("m"); // warning
// production code should handle this exception more gracefully
} catch (NoSuchMethodException x) {
x.printStackTrace();
}
}
}
$ *javac ClassWarning.java*
Note: ClassWarning.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
$ *javac -Xlint:unchecked ClassWarning.java*
ClassWarning.java:6: warning: [unchecked] unchecked call to getMethod
(String,Class<?>...) as a member of the raw type Class
Method m = c.getMethod("m"); // warning
^
1 warning
许多库方法已经使用了泛型声明,包括Class中的几个方法。由于c声明为原始类型(没有类型参数),并且getMethod()的相应参数是参数化类型,因此会发生未经检查的转换。编译器需要生成警告。(参见Java 语言规范,Java SE 7 版,章节未经检查的转换和方法调用转换。)
有两种可能的解决方案。更可取的是修改c的声明以包含适当的通用类型。在这种情况下,声明应为:
Class<?> c = warn.getClass();
或者,可以在有问题的语句之前使用预定义的注释@SuppressWarnings来明确抑制警告。
Class c = ClassWarning.class;
@SuppressWarnings("unchecked")
Method m = c.getMethod("m");
// warning gone
提示: 作为一个一般原则,不应忽略警告,因为它们可能表明存在错误。应适当使用参数化声明。如果不可能(也许是因为应用程序必须与库供应商的代码交互),则可以使用@SuppressWarnings对有问题的行进行注释。
当构造函数不可访问时会出现 InstantiationException
Class.newInstance()如果尝试创建一个类的新实例且零参数构造函数不可见,则会抛出InstantiationException。ClassTrouble示例展示了生成的堆栈跟踪。
class Cls {
private Cls() {}
}
public class ClassTrouble {
public static void main(String... args) {
try {
Class<?> c = Class.forName("Cls");
c.newInstance(); // InstantiationException
// production code should handle these exceptions more gracefully
} catch (InstantiationException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}
}
$ *java ClassTrouble*
java.lang.IllegalAccessException: Class ClassTrouble can not access a member of
class Cls with modifiers "private"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65)
at java.lang.Class.newInstance0(Class.java:349)
at java.lang.Class.newInstance(Class.java:308)
at ClassTrouble.main(ClassTrouble.java:9)
Class.newInstance()的行为非常类似于new关键字,并且会因为与new相同的原因而失败。在反射中的典型解决方案是利用java.lang.reflect.AccessibleObject类,该类提供了抑制访问控制检查的能力;然而,这种方法不起作用,因为java.lang.Class不扩展AccessibleObject。唯一的解决方案是修改代码以使用Constructor.newInstance(),该方法确实扩展了AccessibleObject。
提示: 一般来说,最好使用Constructor.newInstance(),原因在创建新类实例部分的成员课程中有描述。
使用Constructor.newInstance()可能会出现潜在问题的其他示例,可以在构造函数故障排除部分的成员课程中找到。
课程:成员
原文:
docs.oracle.com/javase/tutorial/reflect/member/index.html
反射定义了一个接口java.lang.reflect.Member,该接口由java.lang.reflect.Field、java.lang.reflect.Method和java.lang.reflect.Constructor实现。这些对象将在本课程中讨论。对于每个成员,本课程将描述相关的 API 以检索声明和类型信息,成员特有的任何操作(例如,设置字段的值或调用方法),以及常见的错误。每个概念将通过代码示例和相关输出进行说明,这些输出近似一些预期的反射用法。
注意: 根据Java 语言规范,Java SE 7 版,类的成员是类主体的继承组件,包括字段、方法、嵌套类、接口和枚举类型。由于构造函数不会被继承,因此它们不是成员。这与java.lang.reflect.Member的实现类有所不同。
字段
字段具有类型和值。java.lang.reflect.Field类提供了用于访问类型信息以及在给定对象上设置和获取字段值的方法。
-
获取字段类型 描述了如何获取字段的声明类型和泛型类型
-
检索和解析字段修饰符 展示了如何获取字段声明的部分,如
public或transient -
获取和设置字段值 说明了如何访问字段的值
-
故障排除 描述了可能导致混淆的一些常见编码错误
方法
方法具有返回值、参数,并可能抛出异常。java.lang.reflect.Method类提供了用于获取参数和返回值的类型信息的方法。它还可以用于在给定对象上调用方法。
-
获取方法类型信息 展示了如何枚举类中声明的方法并获取类型信息
-
获取方法参数的名称 展示了如何检索方法或构造函数的参数的名称和其他信息
-
检索和解析方法修饰符描述了如何访问和解码与方法相关的修饰符和其他信息
-
调用方法说明了如何执行一个方法并获得其返回值
-
故障排除涵盖了在查找或调用方法时遇到的常见错误
构造函数
构造函数的反射 API 在java.lang.reflect.Constructor中定义,与方法的 API 类似,但有两个主要例外:首先,构造函数没有返回值;其次,调用构造函数会为给定类创建一个新的对象实例。
-
查找构造函数说明了如何检索具有特定参数的构造函数
-
检索和解析构造函数修饰符展示了如何获取构造函数声明的修饰符以及有关构造函数的其他信息
-
创建新的类实例展示了如何通过调用其构造函数来实例化一个对象的实例
-
故障排除描述了在查找或调用构造函数时可能遇到的常见错误
字段
原文:
docs.oracle.com/javase/tutorial/reflect/member/field.html
一个字段是一个具有关联值的类、接口或枚举。java.lang.reflect.Field类中的方法可以检索有关字段的信息,比如它的名称、类型、修饰符和注解。(检查类修饰符和类型章节中的类课程描述了如何检索注解。)还有一些方法可以实现对字段值的动态访问和修改。这些任务在以下章节中介绍:
-
获取字段类型描述了如何获取字段的声明类型和泛型类型
-
检索和解析字段修饰符展示了如何获取字段声明的部分,比如
public或transient -
获取和设置字段值说明了如何访问字段值
-
故障排除描述了可能导致混淆的一些常见编码错误
当编写一个应用程序,比如一个类浏览器时,找出哪些字段属于特定类可能会很有用。通过调用Class.getFields()来识别类的字段。getFields()方法返回一个包含每个可访问的公共字段的Field对象数组。
如果一个公共字段是以下任一成员,则可以访问它:
-
这个类
-
这个类的一个超类
-
由这个类实现的接口
-
由这个类实现的接口扩展的一个接口
一个字段可以是一个类(实例)字段,比如java.io.Reader.lock,一个静态字段,比如java.lang.Integer.MAX_VALUE,或一个枚举常量,比如java.lang.Thread.State.WAITING。
获取字段类型
原文:
docs.oracle.com/javase/tutorial/reflect/member/fieldTypes.html
字段可以是原始类型或引用类型。有八种原始类型:boolean、byte、short、int、long、char、float 和 double。引用类型是任何直接或间接是 java.lang.Object 的子类,包括接口、数组和枚举类型。
FieldSpy 示例根据完全限定的二进制类名和字段名打印字段的类型和泛型类型。
import java.lang.reflect.Field;
import java.util.List;
public class FieldSpy<T> {
public boolean[][] b = {{ false, false }, { true, true } };
public String name = "Alice";
public List<Integer> list;
public T val;
public static void main(String... args) {
try {
Class<?> c = Class.forName(args[0]);
Field f = c.getField(args[1]);
System.out.format("Type: %s%n", f.getType());
System.out.format("GenericType: %s%n", f.getGenericType());
// production code should handle these exceptions more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
} catch (NoSuchFieldException x) {
x.printStackTrace();
}
}
}
获取此类中三个公共字段(b、name 和参数化类型 list)的类型的示例输出如下。用户输入以斜体表示。
$ *java FieldSpy FieldSpy b*
Type: class [[Z
GenericType: class [[Z
$ *java FieldSpy FieldSpy name*
Type: class java.lang.String
GenericType: class java.lang.String
$ *java FieldSpy FieldSpy list*
Type: interface java.util.List
GenericType: java.util.List<java.lang.Integer>
$ *java FieldSpy FieldSpy val*
Type: class java.lang.Object
GenericType: T
字段 b 的类型是布尔值的二维数组。类型名称的语法在 Class.getName() 中有描述。
字段 val 的类型报告为 java.lang.Object,因为泛型是通过类型擦除实现的,在编译期间删除了关于泛型类型的所有信息。因此,T 被替换为类型变量的上界,在本例中为 java.lang.Object。
Field.getGenericType() 如果存在,将查阅类文件中的 Signature 属性。如果该属性不可用,则退而求其次使用 Field.getType(),这个方法在引入泛型之后并没有改变。反射中其他以 getGeneric*Foo* 命名的方法,对于某个 Foo 值的实现方式类似。
检索和解析字段修饰符
原文:
docs.oracle.com/javase/tutorial/reflect/member/fieldModifiers.html
有几个修饰符可能是字段声明的一部分:
-
访问修饰符:
public、protected和private -
控制运行时行为的字段特定修饰符:
transient和volatile -
限制为一个实例的修饰符:
static -
禁止值修改的修饰符:
final -
注解
方法Field.getModifiers()可用于返回表示字段的声明修饰符集合的整数。该整数中表示修饰符的位在java.lang.reflect.Modifier中定义。
FieldModifierSpy示例演示了如何搜索具有给定修饰符的字段。它还通过调用Field.isSynthetic()和Field.isEnumCostant()确定所定位的字段是合成的(编译器生成的)还是枚举常量。
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import static java.lang.System.out;
enum Spy { BLACK , WHITE }
public class FieldModifierSpy {
volatile int share;
int instance;
class Inner {}
public static void main(String... args) {
try {
Class<?> c = Class.forName(args[0]);
int searchMods = 0x0;
for (int i = 1; i < args.length; i++) {
searchMods |= modifierFromString(args[i]);
}
Field[] flds = c.getDeclaredFields();
out.format("Fields in Class '%s' containing modifiers: %s%n",
c.getName(),
Modifier.toString(searchMods));
boolean found = false;
for (Field f : flds) {
int foundMods = f.getModifiers();
// Require all of the requested modifiers to be present
if ((foundMods & searchMods) == searchMods) {
out.format("%-8s [ synthetic=%-5b enum_constant=%-5b ]%n",
f.getName(), f.isSynthetic(),
f.isEnumConstant());
found = true;
}
}
if (!found) {
out.format("No matching fields%n");
}
// production code should handle this exception more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}
private static int modifierFromString(String s) {
int m = 0x0;
if ("public".equals(s)) m |= Modifier.PUBLIC;
else if ("protected".equals(s)) m |= Modifier.PROTECTED;
else if ("private".equals(s)) m |= Modifier.PRIVATE;
else if ("static".equals(s)) m |= Modifier.STATIC;
else if ("final".equals(s)) m |= Modifier.FINAL;
else if ("transient".equals(s)) m |= Modifier.TRANSIENT;
else if ("volatile".equals(s)) m |= Modifier.VOLATILE;
return m;
}
}
示例输出如下:
$ *java FieldModifierSpy FieldModifierSpy volatile*
Fields in Class 'FieldModifierSpy' containing modifiers: volatile
share [ synthetic=false enum_constant=false ]
$ *java FieldModifierSpy Spy public*
Fields in Class 'Spy' containing modifiers: public
BLACK [ synthetic=false enum_constant=true ]
WHITE [ synthetic=false enum_constant=true ]
$ *java FieldModifierSpy FieldModifierSpy\$Inner final*
Fields in Class 'FieldModifierSpy$Inner' containing modifiers: final
this$0 [ synthetic=true enum_constant=false ]
$ *java FieldModifierSpy Spy private static final*
Fields in Class 'Spy' containing modifiers: private static final
$VALUES [ synthetic=true enum_constant=false ]
请注意,有些字段即使在原始代码中未声明也会被报告。这是因为编译器会生成一些合成字段,这些字段在运行时是必需的。为了测试一个字段是否是合成的,示例调用Field.isSynthetic()。合成字段的集合是依赖于编译器的;然而,常用的字段包括用于内部类(即非静态成员类)引用最外层封闭类的this$0和用于枚举实现隐式定义的静态方法values()的$VALUES。合成类成员的名称未指定,可能在所有编译器实现或版本中不同。这些和其他合成字段将包含在Class.getDeclaredFields()返回的数组中,但不会被Class.getField()识别,因为合成成员通常不是public。
因为Field实现了接口java.lang.reflect.AnnotatedElement,所以可以使用java.lang.annotation.RetentionPolicy.RUNTIME来检索任何运行时注解。有关获取注解的示例,请参见检查类修饰符和类型部分。
获取和设置字段值
原文:
docs.oracle.com/javase/tutorial/reflect/member/fieldValues.html
给定一个类的实例,可以使用反射来设置该类中字段的值。通常只在特殊情况下进行此操作,当通常方式无法设置值时。由于这种访问通常违反了类的设计意图,应该谨慎使用。
Book类演示了如何设置长整型、数组和枚举字段类型的值。获取和设置其他基本类型的方法在Field中有描述。
import java.lang.reflect.Field;
import java.util.Arrays;
import static java.lang.System.out;
enum Tweedle { DEE, DUM }
public class Book {
public long chapters = 0;
public String[] characters = { "Alice", "White Rabbit" };
public Tweedle twin = Tweedle.DEE;
public static void main(String... args) {
Book book = new Book();
String fmt = "%6S: %-12s = %s%n";
try {
Class<?> c = book.getClass();
Field chap = c.getDeclaredField("chapters");
out.format(fmt, "before", "chapters", book.chapters);
chap.setLong(book, 12);
out.format(fmt, "after", "chapters", chap.getLong(book));
Field chars = c.getDeclaredField("characters");
out.format(fmt, "before", "characters",
Arrays.asList(book.characters));
String[] newChars = { "Queen", "King" };
chars.set(book, newChars);
out.format(fmt, "after", "characters",
Arrays.asList(book.characters));
Field t = c.getDeclaredField("twin");
out.format(fmt, "before", "twin", book.twin);
t.set(book, Tweedle.DUM);
out.format(fmt, "after", "twin", t.get(book));
// production code should handle these exceptions more gracefully
} catch (NoSuchFieldException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
}
}
}
这是相应的输出:
$ *java Book*
BEFORE: chapters = 0
AFTER: chapters = 12
BEFORE: characters = [Alice, White Rabbit]
AFTER: characters = [Queen, King]
BEFORE: twin = DEE
AFTER: twin = DUM
注意: 通过反射设置字段的值会带来一定的性能开销,因为必须执行各种操作,如验证访问权限。从运行时的角度来看,效果是相同的,操作与直接在类代码中更改值一样原子。
使用反射可能导致一些运行时优化丢失。例如,以下代码很可能会被 Java 虚拟机优化:
int x = 1;
x = 2;
x = 3;
使用Field.set*()的等效代码可能不会。
故障排除
原文:
docs.oracle.com/javase/tutorial/reflect/member/fieldTrouble.html
开发人员遇到的一些常见问题及其解释和解决方法如下。
由于不可转换类型而引发的IllegalArgumentException
FieldTrouble示例将生成一个IllegalArgumentException。调用Field.setInt() 来设置一个引用类型为Integer的字段,其值为原始类型。在非反射等效的Integer val = 42中,编译器会将原始类型42转换(或装箱)为引用类型new Integer(42),以便其类型检查接受该语句。在使用反射时,类型检查仅在运行时发生,因此没有机会对值进行装箱。
import java.lang.reflect.Field;
public class FieldTrouble {
public Integer val;
public static void main(String... args) {
FieldTrouble ft = new FieldTrouble();
try {
Class<?> c = ft.getClass();
Field f = c.getDeclaredField("val");
f.setInt(ft, 42); // IllegalArgumentException
// production code should handle these exceptions more gracefully
} catch (NoSuchFieldException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
}
}
}
$ *java FieldTrouble*
Exception in thread "main" java.lang.IllegalArgumentException: Can not set
java.lang.Object field FieldTrouble.val to (long)42
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException
(UnsafeFieldAccessorImpl.java:146)
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException
(UnsafeFieldAccessorImpl.java:174)
at sun.reflect.UnsafeObjectFieldAccessorImpl.setLong
(UnsafeObjectFieldAccessorImpl.java:102)
at java.lang.reflect.Field.setLong(Field.java:831)
at FieldTrouble.main(FieldTrouble.java:11)
要消除此异常,问题行应替换为以下调用Field.set(Object obj, Object value):
f.set(ft, new Integer(43));
提示: 当使用反射设置或获取字段时,编译器没有机会执行装箱。它只能转换由Class.isAssignableFrom()规范描述的相关类型。该示例预计会失败,因为在此测试中isAssignableFrom()将返回false,可用于编程验证特定转换是否可能:
Integer.class.isAssignableFrom(int.class) == false
同样,在反射中也不可能自动将原始类型转换为引用类型。
int.class.isAssignableFrom(Integer.class) == false
针对非公共字段的NoSuchFieldException
机智的读者可能会注意到,如果之前展示的FieldSpy示例用于获取非公共字段的信息,它将失败:
$ *java FieldSpy java.lang.String count*
java.lang.NoSuchFieldException: count
at java.lang.Class.getField(Class.java:1519)
at FieldSpy.main(FieldSpy.java:12)
提示: Class.getField() 和 Class.getFields() 方法返回由Class对象表示的类、枚举或接口的公共成员字段。要检索在Class中声明的所有字段(但不是继承的字段),请使用Class.getDeclaredFields() 方法。
修改最终字段时的IllegalAccessException
如果尝试获取或设置private或其他不可访问字段的值,或者设置final字段的值(无论其访问修饰符如何),可能会抛出IllegalAccessException。
FieldTroubleToo示例展示了尝试设置 final 字段时产生的堆栈跟踪类型。
import java.lang.reflect.Field;
public class FieldTroubleToo {
public final boolean b = true;
public static void main(String... args) {
FieldTroubleToo ft = new FieldTroubleToo();
try {
Class<?> c = ft.getClass();
Field f = c.getDeclaredField("b");
// f.setAccessible(true); // solution
f.setBoolean(ft, Boolean.FALSE); // IllegalAccessException
// production code should handle these exceptions more gracefully
} catch (NoSuchFieldException x) {
x.printStackTrace();
} catch (IllegalArgumentException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
}
}
}
$ *java FieldTroubleToo*
java.lang.IllegalAccessException: Can not set final boolean field
FieldTroubleToo.b to (boolean)false
at sun.reflect.UnsafeFieldAccessorImpl.
throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:55)
at sun.reflect.UnsafeFieldAccessorImpl.
throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:63)
at sun.reflect.UnsafeQualifiedBooleanFieldAccessorImpl.setBoolean
(UnsafeQualifiedBooleanFieldAccessorImpl.java:78)
at java.lang.reflect.Field.setBoolean(Field.java:686)
at FieldTroubleToo.main(FieldTroubleToo.java:12)
提示:存在访问限制,阻止在类初始化后设置final字段。但是,Field声明为扩展AccessibleObject,从而提供了抑制此检查的能力。
如果AccessibleObject.setAccessible()成功,那么对该字段值的后续操作将不会因此问题而失败。这可能会产生意想不到的副作用;例如,有时候原始值将继续被应用程序的某些部分使用,即使该值已被修改。只有在安全上下文允许的情况下,AccessibleObject.setAccessible()才会成功。
方法
原文:
docs.oracle.com/javase/tutorial/reflect/member/method.html
方法 包含可调用的可执行代码。方法是继承的,在非反射代码中,编译器强制执行重载、覆盖和隐藏等行为。相比之下,反射代码使得方法选择可以限制在特定类中而不考虑其超类。可以访问超类方法,但可以确定它们的声明类;这在没有反射的情况下是不可能通过编程方式发现的,这是许多微妙错误的根源。
java.lang.reflect.Method 类提供了访问有关方法修饰符、返回类型、参数、注解和抛出异常的信息的 API。它还可以用于调用方法。以下部分涵盖了这些主题:
-
获取方法类型信息展示了如何枚举在类中声明的方法并获取类型信息
-
获取方法参数的名称展示了如何检索方法或构造函数的参数的名称和其他信息
-
检索和解析方法修饰符描述了如何访问和解码与方法相关的修饰符和其他信息
-
调用方法演示了如何执行一个方法并获取其返回值
-
故障排除 涵盖了在查找或调用方法时遇到的常见错误
获取方法类型信息
原文:
docs.oracle.com/javase/tutorial/reflect/member/methodType.html
方法声明包括名称、修饰符、参数、返回类型和可抛出异常列表。java.lang.reflect.Method类提供了获取这些信息的方法。
MethodSpy示例演示了如何枚举给定类中声明的所有方法,并检索给定名称的所有方法的返回、参数和异常类型。
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import static java.lang.System.out;
public class MethodSpy {
private static final String fmt = "%24s: %s%n";
// for the morbidly curious
<E extends RuntimeException> void genericThrow() throws E {}
public static void main(String... args) {
try {
Class<?> c = Class.forName(args[0]);
Method[] allMethods = c.getDeclaredMethods();
for (Method m : allMethods) {
if (!m.getName().equals(args[1])) {
continue;
}
out.format("%s%n", m.toGenericString());
out.format(fmt, "ReturnType", m.getReturnType());
out.format(fmt, "GenericReturnType", m.getGenericReturnType());
Class<?>[] pType = m.getParameterTypes();
Type[] gpType = m.getGenericParameterTypes();
for (int i = 0; i < pType.length; i++) {
out.format(fmt,"ParameterType", pType[i]);
out.format(fmt,"GenericParameterType", gpType[i]);
}
Class<?>[] xType = m.getExceptionTypes();
Type[] gxType = m.getGenericExceptionTypes();
for (int i = 0; i < xType.length; i++) {
out.format(fmt,"ExceptionType", xType[i]);
out.format(fmt,"GenericExceptionType", gxType[i]);
}
}
// production code should handle these exceptions more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}
}
这是Class.getConstructor()的输出,这是一个具有参数化类型和可变数量参数的方法的示例。
$ *java MethodSpy java.lang.Class getConstructor*
public java.lang.reflect.Constructor<T> java.lang.Class.getConstructor
(java.lang.Class<?>[]) throws java.lang.NoSuchMethodException,
java.lang.SecurityException
ReturnType: class java.lang.reflect.Constructor
GenericReturnType: java.lang.reflect.Constructor<T>
ParameterType: class [Ljava.lang.Class;
GenericParameterType: java.lang.Class<?>[]
ExceptionType: class java.lang.NoSuchMethodException
GenericExceptionType: class java.lang.NoSuchMethodException
ExceptionType: class java.lang.SecurityException
GenericExceptionType: class java.lang.SecurityException
这是源代码中方法的实际声明:
public Constructor<T> getConstructor(Class<?>... parameterTypes)
首先注意返回和参数类型是通用的。如果存在类文件中的签名属性,Method.getGenericReturnType()将会查看它。如果属性不可用,则会回退到未更改的Method.getReturnType(),这是在引入泛型之前没有更改的。其他以反射中某个值Foo为名称的getGeneric*Foo*()方法实现方式类似。
接下来,请注意最后(也是唯一的)参数parameterType是可变参数(具有可变数量的参数)类型为java.lang.Class。它被表示为java.lang.Class类型的单维数组。这可以通过调用Method.isVarArgs()来区分明确为java.lang.Class数组的参数。Method.get*Types()返回值的语法在Class.getName()中描述。
以下示例说明了具有通用返回类型的方法。
$ *java MethodSpy java.lang.Class cast*
public T java.lang.Class.cast(java.lang.Object)
ReturnType: class java.lang.Object
GenericReturnType: T
ParameterType: class java.lang.Object
GenericParameterType: class java.lang.Object
方法Class.cast()的通用返回类型报告为java.lang.Object,因为泛型是通过类型擦除实现的,在编译期间删除了有关泛型类型的所有信息。T的擦除由Class的声明定义:
public final class Class<T> implements ...
因此,T被类型变量的上界替换,即java.lang.Object。
最后一个示例说明了具有多个重载的方法的输出。
$ *java MethodSpy java.io.PrintStream format*
public java.io.PrintStream java.io.PrintStream.format
(java.util.Locale,java.lang.String,java.lang.Object[])
ReturnType: class java.io.PrintStream
GenericReturnType: class java.io.PrintStream
ParameterType: class java.util.Locale
GenericParameterType: class java.util.Locale
ParameterType: class java.lang.String
GenericParameterType: class java.lang.String
ParameterType: class [Ljava.lang.Object;
GenericParameterType: class [Ljava.lang.Object;
public java.io.PrintStream java.io.PrintStream.format
(java.lang.String,java.lang.Object[])
ReturnType: class java.io.PrintStream
GenericReturnType: class java.io.PrintStream
ParameterType: class java.lang.String
GenericParameterType: class java.lang.String
ParameterType: class [Ljava.lang.Object;
GenericParameterType: class [Ljava.lang.Object;
如果发现同一方法名的多个重载,它们都会被Class.getDeclaredMethods()返回。由于format()有两个重载(一个带有Locale,一个没有),MethodSpy都会显示出来。
注意: Method.getGenericExceptionTypes()的存在是因为实际上可以声明一个带有泛型异常类型的方法。然而,这很少被使用,因为无法捕获泛型异常类型。
获取方法参数的名称
原文:
docs.oracle.com/javase/tutorial/reflect/member/methodparameterreflection.html
您可以使用方法java.lang.reflect.Executable.getParameters获取任何方法或构造函数的形式参数的名称。(类Method和Constructor扩展了类Executable,因此继承了方法Executable.getParameters。)但是,默认情况下,.class文件不会存储形式参数名称。这是因为许多生成和消费类文件的工具可能不希望.class文件包含参数名称的更大的静态和动态占用空间。特别是,这些工具将不得不处理更大的.class文件,并且 Java 虚拟机(JVM)将使用更多内存。此外,一些参数名称,如secret或password,可能会暴露有关安全敏感方法的信息。
要在特定的.class文件中存储形式参数名称,并使反射 API 能够检索形式参数名称,请使用javac编译器的-parameters选项编译源文件。
MethodParameterSpy示例演示了如何检索给定类的所有构造函数和方法的形式参数的名称。该示例还打印有关每个参数的其他信息。
以下命令打印类ExampleMethods的构造函数和方法的形式参数名称。注意:记得使用-parameters编译器选项编译示例ExampleMethods:
*java MethodParameterSpy ExampleMethods*
此命令打印以下内容:
Number of constructors: 1
Constructor #1
public ExampleMethods()
Number of declared constructors: 1
Declared constructor #1
public ExampleMethods()
Number of methods: 4
Method #1
public boolean ExampleMethods.simpleMethod(java.lang.String,int)
Return type: boolean
Generic return type: boolean
Parameter class: class java.lang.String
Parameter name: stringParam
Modifiers: 0
Is implicit?: false
Is name present?: true
Is synthetic?: false
Parameter class: int
Parameter name: intParam
Modifiers: 0
Is implicit?: false
Is name present?: true
Is synthetic?: false
Method #2
public int ExampleMethods.varArgsMethod(java.lang.String...)
Return type: int
Generic return type: int
Parameter class: class [Ljava.lang.String;
Parameter name: manyStrings
Modifiers: 0
Is implicit?: false
Is name present?: true
Is synthetic?: false
Method #3
public boolean ExampleMethods.methodWithList(java.util.List<java.lang.String>)
Return type: boolean
Generic return type: boolean
Parameter class: interface java.util.List
Parameter name: listParam
Modifiers: 0
Is implicit?: false
Is name present?: true
Is synthetic?: false
Method #4
public <T> void ExampleMethods.genericMethod(T[],java.util.Collection<T>)
Return type: void
Generic return type: void
Parameter class: class [Ljava.lang.Object;
Parameter name: a
Modifiers: 0
Is implicit?: false
Is name present?: true
Is synthetic?: false
Parameter class: interface java.util.Collection
Parameter name: c
Modifiers: 0
Is implicit?: false
Is name present?: true
Is synthetic?: false
MethodParameterSpy示例使用Parameter类中的以下方法:
-
getName: 返回参数的名称。如果参数的名称存在,则此方法返回.class文件提供的名称。否则,此方法将合成一个形式为arg*N*的名称,其中*N*是声明参数的方法描述符中的参数索引。例如,假设您编译了类
ExampleMethods而没有指定-parameters编译器选项。示例MethodParameterSpy将为方法ExampleMethods.simpleMethod打印以下内容:public boolean ExampleMethods.simpleMethod(java.lang.String,int) Return type: boolean Generic return type: boolean Parameter class: class java.lang.String Parameter name: arg0 Modifiers: 0 Is implicit?: false Is name present?: false Is synthetic?: false Parameter class: int Parameter name: arg1 Modifiers: 0 Is implicit?: false Is name present?: false Is synthetic?: false -
getModifiers:返回一个整数,表示形式参数具有的各种特征。如果适用于形式参数,则此值是以下值的总和:值(十进制) 值(十六进制) 描述 16 0x0010 形式参数声明为 final4096 0x1000 形式参数是合成的。或者,您可以调用方法 isSynthetic。32768 0x8000 参数在源代码中是隐式声明的。或者,您可以调用方法 isImplicit -
isImplicit:如果此参数在源代码中是隐式声明的,则返回true。有关更多信息,请参阅隐式和合成参数部分。 -
isNamePresent:如果参数在.class文件中具有名称,则返回true。 -
isSynthetic:如果此参数在源代码中既不是隐式声明也不是显式声明,则返回true。有关更多信息,请参阅隐式和合成参数部分。
隐式和合成参数
如果源代码中未明确编写某些构造,则某些构造将被隐式声明。例如,ExampleMethods示例不包含构造函数。它将隐式声明一个默认构造函数。MethodParameterSpy示例打印有关ExampleMethods隐式声明构造函数的信息:
Number of declared constructors: 1
public ExampleMethods()
请考虑来自MethodParameterExamples的以下摘录:
public class MethodParameterExamples {
public class InnerClass { }
}
类InnerClass是一个非静态嵌套类或内部类。内部类也会隐式声明一个构造函数。但是,此构造函数将包含一个参数。当 Java 编译器编译InnerClass时,它会创建一个代表以下代码的.class文件:
public class MethodParameterExamples {
public class InnerClass {
final MethodParameterExamples parent;
InnerClass(final MethodParameterExamples this$0) {
parent = this$0;
}
}
}
InnerClass构造函数包含一个参数,其类型是包含InnerClass的类,即MethodParameterExamples。因此,示例MethodParameterExamples打印如下内容:
public MethodParameterExamples$InnerClass(MethodParameterExamples)
Parameter class: class MethodParameterExamples
Parameter name: this$0
Modifiers: 32784
Is implicit?: true
Is name present?: true
Is synthetic?: false
因为类InnerClass的构造函数是隐式声明的,所以它的参数也是隐式的。
注意:
-
Java 编译器为内部类的构造函数创建一个形式参数,以便编译器能够从创建表达式传递一个引用(表示立即封闭实例)到成员类的构造函数。
-
值 32784 表示
InnerClass构造函数的参数既是 final(16)又是隐式的(32768)。 -
Java 编程语言允许在变量名中使用美元符号(
$);然而,按照惯例,在变量名中不使用美元符号。
Java 编译器生成的构造如果不对应于源代码中显式或隐式声明的构造,则标记为合成的,除非它们是类初始化方法。合成的构造是编译器生成的在不同实现之间变化的工件。考虑以下摘录来自MethodParameterExamples:
public class MethodParameterExamples {
enum Colors {
RED, WHITE;
}
}
当 Java 编译器遇到enum构造时,它会创建几个与.class文件结构兼容且提供enum构造所期望功能的方法。例如,Java 编译器会为代表以下代码的enum构造Colors创建一个.class文件:
final class Colors extends java.lang.Enum<Colors> {
public final static Colors RED = new Colors("RED", 0);
public final static Colors BLUE = new Colors("WHITE", 1);
private final static values = new Colors[]{ RED, BLUE };
private Colors(String name, int ordinal) {
super(name, ordinal);
}
public static Colors[] values(){
return values;
}
public static Colors valueOf(String name){
return (Colors)java.lang.Enum.valueOf(Colors.class, name);
}
}
Java 编译器为这个enum构造创建了三个构造函数和方法:Colors(String name, int ordinal)、Colors[] values()和Colors valueOf(String name)。方法values和valueOf是隐式声明的。因此,它们的形式参数名称也是隐式声明的。
enum构造函数Colors(String name, int ordinal)是一个默认构造函数,它是隐式声明的。然而,这个构造函数的形式参数(name和ordinal)并没有隐式声明。因为这些形式参数既没有显式声明也没有隐式声明,它们是合成的。(enum构造函数的默认构造函数的形式参数不是隐式声明的,因为不同的编译器可能对这个构造函数的形式参数形式有不同的规定;另一个 Java 编译器可能为其指定不同的形式参数。当编译器编译使用enum常量的表达式时,它们仅依赖于enum构造的公共静态字段,这些字段是隐式声明的,而不依赖于它们的构造函数或这些常量是如何初始化的。)
因此,示例MethodParameterExample关于enum构造Colors打印如下内容:
enum Colors:
Number of constructors: 0
Number of declared constructors: 1
Declared constructor #1
private MethodParameterExamples$Colors()
Parameter class: class java.lang.String
Parameter name: $enum$name
Modifiers: 4096
Is implicit?: false
Is name present?: true
Is synthetic?: true
Parameter class: int
Parameter name: $enum$ordinal
Modifiers: 4096
Is implicit?: false
Is name present?: true
Is synthetic?: true
Number of methods: 2
Method #1
public static MethodParameterExamples$Colors[]
MethodParameterExamples$Colors.values()
Return type: class [LMethodParameterExamples$Colors;
Generic return type: class [LMethodParameterExamples$Colors;
Method #2
public static MethodParameterExamples$Colors
MethodParameterExamples$Colors.valueOf(java.lang.String)
Return type: class MethodParameterExamples$Colors
Generic return type: class MethodParameterExamples$Colors
Parameter class: class java.lang.String
Parameter name: name
Modifiers: 32768
Is implicit?: true
Is name present?: true
Is synthetic?: false
有关隐式声明的构造的更多信息,请参考Java 语言规范,包括在反射 API 中出现为隐式的参数。
检索和解析方法修饰符
原文:
docs.oracle.com/javase/tutorial/reflect/member/methodModifiers.html
方法声明中可能包含的几个修饰符:
-
访问修饰符:
public、protected和private -
限制为一个实例的修饰符:
static -
禁止值修改的修饰符:
final -
要求覆盖的修饰符:
abstract -
防止重入的修饰符:
synchronized -
表示在另一种编程语言中实现的修饰符:
native -
强制严格浮点行为的修饰符:
strictfp -
注解
MethodModifierSpy示例列出了具有给定名称的方法的修饰符。它还显示方法是否是合成的(编译器生成的)、可变参数的,或者是桥接方法(编译器生成的以支持泛型接口)。
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import static java.lang.System.out;
public class MethodModifierSpy {
private static int count;
private static synchronized void inc() { count++; }
private static synchronized int cnt() { return count; }
public static void main(String... args) {
try {
Class<?> c = Class.forName(args[0]);
Method[] allMethods = c.getDeclaredMethods();
for (Method m : allMethods) {
if (!m.getName().equals(args[1])) {
continue;
}
out.format("%s%n", m.toGenericString());
out.format(" Modifiers: %s%n",
Modifier.toString(m.getModifiers()));
out.format(" [ synthetic=%-5b var_args=%-5b bridge=%-5b ]%n",
m.isSynthetic(), m.isVarArgs(), m.isBridge());
inc();
}
out.format("%d matching overload%s found%n", cnt(),
(cnt() == 1 ? "" : "s"));
// production code should handle this exception more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}
}
MethodModifierSpy产生的输出示例如下。
$ *java MethodModifierSpy java.lang.Object wait*
public final void java.lang.Object.wait() throws java.lang.InterruptedException
Modifiers: public final
[ synthetic=false var_args=false bridge=false ]
public final void java.lang.Object.wait(long,int)
throws java.lang.InterruptedException
Modifiers: public final
[ synthetic=false var_args=false bridge=false ]
public final native void java.lang.Object.wait(long)
throws java.lang.InterruptedException
Modifiers: public final native
[ synthetic=false var_args=false bridge=false ]
3 matching overloads found
$ *java MethodModifierSpy java.lang.StrictMath toRadians*
public static double java.lang.StrictMath.toRadians(double)
Modifiers: public static strictfp
[ synthetic=false var_args=false bridge=false ]
1 matching overload found
$ *java MethodModifierSpy MethodModifierSpy inc*
private synchronized void MethodModifierSpy.inc()
Modifiers: private synchronized
[ synthetic=false var_args=false bridge=false ]
1 matching overload found
$ *java MethodModifierSpy java.lang.Class getConstructor*
public java.lang.reflect.Constructor<T> java.lang.Class.getConstructor
(java.lang.Class<T>[]) throws java.lang.NoSuchMethodException,
java.lang.SecurityException
Modifiers: public transient
[ synthetic=false var_args=true bridge=false ]
1 matching overload found
$ *java MethodModifierSpy java.lang.String compareTo*
public int java.lang.String.compareTo(java.lang.String)
Modifiers: public
[ synthetic=false var_args=false bridge=false ]
public int java.lang.String.compareTo(java.lang.Object)
Modifiers: public volatile
[ synthetic=true var_args=false bridge=true ]
2 matching overloads found
请注意,Method.isVarArgs()对于Class.getConstructor()返回true。这表明方法声明如下:
public Constructor<T> getConstructor(Class<?>... parameterTypes)
不要这样:
public Constructor<T> getConstructor(Class<?> [] parameterTypes)
请注意,String.compareTo()的输出包含两种方法。在String.java中声明的方法:
public int compareTo(String anotherString);
和第二个合成或编译器生成的桥接方法。这是因为String实现了参数化接口Comparable。在类型擦除期间,继承方法Comparable.compareTo()的参数类型从java.lang.Object更改为java.lang.String。由于Comparable和String中的compareTo方法的参数类型在擦除后不再匹配,因此无法进行覆盖。在所有其他情况下,这将产生编译时错误,因为接口未实现。桥接方法的添加避免了这个问题。
Method实现了java.lang.reflect.AnnotatedElement。因此,任何具有java.lang.annotation.RetentionPolicy.RUNTIME的运行时注解都可以被检索。有关获取注解的示例,请参见检查类修饰符和类型部分。


浙公网安备 33010602011771号