`
deepinmind
  • 浏览: 451335 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
1dc14e59-7bdf-33ab-841a-02d087aed982
Java函数式编程
浏览量:41606
社区版块
存档分类
最新评论

使用Byte Buddy生成Java字节码

阅读更多
上周我们探索了下Java的强类型及静态类型系统。我承认这样的类型让我们的代码表达性更强,但是同时也限制了第三方库提供面向POJO的API的能力。

我们明白了Java的反射的确是一种和用户代码交互的不错方式,但是这样损失了类型安全的好处。为了实现类型安全,_或许更好的方式就是在运行时通过代码生成来创建指定用户类的子类_?对于这些类,我们会重写它们的方法以实现我们的框架逻辑,而不需要用户依赖于框架的类型。

我建议你读下这篇文章的第一部分,如果你还没读过的话。我希望它能让你明白代码生成想要解决的是哪类问题,以及什么时候用反射的话会更合适一些 。

可以开始了吗

我们都知道如何去定义一个Java类(我希望如此),因此这里也没有什么新的东西可讲了。然而一般我们都是通过某种JVM语言来表述这些类的定义,然后才请求编译器去把我们的东西翻译成字节码。

Java字节码是一个二进制格式,但是,不止如此,Java编程语言和它对应的字节码非常相似。这也不奇怪,因为Java语言某种程度上也驱动着字节码指令集的演进。如果你想了解更多关于字节码的知识,可以参考下RebelLabs上的这篇_掌握JVM字节码_的不错的分享,它会告诉你里面的很多技巧。

虽然直接操作字节码可以让你完全掌控类的生成过程,不过它的确很难搞定。字节码并没有给你提供像自动装箱或者高层面的控制流程之类的便利性。同样的,生成字节码需要你做许多繁琐的工作比如计算操作数栈的大小以及计算栈帧。如果可以跳过这些,只关注于你要生成的运行时代码不是更好吗?

当然如此。也正由于这个原因,即便是Java类库也自带了一些有限的支持。这些类又被称为JDK代理,它可以在运行时生成实现了某组接口的某个类的定义。

调用里面任何一个接口的方法都会被重定向到一个调用handler里面,你得去实现并提供这个handler。说起JDK代理,它依赖于接口的实现。你不能用这些Java代理来扩展现有的类。

对于我们上篇博客中的那个安全框架而言,这意味着我们只能确保接口方法的安全,而不能是别的类型的方法。这个限制真是弱爆了。正是因为这个,在JDK代理出现了之后,很快cglib就作为一个独立的库发布了。

cglib的工作原理与JDK代理类似,但是它可以动态生成子类而不止是实现接口,因此它可以代理任何方法。cglib至今仍非常流行,像Spring这样的框架也依赖于它来实现某些功能。

然而,尽管它非常流行,cglib这些年的开发已经不那么活跃了,甚至bug修复也只是偶尔才发布一次。由于CGLIB不再支持Java 8的默认方法等新特性,这个问题就变得相当严重了。

由于cglib这些未解决的问题,这些年越来越多的工程开始选择移除这个库。至少在Hiberate将javassist作为首选之后,这个_Java辅助_(javassist: java assistant)库明显成为了主流的选择。

javassist有一个和cglib功能非常类似的一个代码库。除此之外,它还提供了一个运行时的Java编译器,这使得你可以通过提供文本的Java代码来进行任何Java类的定义及重定义。

这听起来非常酷,在一个字符串中写代码就可以快速地解决问题,尤其是你需要将一个已编译的功能和一个动态功能进行混入的时候。下图中的代码是一个使用javassist的真实例子。



更重要的是,尽管Java源码是表达Java类的一个不错的方式,不幸的是,尽管Red Hat已经开始赞助这个库了,但迄今为止javassist仍然只能算是一个个人的项目。随着Java语言的不断改进,javassist的编译器慢慢也开始落后于JDK的版本了。

因此,尽管可以用javassist来写Java代码,你还是得考虑一下字节码。而且这也是必须的,比如说,进行基础类型的显式装箱。猜猜为什么?这是因为由于Java 8新的语言特性的引入,两个编译器间的差距变得越来越大了。

少抱怨一点好吧?

你很容易就能指出别人写的库中存在的问题,但是我们可以改善这种情况吗?cglib和javassist都 是本世纪初期的时候建立的,它们提供的API是围绕着Java在这段时期提供的语言特性来设计 的。

这些库的介入之后一个显著的创新就是注解。这多少有点可笑,代码生成主要就是用于实现基于注解的API,但是居然没有一个库是内建了这个功能的。由于这个原因,我接受了这个挑战,自己写了另一个库来实现这个功能。

这个库叫做byte buddy,它使用注解和一个领域特定的语言来实现它的目标。你可以像下面这段代码那样创建一个运行时类:



new ByteBuddy()
  .subclass(Object.class)
  .method(named(“toString”))
  .intercept(MethodDelegation.to(ToStringInterception.class))
  .make()



如果你能读懂这段代码的含义,那么这个库就已经达到我的预期了。还有一个比较神秘的一点或许就是方法拦截了,它接受一个方法代理作为参数。当接受到这样一个方法代理的时候,Byte Buddy会将方法调用重定向到指定类中匹配给定名字的一个静态方法里。目标类和方法可以实现如下:



class ToStringInterception {
  public static String intercept(@Context Class<?> type) {
    return type.getSimpleName();
  }
}




有了上述的这个拦截器,这个动态类上任何toString方法的调用都可以委托给这个静态方法。通过使用了@Context注解,这个方法接受一个类引用作为它的唯一参数。因此,运行时toString的返回值就是这个被拦截类的名字。

这只是Byte Buddy API的一只皮毛功夫,这里我们并不想深入这个库的细节。如果你感兴趣的话,可以参数下它的官网文档,上面有详细的说明。


不管怎么说,为了演示下这个库非常适用于日常的编程工作,我们来用Byte Buddy实现我们在文章前面所提到的那个安全库。正如你所看到的,这个实现只是简单的将登录用户存储在了一个静态字段中,它只需几行代码就能完成:



class ByteBuddySecurityLibrary implements SecurityLibrary {
 
  public static String currentUser = “admin”;
 
  @Override
  public  Class<? extends T> secure(Class type) {
    return new ByteBuddy()
      .method(isAnnotatedBy(Secured.class))
      .intercept(MethodDelegation.to(ByteBuddySecurityLibrary.class))
      .make()
      .load(type.getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
      .getLoaded();
  }
 
  @RuntimeType
  public static Object intercept(@SuperCall Callable<?> superMethod,
                                 @Origin Method method) throws Exception {
    if (!method.getAnnotation(Secured.class).requiredUser().equals(currentUser)) {
      throw new IllegalStateException(method + " requires appropriate login”);
    }
    return superMethod.call();
  }
}




和第一个例子类似,我们使用Byte Buddy来拦截方法以便将它们的调用委派给框架方法。这次我们是拦截了被Secured所注解的方法。

对这些方法而言,我们将注解的值和当前登录用户进行比较,如果用户不是注解所需的用户的话,就抛出异常。否则,我们调用原始的那个方法。

在Java中,在实例外部调用父方法一般是不可能的。为了解决这个问题,Byte Buddy自动创建了一个代理类,它的定义有点类似一个非静态的内部类。这么做的话,即便从拦截方法的外部也可以调用super方法了。



未完待续。




原创文章转载请注明出处:http://it.deepinmind.com

英文原文链接
2
0
分享到:
评论

相关推荐

    ByteBuddy在运行期生成Java代码的库

    总之,ByteBuddy是一个强大的工具,它让开发者在运行时对Java字节码的操作变得简单而高效,是进行元编程和动态类型生成的首选库之一。通过理解和熟练使用ByteBuddy,开发者可以构建出更加灵活和富有创新性的Java应用...

    基于Bytebuddy的Java Agent AOP框架.zip

    Java Agent是一种在JVM级别对程序进行...通过学习和实践,我们可以掌握如何使用ByteBuddy来创建高效、灵活的代理类,以及如何利用Java Agent实现字节码级别的方法拦截,从而提升我们的编程技巧和对Java底层机制的理解。

    bytebuddy 字节码增强 创建方法和属性

    ByteBuddy是一款强大的Java字节码操作库,它允许开发者在运行时动态地创建和修改类与接口。这个工具在很多场景下非常有用,比如测试、AOP(面向切面编程)以及性能优化等。本资源是针对初学者的基础教程,主要讲解...

    byte-buddy,Java虚拟机的运行时代码生成。.zip

    1. **类和接口生成**:使用ByteBuddy,开发者可以构建全新的Java类和接口,无需预先编写源代码或字节码。它提供了一套API,使得创建类的结构变得直观,如定义字段、方法、构造器等。 2. **类型增强**:ByteBuddy...

    bytebuddy 字节码增强 定制化相关

    ByteBuddy是一个强大的Java字节码操作库,它允许开发者在运行时动态生成或修改类和接口。在Java世界中,字节码增强是一种技术,它让我们能够在程序运行时对类进行修改或扩展,而无需重新编译源代码。ByteBuddy就是...

    bytebuddy 字节码增强 创建类

    ByteBuddy 是一个流行的 Java 字节码操作库,它允许开发者在运行时动态地创建、修改或增强类和接口。这个工具广泛应用于测试框架、AOP(面向切面编程)和代理实现等领域,以实现对Java类的行为进行灵活的定制。 在...

    ASM操作字节码,动态生成Java类class文件

    ASM是一个开源的Java字节码操控和分析框架,它能够用来动态生成类或者增强已有类的功能。ASM可以被用来创建Java代理、实现元编程、甚至深入到Java虚拟机(JVM)层面进行性能优化。在Java开发中,ASM库允许我们直接...

    Java字节码实现Aop

    Java字节码实现AOP(面向切面编程)是一种在程序运行时动态插入代码的技术,它使得我们可以在不修改原有代码的情况下,增加新的功能或监控已有功能。在Java中,AOP通常通过代理模式和字节码操作来实现,如Spring AOP...

    byte-buddy-agent1.12.17.zip

    ByteBuddy是一款强大的Java字节码操作库,它允许开发者在运行时动态生成或修改类。这个压缩包“byte-buddy-agent1.12.17.zip”包含了ByteBuddy Agent的1.12.17版本,以及对应的源代码jar文件。 1. **ByteBuddy ...

    Java字节码和asm入门资料

    ASM是一个开源的Java字节码操控和分析框架,它可以直接用来生成和修改Java类文件,是Java动态代理和字节码增强技术的重要工具。在深入学习Java字节码和ASM之前,我们需要先理解Java编译和运行的基本过程。 1. **...

    byte-buddy使用demo,无代码侵入的方式,监控记录函数方法运行时间

    ByteBuddy是一个强大的Java字节码操作库,它允许开发者在运行时动态生成或修改类。这个工具在很多场景下非常有用,比如AOP(面向切面编程)、测试、性能监控以及实现代理类等。本篇文章将深入探讨如何使用ByteBuddy...

    行业分类-设备装置-基于Java字节码的大型应用回归测试信息处理方法.zip

    Java字节码是Java虚拟机(JVM)运行的基础,它是由Java源代码编译生成的中间表示。由于Java字节码独立于具体的硬件平台,使得Java程序具有跨平台的特性。在进行回归测试时,通过分析和操作字节码,我们可以对应用...

    byte-buddy-demo

    ByteBuddy是一个强大的Java字节码操作库,它允许开发者在运行时动态生成或修改类和接口。这个“byte-buddy-demo”项目很可能是用来演示如何使用ByteBuddy进行各种编程任务的实例集合。在Java开发中,字节码操作可以...

    面向Java锁机制的字节码自动重构框架.zip

    例如,ASM、ByteBuddy和Javassist等库可以用来动态生成和修改字节码。在重构过程中,需要考虑并发模型、死锁检测、活锁和饥饿等问题,以确保重构后的代码在多线程环境下仍然正确且高效。 字节码自动重构的优势在于...

    mockito-cglib:包含与cglib的集成,该集成已在核心嘲笑中终止,而使用ByteBuddy

    然而,随着技术的发展,Mockito 团队发现 CGLIB 在某些场景下可能性能较差,并且与 ByteBuddy 这样的现代字节码生成库相比,ByteBuddy 提供了更好的性能和更多的功能。因此,Mockito 逐渐停止了对 CGLIB 的支持,并...

    JavaByte.exe.zip

    Java字节码是Java源代码在编译后生成的一种中间表示形式,它是一种平台无关的二进制代码,存储在扩展名为`.class`的文件中。字节码的存在使得Java程序具有“一次编写,到处运行”的特性。它由JVM(Java虚拟机)负责...

    79程序员练级攻略(2018):Java底层知识1

    通过字节码编程,开发者可以在运行时动态修改或生成Java字节码,实现高级功能,如代码注入和性能优化。以下是一些学习资源: 1. **Java Zone: Introduction to Java Bytecode** 提供了字节码的基础知识和细节。 2. ...

    基于 javaagent 对 java 原生类的 方法进行字节码动态修改, 以此引发的一些关于 绕过 Java 软件.zip

    在实际操作中,我们可能会使用如 ASM、ByteBuddy 或 Javassist 这样的字节码库来帮助创建和应用 ClassFileTransformer。这些库提供了友好的 API,让我们能够方便地读取、修改和生成字节码。 然而,这种方法并不总是...

    byte-buddy-1.12.17.jar

    Byte Buddy是一个字节码生成和操作库,用于在Java应用程序运行时创建和修改Java类,而无需编译器的帮助

    Java动态生成代码并编译载入

    另外,有一些库如ASM、Javassist和Byte Buddy,它们提供了更高级的字节码操作能力,可以让我们更方便地生成和修改Java类的字节码。这些库通常用于AOP(面向切面编程)、动态代理或者在运行时优化代码等场景。例如,...

Global site tag (gtag.js) - Google Analytics