字节码增强技术,不止有 Java Proxy、 Cglib 和 Javassist 还有 Byte Buddy

根据 Byte Buddy​ 官网所说,Byte Buddy​ 是一个代码生成和操作库,用于在 Java​ 应用程序运行时创建和修改 Java 类,而无需编译器的帮助。

根据 Byte Buddy​ 官网所说,Byte Buddy​ 是一个代码生成和操作库,用于在 Java​ 应用程序运行时创建和修改 Java 类,而无需编译器的帮助。

提到字节码增强技术,相信用过Spring的小伙伴都会知道Java Proxy和Cglib。

毕竟面试准备的八股文中说过,Spring的动态代理有两种实现方式,在有接口存在的时候使用Java Proxy,当没有接口的时候使用的是Cglib。

这两种方式的区别不在本文的讨论范围之内,今天想给大家介绍了是另一个字节码增强技术Byte Buddy。

Byte Buddy

根据Byte Buddy官网所说,Byte Buddy是一个代码生成和操作库,用于在Java应用程序运行时创建和修改Java类,而无需编译器的帮助。

Byte Buddy提供一套简单易用的API,可以很方便的使用Java流式编程的形式来动态创建类或者创建接口的实现类,这一点跟Java Proxy和Cglib不一样。

使用Byte Buddy的方式也非常简单,只要直接引入Maven依赖即可,没有其他繁琐的依赖。总的来说,使用Byte Buddy有下面的优势:

  1. 无需理解字节码格式,简单易用的API能很容易操作字节码;
  2. 支持Java任何版本,库轻量,仅取决于Java字节代码解析器库ASM的访问者API,它本身不需要任何其他依赖项。
  3. 比起JDK动态代理、cglib、Javassist,Byte Buddy在性能上具有优势。

图片图片

这一份测试报告是官网提供的,表中的每一行分别为,类的创建、接口实现、方法调用、类型扩展、父类方法调用的性能结果。

从性能报告中可以看出,Byte Buddy在一些场景是有优势的,但是在有些场景也不见得特别有优势,不过整体来看还是不错的。

测试

说了那么多,下面给大家演示一下,如果使用Byte Buddy,首先我们需要引入Maven依赖,我这里用的版本是 1.14.6,也可以使用其他版本。

<dependency>
    <groupId>net.bytebuddy</groupId>
    <artifactId>byte-buddy</artifactId>
    <version>1.14.6</version>
</dependency>

创建一个类,并覆盖 toString

public static void test1() {
        try {
            Class<?> dynamicType = new ByteBuddy().
                    subclass(Object.class)
                    .method(ElementMatchers.named("toString"))
                    .intercept(FixedValue.value("Hello World!"))
                    .make()
                    .load(ByteBuddyDemo.class.getClassLoader())
                    .getLoaded();
            System.out.println(dynamicType.newInstance().toString());
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

public static void test2() {
        try {
            DynamicType.Unloaded<Object> unloaded = new ByteBuddy()
                    .subclass(Object.class)
                    .method(ElementMatchers.named("toString"))
                    .intercept(FixedValue.value("Hello World!"))
                    .make();
            DynamicType.Loaded<Object> load = unloaded.load(ByteBuddyDemo.class.getClassLoader());
            System.out.println(load.getLoaded().newInstance().toString());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

整个代码的思路是通过Byte Buddy,构造出一个Class对象,然后调用Class对象的newInstance()方法,再执行toString()方法。上面两个方式的功能是一样的,写出来更方便大家理解。

其中各个方法的含义如下:

subClass:表示构造的类是Object的子类;

method:表示要构造的具体方法,类似于过滤的功能;

intercept:表示对过滤后的方法进行拦截;

FixedValue.value("Hello World!"):表示构造返回一个”Hello World!“ 字符串;

make:创建DynamicType.Unloaded对象,此时这个对象被构造出来,但是还没有被JVM加载,还不能使用;

load,getLoaded:加载当前类的构造器,并进行加载;

等到加载到JVM过后,就可以使用newInstance().toString()进行调用了。

代理方法

上面的例子是创建一个简单的类和方法,下面我们介绍一个代理方法的使用,这里我们有一个目标类 Target 和一个方法saySomething()方法,有一个代理类Agent,里面有一个代理方法agentSaySomething(),如下所示:

public class Target {
    public String saySomething() {
        return "Hello target";
    }
}

public class Agent {
    public static String agentSaySomething() {
        System.out.println("agentSaySomething");
        return "hello agent";
    }
}


public static void test4() {
        try {
            DynamicType.Unloaded<Target> agent = new ByteBuddy()
                    .subclass(Target.class)
                    .method(named("saySomething")
                            .and(isDeclaredBy(Target.class)
                                    .and(returns(String.class))))
                    .intercept(MethodDelegation.to(Agent.class))
                    .make();
            // 将 agent 字节码写入文件中
            outputClazz(agent.getBytes());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static void outputClazz(byte[] bytes) {
        FileOutputStream out = null;
        try {
            String pathName = ByteBuddyDemo.class.getResource("/").getPath() + "AgentTarget.class";
            out = new FileOutputStream(new File(pathName));
            System.out.println("类输出路径:" + pathName);
            out.write(bytes);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != out) try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

   public static void main(String[] args) {
        test4();
    }

运行过后我们可以看到生成了一个class文件,通过查看代码如下,可以看到是创建了一个Target的子类,并且调用了Agent的agentSaySomething方法。

图片图片

总结

Byte Buddy的API很丰富,这里只是很简单的给大家使用了几个API,还有包括方法,字段的设定等等,感兴趣的小伙伴可以继续去学习学习。

©本文为清一色官方代发,观点仅代表作者本人,与清一色无关。清一色对文中陈述、观点判断保持中立,不对所包含内容的准确性、可靠性或完整性提供任何明示或暗示的保证。本文不作为投资理财建议,请读者仅作参考,并请自行承担全部责任。文中部分文字/图片/视频/音频等来源于网络,如侵犯到著作权人的权利,请与我们联系(微信/QQ:1074760229)。转载请注明出处:清一色财经

(0)
打赏 微信扫码打赏 微信扫码打赏 支付宝扫码打赏 支付宝扫码打赏
清一色的头像清一色管理团队
上一篇 2024年1月30日 17:04
下一篇 2024年1月30日 17:05

相关推荐

发表评论

登录后才能评论

联系我们

在线咨询:1643011589-QQbutton

手机:13798586780

QQ/微信:1074760229

QQ群:551893940

工作时间:工作日9:00-18:00,节假日休息

关注微信