AnthonyZero's Bolg

Java字节码增强

字节码增强技术

个人理解,是在Java字节码生成之后,运行期对其进行修改,增强其功能,这种方式相当于对应用程序的二进制文件进行修改。这类增强字节码的技术基本都应用在减少冗余和屏蔽实现细节上,Java的字节代码的增强在很多的Java框架中都可以看到,比如Spring使用cglib对字节码的增强实现面向切面的编程AOP和自动代理类的生成等上。

字节码操作框架

  • JDK的动态代理(利用JAVA反射)
  • ASM:一个JAVA字节码分析、创建和修改的开源应用框架
  • AspectJ:意思是Java的AspectJava的AOP,提供了强大的AOP功能
  • CGLIB:功能强大,高性能的代码生成包,使用字节码处理框架ASM(对ASM的封装),来转换字节码并生成新的类
  • Javassist:一个开源的分析、编辑和创建Java字节码的类库,在性能上Javassist高于反射,但低于ASM

补充: CGLIB它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。通常可以使用Java的动态代理创建代理,但当要代理的类没有实现接口或者为了更好的性能,CGLIB是一个好的选择

Alt text

字节码增强步骤

  1. 在内存中获取到原始的字节码,然后通用一些开源提供的API来修改它的byte[]数组,得到一个新的byte[]。(ASM,javassist,cglib等技术)
  2. 将这个新的数组写到PermGen区域,也就是加载它或替换原来的Class字节码(也可以在进程外部调用完成)

实现机制:一种是通过创建原始类的一个子类,也就是动态创建的这个类继承原来的类,现在的SpringAOP正式通过这种方式实现,另一种是非常暴力的,即直接修改原先的Class字节码,在许多类的跟踪过程中会用到这技术(类加载时修改字节码信息,运行时修改)

为什么要动态生成 Java 类?

动态生成 Java 类与 AOP 是密切相关的。AOP 的初衷在于软件设计世界中存在这么一类代码,零散而又耦合:零散是由于一些公有的功能(诸如著名的 log 例子)分散在所有模块之中;同时改变 log 功能又会影响到所有的模块。出现这样的缺陷,很大程度上是由于传统的 面向对象编程注重以继承关系为代表的“纵向”关系,而对于拥有相同功能或者说方面 (Aspect)的模块之间的“横向”关系不能很好地表达。
动态改变 Java 类就是要解决 AOP 的问题,提供一种得到系统支持的可编程的方法,自动化地生成或者增强 Java 代码。这种技术已经广泛应用于最新的 Java 框架内,如 Hibernate,Spring 等

AOP编程:CGLIB动态字节码生成

使用动态字节码生成技术实现AOP原理是在运行期间目标字节码加载后,生成目标类的子类,将切面逻辑加入到子类中,所以cglib实现AOP不需要基于接口
Alt text

一个普通类,没有接口实现

/**
 * 这是一个没有实现接口的实现类
 */
public class SystemLogImpl {
    public void saveLog() {
        System.out.println("记录日志");
    }

    public void filterLog() {
        System.out.println("过滤日志");
    }
}

使用Cglib动态代理

import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

/**
 * 使用Cglib动态代理
 */
public class SystemLogCglib implements MethodInterceptor {
    private Object target;

    /**
     * 创建代理对象
     * 
     * @param target
     * @return
     */
    public Object getInstacne(Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.target.getClass());
        // 回调方法
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
        if (arg1.getName().equals("filterLog")) {
            System.out.println("当前方法为: filterLog方法");
        }
        arg3.invokeSuper(arg0, arg2);
        return null;
    }
}

测试AOP

public static void main(String[] args) {
    SystemLogCglib cglib = new SystemLogCglib();
    SystemLogImpl systemLog = (SystemLogImpl) cglib.getInstacne(new SystemLogImpl());
    systemLog.filterLog();
    systemLog.saveLog();
}

输出

当前方法为: filterLog方法
过滤日志
记录日志