sun jdk 与jdk
使用JDK 11后,就sun.misc.Unsafe
的第一种方法。 其中, defineClass
方法已删除。 代码生成框架通常使用此方法在现有的类加载器中定义新的类。 尽管此方法易于使用,但它的存在也使JVM本质上不安全,正如其定义类的名称所暗示的那样。 通过允许在任何类加载器和程序包中定义一个类,可以通过在其中定义一个类来获得对任何程序包的程序包范围访问,从而突破了原本封装的程序包或模块的边界。
为了删除sun.misc.Unsafe
的目标,OpenJDK开始提供在运行时定义类的替代方法。 从版本9开始, MethodHandles.Lookup
类提供了类似于不安全版本的方法defineClass
。 但是,类定义仅适用于与查找的宿主类位于同一包中的类。 由于模块只能解析对某个模块拥有或已打开的包的查找,因此无法将类再注入到不打算提供此类访问权限的包中。
使用方法句柄查找,可以在运行时定义类foo.Qux
,如下所示:
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandles.Lookup privateLookup = MethodHandles.privateLookupIn(foo.Bar.class, lookup);
byte[] fooQuxClassFile = createClassFileForFooQuxClass();
privateLookup.defineClass(fooQuxClassFile);
为了执行类定义,需要MethodHandles.Lookup
的实例,可以通过调用MethodHandles::lookup
方法来检索该MethodHandles::lookup
。 调用后一种方法对呼叫点敏感。 因此,返回的实例将代表从方法内部调用的类和包的特权。 要在另一个包中定义一个类,然后在当前包中定义一个类,则需要使用MethodHandles::privateLookupIn
对此包中的类进行解析。 仅当此目标类的程序包与原始查找类位于同一模块中,或者此包显式打开到查找类的模块时,才有可能。 如果不满足这些要求,则尝试解决私有查找将引发IllegalAccessException
,从而保护JPMS隐含的边界。
当然,代码生成库也受此限制的约束。 否则,它们可能被用来创建和注入恶意代码。 而且由于方法句柄的创建对调用站点敏感,因此在不要求用户通过提供表示其模块特权的适当查找实例的情况下,不要求用户做一些额外工作的情况下就不可能合并新的类定义机制。
使用Byte Buddy时,所需的更改很小。 该库使用ClassDefinitionStrategy
定义类,该类负责从其二进制格式加载类。 在Java 11之前,可以使用Reflection或sun.misc.Unsafe
使用ClassDefinitionStrategy.Default.INJECTION
定义一个类。 为了支持Java 11,此策略需要由ClassDefinitionStrategy.UsingLookup.of(lookup)
代替,在ClassDefinitionStrategy.UsingLookup.of(lookup)
中,提供的查找必须有权访问将驻留类的包。
将cglib代理迁移到Byte Buddy
到目前为止,其他代码生成库尚未提供这种机制,并且尚不确定何时以及是否添加了这种功能。 尤其是对于cglib而言,由于库的过时以及在不再更新且不会采用修改的遗留应用程序中的广泛使用,过去已证明API更改会带来问题。 对于希望采用Byte Buddy作为更现代且积极开发的替代产品的用户,因此以下部分将介绍可能的迁移。
例如,我们使用一个方法为以下示例类生成代理:
public class SampleClass {
public String test() {
return "foo";
}
}
为了创建代理,通常在所有方法都被覆盖以派发侦听逻辑的情况下对代理类进行子类化。 为此,作为示例,我们将一个值栏附加到原始实现的返回值上。
通常使用Enhancer
类和MethodInterceptor
一起定义cglib代理。 方法拦截器提供代理实例,代理方法及其参数。 最后,它还提供了MethodProxy
的实例,该实例允许调用原始代码。