zoukankan      html  css  js  c++  java
  • Byte Buddy学习笔记

    本文转载自Byte Buddy学习笔记

    简介

    Byte Buddy是一个JVM的运行时代码生成器,你可以利用它创建任何类,且不像JDK动态代理那样强制实现一个接口。Byte Buddy还提供了简单的API,便于手工、通过Java Agent,或者在构建期间修改字节码。
    Java反射API可以做很多和字节码生成器类似的工作,但是它具有以下缺点:

    1. 相比硬编码的方法调用,使用 反射 API 非常慢
    2. 反射 API 能绕过类型安全检查
      比起JDK动态代理、cglib、Javassist,Byte Buddy在性能上具有优势。

    入门

    创建新类型

    下面是一个最简单的例子:

    Class<?> dynamicType = new ByteBuddy()
      // 指定父类
      .subclass(Object.class)
       // 根据名称来匹配需要拦截的方法
      .method(ElementMatchers.named("toString"))
      // 拦截方法调用,返回固定值
      .intercept(FixedValue.value("Hello World!"))
      // 产生字节码
      .make()
      // 加载类
      .load(getClass().getClassLoader())
      // 获得Class对象
      .getLoaded();
     
    assertThat(dynamicType.newInstance().toString(), is("Hello World!"));
    

    ByteBuddy利用Implementation接口来表示一个动态定义的方法,FixedValue.value就是该接口的实例。

    完全实现Implementation比较繁琐,因此实际情况下会使用MethodDelegation代替。使用MethodDelegation,你可以在一个POJO中实现方法拦截器:

    public class GreetingInterceptor {
      // 方法签名随意
      public Object greet(Object argument) {
        return "Hello from " + argument;
      }
    }
     
    Class<? extends java.util.function.Function> dynamicType = new ByteBuddy()
      // 实现一个Function子类
      .subclass(java.util.function.Function.class)
      .method(ElementMatchers.named("apply"))
      // 拦截Function.apply调用,委托给GreetingInterceptor处理
      .intercept(MethodDelegation.to(new GreetingInterceptor()))
      .make()
      .load(getClass().getClassLoader())
      .getLoaded();
     
    assertThat((String) dynamicType.newInstance().apply("Byte Buddy"), is("Hello from Byte Buddy"));
    

    编写拦截器时,你可以指定一些注解,ByteBuddy会自动注入:

    public class GeneralInterceptor {
      // 提示ByteBuddy根据被拦截方法的实际类型,对此拦截器的返回值进行Cast
      @RuntimeType
      //                      所有入参的数组
      public Object intercept(@AllArguments Object[] allArguments,
      //                      被拦截的原始方法
                              @Origin Method method) {
      }
    }
    

    修改已有类型

    上面的两个例子中,我们利用ByteBuddy创建了指定接口的新子类型,ByteBuddy也可以用来修改已存在的。

    ByteBuddy提供了便捷的创建Java Agent的API,本节的例子就是通过Java Agent方式来修改已存在的Java类型:public class TimerAgent {

    public static void premain(String arguments, 
                                 Instrumentation instrumentation) {
        new AgentBuilder.Default()
          // 匹配被拦截方法
          .type(ElementMatchers.nameEndsWith("Timed"))
          .transform(
              (builder, type, classLoader, module) -> 
                  builder.method(ElementMatchers.any()) .intercept(MethodDelegation.to(TimingInterceptor.class))
          ).installOn(instrumentation);
      }
    }
     
    public class TimingInterceptor {
      @RuntimeType
      public static Object intercept(@Origin Method method, 
                                     // 调用该注解后的Runnable/Callable,会导致调用被代理的非抽象父方法
                                     @SuperCall Callable<?> callable) {
        long start = System.currentTimeMillis();
        try {
          return callable.call();
        } finally {
          System.out.println(method + " took " + (System.currentTimeMillis() - start));
        }
      }
    }
    

    API

    创建类

    subclass

    调用此方法可以创建一个目标类的子类:

    DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
      .subclass(Object.class)
      .name("example.Type")  // 子类的名称
      .make();
    

    如果不指定子类名称,Byte Buddy会有一套自动的策略来生成。你还可以指定子类命名策略:

    DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
      .with(new NamingStrategy.AbstractBase() {
        @Override
        public String subclass(TypeDescription superClass) {
            return "i.love.ByteBuddy." + superClass.getSimpleName();
        }
      })
      .subclass(Object.class)
      .make();
    

    加载类

    上节创建的DynamicType.Unloaded,代表一个尚未加载的类,你可以通过ClassLoadingStrategy来加载这种类。

    如果不指定ClassLoadingStrategy,Byte Buffer根据你提供的ClassLoader来推导出一个策略,内置的策略定义在枚举ClassLoadingStrategy.Default中:

    1. WRAPPER:创建一个新的Wrapping类加载器
    2. CHILD_FIRST:类似上面,但是子加载器优先负责加载目标类
    3. INJECTION:利用反射机制注入动态类型

    示例:

    Class<?> type = new ByteBuddy()
      .subclass(Object.class)
      .make()
      .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
      .getLoaded();
    

    修改类

    redefine

    重定义一个类时,Byte Buddy 可以对一个已有的类添加属性和方法,或者删除已经存在的方法实现。新添加的方法,如果签名和原有方法一致,则原有方法会消失。

    rebase

    类似于redefine,但是原有的方法不会消失,而是被重命名,添加后缀 $original,例如类:

    class Foo {
      String bar() { return "bar"; }
    }
    

    在rebase之后,会变成:

    class Foo {
      String bar() { return "foo" + bar$original(); }
      private String bar$original() { return "bar"; }
    }
    

    重新加载类

    得益于JVM的HostSwap特性,已加载的类可以被重新定义:

    // 安装Byte Buddy的Agent,除了通过-javaagent静态安装,还可以:
    ByteBuddyAgent.install();
    Foo foo = new Foo();
    new ByteBuddy()
      .redefine(Bar.class)
      .name(Foo.class.getName())
      .make()
      .load(Foo.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
     
    assertThat(foo.m(), is("bar"));
    

    可以看到,即使时已经存在的对象,也会受到类Reloading的影响。
    当前HostSwap具有限制:

    1. 类再重新载入前后,必须具有相同的Schema,也就是方法、字段不能减少(可以增加)
    2. 不支持具有静态初始化块的类

    操控未加载类

    Byte Buddy提供了类似于Javassist的、操控未加载类的API。它在TypePool中维护类型的元数据TypeDescription:

    // 获取默认类型池
    TypePool typePool = TypePool.Default.ofClassPath();
    new ByteBuddy()
      .redefine(typePool.describe("foo.Bar").resolve(), // 根据名称进行解析类
                // ClassFileLocator用于定位到被修改类的.class文件
                ClassFileLocator.ForClassLoader.ofClassPath())
      .defineField("qux", String.class) // 定义一个新的字段
      .make()
      .load(ClassLoader.getSystemClassLoader());
    assertThat(Bar.class.getDeclaredField("qux"), notNullValue());
    

    拦截方法

    匹配方法

    Byte Buddy提供了很多用于匹配方法的DSL:

    class Foo {
      public String bar() { return null; }
      public String foo() { return null; }
      public String foo(Object o) { return null; }
    }
     
    Foo dynamicFoo = new ByteBuddy()
      .subclass(Foo.class)
      // 匹配由Foo.class声明的方法
      .method(isDeclaredBy(Foo.class)).intercept(FixedValue.value("One!"))
      // 匹配名为foo的方法
      .method(named("foo")).intercept(FixedValue.value("Two!"))
      // 匹配名为foo,入参数量为1的方法
      .method(named("foo").and(takesArguments(1))).intercept(FixedValue.value("Three!"))
      .make()
      .load(getClass().getClassLoader())
      .getLoaded()
      .newInstance();
    

    委托方法

    使用MethodDelegation可以将方法调用委托给任意POJO。Byte Buddy不要求Source(被委托类)、Target类的方法名一致:

    class Source {
      public String hello(String name) { return null; }
    }
     
    String helloWorld = new ByteBuddy()
      .subclass(Source.class)
      .method(named("hello")).intercept(MethodDelegation.to(Target.class))
      .make()
      .load(getClass().getClassLoader())
      .getLoaded()
      .newInstance()
      .hello("World");
    

    Target的实现可以如下:

    class Target {
      public static String hello(String name) {
        return "Hello " + name + "!";
      }
    } 
    

    也可以如下:

    class Target {
      public static String intercept(String name) { return "Hello " + name + "!"; }
      public static String intercept(int i) { return Integer.toString(i); }
      public static String intercept(Object o) { return o.toString(); }
    }
    

    前一个实现很好理解,那么后一个呢,Byte Buddy到底会委托给哪个方法?Byte Buddy遵循一个最接近原则:

    1. intercept(int)因为参数类型不匹配,直接Pass
    2. 另外两个方法参数都匹配,但是 intercept(String)类型更加接近,因此会委托给它

    参数绑定

    你可以在Target的方法中使用注解进行参数绑定:

    void foo(Object o1, Object o2)
    // 等价于
    void foo(@Argument(0) Object o1, @Argument(1) Object o2)
    

    全部注解如下表

    注解 说明
    @Argument 绑定单个参数
    @AllArguments 绑定所有参数的数组
    @This 当前被拦截的、动态生成的那个对象
    @Super 当前被拦截的、动态生成的那个对象的父类对象
    @Origin 可以绑定到以下类型的参数:Method 被调用的原始方法 Constructor 被调用的原始构造器 Class 当前动态创建的类 MethodHandle MethodType String 动态类的toString()的返回值 int 动态方法的修饰符
    @DefaultCall 调用默认方法而非super的方法
    @SuperCall 用于调用父类版本的方法
    @Super 注入父类型对象,可以是接口,从而调用它的任何方法
    @RuntimeType 可以用在返回值、参数上,提示ByteBuddy禁用严格的类型检查
    @Empty 注入参数的类型的默认值
    @StubValue 注入一个存根值。对于返回引用、void的方法,注入null;对于返回原始类型的方法,注入0
    @FieldValue 注入被拦截对象的一个字段的值
    @Morph 类似于@SuperCall,但是允许指定调用参数

    添加字段

    Class<? extends UserType> dynamicUserType = new ByteBuddy()
      .subclass(UserType.class)
      .defineField("interceptor", Interceptor.class, Visibility.PRIVATE);
    

    方法调用也可以委托给字段(而非外部对象):

    Class<? extends UserType> dynamicUserType = new ByteBuddy()
      .subclass(UserType.class)
        .method(not(isDeclaredBy(Object.class)))
        .intercept(MethodDelegation.toField("interceptor"));  
    
  • 相关阅读:
    1150 Travelling Salesman Problem(25 分)
    poj 2408 Anagram Groups
    guava学习--ratelimiter
    guava学习--Objects
    guava学习--ComparisonChain
    guava学习--Preconditions
    guava学习--Function、Predicate
    guava学习--FutureFallback
    guava学习--FutureCallback
    guava学习--SettableFuture
  • 原文地址:https://www.cnblogs.com/yungyu16/p/13167212.html
Copyright © 2011-2022 走看看