乐趣区

关于java:字节码增强概述

前言

字节码咱们都晓得是 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

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 源码、架构、算法和面试。

退出移动版