首页 » 网站建设 » phpbyteclass技巧_Java ASM 技能简介

phpbyteclass技巧_Java ASM 技能简介

访客 2024-12-02 0

扫一扫用手机浏览

文章目录 [+]

什么是ASM

ASM 是一个 Java 字节码操控框架。
它能被用来动态天生类或者增强既有类的功能。
ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。
Java class 被存储在严格格式定义的 .class 文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。
ASM 从类文件中读入信息后,能够改变类行为,剖析类信息,乃至能够根据用户哀求天生新类。

phpbyteclass技巧_Java ASM 技能简介

与 BCEL 和 SERL 不同,ASM 供应了更为当代的编程模型。
对付 ASM 来说,Java class 被描述为一棵树;利用 “Visitor” 模式遍历全体二进制构造;事宜驱动的处理办法使得用户只须要关注于对其编程故意义的部分,而不必理解 Java 类文件格式的所有细节:ASM 框架供应了默认的 “response taker”处理这统统。

phpbyteclass技巧_Java ASM 技能简介
(图片来自网络侵删)

为什么要动态天生Java类

动态天生 Java 类与 AOP 密切干系的。
AOP 的初衷在于软件设计天下中存在这么一类代码,零散而又耦合:零散是由于一些公有的功能(诸若有名的 log 例子)分散在所有模块之中;同时改变 log 功能又会影响到所有的模块。
涌现这样的毛病,很大程度上是由于传统的 面向工具编程看重以继续关系为代表的“纵向”关系,而对付拥有相同功能或者说方面 (Aspect)的模块之间的“横向”关系不能很好地表达。
例如,目前有一个既有的银行管理系统,包括 Bank、Customer、Account、Invoice 等工具,现在要加入一个安全检讨模块, 对已有类的所有操作之前都必须进行一次安全检讨。

然而 Bank、Customer、Account、Invoice 是代表不同的事务,派生自不同的父类,很难在高层上加入关于 Security Checker 的共有功能。
对付没有多继续的 Java 来说,更是如此。
传统的办理方案是利用 Decorator 模式,它可以在一定程度上改进耦合,而功能仍旧是分散的 —— 每个须要 Security Checker 的类都必须要派生一个 Decorator,每个须要 Security Checker 的方法都要被包装(wrap)。
下面我们以 Account类为例看一下 Decorator:

首先,我们有一个 SecurityChecker类,其静态方法 checkSecurity实行安全检讨功能:

