使用Byte Buddy生成Java字节码

2024-01-26 05:58

本文主要是介绍使用Byte Buddy生成Java字节码,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

上周我们探索了下[url=http://it.deepinmind.com/jvm/2014/07/03/how-to-make-java-more-dynamic-with-runtime-code-generation.html]Java的强类型及静态类型系统[/url]。我承认这样的类型让我们的代码表达性更强,但是同时也限制了第三方库提供面向POJO的API的能力。

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

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

[b]可以开始了吗[/b]

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

Java字节码是一个二进制格式,但是,不止如此,Java编程语言和它对应的字节码非常相似。这也不奇怪,因为Java语言某种程度上也驱动着字节码指令集的演进。如果你想了解更多关于字节码的知识,可以参考下RebelLabs上的这篇_[url=http://zeroturnaround.com/rebellabs/rebel-labs-report-mastering-java-bytecode-at-the-core-of-the-jvm/]掌握JVM字节码[/url]_的不错的分享,它会告诉你里面的很多技巧。

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

当然如此。也正由于这个原因,即便是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的真实例子。

[img]http://zeroturnaround.com/wp-content/uploads/2014/07/javassist-horrors-640x130.png[/img]

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

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

[b]少抱怨一点好吧?[/b]

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

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

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




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的一只皮毛功夫,这里我们并不想深入这个库的细节。如果你感兴趣的话,可以参数下它的[url=http://bytebuddy.net/javadoc/0.2.1/index.html]官网文档[/url],上面有详细的说明。


不管怎么说,为了演示下这个库非常适用于日常的编程工作,我们来用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方法了。


未完待续。


原创文章转载请注明出处:[url=http://it.deepinmind.com/jvm/2014/07/10/how-my-new-friend-byte-buddy-enables-annotation-driven-java-runtime-code-generation.html]http://it.deepinmind.com[/url]

[url=http://zeroturnaround.com/rebellabs/how-my-new-friend-byte-buddy-enables-annotation-driven-java-runtime-code-generation/]英文原文链接[/url]

这篇关于使用Byte Buddy生成Java字节码的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/645837

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数