6

字节码增强概述

 3 years ago
source link: https://segmentfault.com/a/1190000040226596
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

字节码我们都知道是java文件经过编译之后的class文件,每一个字节码文件都要由10部分按照固定的顺序组成;增强其实就是对字节码文件进行改造生成一个新的文件,已达到我们的目的,比如动态代理,AOP等;当然增强完需要能被使用,所以涉及到到加载的问题;在介绍之前我们先来看看都有哪些字节码增强技术。

常见的字节码增强技术大致分为两类:静态增强和动态增强;静态增强最常见的就是AspectJ了,可以直接编译类,有自己的语法;动态增强包括:ASMJavassistCglibJava Proxy;下面分别做简单介绍。

AspectJ

AspectJ来自于Eclipse基金会,属于静态织入,主要采用的是编译期织入,在这个期间使用AspectJacj编译器(类似javac)把aspect类编译成class字节码;下面看一下AspectJ是如何使用的;

  • 下载安装
    AspectJ官网地址:https://www.eclipse.org/aspectj/
    直接下载最新版:AspectJ 1.9.6;直接运行以下命令即可安装:

    java -jar aspectj-1.9.6.jar

指定安装目录,然后配置classPathpath即可:

ASPECTJ_HOME=E:\aspectj1.9
CLASSPATH=...%ASPECTJ_HOME%\lib\aspectjrt.jar
PATH=...%ASPECTJ_HOME%\bin
  • 编译使用
    可以直接使用AspectJ提供的Demo进行测试examples\tjp目录下:
    里面有两个java文件分别是Demo.javaGetInfo.javaDemo就是我们正常的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是一个通用的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处理等;

更多:http://www.javassist.org/tuto...

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)

更多:https://docs.oracle.com/javas...

本文简单介绍了一下常用的一些字节码增强技术,正是这些底层技术的支持,帮助我们在开发的过程中大大提升开发的效率比如lombok、aop等;提升性能比如FastJson、ReflectASM等;配合Java Agent来进行热更新等等。

可以关注微信公众号「回滚吧代码」,第一时间阅读,文章持续更新;专注Java源码、架构、算法和面试。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK