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的实例,该实例允许调用原始代码。

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SampleClass.class);
enhancer.setCallback(new MethodInterceptor() {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) {return proxy.invokeSuper(obj, method, args) + "bar";}
});
SampleClass proxy = (SampleClass) enhancer.create();
assertEquals("foobar", proxy.test());

请注意,如果在代理实例上调用了诸如hashCodeequalstoString类的任何其他方法,则上述代码将引起问题。 前两个方法也将由拦截器分派,因此当cglib尝试返回字符串类型的返回值时,将导致类强制转换异常。 相反, toString方法可以工作,但是会返回意外的结果,因为原始实现的前缀是bar作为返回值。

在Byte Buddy中,代理不是专门的概念,但是可以使用库的通用代码生成DSL进行定义。 对于与cglib最相似的方法,使用MethodDelegation提供了最简单的迁移路径。 这样的委派以用户定义的拦截器类为目标,方法调用将调度到该类:

public class SampleClassInterceptor {public static String intercept(@SuperCall Callable<String> zuper) throws Exception {return zuper.call() + "bar";}
}

上面的拦截器首先通过由Byte Buddy提供的帮助程序实例来调用原始代码。 使用Byte Buddy的代码生成DSL来实现对此拦截器的委托,如下所示:

SampleClass proxy = new ByteBuddy().subclass(SampleClass.class).method(ElementMatchers.named("test")).intercept(MethodDelegation.to(SampleClassInterceptor.class)).make().load(someClassLoader, ClassLoadingStrategy.UsingLookup.of(MethodHandles.privateLookupIn(SampleClass.class, MethodHandles.lookup())).getLoaded().getDeclaredConstructor().newInstance();
assertEquals("foobar", proxy.test());

除了cglib之外,Byte Buddy还需要使用ElementMatcher指定方法过滤器。 尽管在cglib中完全可以进行过滤,但是它非常麻烦并且没有明确要求,因此很容易被遗忘。 在Byte Buddy中,仍然可以使用ElementMatchers.any()匹配器拦截所有方法,但是通过要求指定这样的匹配器,希望提醒用户做出有意义的选择。

使用上述匹配器,每当调用名为test的方法时,都会使用所讨论的方法委派将调用委派给指定的拦截器。

但是,引入的拦截器将无法分派不返回字符串实例的方法。 实际上,代理创建会产生由Byte Buddy发出的异常。 但是,完全有可能定义一个更通用的拦截器,该拦截器可应用于与cglib的MethodInterceptor提供的方法类似的任何方法:

public class SampleClassInterceptor {@RuntimeTypepublic static Object intercept(@Origin Method method,@This Object self,@AllArguments Object[] args,@SuperCall Callable<String> zuper) throws Exception {return zuper.call() + "bar";}
}

当然,由于在这种情况下不使用拦截器的其他参数,因此可以省略它们,从而使代理更有效。 Byte Buddy仅在需要时才提供论据,如果实际需要的话。

由于上述代理是无状态的,因此将拦截方法定义为静态。 同样,这是一个简单的优化,因为Byte Buddy否则需要在代理类中定义一个字段,该字段包含对拦截器实例的引用。 但是,如果需要实例,则可以使用MethodDelegation.to(new SampleClassInterceptor())将委托定向到实例的成员方法。

缓存代理类以提高性能

使用Byte Buddy时,不会自动缓存代理类。 这意味着每次运行上述代码时,都会生成并加载一个新类。 由于代码生成和类定义是昂贵的操作,因此这当然效率低下,如果可以重复使用代理类,则应避免这种情况。 在cglib中,如果两次增强的输入相同,则返回先前生成的类,这通常在两次运行同一代码段时是正确的。 然而,由于通常可以更容易地计算缓存密钥,因此该方法相当容易出错并且通常效率低下。 使用字节伙伴,可以使用专用的缓存库(如果已有的话)。 另外,Byte Buddy还提供了TypeCache ,它通过用户定义的缓存键为类实现了简单的缓存。 例如,可以使用以下代码使用基类作为键来缓存以上类的生成:

TypeCache<Class<?>> typeCache = new TypeCache<>(TypeCache.Sort.SOFT);
Class<?> proxyType = typeCache.findOrInsert(classLoader, SampleClass.class, () -> new ByteBuddy().subclass(SampleClass.class).method(ElementMatchers.named("test")).intercept(MethodDelegation.to(SampleClassInterceptor.class)).make().load(someClassLoader, ClassLoadingStrategy.UsingLookup.of(MethodHandles.privateLookupIn(SampleClass.class, MethodHandles.lookup())).getLoaded()
});

不幸的是,Java中的缓存类带来了一些警告。 如果创建了代理,则它当然会继承它所代理的类的子类,该类使该基类不适合进行垃圾收集。 因此,如果代理类被强引用,则密钥也将被强引用。 这将使高速缓存无用,并因内存泄漏而打开。 因此,必须通过构造函数参数指定的内容来轻而易举地引用代理类。 将来,如果Java引入了星历作为参考类型,则可能会解决此问题。 同时,如果不存在代理类垃圾回收的问题,则可以使用ConcurrentMap在不存在时计算值。

拓宽代理类的可用性

为了包含代理类的重用,将代理类重构为无状态并将状态隔离到实例字段中通常是有意义的。 然后,可以在侦听期间使用提到的依赖项注入机制来访问此字段,例如,以使后缀值可针对每个代理实例进行配置:

public class SampleClassInterceptor {public static String intercept(@SuperCall Callable<String> zuper, @FieldValue("qux") String suffix) throws Exception {return zuper.call() + suffix;}
}

上面的拦截器现在接收字段qux的值作为第二个参数,可以使用Byte Buddy的类型创建DSL声明它:

TypeCache<Class<?>> typeCache = new TypeCache<>(TypeCache.Sort.SOFT);
Class<?> proxyType = typeCache.findOrInsert(classLoader, SampleClass.class, () -> new ByteBuddy().subclass(SampleClass.class).defineField(“qux”, String.class, Visibility.PUBLIC).method(ElementMatchers.named("test")).intercept(MethodDelegation.to(SampleClassInterceptor.class)).make().load(someClassLoader, ClassLoadingStrategy.UsingLookup.of(MethodHandles.privateLookupIn(SampleClass.class, MethodHandles.lookup())).getLoaded()
});

现在,可以使用Java反射在每个实例创建后在每个实例上设置该字段值。 为了避免反射,DSL还可以用于实现一些接口,该接口声明用于所提及字段的设置方法,可以使用Byte Buddy的FieldAccessor实现来实现。

加权代理运行时和创建性能

最后,在使用Byte Buddy创建代理时,需要考虑一些性能。 在生成代码时,需要在代码生成本身的性能与所生成代码的运行时性能之间进行权衡。 与cglib或其他proxing库相比,Byte Buddy通常旨在创建尽可能高效地运行的代码,这可能需要更多时间来创建此类代码。 这是基于这样的假设,即大多数应用程序运行时间很长,但是一次只能创建代理,但是代理不适用于所有类型的应用程序。

与cglib的一个重要区别是,Byte Buddy为每个方法生成一个专用的超级调用委托,该方法被拦截,而不是单个MethodProxy 。 这些额外的类需要花费更多的时间来创建和加载,但是使这些类可用可以为每个方法执行带来更好的运行时性能。 如果在循环中调用代理方法,则这种差异很快就很关键。 但是,如果运行时性能不是主要目标,并且在短时间内创建代理类更重要,则以下方法可避免完全创建其他类:

public class SampleClassInterceptor {public static String intercept(@SuperMethod Method zuper, @This Object target, @AllArguments Object[] arguments) throws Exception {return zuper.invoke(target, arguments) + "bar";}
}

模块化环境中的代理

对拦截器使用简单形式的依赖注入,而不是依赖于特定于库的类型,例如cglib的
MethodInterceptor ,Byte Buddy在模块化环境中提供了另一个优势:由于生成的代理类将直接引用拦截器类,而不是引用库特定的调度程序类型(例如cglib的MethodInterceptor ,因此被代理类的模块不需要读取Byte Buddy的模块。 对于cglib,代理的类模块必须读取cglib的模块,该模块定义了MethodInterceptor接口,而不是实现该接口的模块。 对于使用cglib作为传递依赖的库的用户,这很可能是不直观的,尤其是如果将后者依赖视为不应公开的实现细节时,尤其如此。

在某些情况下,代理类的模块读取提供拦截器的框架模块甚至是不可能或不希望的。 对于这种情况,Byte Buddy还提供了一种解决方案,通过使用它来完全避免这种依赖性
Advice组件。 该组件可用于以下示例中的代码模板:

public class SampleClassAdvice {@Advice.OnMethodExitpublic static void intercept(@Advice.Returned(readOnly = false) String returned) {returned += "bar";}
}

上面的代码看起来似乎没有多大意义,实际上,它将永远不会执行。 该类仅用作Byte Buddy的字节代码模板,后者可读取带注释的方法的字节代码,然后将其内联到生成的代理类中。 为此,必须对上述方法的每个参数进行注释,以代表代理方法的值。 在上述情况下,注释定义了参数,以定义方法的返回值,在给定模板的情况下,将bar添加为后缀。 给定此建议类,可以如下定义代理类:

new ByteBuddy().subclass(SampleClass.class).defineField(“qux”, String.class, Visibility.PUBLIC).method(ElementMatchers.named(“test”)).intercept(Advice.to(SampleClassAdvice.class).wrap(SuperMethodCall.INSTANCE)).make()

通过将建议包装在SuperMethodCall周围,​​在对重写方法进行调用之后,将内联上述建议代码。 要在原始方法调用之前内联代码,可以使用OnMethodEnter批注。

9和10之前的Java版本上的支持代理

在为JVM开发应用程序时,通常可以依赖在特定版本上运行的应用程序,也可以在更高版本上运行。 即使使用了内部API,也已经有很长时间了。 但是,由于删除了此内部API,从Java 11开始,情况不再如此,依赖于sun.misc.Unsafe代码生成库将不再起作用。 同时,通过MethodHandles.Lookup类定义MethodHandles.Lookup用于版本9之前的JVM。

对于Byte Buddy,用户有责任使用与当前JVM兼容的类加载策略。 为了支持所有JVM,需要进行以下选择:

ClassLoadingStrategy<ClassLoader> strategy;
if (ClassInjector.UsingLookup.isAvailable()) {Class<?> methodHandles = Class.forName("java.lang.invoke.MethodHandles");Object lookup = methodHandles.getMethod("lookup").invoke(null);Method privateLookupIn = methodHandles.getMethod("privateLookupIn", Class.class, Class.forName("java.lang.invoke.MethodHandles$Lookup"));Object privateLookup = privateLookupIn.invoke(null, targetClass, lookup);strategy = ClassLoadingStrategy.UsingLookup.of(privateLookup);
} else if (ClassInjector.UsingReflection.isAvailable()) {strategy = ClassLoadingStrateg.Default.INJECTION;
} else {throw new IllegalStateException(“No code generation strategy available”);
}

上面的代码使用反射来解决方法句柄查找并加以解决。 这样做,可以在Java 9之前的JDK上编译和加载代码。不幸的是,由于MethodHandles::lookup是调用站点敏感的,因此Byte Buddy无法实现此代码,因此必须在驻留在其中的类中定义以上内容。用户模块,而不在Byte Buddy中。

最后,值得考虑的是完全避免类注入。 代理类也可以使用ClassLoadingStrategy.Default.WRAPPER策略在自己的类加载器中定义。 此策略不使用任何内部API,并且可以在任何JVM版本上使用。 但是,必须牢记创建专用类加载器的性能成本。 最后,即使代理类的软件包名称与代理类相同,通过在不同的类加载器中定义代理,JVM将不再将其运行时软件包视为相等,因此不允许覆盖任何软件包,私人方法。

最后的想法

最后一点,我想表达我的观点,尽管迁移成本很高,但退出sun.misc.Unsafe是朝着更安全,模块化的JVM迈出的重要一步。 在删除此非常强大的类之前,可以使用sun.misc.Unsafe仍然提供的特权访问来绕过JPMS设置的任何边界。 如果不进行此删除,则JPMS会付出额外封装带来的所有不便,而无法依赖它。

JVM上的大多数开发人员很可能永远不会遇到这些附加限制的任何问题,但是如上所述,代码生成和代理库需要适应这些更改。 对于cglib,不幸的是,这确实意味着道路的尽头。 Cglib最初被建模为Java内置代理API的更强大版本,在该版本中,Cglib要求代理类引用其自己的调度程序API,类似于Java API要求引用其类型的方式。 但是,这些后面的类型驻留在java.base模块中,该模块始终由任何模块读取。 因此,Java代理API仍然可以正常工作,而cglib模型则无法修复。 过去,这已经使cglib成为OSGi环境的一个困难候选者,但是对于JPMS,作为库的cglib不再起作用。 Javassist提供的相应代理API存在类似问题。

这种变化的好处是,JVM最终提供了一个稳定的API,用于在应用程序运行时定义类,这是一种依赖内部API二十多年的常见操作。 除了我认为仍然需要更灵活方法的Javaagents之外,这意味着在所有代理用户完成此最终迁移之后,可以保证将来的Java版本始终能够正常工作。 鉴于cglib的开发多年来一直处于Hibernate状态,并且该库受到许多限制,因此无论如何,如今的库用户最终迁移都是不可避免的。 Javassist代理可能也是如此,因为后者的库在近半年内也没有提交。

翻译自: https://www.javacodegeeks.com/2018/04/jdk-11-and-proxies-in-a-world-past-sun-misc-unsafe.html

sun jdk 与jdk

sun jdk 与jdk_Sun过去的世界中的JDK 11和代理相关推荐

  1. jenkins中jdk配置_如何在Jenkins中配置JDK

    jenkins中jdk配置 In the previous chapter, we learnt how to configure the Jenkins and start the server a ...

  2. Sun过去的世界中的JDK 11和代理

    使用JDK 11后,就sun.misc.Unsafe的第一种方法. 其中, defineClass方法已删除. 代码生成框架通常使用此方法在现有的类加载器中定义新的类. 尽管此方法易于使用,但它的存在 ...

  3. JDK与JRE及其在Eclipse中的使用

    转载自:http://blog.csdn.net/gx1058742912/article/details/51033942 JDK与jRE的区别 JDK(java development kit): ...

  4. ubuntu安装jdk语句_JDK 12:实际中的切换语句/表达式

    ubuntu安装jdk语句 我的上一篇文章" 玩JDK 12的Switch表达式 "讨论了使用JDK 12 Early Access Builds尝试JEP 325 switch ...

  5. 在Spring中使用JDK定时器实现调度任务

    在Spring中使用JDK定时器实现调度任务 作者:chszs,转载需注明.博客主页: http://blog.csdn.net/chszs 本文探讨Spring如何集成JDK的Timer定时器,实现 ...

  6. 世界顶级黑客Kevin D.Mitnick教你如何在数字世界中真正隐形

    世界顶级黑客Kevin D.Mitnick教你如何在数字世界中真正隐形.对计算机历史有一定了解的人,相信对kevin Mitnick一定不会陌生,可以说他是黑客的代名词.他的黑客生涯可谓充满传奇,已被 ...

  7. 世界顶级黑客Kevin D.Mitnick教你如何在数字世界中真正隐形?

    本文讲的是世界顶级黑客Kevin D.Mitnick教你如何在数字世界中真正隐形?, 对计算机历史有一定了解的人,相信对kevin Mitnick一定不会陌生,可以说他是黑客的代名词.他的黑客生涯可谓 ...

  8. CMD中指定JDK版本命令

    CMD中指定JDK版本命令 问题表述启动jar包时报错,原因是当前本机jdk版本和启动的jar包版本不匹配,找不到类(本地系统是jdk11,jar包是jdk8) PS D:\ApplicationZI ...

  9. 现实世界中哪些地方用到了Java?

     现实世界中哪些地方用到了Java? java android应用 电子商务 编程语言 应用程序 操作系统 除了Minecraft这款游戏以外,你有没有见过用Java编写的游戏.桌面系统.办公软件 ...

最新文章

  1. Apache+php+mysql在windows下的安装与配置(图文)
  2. HDU 4166 BNU 32715 Robot Navigation (记忆化bfs)
  3. 033-Unit 12 Introduction to String Processiong
  4. OpenCASCADE:读取和写入 IGES
  5. Fiddler 跟踪数据包
  6. NS3入门--first.cc
  7. python利用opencv去除图片logo_利用python和opencv批量去掉图片黑边
  8. 毛概 第二章新民主主义革命理论
  9. linux系统中自动生成snap文件_在Linux操作系统下自动生成Makefile的方法
  10. 44 年前的今天,改变世界的 TA 诞生了!
  11. 深度学习必备的几款流行网络与数据集
  12. 大学生体育运动网页设计模板代码 校园篮球网页作业成品 学校篮球网页制作模板 学生简单体育运动网站设计成品...
  13. idea 编程字体推荐
  14. Java 性能调优总结
  15. 新建文本html,创建邮件模板时html内容和文本内容哪种好
  16. proteus中power怎么设置电压_Proteus 电源设置
  17. 含绝对值函数的可导性
  18. 第一篇:瑞吉外卖项目概述
  19. c语言if语句教学设计,if语句教学设计
  20. One-Hot 的使用

热门文章

  1. 字符串hash(一)
  2. 武汉工程大学2020GPLT选拔赛(上)
  3. CSP2019洛谷P5665:划分(单调队列,高精度)
  4. CF666E-Forensic Examination【广义SAM,线段树合并】
  5. AT3957-[AGC023F]01 on Tree【贪心,堆】
  6. 51nod1743-雪之国度【最小生成树,LCA,并查集】
  7. P5290-[十二省联考2019]春节十二响【贪心,堆】
  8. 【线段树】Frog Traveler(CF751D)
  9. 2019年这50个Kafka面试题,你知道答案么
  10. HighChart模拟点击series的name显示隐藏