public class SecurityChecker { public static void checkSecurity() { System.out.println(\"大众SecurityChecker.checkSecurity ...\公众); //TODO real security check } }

另一个是 Account类:

public class Account { public void operation() { System.out.println(\公众operation...\公众); //TODO real operation } }

若想对 operation加入对 SecurityCheck.checkSecurity()调用,标准的 Decorator 须要先定义一个 Account类的接口:

public interface Account { void operation(); }

然后把原来的 Account类定义为一个实现类:

public class AccountImpl extends Account{ public void operation() { System.out.println(\"大众operation...\"大众); //TODO real operation } }

定义一个 Account类的 Decorator,并包装 operation方法:

public class AccountWithSecurityCheck implements Account { private Account account; public AccountWithSecurityCheck (Account account) { this.account = account; } public void operation() { SecurityChecker.checkSecurity(); account.operation(); } }

在这个大略的例子里,改造一个类的一个方法还好,如果是变动全体模块,Decorator 很快就会蜕变成另一个噩梦。
动态改变 Java 类便是要办理 AOP 的问题,供应一种得到系统支持的可编程的方法,自动化地天生或者增强 Java 代码。
这种技能已经广泛运用于最新的 Java 框架内,如 Hibernate,Spring 等。

为什么选择ASM

最直接的改造 Java 类的方法莫过于直接改写 class 文件。
Java 规范详细解释了 class 文件的格式,直接编辑字节码确实可以改变 Java 类的行为。
直到本日,还有一些 Java 高手们利用最原始的工具,如 UltraEdit 这样的编辑器对 class 文件动手术。
是的,这是最直接的方法,但是哀求利用者对 Java class 文件的格式了熟于心:小心地推算出想改造的函数相对文件首部的偏移量,同时重新打算 class 文件的校验码以通过 Java 虚拟机的安全机制。

Java 5 中供应的 Instrument 包也可以供应类似的功能:启动时往 Java 虚拟机中挂上一个用户定义的 hook 程序,可以在装入特定类的时候改变特定类的字节码,从而改变该类的行为。
但是其缺陷也是明显的:

- Instrument 包是在全体虚拟机上挂了一个钩子程序,每次装入一个新类的时候,都必须实行一遍这段程序,纵然这个类不须要改变。

- 直接改变字节码事实上类似于直接改写 class 文件,无论是调用 ClassFileTransformer. transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer),还是 Instrument.redefineClasses(ClassDefinition[] definitions),都必须供应新 Java 类的字节码。
也便是说,同直接改写 class 文件一样,利用 Instrument 也必须理解想改造的方法相对类首部的偏移量,才能在适当的位置上插入新的代码。

只管 Instrument 可以改造类,但事实上,Instrument 更适用于监控和掌握虚拟机的行为。

首先,Proxy 编程是面向接口的。
下面我们会看到,Proxy 并不卖力实例化工具,和 Decorator 模式一样,要把 Account定义成一个接口,然后在 AccountImpl里实现 Account接口,接其实现一个 InvocationHandlerAccount方法被调用的时候,虚拟机都会实际调用这个 InvocationHandler的 invoke方法:

```末了,在运用程序中指定 InvocationHandler天生代理工具:```java<div class=\公众se-preview-section-delimiter\公众></div>

其不敷之处在于:

- Proxy 是面向接口的,所有利用 Proxy 的工具都必须定义一个接口,而且用这些工具的代码也必须是对接口编程的:Proxy 天生的工具是接口同等的而不是工具同等的:例子中 Proxy.newProxyInstance天生的是实现 Account接口的工具而不是 AccountImpl的子类。
这对付软件架构设计,尤其对付既有软件系统是有一定掣肘的。

- Proxy 毕竟是通过反射实现的,必须在效率上付出代价:有实验数据表明,调用反射比一样平常的函数开销至少要大 10 倍。
而且,从程序实现上可以看出,对 proxy class 的所有方法调用都要通过利用反射的 invoke 方法。
因此,对付性能关键的运用,利用 proxy class 是须要精心考虑的,以避免反射成为全体运用的瓶颈。

ASM 能够通过改造既有类,直接天生须要的代码。
增强的代码是硬编码在新天生的类文件内部的,没有反射带来性能上的付出。
同时,ASM 与 Proxy 编程不同,不须要为增强代码而新定义一个接口,天生的代码可以覆盖原来的类,或者是原始类的子类。
它是一个普通的 Java 类而不是 proxy 类,乃至可以在运用程序的类框架中拥有自己的位置,派生自己的子类。

比较于其他盛行的 Java 字节码操纵工具,ASM 更小更快。
ASM 具有类似于 BCEL 或者 SERP 的功能,而只有 33k 大小,而后者分别有 350k 和 150k。
同时,同样类转换的负载,如果 ASM 是 60% 的话,BCEL 须要 700%,而 SERP 须要 1100% 或者更多。

ASM 已经被广泛运用于一系列 Java 项目:AspectWerkz、AspectJ、BEA WebLogic、IBM AUS、OracleBerkleyDB、Oracle TopLink、Terracotta、RIFE、EclipseME、Proactive、Speedo、Fractal、EasyBeans、BeanShell、Groovy、Jamaica、CGLIB、dynaop、Cobertura、JDBCPersistence、JiP、SonarJ、Substance L&F、Retrotranslator 等。
Hibernate 和 Spring 也通过 cglib,另一个更高层一些的自动代码天生工具利用了 ASM。

利用 ASM 动态天生类,不须要像从前的 class hacker 一样,熟知 class 文件的每一段,以及它们的功能、长度、偏移量以及编码办法。
ASM 会给我们照顾好这统统的,我们只要见告 ASM 要改动什么就可以了 —— 当然,我们首先得知道要改什么:对类文件格式理解的越多,我们就能更好地利用 ASM 这个利器。

ASM 3.0 编程框架

ASM 通过树这种数据构造来表示繁芜的字节码构造,并利用 Push 模型来对树进行遍历,在遍历过程中对字节码进行修正。
所谓的 Push 模型类似于大略的 Visitor 设计模式,由于须要处理字节码构造是固定的,以是不须要专门抽象出一种 Vistable 接口,而只须要供应 Visitor 接口。
所谓 Visitor 模式和 Iterator 模式有点类似,它们都被用来遍历一些繁芜的数据构造。
Visitor 相称于用户派出的代表,深入到算法内部,由算法安排访问行程。
Visitor 代表可以改换,但对算法流程无法干涉,因此是被动的,这也是它和 Iterator 模式由用户主动调遣算法办法的最大的差异。

在 ASM 中,供应了一个 ClassReader类,这个类可以直接由字节数组或由 class 文件间接的得到字节码数据,它能精确的剖析字节码,构建出抽象的树在内存中表示字节码。
它会调用 accept方法,这个方法接管一个实现了 ClassVisitor接口的工具实例作为参数,然后依次调用 ClassVisitor接口的各个方法。
字节码空间上的偏移被转换成 visit 事宜韶光上调用的先后,所谓 visit 事宜是指对各种不同 visit 函数的调用,ClassReader知道如何调用各种 visit 函数。
在这个过程中用户无法对操作进行干涉,以是遍历的算法是确定的,用户可以做的是供应不同的 Visitor 来对字节码树进行不同的修正。
ClassVisitor会产生一些子过程,比如 visitMethod会返回一个实现 MethordVisitor接口的实例,visitField会返回一个实现 FieldVisitor接口的实例,完成子过程后掌握返回到父过程,连续访问下一节点。
因此对付 ClassReader来说,其内部顺序访问是有一定哀求的。
实际上用户还可以不通过 ClassReader类,自行手工掌握这个流程,只要按照一定的顺序,各个 visit 事宜被先后精确的调用,末了就能天生可以被精确加载的字节码。
当然得到更大灵巧性的同时也加大了调度字节码的繁芜度。

各个 ClassVisitor通过职责链 (Chain-of-responsibility) 模式,可以非常大略的封装对字节码的各种修正,而无须关注字节码的字节偏移,由于这些实现细节对付用户都被隐蔽了,用户要做的只是覆写相应的 visit 函数。

ClassAdaptor类实现了 ClassVisitor接口所定义的所有函数,当新建一个 ClassAdaptor工具的时候,须要传入一个实现了 ClassVisitor接口的工具,作为职责链中的下一个访问者 (Visitor),这些函数的默认实现便是大略的把调用委派给这个工具,然后依次通报下去形成职责链。
当用户须要对字节码进行调度时,只需从 ClassAdaptor类派生出一个子类,覆写须要修正的方法,完成相应功能后再把调用通报下去。
这样,用户无需考虑字节偏移,就可以很方便的掌握字节码。

每个 ClassAdaptor类的派生类可以仅封装单一功能,比如删除某函数、修正字段可见性等等,然后再加入到职责链中,这样耦合更小,重用的概率也更大,但代价是产生很多小工具,而且职责链的层次太长的话也会加大系统调用的开销,用户须要在低耦合和高效率之间作出权衡。
用户可以通过掌握职责链中 visit 事宜的过程,对类文件进行如下操作:

1. 删除类的字段、方法、指令:只需在职责链通报过程中中断委派,不访问相应的 visit 方法即可,比如删除方法时只需直接返回 null,而不是返回由 visitMethod方法返回的 MethodVisitor工具。

class DelLoginClassAdapter extends ClassAdapter { public DelLoginClassAdapter(ClassVisitor cv) { super(cv); } public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) { if (name.equals(\"大众login\"大众)) { return null; } return cv.visitMethod(access, name, desc, signature, exceptions); } }<div class=\公众se-preview-section-delimiter\公众></div>修正类、字段、方法的名字或润色符:在职责链通报过程中更换调用参数。

class AccessClassAdapter extends ClassAdapter { public AccessClassAdapter(ClassVisitor cv) { super(cv); } public FieldVisitor visitField(final int access, final String name, final String desc, final String signature, final Object value) { int privateAccess = Opcodes.ACC_PRIVATE; return cv.visitField(privateAccess, name, desc, signature, value); } }<div class=\"大众se-preview-section-delimiter\公众></div>增加新的类、方法、字段 ASM 的终极的目的是天生可以被正常装载的 class 文件,因此其框架构造为客户供应了一个天生字节码的工具类 —— ClassWriter。
它实现了 ClassVisitor接口,而且含有一个 toByteArray()函数,返复天生的字节码的字节流,将字节流写回文件即可生产调度后的 class 文件。
一样平常它都作为职责链的终点,把所有 visit 事宜的先后调用(韶光上的先后),终极转换成字节码的位置的调度(空间上的前后),如下例:

ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassAdaptor delLoginClassAdaptor = new DelLoginClassAdapter(classWriter); ClassAdaptor accessClassAdaptor = new AccessClassAdaptor(delLoginClassAdaptor); ClassReader classReader = new ClassReader(strFileName); classReader.accept(classAdapter, ClassReader.SKIP_DEBUG);<div class=\"大众se-preview-section-delimiter\公众></div>

利用 ASM3.0 进行 AOP 编程

我们还是用上面的例子,给 Account类加上 security check 的功能。
与 proxy 编程不同,ASM 不须要将 Account声明成接口,Account可以仍旧是一个实现类。
ASM 将直接在 Account类上动手术,给 Account类的 operation方法首部加上对 SecurityChecker.checkSecurity的调用。

首先,我们将从 ClassAdapter继续一个类。
ClassAdapter是 ASM 框架供应的一个默认类,卖力沟通 ClassReader和 ClassWriter。
如果想要改变 ClassReader处读入的类,然后从 ClassWriter处输出,可以重写相应的 ClassAdapter函数。
这里,为了改变 Account类的 operation 方法,我们将重写 visitMethdod方法。

class AddSecurityCheckClassAdapter extends ClassAdapter { public AddSecurityCheckClassAdapter(ClassVisitor cv) { //Responsechain 的下一个 ClassVisitor,这里我们将传入 ClassWriter, // 卖力改写后代码的输出 super(cv); } // 重写 visitMethod,访问到 \"大众operation\"大众 方法时, // 给出自定义 MethodVisitor,实际改写方法内容 public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) { MethodVisitor mv = cv.visitMethod(access, name, desc, signature,exceptions); MethodVisitor wrappedMv = mv; if (mv != null) { // 对付 \公众operation\"大众 方法 if (name.equals(\公众operation\公众)) { // 利用自定义 MethodVisitor,实际改写方法内容 wrappedMv = new AddSecurityCheckMethodAdapter(mv); } } return wrappedMv; } }<div class=\公众se-preview-section-delimiter\"大众></div>

下一步便是定义一个继续自 MethodAdapter的 AddSecurityCheckMethodAdapter,在“operation”方法首部插入对 SecurityChecker.checkSecurity()的调用。

class AddSecurityCheckMethodAdapter extends MethodAdapter { public AddSecurityCheckMethodAdapter(MethodVisitor mv) { super(mv); } public void visitCode() { visitMethodInsn(Opcodes.INVOKESTATIC, \"大众SecurityChecker\"大众, \"大众checkSecurity\"大众, \"大众()V\"大众); } }<div class=\公众se-preview-section-delimiter\"大众></div>

个中,ClassReader读到每个方法的首部时调用 visitCode(),在这个重写方法里,我们用 visitMethodInsn(Opcodes.INVOKESTATIC, “SecurityChecker”,”checkSecurity”, “()V”);插入了安全检讨功能。

末了,我们将集成上面定义的 ClassAdapter,ClassReader和 ClassWriter产生修正后的 Account类文件 :

import java.io.File; import java.io.FileOutputStream; import org.objectweb.asm.; public class Generator{ public static void main() throws Exception { ClassReader cr = new ClassReader(\"大众Account\"大众); ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassAdapter classAdapter = new AddSecurityCheckClassAdapter(cw); cr.accept(classAdapter, ClassReader.SKIP_DEBUG); byte[] data = cw.toByteArray(); File file = new File(\公众Account.class\"大众); FileOutputStream fout = new FileOutputStream(file); fout.write(data); fout.close(); } }<div class=\公众se-preview-section-delimiter\"大众></div>

实行完这段程序后,我们会得到一个新的 Account.class 文件,如果我们利用下面代码:

public class Main { public static void main(String[] args) { Account account = new Account(); account.operation(); } }<div class=\"大众se-preview-section-delimiter\"大众></div>

利用这个 Account,我们会得到下面的输出:

SecurityChecker.checkSecurity ... operation...<div class=\"大众se-preview-section-delimiter\公众></div>

也便是说,在 Account原来的 operation内容实行之前,进行了 SecurityChecker.checkSecurity()检讨。

将动态天生类改造成原始类 Account 的子类

上面给出的例子是直接改造 Account类本身的,从此 Account类的 operation方法必须进行 checkSecurity 检讨。
但事实上,我们有时仍希望保留原来的 Account类,因此把天生类定义为原始类的子类是更符合 AOP 原则的做法。
下面先容如何将改造后的类定义为 Account的子类 Account$EnhancedByASM。
个中紧张有两项事情 :

改变 Class Description, 将其命名为 Account$EnhancedByASM,将其父类指定为 Account。

改变布局函数,将个中对父类布局函数的调用转换为对 Account布局函数的调用。

在 AddSecurityCheckClassAdapter类中,将重写 visit方法:

public void visit(final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) { String enhancedName = name + \"大众$EnhancedByASM\公众; // 改变类命名 enhancedSuperName = name; // 改变父类,这里是”Account” super.visit(version, access, enhancedName, signature, enhancedSuperName, interfaces); }<div class=\公众se-preview-section-delimiter\"大众></div>

改进 visitMethod方法,增加对布局函数的处理:

public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) { MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); MethodVisitor wrappedMv = mv; if (mv != null) { if (name.equals(\"大众operation\"大众)) { wrappedMv = new AddSecurityCheckMethodAdapter(mv); } else if (name.equals(\"大众<init>\"大众)) { wrappedMv = new ChangeToChildConstructorMethodAdapter(mv, enhancedSuperName); } } return wrappedMv; }<div class=\公众se-preview-section-delimiter\"大众></div>

这里 ChangeToChildConstructorMethodAdapter将卖力把 Account的布局函数改造成其子类 Account$EnhancedByASM的布局函数:

class ChangeToChildConstructorMethodAdapter extends MethodAdapter { private String superClassName; public ChangeToChildConstructorMethodAdapter(MethodVisitor mv, String superClassName) { super(mv); this.superClassName = superClassName; } public void visitMethodInsn(int opcode, String owner, String name, String desc) { // 调用父类的布局函数时 if (opcode == Opcodes.INVOKESPECIAL && name.equals(\"大众<init>\"大众)) { owner = superClassName; } super.visitMethodInsn(opcode, owner, name, desc);// 改写父类为 superClassName } }<div class=\"大众se-preview-section-delimiter\公众></div>

末了演示一下如何在运行时产生并装入产生的 Account$EnhancedByASM。
我们定义一个 Util 类,作为一个类工厂卖力产生有安全检讨的 Account类:

public class SecureAccountGenerator { private static AccountGeneratorClassLoader classLoader = new AccountGeneratorClassLoade(); private static Class secureAccountClass; public Account generateSecureAccount() throws ClassFormatError, InstantiationException, IllegalAccessException { if (null == secureAccountClass) { ClassReader cr = new ClassReader(\"大众Account\"大众); ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassAdapter classAdapter = new AddSecurityCheckClassAdapter(cw); cr.accept(classAdapter, ClassReader.SKIP_DEBUG); byte[] data = cw.toByteArray(); secureAccountClass = classLoader.defineClassFromClassFile( \"大众Account$EnhancedByASM\公众,data); } return (Account) secureAccountClass.newInstance(); } private static class AccountGeneratorClassLoader extends ClassLoader { public Class defineClassFromClassFile(String className, byte[] classFile) throws ClassFormatError { return defineClass(\"大众Account$EnhancedByASM\"大众, classFile, 0, classFile.length()); } } }

静态方法 SecureAccountGenerator.generateSecureAccount()在运行时动态天生一个加上了安全检讨的 Account子类。
著名的 Hibernate 和 Spring 框架,便是利用这种技能实现了 AOP 的“无损注入”。

以上便是我的对此问题的整理和思考。
如果你对此话题有自己的思考和理解,也欢迎留言一起磋商

私信我:“资料”,可免费领取更多学习资料

标签:

相关文章

微信第三方登录便捷与安全的完美融合

社交平台已成为人们日常生活中不可或缺的一部分。微信作为我国最受欢迎的社交软件之一,拥有庞大的用户群体。为了方便用户在不同平台间切换...

网站建设 2025-02-18 阅读0 评论0

广东高速代码表解码高速公路管理智慧

高速公路作为国家交通动脉,连接着城市与城市,承载着巨大的物流和人流。广东作为我国经济大省,高速公路网络密布,交通流量巨大。为了更好...

网站建设 2025-02-18 阅读0 评论0