检测 "should not" 与反射一起使用的构造函数
Detect constructors that "should not" be used with reflection
我有以下功能:
@SuppressWarnings("unchecked")
public static <T> T createInstance(String className, Object... args) {
try {
Class<?> clazz = Class.forName(className);
Class<?>[] parameterTypes = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new);
return (T) clazz.getDeclaredConstructor(parameterTypes).newInstance(args);
} catch (Exception e) {
throw new RuntimeException("Error", e);
}
}
该函数应该得到一个 class 名称(如 String
),加载它,如果找到与 Object... args
匹配的构造函数,则创建一个新实例这样的对象。
此代码有效,但它可能会在 @HotSpotIntrinsicCandidate
方法上被调用。
例如,如果调用者这样做:
String str = createInstance("java.lang.String", "Some value");
... 然后该函数将调用 class java.lang.String
:
的构造函数
@HotSpotIntrinsicCandidate
public String(String original)
我对 @HotSpotIntrinsicCandidate
构造函数的理解是 JVM 会对它们进行特殊处理,例如这就是为什么我能够做到 String str = "some string";
但我不能做到的原因做 MyObject myObj = "some string"
(假设 MyObject
class 有一个构造函数,它在参数中采用 String
)。
与此同时,我有点理解这些构造函数是“不可靠的”,因为它们严格取决于 JVM 人员的决定。例如,该代码也适用于盒装基元,例如 Integer
:
Integer myInt = createInstance("java.lang.Integer", "3");
... 但是这些构造函数自 Java 9 以来已被弃用(并且这些 classes 的新 @HotSpotIntrinsicCandidate
s 是 valueOf(...)
方法,而不是不再是构造函数)。
我的感觉是,我应该拒绝对 @Deprecated
或 @HotSpotIntrinsicCandidate
构造函数的任何请求,以避免调用者在某天可能会被弃用的东西上构建块。
- 谁能证实(或否定)我的假设?
- 假设我的直觉是正确的,我可以很容易地
getAnnotation(Deprecated.class)
我检索到的 Constructor
(因为这个注释是 public)但我不能 getAnnotation(HotSpotIntrinsicCandidate.class)
因为此注释对 jdk.internal
是包私有的。如果那样的话,我如何才能检测到我应该拒绝构建的所有 classes?
My understanding of @HotSpotIntrinsicCandidate constructors is
完全错误。
HotSpot 指的是 JVM 将 运行 所有 java 代码极其 stupidly/slowly,甚至比你想象的要慢,因为它还做了一大堆看似毫无意义的记账工作(这个 'if' 以一种方式分支与以另一种方式分支的频率 - 让我们无缘无故地计算它)。
... 因为有了所有这些簿记,JVM 可以轻松识别出占用 99% 资源的 1% 的代码,然后将花费相当多的时间(并使用所有这些簿记它正在做)以生成一些经过微调的机器代码,专门针对您 运行 使用的确切硬件,并针对目前观察到的情况(即簿记)最快地优化为 运行。现在你有了非常快的代码。考虑到这 1% 的代码实际上占用了 99% 的资源,这就是 JVM 实际上非常快的原因。 'rewriting'的那个进程,就是热点。 纯粹是 Java-The-Virtual-Machine。 Java-The-Language 没有热点,不知道它是什么,而你在谈论语言功能,因此 - 不,那根本不是 @HotspotIntrinsicCandidate 的意思。
您可以写 String x = "hello"
的原因是 java 语言规范规定了这一点。而已。这是唯一的原因。因为它在规范中是硬编码的。 (同样为什么你可以写 Integer i = 5;
- 因为'5'在JLS中被写为一个概念,并且'嘿,如果代码不能编译但是将一个原语转换成它匹配的包装器类型会让它工作- 然后假设程序员打算这样做并编译它,就好像它说 Integer.valueOf(5)
而不是' - 这被称为 'autoboxing' 并且也是用 JLS 编写的,这使得它起作用。同样,@HotspotIntrinsicCandidate
与它 没有任何关系。
@HIC 指的是 JVM 实现可以自由地拥有它的“pre-written”精细调整的机器代码版本,直接在 JVM 实现本身中可用,并且 JVM 运行 ner 应该寻找并使用这样的东西(如果可用)。这不能保证(但大多数 JVM 实现确实以这种方式工作;记录“j.l.String
's hashCode()
method invoked' 的频率并没有多大意义 - 任何时候你将字符串推入 hashmaps 都意味着它会被调用, 很多。不妨 'precompile' 它的机器代码并将其直接注入 java.exe
。为什么要等,对吗?
考虑到它 纯粹是 一个优化动作,它的存在与否对 'it is a good idea' 是否公开该构造函数绝对没有丝毫影响。
My feeling is that I should reject any request on constructors that are either @Deprecated
弃用是一件奇怪的事情。它用于许多不同的事情。不使用这样的构造函数没有什么特别的意义——当然,它们 可能 在未来消失,但是弃用标记还有其他原因(有时只是“这个方法是个坏主意,在大多数使用它的人感到困惑”。例如,j.u.Date
的 getYear()
方法已被弃用。我很乐意以 1000 比 1 的赔率打赌它仍然存在 10 年从现在开始。它不会去任何地方。使用它只是一个坏主意。
这让我们...
这是个坏主意,就这样。
Class<?>[] parameterTypes = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new);
这行不通。
简单的例子:
class Example {
public Example(Number n) {
System.out.println("Hello!");
}
}
...
createInstance("com.foo.Example", 5);
上面的不行!!
那是因为在那个 5 上调用 .getClass()
会让你 java.lang.Integer.class
。这完全是 'compatible' 和 Number(它是它的子 class)- new Example(5)
工作得很好。但是,getConstructor()
搜索 完全 您所要求的,这是一个采用 1 Integer 的构造函数。那不在那里。您有一个采用 Integer 超类型的构造函数。
解决这个问题的唯一方法是使用 .getDeclaredConstructors()
,检查所有这些,并在每个参数上使用 .isSubclass
和朋友,同时添加代码来处理可变参数如果您愿意,可以调用,然后使用它。
这非常慢。
更一般地说,这种方法意味着你在离开 laissez-faire compiler-less 语言(通常称为 'scripting languages')和 java 最糟糕的方面时采取最糟糕的方面最好的部分出来。这有什么可能的意义?
我敢打赌您正在寻找其中的一些选项:
构建和编译器基础设施比您想象的要好得多
有 许多 工具非常聪明,可以知道需要编译哪些 java 代码(即仅您更改的代码),并且可以编译只是那些东西。例如,eclipse(编辑器)会自动地并且几乎是瞬间地保持你所有的东西被编译,持续不断。如果这是为了避免 compile-build 循环,则没有必要这样做。
您甚至可以 运行 java foo.java
并且 JVM 会自动编译并 运行 它,用于琐碎的 one-file 项目。
有 java-esque 种脚本语言
如果需要,您可以在 运行 javajava 中编写脚本。 GraalVM 项目有一个 java 库,它只是 运行 的 java 脚本。为什么不用那个?如果您希望语法具体为 java-like,则有 beanshell 和 groovy.
有模块系统和动态加载器
您可以设置一个简单的简 java(例如,在 java 文档或 OSGi 项目中查找 ClassLoader class),一个系统可以'live-reload' classes 在 运行ning JVM 中。如果你的目标是你可以启动一个 JVM,然后手动 'program in it' 作为它 运行s,你可以使用那些。
调试器很棒
调试器可以插入 运行ning JVM,如果需要,甚至可以通过 Internet 连接,并且只需 运行 您在断点线程中间按需键入的代码。例如在eclipse中,随便在什么地方设置一个断点,运行这个东西(点击'bug'按钮,那个运行就进入debug模式了),然后打开debug shell 并键入任何你想要的。您甚至可以访问局部变量,它们将具有命中断点时的任何值。如果愿意,您可以在服务器上 'live code' 运行 执行此操作。
有JSP
我强烈、强烈地反对这样做,但是 JSP 是一种你可以在 .jsp
文件中的 HTML 中粘贴 java 代码并拥有一个Web 服务器只是 运行 这个。它会处理所有事情 - 检测到它发生了变化,(重新)编译它,然后 运行ning 它。您可以简单地编辑 JSP 文件并重新加载页面,现在您正在查看新内容。
有些要求你告诉它:是的,请检查更改和re-compile。
我有以下功能:
@SuppressWarnings("unchecked")
public static <T> T createInstance(String className, Object... args) {
try {
Class<?> clazz = Class.forName(className);
Class<?>[] parameterTypes = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new);
return (T) clazz.getDeclaredConstructor(parameterTypes).newInstance(args);
} catch (Exception e) {
throw new RuntimeException("Error", e);
}
}
该函数应该得到一个 class 名称(如 String
),加载它,如果找到与 Object... args
匹配的构造函数,则创建一个新实例这样的对象。
此代码有效,但它可能会在 @HotSpotIntrinsicCandidate
方法上被调用。
例如,如果调用者这样做:
String str = createInstance("java.lang.String", "Some value");
... 然后该函数将调用 class java.lang.String
:
@HotSpotIntrinsicCandidate
public String(String original)
我对 @HotSpotIntrinsicCandidate
构造函数的理解是 JVM 会对它们进行特殊处理,例如这就是为什么我能够做到 String str = "some string";
但我不能做到的原因做 MyObject myObj = "some string"
(假设 MyObject
class 有一个构造函数,它在参数中采用 String
)。
与此同时,我有点理解这些构造函数是“不可靠的”,因为它们严格取决于 JVM 人员的决定。例如,该代码也适用于盒装基元,例如 Integer
:
Integer myInt = createInstance("java.lang.Integer", "3");
... 但是这些构造函数自 Java 9 以来已被弃用(并且这些 classes 的新 @HotSpotIntrinsicCandidate
s 是 valueOf(...)
方法,而不是不再是构造函数)。
我的感觉是,我应该拒绝对 @Deprecated
或 @HotSpotIntrinsicCandidate
构造函数的任何请求,以避免调用者在某天可能会被弃用的东西上构建块。
- 谁能证实(或否定)我的假设?
- 假设我的直觉是正确的,我可以很容易地
getAnnotation(Deprecated.class)
我检索到的Constructor
(因为这个注释是 public)但我不能getAnnotation(HotSpotIntrinsicCandidate.class)
因为此注释对jdk.internal
是包私有的。如果那样的话,我如何才能检测到我应该拒绝构建的所有 classes?
My understanding of @HotSpotIntrinsicCandidate constructors is
完全错误。
HotSpot 指的是 JVM 将 运行 所有 java 代码极其 stupidly/slowly,甚至比你想象的要慢,因为它还做了一大堆看似毫无意义的记账工作(这个 'if' 以一种方式分支与以另一种方式分支的频率 - 让我们无缘无故地计算它)。
... 因为有了所有这些簿记,JVM 可以轻松识别出占用 99% 资源的 1% 的代码,然后将花费相当多的时间(并使用所有这些簿记它正在做)以生成一些经过微调的机器代码,专门针对您 运行 使用的确切硬件,并针对目前观察到的情况(即簿记)最快地优化为 运行。现在你有了非常快的代码。考虑到这 1% 的代码实际上占用了 99% 的资源,这就是 JVM 实际上非常快的原因。 'rewriting'的那个进程,就是热点。 纯粹是 Java-The-Virtual-Machine。 Java-The-Language 没有热点,不知道它是什么,而你在谈论语言功能,因此 - 不,那根本不是 @HotspotIntrinsicCandidate 的意思。
您可以写 String x = "hello"
的原因是 java 语言规范规定了这一点。而已。这是唯一的原因。因为它在规范中是硬编码的。 (同样为什么你可以写 Integer i = 5;
- 因为'5'在JLS中被写为一个概念,并且'嘿,如果代码不能编译但是将一个原语转换成它匹配的包装器类型会让它工作- 然后假设程序员打算这样做并编译它,就好像它说 Integer.valueOf(5)
而不是' - 这被称为 'autoboxing' 并且也是用 JLS 编写的,这使得它起作用。同样,@HotspotIntrinsicCandidate
与它 没有任何关系。
@HIC 指的是 JVM 实现可以自由地拥有它的“pre-written”精细调整的机器代码版本,直接在 JVM 实现本身中可用,并且 JVM 运行 ner 应该寻找并使用这样的东西(如果可用)。这不能保证(但大多数 JVM 实现确实以这种方式工作;记录“j.l.String
's hashCode()
method invoked' 的频率并没有多大意义 - 任何时候你将字符串推入 hashmaps 都意味着它会被调用, 很多。不妨 'precompile' 它的机器代码并将其直接注入 java.exe
。为什么要等,对吗?
考虑到它 纯粹是 一个优化动作,它的存在与否对 'it is a good idea' 是否公开该构造函数绝对没有丝毫影响。
My feeling is that I should reject any request on constructors that are either @Deprecated
弃用是一件奇怪的事情。它用于许多不同的事情。不使用这样的构造函数没有什么特别的意义——当然,它们 可能 在未来消失,但是弃用标记还有其他原因(有时只是“这个方法是个坏主意,在大多数使用它的人感到困惑”。例如,j.u.Date
的 getYear()
方法已被弃用。我很乐意以 1000 比 1 的赔率打赌它仍然存在 10 年从现在开始。它不会去任何地方。使用它只是一个坏主意。
这让我们...
这是个坏主意,就这样。
Class<?>[] parameterTypes = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new);
这行不通。
简单的例子:
class Example {
public Example(Number n) {
System.out.println("Hello!");
}
}
...
createInstance("com.foo.Example", 5);
上面的不行!!
那是因为在那个 5 上调用 .getClass()
会让你 java.lang.Integer.class
。这完全是 'compatible' 和 Number(它是它的子 class)- new Example(5)
工作得很好。但是,getConstructor()
搜索 完全 您所要求的,这是一个采用 1 Integer 的构造函数。那不在那里。您有一个采用 Integer 超类型的构造函数。
解决这个问题的唯一方法是使用 .getDeclaredConstructors()
,检查所有这些,并在每个参数上使用 .isSubclass
和朋友,同时添加代码来处理可变参数如果您愿意,可以调用,然后使用它。
这非常慢。
更一般地说,这种方法意味着你在离开 laissez-faire compiler-less 语言(通常称为 'scripting languages')和 java 最糟糕的方面时采取最糟糕的方面最好的部分出来。这有什么可能的意义?
我敢打赌您正在寻找其中的一些选项:
构建和编译器基础设施比您想象的要好得多
有 许多 工具非常聪明,可以知道需要编译哪些 java 代码(即仅您更改的代码),并且可以编译只是那些东西。例如,eclipse(编辑器)会自动地并且几乎是瞬间地保持你所有的东西被编译,持续不断。如果这是为了避免 compile-build 循环,则没有必要这样做。
您甚至可以 运行 java foo.java
并且 JVM 会自动编译并 运行 它,用于琐碎的 one-file 项目。
有 java-esque 种脚本语言
如果需要,您可以在 运行 javajava 中编写脚本。 GraalVM 项目有一个 java 库,它只是 运行 的 java 脚本。为什么不用那个?如果您希望语法具体为 java-like,则有 beanshell 和 groovy.
有模块系统和动态加载器
您可以设置一个简单的简 java(例如,在 java 文档或 OSGi 项目中查找 ClassLoader class),一个系统可以'live-reload' classes 在 运行ning JVM 中。如果你的目标是你可以启动一个 JVM,然后手动 'program in it' 作为它 运行s,你可以使用那些。
调试器很棒
调试器可以插入 运行ning JVM,如果需要,甚至可以通过 Internet 连接,并且只需 运行 您在断点线程中间按需键入的代码。例如在eclipse中,随便在什么地方设置一个断点,运行这个东西(点击'bug'按钮,那个运行就进入debug模式了),然后打开debug shell 并键入任何你想要的。您甚至可以访问局部变量,它们将具有命中断点时的任何值。如果愿意,您可以在服务器上 'live code' 运行 执行此操作。
有JSP
我强烈、强烈地反对这样做,但是 JSP 是一种你可以在 .jsp
文件中的 HTML 中粘贴 java 代码并拥有一个Web 服务器只是 运行 这个。它会处理所有事情 - 检测到它发生了变化,(重新)编译它,然后 运行ning 它。您可以简单地编辑 JSP 文件并重新加载页面,现在您正在查看新内容。
有些要求你告诉它:是的,请检查更改和re-compile。