网站首页 > 技术文章 正文
前言
字节码我们都知道是java文件经过编译之后的class文件,每一个字节码文件都要由10部分按照固定的顺序组成;增强其实就是对字节码文件进行改造生成一个新的文件,已达到我们的目的,比如动态代理,AOP等;当然增强完需要能被使用,所以涉及到到加载的问题;在介绍之前我们先来看看都有哪些字节码增强技术。
常见技术
常见的字节码增强技术大致分为两类:静态增强和动态增强;静态增强最常见的就是AspectJ了,可以直接编译类,有自己的语法;动态增强包括:ASM、Javassist、Cglib、Java Proxy;下面分别做简单介绍。
AspectJ
AspectJ来自于Eclipse基金会,属于静态织入,主要采用的是编译期织入,在这个期间使用AspectJ的acj编译器(类似javac)把aspect类编译成class字节码;下面看一下AspectJ是如何使用的;
- 下载安装 AspectJ官网地址:www.eclipse.org/aspectj/ 直接下载最新版:AspectJ 1.9.6;直接运行以下命令即可安装: java -jar aspectj-1.9.6.jar 复制代码 指定安装目录,然后配置classPath和path即可: ASPECTJ_HOME=E:\aspectj1.9 CLASSPATH=...%ASPECTJ_HOME%\lib\aspectjrt.jar PATH=...%ASPECTJ_HOME%\bin 复制代码
- 编译使用 可以直接使用AspectJ提供的Demo进行测试examples\tjp目录下: 里面有两个java文件分别是Demo.java和GetInfo.java,Demo就是我们正常的java文件,而GetInfo是增强文件,里面有一些AspectJ语法,需要使用ajc命令编译 E:\aspectj1.9\doc\examples\tjp>ajc -argfile files.lst E:\aspectj1.9\doc\examples\tjp>cd .. E:\aspectj1.9\doc\examples>java tjp.Demo Intercepted message: foo in class: tjp.Demo Arguments: 0. i : int = 1 1. o : java.lang.Object = tjp.Demo@6e3c1e69 Running original method: Demo.foo(1, tjp.Demo@6e3c1e69) result: null Intercepted message: bar in class: tjp.Demo Arguments: 0. j : java.lang.Integer = 3 Running original method: Demo.bar(3) result: Demo.bar(3) Demo.bar(3) 复制代码 可以发现经过ajc编译后的新class文件,go和bar两个方法都得到了增强,在方法调用前和后、以及方法参数都添加了日志输出;可以发现AspectJ在运行前就已经对class文件做了增强处理;Spring AOP借鉴了AspectJ的一些概念,但是在实现上并没有采用AspectJ而使用动态增强技术;
ASM
ASM是一个通用的Java字节码操作和分析框架,它可以用来修改现有的类或直接以二进制形式动态生成类;ASM提供了一些常见的字节码转换和分析算法,从中可以构建定制的复杂转换和代码分析工具;几个核心的类:
- ClassVisitor:用于生成和转换编译类的ASM API基于ClassVisitor抽象类,这个类中的每个方法都对应于同名的类文件结构;
- ClassReader:此类主要功能就是读取字节码文件,然后把读取的数据通知ClassVisitor;
- ClassWriter:其继承于ClassVisitor,主要用来生成类;
ASM偏底层字节码操作,所有需要对字节码命令比较熟悉,但是性能高;比如FastJson、Cglib、Lombok等都依赖ASM;大体的操作步骤就是首先需要加载原Class文件,然后通过访问者模式访问所有元素,在访问的过程中对各元素进行改造,最后重新生成一个字节码的二进制文件,根据需求进行加载新Class或者重加载;
更多参考:ASM入门篇
Javassist
正因为ASM需要对字节码命令熟悉,而字节码本身就比较晦涩难懂,所有就有了更容易理解的增强工具Javassist,可以直接使用Java编码的方式,无需了解相关字节码指令,对开发者更加友好,当然性能肯定不及ASM,下面看一下几个常见的类:
- ClassPool:可以简单理解就是存放类的池子,所有CtClass都要从池中获取;
- CtClass:表示一个类文件,可以通过一个类的全限定名来获取一个CtClass对;
- CtMethod:对应的类中的方法,可以通过CtClass获取指定方法;
- CtField:对应类中的属性,可以通过CtClass获取指定属性;
结合以上几个核心类看一个简单的日志增强实例:
public class JavassistTest {
public static void main(String[] args) throws Exception {
//增强后的类信息存放路径
CtClass.debugDump = "./dump";
ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get("com.zh.asm.TestService");
CtMethod m = cc.getDeclaredMethod("query");
m.insertBefore("{ System.out.println(\"start\"); }");
m.insertAfter("{ System.out.println(\"end\"); }");
TestService h = (TestService) c.newInstance();
h.query();
}
}
复制代码
Dubbo使用Javassist是做动态编译处理,JBoss中使用Javassist来做AOP处理等;
更多:www.javassist.org/tutorial/tu…
Cglib
Cglib是一个功能强大,高性能的代码生成包,底层依赖ASM;为JDK的动态代理提供了很好的补充,Java代理不支持没有接口的情况,另外就是Cglib的性能更强大;几个核心的类如下:
- Enhancer:Cglib中最常用的一个类,和动态代理中引入的Proxy类类似,不同的是Enhancer既能够代理普通的class,也能够代理接口;
- MethodInterceptor:拦截器,在调用目标方法时,CGLib会回调MethodInterceptor接口方法拦截,来实现你自己的代理逻辑,类似于JDK中的InvocationHandler接口;
public class CgLibProxy {
public static void main(String[] args) {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "F:/asm/cglib");
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TestService.class);
enhancer.setCallback(new MyMethodInterceptor());
TestService testService = (TestService)enhancer.create();
testService.query();
}
}
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("增强处理");
Object object = proxy.invokeSuper(obj, args);
return object;
}
}
复制代码
Cglib会动态生成一个代理类,真正执行的时候其实就是代理类,在代理类里面又调用了原始类,实现功能增强;
Java Proxy
利用反射机制在运行时创建代理类;接口、被代理类不变,构建一个handler类来实现InvocationHandler接口;核心类如下:
- Proxy:指定 ClassLoader 对象和一组interface来创建动态代理类;
- InvocationHandler:创建自己的调用处理器,也就是增强处理;
public class MyHandler implements InvocationHandler{
private Object object;
public MyHandler(Object object){
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before invoke...");
method.invoke(object, args);
System.out.println("After invoke...");
return null;
}
}
复制代码
通过反射机制获得动态代理类的构造函数,通过构造函数创建动态代理类实例;而Cglib里面底层依赖ASM,比直接使用反射性能更强;FastJson中就是用了ASM来代替反射的使用,Spring AOP实现也是使用Cglib来实现。
加载问题
以上对各种字节码增强技术做了一个简单的介绍,不管哪种增强技术,增强完之后的类信息都需要被加载,根据使用不同的增强技术,以及增强的作用不同,使用的类加载方式也不一样,大致总结了以下几种情况;
静态编译
这种情况也就是上面介绍的AspectJ技术,在运行前就对class做了增强处理,所以加载方式和普通类的加载方式没有任何区别,在运行期间可能都不知道对class文件做了增强;这种方式类加载是最简单的;
动态代理
这种方式使用动态增强技术,创建代理类,代理类有自己的唯一名称,代理类实现接口类或者继承于原始类,其实就是生成了一个新的类,在介绍ASM的时候也提到它的两项功能:生成类和转换类;FastJson中使用的也是ASM的生成类功能;所以这种情况下只要准备一个类加载即可;ASM没有提供类加载,而其他几种动态增强技术都提供了类加载功能;
热更新
这种情况是最复杂的,如果需要被增强的类已经被加载到内存中,如何在字节码增强之后重加载类;instrument是JVM提供的一个可以修改已加载类的类库,专门为Java语言编写的插桩服务提供支持;在JDK 1.6以前,instrument只能在JVM刚启动开始加载类时生效,而在JDK 1.6之后,instrument支持了在运行时对类定义的修改;具体使用需要提供一个ClassFileTransformer实现类,实现transform方法,此方法返回一个字节数组,这个字节数组可以使用以上介绍的字节码增强工具来生成;
使用 Instrumentation,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,甚至能够替换和修改某些类的定义。有了这样的功能,开发者就可以实现更为灵活的运行时虚拟机监控和 Java 类操作了,这样的特性实际上提供了一种虚拟机级别支持的 AOP 实现方式,使得开发者无需对 JDK 做任何升级和改动,就可以实现某些 AOP 的功能了。
以上介绍的instrument机制依赖JPDA:Java 平台调试架构(Java Platform Debugger Architecture),它是 Java 虚拟机为调试和监控虚拟机专门提供的一套接口;JPDA 由三个规范组成:JVMTI(JVM Tool Interface)、JDWP(Java Debug Wire Protocol)、JDI(Java Debug Interface);
更多:docs.oracle.com/javase/8/do…
总结
本文简单介绍了一下常用的一些字节码增强技术,正是这些底层技术的支持,帮助我们在开发的过程中大大提升开发的效率比如lombok、aop等;提升性能比如FastJson、ReflectASM等;配合Java Agent来进行热更新等等。
感谢关注
作者:ksfzhaohui
链接:https://juejin.cn/post/6976894759134560292
来源:掘金
- 上一篇: 探究 Spring 的定时任务配置
- 下一篇: 一个测试工程师走进一家酒吧……
猜你喜欢
- 2024-11-19 Java Java命令学习系列(一)——Jps
- 2024-11-19 langchain 如何提示大模型使用哪个工具函数
- 2024-11-19 ECMAScript和JavaScript有啥区别?
- 2024-11-19 java枚举、反射以及注解,看这一篇就够了
- 2024-11-19 揭秘:Proxy 与 Reflect,为何总是形影不离?
- 2024-11-19 Go 语言反射的实现原理
- 2024-11-19 Java学习中你所不知道的12个常见语法糖详解
- 2024-11-19 了解 JS 的加载顺序和方式,实现 Ready 方法
- 2024-11-19 js 箭头函数
- 2024-11-19 6 款 Java 8 自带工具,轻松分析定位 JVM 问题
- 标签列表
-
- content-disposition (47)
- nth-child (56)
- math.pow (44)
- 原型和原型链 (63)
- canvas mdn (36)
- css @media (49)
- promise mdn (39)
- readasdataurl (52)
- if-modified-since (49)
- css ::after (50)
- border-image-slice (40)
- flex mdn (37)
- .join (41)
- function.apply (60)
- input type number (64)
- weakmap (62)
- js arguments (45)
- js delete方法 (61)
- blob type (44)
- math.max.apply (51)
- js (44)
- firefox 3 (47)
- cssbox-sizing (52)
- js删除 (49)
- js for continue (56)
- 最新留言
-