精华内容
下载资源
问答
  • ByteBuddy

    2016-09-21 09:40:00
    http://bytebuddy.net/#/

    http://bytebuddy.net/#/

    展开全文
  • The default ByteBuddy configuration which is used in the above example creates a Java class in the newest version of the class file format that is understood by the processing Java virtual machine. As...
  • simple-bytebuddy

    2021-03-04 17:22:18
    ByteBuddy byteBuddy = new ByteBuddy(); 环境设置 byteBuddy.with(new NamingStrategy() { private final String genericPrefix = "com.godme.bytebuddy."; public String subclass(TypeDescription.Generic ...

    操作对象

    // 这个对象作为操作工具起点
    ByteBuddy byteBuddy = new ByteBuddy();
    

    环境设置

    byteBuddy.with(new NamingStrategy() {
                private final String genericPrefix = "com.godme.bytebuddy.";
                public String subclass(TypeDescription.Generic generic) {
                    return genericPrefix +"subclass."+ generic.getTypeName();
                }
    
                public String redefine(TypeDescription typeDescription) {
                    return genericPrefix + "redefine." + typeDescription.getSimpleName();
                }
    
                public String rebase(TypeDescription typeDescription) {
                    return genericPrefix + "rebase." + typeDescription.getSimpleName();
                }
            });
    

    通过with方法,可以进行环境设置,在关注的方面进行特殊的指定。

    出生世家

    // 类不能,至少不应该,忘记自己的出身
    DynamicType.Builder<Object> dynamicTypeBuilder = bytebuddy.subclass(Object.class);
    

    基于使用的场景,会有这三种方式

    operationdescription
    subclass创建一个继承类,添加方法或重写
    redefine重定义一个类,可以增加或者删除方法,非此即彼
    rebase重定义一个类,修改的操作会和原来的操作融合,不会覆盖或丢失

    养父养母

    // 是否继承了接口
    dynamicTypeBuilder.implement(Serializable.class);
    

    名称限定

    // 哪一个类
    dynamicTypeBuilder.name("com.godme.Person");
    

    方法限定

    // ElementMatcher
    // 哪一个方法,获取定义工具类
    DynamicType.Builder.MethodDefinition.ImplementationDefinition<Object> toStringDefinition = dynamicBuilder.method(named("toString"));
    
    matcherdescription
    named叫啥
    returns回啥
    takesArguments传啥
    isDeclaredBy谁的
    isAnnotatedWith帽子

    更多方法自行发觉

    #内容替换

    • 简单值返回
    // 拦截设置固定值
    toStringDefinition.intercept(FixedValue.value("person"));
    
    • 委托处理
    // MethodDelegation委托给Dog,调用同签名方法
    toStringDefinition.intercept(MethodDelegation.to(Dog.class));
    

    属性修改

    dynamicTypeBuilder.define();// 万能改造
    
    • field
    • method
    • constructor

    灵魂重造

    // 根据前面的定制信息,自动构建底层字节码和相关信息,并封装为对象
    DynamicType.UnLoaded<Object> unLoadedDynamicType = dynamicTypeBuilder.make();
    

    投胎转世

    // JVM加载
    DynamicType.Loaded<Object> loadedDynamicType = unLoadedDynamicType.load(getClass().getClassLoader());
    // DynamicType.Loaded<Object> loadedDynamicType = unLoadedDynamicType.load(
    //   getClass().getClassLoader(),
    //	ClassLoadingStrategy.Default.CHILD_FIRST);
    
    strategydescription
    ClassLoadingStrategy.Default.WRAPPER新包装classloader
    ClassLoadingStrategy.Default.CHILD_FIRST子类优先
    ClassLoadingStrategy.Default.INJECTION反射注入

    今夕何夕

    // JVM加载过的类
    Class<?> dynamicTypeClass = loadedDynamicType.getLoaded();
    

    代理一下

    public class ToStringAgent {
    
        public static void premain(String args, Instrumentation instrumentation){
            new AgentBuilder.Default()
                    .type(isAnnotatedWith(ToString.class))
                    .transform(new AgentBuilder.Transformer(){
    
                        public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule) {
                            builder.method(named("toString").and(ElementMatchers.returns(String.class)))
                                    .intercept(FixedValue.value("啊哈,啥也看不到!"));
                            return null;
                        }
                    }).installOn(instrumentation);
        }
    }
    
    展开全文
  • byte buddy学习笔记

    千次阅读 2019-03-06 16:51:36
    字节码工程库具有ConstantDynamic特性,也可以被称为condy特性,和Java11的Nestmates。...最近采访了ByteBuddy的作者Rafael Winterhalter,让我们来了解一下ByteBuddy字节码工程库吧。 Byte Buddy Rafael Winte...

    字节码工程库具有ConstantDynamic特性,也可以被称为condy特性,和Java11的Nestmates。字节码工程库Byte Buddy支持Java 11以及自Java 8以来引入的所有类文件和节码工程库新特性。最近采访了ByteBuddy的作者Rafael Winterhalter,让我们来了解一下ByteBuddy字节码工程库吧。

    Byte Buddy

    Rafael Winterhalter:Byte Buddy是一个代码生成库,通过简单的Java API定义新类或修改现有类。这个字节码工程库会生成并操作Java字节码。通过处理节码工程库,它可以与使用任何JVM语言编写的代码进行交互,节码工程库,并且可以在Java应用程序的运行期间使用该字节码工程库来修改当前要执行的代码,甚至包括自己的代码。

    为什么Java 11将ConstantDynamic作为一项新特性发布

    ava开发人员通常使用static final关键字来定义常量。字节码工程库,不过,Java类文件中的常量可以是通过符号引用的非类字段。节码工程库,有几种类型已经通过这些符号来表示字面量,例如Java字符串。

    有了ConstantDynamic,节码工程库,就可以将任意变量表示为常量池中引用的常量值。字节码工程库,使用类文件常量的主要好处是只在第一次使用时才创建,节码工程库,而不是在类加载时创建,而static final字段是在类加载时创建的。使用ConstantDynamic,将来可以避免在JVM中进行大量的加载。字节码工程库,例如,在JVM启动时,节码工程库,它必须初始化Locale类,而这个类引用了JVM支持的所有语言。这种初始化相当昂贵并且通常是不必要的,因为大多数程序只使用默认语言。字节码工程库,通过使用ConstantDyanmic,节码工程库,未来可以改进核心库,实现更快的JVM启动,当然其他库也可以做类似的事情。

    字节码工程库Byte Buddy的功能

    字节码工程库Byte Buddy主要被用在其他库和框架中。节码工程库,例如,Hibernate使用Byte Buddy来实现实体代理,Mockito使用它来生成模拟类。节码工程库,Byte Buddy也渐渐被用于开发可以改变整个应用程序行为的Java代理。APM工具(例如Instana)正在使用这类代理收集应用程序运行期间的度量指标。节码工程库,2014年开始开发Byte Buddy,第一个非beta版本出现后,就受到大量人的关注,每年具有一亿次的下载量。

    最新添加了什么特性

    在最新的版本中,字节码工程库,我一直在尝试支持Java 11和12。不过我的大部分时间都用于添加对Java模块系统的支持,这是一条漫漫长路。随着最新版本的发布,对模块系统的支持变得更加稳定,节码工程库,Byte Buddy甚至会在即将发布的1.9.0版本中添加一个module-info.class。字节码工程库,同时,这个库保留了对Java 6、7和8的兼容性。

    用户特别要求的新特性有哪些

    因为缺乏对新版本的支持通常会影响项目在较新的VM上编译。节码工程库,类似地,还有很多特性请求要求支持Kotlin或Scala等JVM语言,这些语言在字节码转换方面有一些特别之处。节码工程库,假如Java语言正在添加新特性,通过扩展JVM的功能来实现,节码工程库,这个时候要小心,否则Byte Buddy会出现问题。

    开发人员怎么使用

    要想使用这个字节码工程库,只需将它添加到项目中,节码工程库,然后使用它的DSL生成类。Byte Buddy的GitHub页面和官网都提供了如何创建简单类的示例。节码工程库,网页上还提供了综合性文档,还有很多博客文章、YouTube和Vimeo上的视频资料。

    目前路线图中即将推出的特性有何看法?

    目前,几乎所有即将推出的Java特性都让我兴奋不已。字节码工程库,我最期待的是Loom,它将提供对JVM continuation的原生支持。节码工程库,作为一名软件顾问,我参与的项目通常使用actor模型或反应式回调之类的抽象来实现并发。字节码工程库,这些应用程序通常会越变越复杂,业务逻辑被埋没在模拟并发性的仪式代码中。节码工程库,满足了技术的需求忽略领域逻辑,导致业务代码难以重构。

    Java 8以来类文件格式是否有其他重大变化?ByteBuddy如何应对这些变化?

    除了ConstantDynamic之外,JVM还引入了嵌套伴侣(nest mate)的概念,将它们定义为嵌套伴侣,就可以提供更好的方法访问控制,节码工程库,这样既有获得调用对方私有方法的权限。javac编译器通过添加package-private访问器方法来实现嵌套类的私有方法调用。字节码工程库,Byte Buddy支持嵌套伴侣,但目前DSL不允许更改或添加嵌套伴侣。节码工程库,这是一个比较大的特性,我想在今年晚些时候解决这个问题。

    你有其他意见或想法想与我们的读者分享的吗?

    一部分Java开发人员对甲骨文的管理持怀疑态度,他们觉得模块系统或新发布周期对甲骨文来说可能意味着更大的负担而不是好处。字节码工程库,我认为甲骨文最终解决了很多可能危及平台长期发展的问题。JVM现在处在一个更好的状态,很多即将发生的变化都是基于这个基础。

    展开全文
  • ByteBuddy使用入坑

    千次阅读 2020-07-09 18:14:09
    ByteBuddy官方文档 ByteBuddy 介绍 首先需要了解ByteBuddy是什么,ByteBuddy是一款java字节码增强框架,可以动态的生成java字节码文件,比起我们自己进行字节码文件的生成,它屏蔽了底层细节,提供一套统一易上手的...

    ByteBuddy官方文档

    ByteBuddy 介绍

    首先需要了解ByteBuddy是什么,ByteBuddy是一款java字节码增强框架,可以动态的生成java字节码文件,比起我们自己进行字节码文件的生成,它屏蔽了底层细节,提供一套统一易上手的Api,简化了字节码增强的学习难度。
    为什么需要字节码增强技术?ByteBuddy官方文档已经给出了答案

    The Java language comes with a comparatively strict type system. Java requires all variables and objects to be of a specific type and any attempt to assign incompatible types always causes an error. These errors are usually emitted by the Java compiler or at the very least by the Java runtime when casting a type illegally. Such strict typing is often desirable, for example when writing business applications. Business domains can usually be described in such an explicit manner where any domain item represents its own type. This way, we can use Java to build very readable and robust applications where mistakes are caught close to their source. Among other things, it is Java’s type system that is responsible for Java’s popularity in enterprise programming.
    =
    However, by enforcing its strict type system, Java imposes limitations that restrict the language’s scope in other domains. For example, when writing a general-purpose library that is to be used by other Java applications, we are normally not able to reference any type that is defined in the user’s application because these types are unknown to us when our library is compiled. In order to call methods or to access fields of the user’s unknown code, the Java Class Library comes with a reflection API. Using the reflection API, we are able to introspect unknown types and to call methods or access fields. Unfortunately, the use of the reflection API has two significant downsides:
    Using the reflection API is slower than a hard-coded method invocation: First, one needs to perform a rather expensive method lookup to get hold of an object that describes a specific method. And when a method is invoked, this requires the JVM to run native code which requires long run time compared to a direct invocation. However, modern JVMs know a concept called inflation where the JNI-based method invocation is replaced by generated byte code that is injected into a dynamically created class. (Even the JVM itself uses code generation!) After all, Java’s inflation system remains with the drawback of generating very general code that for example only works with boxed primitive types such that the performance drawback is not entirely settled.
    The reflection API defeats type-safety: Even though the JVM is capable of invoking code by reflection, the reflection API is itself not type-safe. When writing a library, this is not a problem as long as we do not need to expose the reflection API to the library’s user. After all, we do not know the user code during compilation and could not validate our library code against its types. Sometimes, it is however required to expose the reflection API to a user by for example letting a library invoke one of our own methods for us. This is where using the reflection API becomes problematic as the Java compiler would have all the information to validate our program’s type safety. For example, when implementing a library for method-level security, a user of this library would want the library to invoke a method only after enforcing a security constraint. For this, the library would need to reflectively call a method after the user handed over the required arguments for this method. Doing so, there is however no longer a compile-time type check if these method arguments match with the method’s reflective invocation. The method call is still validated but the check is delayed until runtime. Doing so, we voided a great feature of the Java programming language.
    This is where runtime code generation can help us out. It allows us to emulate some features that are normally only accessible when programming in a dynamic languages without discarding Java’s static type checks. This way, we can get the best of both worlds and additionally improve runtime performance. To get a better understanding of this problem, let us look at the example of implementing the mentioned method-level security library.

    简单总结就是java的反射存在诸多限制,java开发者需要一种手段模拟一些动态语言才具有的特性,而且不失去自己安全类型的特性,相比cglib,javasist等相同功能的工具,bytebuddy 更容易上手且具有更高的性能。
    本篇博客将根据官方文档,介绍bytebuddy的一些功能和特性的使用,也作为笔者自己的一个学习记录。

    本篇博客所有的代码示例都基于bytebuddy的1.8.0版本

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

    从生成一个类开始

    既然是字节码增强框架,那么作为入门的HelloWorld程序生成一个类用来演示是再好不过的了,ByteBuddy的Api设计相当优秀,我们只需要简单几行代码就可以生成一个自定义的类:

     @Test
     public void test() throws Exception {
         Object helloWorld = new ByteBuddy()
                 .subclass(Object.class)
                 .name("com.tinysakura.HelloWorld")
                 .make()
                 .load(ClassLoader.getSystemClassLoader())
                 .getLoaded()
                 .newInstance();
     }
    

    我们也可以将生成的字节码输出到指定的文件观察

     @Test
     public void test() throws Exception {
         new ByteBuddy()
                 .subclass(Object.class)
                 .name("com.tinysakura.HelloWorld")
                 .make()
                 .saveIn(new File("/Users/chenfeihao/Desktop"));
     }
    

    我们可以在这个路径下找到我们生成的class文件

    /Users/chenfeihao/Desktop/com/tinysakura
    

    反编译一下看看长什么样

    package com.tinysakura;
    
    public class HelloWorld {
        public HelloWorld() {
        }
    }
    

    除了构造方法光秃秃的什么都没有,接下来会一步步去充实生成的字节码文件。
    我们在subClass中指定了父类为Object,而Object是所有java的父类所以在反编译的文件中没有显式的展现出来,那我们可以指定生成其它类型的子类吗?当然可以

    @Test
    public void test() throws Exception {
        Object helloWorld = new ByteBuddy()
                .subclass(Moo.class)
                .name("com.tinysakura.HelloWorld")
                .make()
                .saveIn(new File("/Users/chenfeihao/Desktop/com/tinysakura"));
    }
    

    反编译的结果:

    package com.tinysakura;
    
    import com.tinysakura.bytebuddylearn.method.Moo;
    
    public class HelloWorld extends Moo {
        public HelloWorld() {
        }
    }
    

    可以看到这次生成的class文件继承了我们指定的Moo类型,那么所有类型的类都可以被继承吗?
    尝试去继承String类型:

    Object helloWorld = new ByteBuddy()
                    .subclass(String.class)
    

    直接抛出了一个异常:

    java.lang.IllegalArgumentException: Cannot subclass primitive, array or final types: class java.lang.String
    

    可以看到原基本类型(int,double,char…),数组类型和final类型的类不允许被继承,看来ByteBuddy并没有打破java的规范。

    以上几个示例里我都指定了生成类的类名,其实我们也可以不指定,ByteBuddy提供了一套命名策略,我们也可以提供自定义的命名策略(比如生成的子类如果是父类的实现类,我们可以指定生成类的类名为父类名+impl),下面这个例子来自bytebuddy官方文档:

    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();
    

    提供了一个NamingStrategy的实现,在自定义的i.love.ByteBuddy包下生成了与父类类名相同的子类。

    可以看到bytebuddy生成类的代码相当简单,比起自己动手生成不知道简单了多少,而且十分灵活,看到这里是不是更想要了解
    它了呢


    重新定义已经存在的类

    比起生成一个全新的类,工作中更多的需求是对已有的类做修改或增强(大部分是第三方中间件甚至基础java类),bytebuddy自然对这部分能力做了支持
    现在我们已经定义了一个HelloWorld类:

    package com.tinysakura.bytebuddylearn.clazz;
    public class HelloWorld {
        String helloWorld() {
            return "hello world";
        }
    }
    

    简单的对其redefine一下:

    import com.tinysakura.bytebuddylearn.clazz.HelloWorld;
    public void test() throws Exception {
       new ByteBuddy().redefine(HelloWorld.class)
               .make()
               .saveIn(new File("/Users/chenfeihao/Desktop/"));
    
    }
    

    最终我们会在如下路径找到重新生成的class文件

    /Users/chenfeihao/Desktop/com/tinysakura/bytebuddylearn/clazz
    

    反编译一下:

    package com.tinysakura.bytebuddylearn.clazz;
    
    public class HelloWorld {
        public HelloWorld() {
        }
    
        String helloWorld() {
            return "hello world";
        }
    }
    

    好像和redefine之前的类没有什么区别?因为我们根本还什么都没做呀,后面的内容会介绍如何修改已存在的类,bytebuddy的redifine和rebase只能重定义没有被jvm加载的类,如果试图redifine已经加载的类,会抛出异常

    java.lang.IllegalStateException: Cannot inject already loaded type: class java.lang.Object
    

    bytebuddy提供了一个类型池的概念,我们不仅可以redifine classpath路径下的类,也可以从外部文件,jar包,网络redefine,我们需要一个ClassFileLocator去指导bytebuddy从那找到我们需要redefine的字节码文件:

    ClassFileLocator fileLocator = ClassFileLocator.ForJarFile.of(new File("/Users/chenfeihao/Desktop/test.jar"));
    TypePool typePool = TypePool.Default.of(fileLocator);
    
    new ByteBuddy().redefine(typePool.describe("com.tinysakura.TestClassFileLocator").resolve(), fileLocator)
            .make()
            .load(ClassLoader.getSystemClassLoader());
    

    操作Method

    前几章简单的介绍了bytebuddy的使用,但是好像不能满足我们使用字节码增强的核心诉求:增强。这章将从组成java类的两个基本属性fied,method开始,介绍如何对你的类做增强

    还是从hello world开始,介绍类生成的时候我们已经生成了一个HellowWorld类,但是这个类除了构造方法没有任何方法,不会说hello world的hello world程序是没有灵魂的,我们通过简单几行代码给他注入灵魂:

    @Test
    public void test() throws Exception {
        Object helloWorld = new ByteBuddy()
                .subclass(Object.class)
                .name("com.tinysakura.HelloWorld")
                .defineMethod("helloWorld", String.class, Modifier.PUBLIC)
                .intercept(FixedValue.value("hello world"))
                .make()
                .load(ClassLoader.getSystemClassLoader())
                .getLoaded()
                .newInstance();
    
        Method method = helloWorld.getClass().getMethod("helloWorld");
        System.out.println(method.invoke(helloWorld));
    }
    

    在这里插入图片描述
    相比之前单纯生成类的代码,我们新加了defineMethod和interceptor两行代码

    defineMethod
    顾名思议,作用是定义一个方法,三个参数分别描述了方法名,返回值类型,方法可见性
    如果我们希望方法接收参数,可以在defineMethod后通过withParameter方法指定

    defineMethod("helloWorld", String.class, Modifier.PUBLIC).withParameter(String.class, "arg1")
    

    interceptor
    拦截方法,提供或修改实现,在我们的示例代码中我们拦截了自定义的helloWorld方法,然后定义他的返回值为我们期望的hello world,我们当然不只是可以返回一个固定的值,相反interceptor的机制非常灵活,我们可以钩入任何自定义的逻辑。

    现实生产中需要自定义方法的情况是很少的,大部分需求是修改已有方法的逻辑,或者在已有方法中织入自定义的逻辑,而官方的示例就是从修改Object类型的toString方法开始的

    String toString = new ByteBuddy()
      .subclass(Object.class)
      .name("example.Type")
      .method(named("toString")).intercept(FixedValue.value("Hello World!"))
      .make()
      .load(getClass().getClassLoader())
      .getLoaded()
      .newInstance()
      .toString();
    

    bytebuddy提供了一套ElementMatcher机制去匹配符合各种条件组合的方法(包括其他属性也是通过这套机制去匹配)

    举几个例子

    1. 匹配类中所有public方法
    .method(ElementMatchers.isPublic())
    
    1. 匹配名称为helloWorld且可见性为public的所有方法
    .method(ElementMatchers.isPublic().and(ElementMatchers.named("helloWorld")))
    
    1. 匹配名称为helloWorld且可见性为public但返回值类型不为String的所有方法
    .method(ElementMatchers.isPublic().and(ElementMatchers.named("helloWorld")).and(ElementMatchers.not(ElementMatchers.returns(String.class))))
    
    1. 匹配名称为helloWorld且第二个入参为String类型的所有方法
    .method(ElementMatchers.isPublic().and(ElementMatchers.named("helloWorld")).and(ElementMatchers.takesArgument(1, String.class)))
    

    可以看出这套匹配机制十分灵活,限定范围除了上述还包括注解,是否属于static等诸多其它维度,可以满足绝大部分场景的匹配需求,但也因为ElementMatcher太过灵活,匹配的时候注意不要误匹配到了不想增强的方法导致bug

    匹配到了方法后就要对方法做增强,helloWorld的示例中的增强有些简陋,返回固定值没法满足大部分场景的需求,我们的需求大多是比较复杂的,这时候就可以依靠bytebuddy的MethodDelegation机制,将匹配到的方法"委派"给定义好的实现

    In many scenarios, returning a fixed value from a method is of course insufficient. For more flexibility, Byte Buddy provides the MethodDelegation implementation which offers maximal freedom in reacting to method calls. A method delegation defines a method of the dynamically created type to forward any call to another method which may live outside of the dynamic type. This way, a dynamic class’s logic can be represented using plain Java while only the binding to another method is achieved by code generation

    MethodDelegation
    直接通过示例进行学习,我们定义一个Moo类,他有两个方法:

    public class Moo {
    public String Moo1(String param1) {return "parm1:" + param1;}
    
    public String Moo(String param1, Integer param2) {return "param1:" + param1 + ",param2:"+ param2;}
    }
    

    这两个个方法都已经有默认的实现,我们现在定义一个“委托类”去覆盖他们的实现:

    public class DelegateMoo {
        public static String Moo(String param1, Integer param2) {
            return "my name is " + param1 + ",my age is " + param2;
        }
    
        public static String Moo1(String param1) {
            return "my name is " + param1;
        }
    }
    

    委托类中有两个和Moo中很类似的方法,接下来我们调用bytebuddy的api去实现覆盖

    @Test
    public void testMethodDelegation() throws Exception {
        Moo moo = new ByteBuddy()
                .subclass(Moo.class)
                .method(ElementMatchers.named("Moo").or(ElementMatchers.named("Moo1")))
                .intercept(MethodDelegation.to(DelegateMoo.class))
                .make()
                .load(ClassLoader.getSystemClassLoader())
                .getLoaded()
                .newInstance();
    
        System.out.println("moo:" +  moo.Moo("tinysakura", 21));
        System.out.println("moo1:" + moo.Moo1("tinysakura"));
    }
    

    输出结果
    在这里插入图片描述
    可以看到被匹配到的Moo和Moo1方法的实现都被覆盖为了DelegateMoo中的实现,ByteBuddy会根据最相似匹配原则去匹配委托类中可以覆盖原方法的实现,因此即使我将DelegateMoo中的Moo方法改名为Foo:

    public class DelegateMoo {
        public static String Moo1(String param1) {
            return "my name is " + param1;
        }
        public static String Foo(String param1, Integer param2) {
            return "my name is " + param1 + ",my age is " + param2;
        }
    }
    

    得到的依然是一样的结果,因为bytebuddy这个方法除了方法名以外的签名都和委托的Moo方法一致。根据最似匹配原则会使用Foo方法的实现进行委托,而当匹配到的委托方法在被委托类中找不到任何可以匹配到的方法时,bytebuddy会抛出异常,比如我们在委托类中加入一个重载的不接受任何参数的Moo1方法:

    public class Moo {
        public String Moo1() {return null;}
    
        public String Moo1(String param1) {return "parm1:" + param1;}
    
        public String Moo(String param1, Integer param2) {return "param1:" + param1 + ",param2:"+ param2;}
    }
    

    此时再执行下面这段代码就会报错

     Moo moo = new ByteBuddy()
                .subclass(Moo.class)
                .method(ElementMatchers.named("Moo").or(ElementMatchers.named("Moo1")))
                .intercept(MethodDelegation.to(DelegateMoo.class))
                .make()
                .load(ClassLoader.getSystemClassLoader())
                .getLoaded()
                .newInstance();
    
    java.lang.IllegalArgumentException: None of [public static java.lang.String com.tinysakura.bytebuddylearn.method.DelegateMoo.Moo1(java.lang.String), public static java.lang.String com.tinysakura.bytebuddylearn.method.DelegateMoo.Foo(java.lang.String,java.lang.Integer)] allows for delegation from public java.lang.String com.tinysakura.bytebuddylearn.method.Moo.Moo1()
    

    因为在DelegateMoo中找不到任何可以匹配无参Moo1的实现,此时使用者需要被委托类的实现保证委托方法可以顺利找到被委托的方法。

    上面的例子说明delegate的机制已经十分灵活了,但不止于此,比如我们想通过bytebuddy实现一个类似切面的功能,在匹配到的方法调用前加入执行时刻的日志,此时我们需要知道到委托方法原本的实现,该如何做?bytebuddy已经帮你想好了

    @Super
    我们定义一个新的被委托类DelegateMooWithSuper,在被委托方法Moo1的入参中加入了一个使用了@Super注解的委托类对象,此时这个被注解的入参在进行最似匹配时会被忽略,bytebuddy匹配到的依然是只有一个String入参的委托方法。这里也体现了bytebuddy结合注解达到高使用灵活度的代码风格。

    public class DelegateMooWithSuper {
        public static String Moo1(String param1, @Super Moo zuper) {
            System.out.println("invoke time:" + System.currentTimeMillis());
            return zuper.Moo1(param1);
        }
    }
    
    @Test
        public void testMethodDelegation() throws Exception {
            Moo moo = new ByteBuddy()
                    .subclass(Moo.class)
                    .method(ElementMatchers.named("Moo").or(ElementMatchers.named("Moo1")))
                    .intercept(MethodDelegation.to(DelegateMooWithSuper.class))
                    .make()
                    .load(ClassLoader.getSystemClassLoader())
                    .getLoaded()
                    .newInstance();
    
            System.out.println("moo1:" + moo.Moo1("tinysakura"));
        }
    

    在这里插入图片描述
    可以看到执行方法前打印了一行执行时间日志,我们这次没有在被委托方法中修改原方法的实现,所以依然使用的原方法的实现,通过这种机制我们可以在不破坏原方法逻辑的前提下,插入很多前置后置的逻辑,改变参数等,大大增加了字节码修改的灵活度。

    @superCall
    如果不需要修改入参,或者在被委托方法中调用超类其它方法,也可以使用@superCall注解

    public static String Moo(String param1, Integer param2,
                                 @SuperCall Callable<String> superCall) {
            try {
                String superCallResult = superCall.call();
                return "wrapped by delegate:{" + superCallResult + "}";
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    

    调用callable的call方法可以获取被委托方法的实现结果。

    bytebuddy还提供了一些api对一些特殊方法,如构造方法,接口的默认方法做操作,这部分内容可以直接参考官方文档,最后,bytebuddy允许在interceptor中通过MethodCall去调用一个特定的方法,可以调用的包括构造方法,方法描述,可以通过这种机制更灵活的替换委托方法的实现。


    操作field

    上一节我们看到如何通过bytebuddy生成、实现方法,作为java类另一个非常重要的部分,bytebuddy也提供了大量操作属性的api,我们先从最简单的定义一个属性开始:

     @Test
    public void testField() throws Exception {
        Moo moo = new ByteBuddy()
                .subclass(Moo.class)
                .defineField("name", String.class)
                .make()
                .load(ClassLoader.getSystemClassLoader())
                .getLoaded()
                .newInstance();
        System.out.println(moo.getClass().getDeclaredField("name") != null);
    }
    

    看看反编译的结果,生成的子类中定义了一个string类型的name属性。

    接下来我想给这个name赋值,那么我们需要一个getter和setter方法,可以通过FieldAccessor方便的为类的属性生成对应的getter和setter方法,为此我们需要准备一个对应属性的interceptor接口:

    public interface NameInterceptor {
        String getName();
    
        void setName(String name);
    }
    

    然后通过FieldAccessor为我们新声明的name属性加入getter/setter方法:

     @Test
    public void testField() throws Exception {
        new ByteBuddy()
                .subclass(Moo.class)
                .defineField("name", String.class, Modifier.PUBLIC)
                .implement(NameInterceptor.class).intercept(FieldAccessor.ofBeanProperty())
                .make().saveIn(new File("/Users/chenfeihao/Desktop/"));
    }
    

    下面是反编译的结果

    public class Moo$ByteBuddy$FDuX1G5O extends Moo implements NameInterceptor {
        public String name;
    
        public String getName() {
            return this.name;
        }
    
        public void setName(String var1) {
            this.name = var1;
        }
    
        public Moo$ByteBuddy$FDuX1G5O() {
        }
    }
    

    @FieldValue
    之后我们就可以通过反射给name注入值了,在后续方法的代理实现中也可以通过@FieldValue注解使用这个原本的类中不存在的属性
    我们通过DelegateWithField代理原始类的Moo1方法,同时打印出传参和name属性的值

    public class DelegateWithField {
        String name;
        public static String Moo1(String param1, @FieldValue(value = "name") String name) throws Exception {return "parm1:" + param1 + ";name:" + name;}
    }
    
    @Test
    public void testField() throws Exception {
        Moo moo = new ByteBuddy()
                .subclass(Moo.class)
                .defineField("name", String.class, Modifier.PUBLIC)
                .implement(NameInterceptor.class).intercept(FieldAccessor.ofBeanProperty())
                .method(ElementMatchers.named("Moo1"))
                .intercept(MethodDelegation.to(DelegateWithField.class))
                .make().load(ClassLoader.getSystemClassLoader())
                .getLoaded().newInstance();
    
    
        moo.getClass().getMethod("setName", String.class).invoke(moo, "tinysakura");
        System.out.println(moo.getClass().getMethod("Moo1", String.class).invoke(moo, "param1"));
    }
    

    在这里插入图片描述
    照例看一波反编译的结果:

    public class Moo$ByteBuddy$YNq6kjNU extends Moo implements NameInterceptor {
        public String name;
    
        public String Moo1(String var1) {
            return DelegateWithField.Moo1(var1, this.name);
        }
    
        public String getName() {
            return this.name;
        }
    
        public void setName(String var1) {
            this.name = var1;
        }
    
        public Moo$ByteBuddy$YNq6kjNU() {
        }
    }
    

    注解

    bytebuddy中使用了非常多的注解方便我们coding,当然也支持在字节码生成时加入注解,事实上注解在java代码中的使用非常普遍,bytebuddy提供的这些能力可以更方便的为增强的类提供能力

    首先声明一个自定义的注解以及它的实现

    @Retention(RetentionPolicy.RUNTIME)
    public @interface RuntimeDefinition {
    }
    
    public class RuntimeDefinitionImpl implements RuntimeDefinition {
        @Override
        public Class<? extends Annotation> annotationType() {
            return RuntimeDefinition.class;
        }
    }
    

    然后我们可以在任何声明,匹配到的属性或方法上加上我们自定义的注解

    @Test
        public void testAnnotation() throws Exception {
            new ByteBuddy()
                    .subclass(Moo.class)
                    .defineField("name", String.class, Modifier.PUBLIC)
                    .annotateField(new RuntimeDefinitionImpl())
                    .method(ElementMatchers.named("Moo1"))
                    .intercept(MethodDelegation.to(DelegateMoo.class))
                    .annotateMethod(new RuntimeDefinitionImpl())
                    .make().saveIn(new File("/Users/chenfeihao/Desktop/"));
        }
    

    反编译结果:

    public class Moo$ByteBuddy$n5Zp31xO extends Moo {
        @RuntimeDefinition
        public String name;
    
        @RuntimeDefinition
        public String Moo1(String var1) {
            return DelegateMoo.Moo1(var1);
        }
    
        public Moo$ByteBuddy$n5Zp31xO() {
        }
    }
    
    

    Java Agent

    之前介绍类的redefine时特别说明了我们只能redefine尚未被加载的类,然而有些需求必须得对那些”已加载的类"做增强,java自己本身提供了一套java agent的机制在虚拟机启动前对字节码做增强,而byte buddy很好的对其做了支持,阅读这一节需要有java agent方面的基础,可以通过阅读JAVA SE 6新特性:Instrumentation了解

    我们还是通过一个示例进行学习,内容是对线程做增强,打印出所有线程执行前后的时间戳,并在任务执行前输出一句hello world。

    首先定义我们的preMain程序

    public class BytebuddyAgent {
    
        public static void premain(String agentArgs, Instrumentation inst) throws Exception {
            Class<?> bootStrapClass = Class.forName("com.tinysakura.bytebuddylearn.BootStrap", true, getClassLoader());
            //Class<?> bootStrapClass = Class.forName("com.tinysakura.bytebuddylearn.BootStrap");
            Method preMain = bootStrapClass.getMethod("preMain", String.class, Instrumentation.class);
            preMain.invoke(null, agentArgs, inst);
        }
    
        public static ClassLoader getClassLoader(){
            ClassLoader classLoader= Thread.currentThread().getContextClassLoader();
            if (classLoader!=null){
                return classLoader;
            }
            return ClassLoader.getSystemClassLoader();
        }
    }
    

    agent启动的时候会执行preMain方法,我们在preMain方法中通过反射调用另外一个jar包中BootStrap类的同名preMain方法,注意这里将Instrumentation作为参数进行了传递

    public static void preMain(String args, Instrumentation inst) {
            AgentBuilder.Transformer transformer = (builder, typeDescription, classLoader, module) -> builder.visit(Advice.to(Interceptor.class).on(ElementMatchers.named("start")));
    
            new AgentBuilder.Default()
                    .disableClassFormatChanges()
                    .enableBootstrapInjection(inst, new File("/Users/chenfeihao/Desktop/lib"))
                    .ignore(new AgentBuilder.RawMatcher.ForElementMatchers(nameStartsWith("net.bytebuddy.")))
                    .ignore(ElementMatchers.noneOf(Thread.class))
                    .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
                    .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
                    .with(AgentBuilder.TypeStrategy.Default.REDEFINE)
                    .type(ElementMatchers.is(Thread.class))
                    .transform(transformer)
                    .with(new MyAgentBuilderListener())
                    .installOn(inst);
        }
    

    bootStrap的preMain方法看上去有点复杂,我们逐步拆解

    1. 定义了一个transformer类型,通过访问者模式切入start方法
    2. 通过enableBootstrapInjection加入了新的classpath,确保我们的类文件能被找到
    3. 忽略一部分不想被误匹配的类
    4. 指定一系列redefine策略
    5. 通过type方法指定要匹配的类
    6. 通过transform方法加入1中定义的访问者,这样Thread类的start方法将被这个transformer访问到
    7. 增加一个redefine前的回调,完成一些前序工作
    8. 通过install Instrumentation赋予byte agent 在thread类加载前redefine的能力

    先来看增强逻辑

    public static class Interceptor {
            @Advice.OnMethodEnter
            public static void enter(@Advice.This Thread thread, @Advice.Origin Method origin) {
                System.out.println("thread:" + thread.getName() + " enter thread timeMills:" + System.currentTimeMillis());
                ClassLoader classLoader = ClassLoader.getSystemClassLoader();
    
                if (classLoader == null) {
                    try {
                        origin.invoke(null, thread);
                    } catch (Throwable e) {
                        e.printStackTrace();
                    }
                    return;
                }
    
                try {
                    Class reflectClass = Class.forName("com.tinysakura.bytebuddylearn.CustomThread", true, classLoader);
                    Method start = reflectClass.getMethod("start", Thread.class);
                    start.invoke(null, thread);
                } catch (Throwable e) {
                    e.printStackTrace();
                }
            }
    
            @Advice.OnMethodExit
            public static void exit(@Advice.This Thread thread, @Advice.Origin Method origin) {
                System.out.println("thread:" + thread.getName() + " exit thread timeMills:" + System.currentTimeMillis());
            }
        }
    

    通过@Advice.This注解我们可以拿到实际执行方法的对象,通过@Advice.Origin注解可以拿到原方法,我们首先在进入和退出方法前打印出当前线程的线程名和当前时间戳,其中在enter方法的实现中通过反射获取到了我们”增强“过的start方法交给thread执行,下面我们看这个增强的start方法是怎么实现的

    public class CustomThread {
        public static void start(Thread thread) {
            try {
                System.out.println("welcome to my custom thread");
                Field field = Thread.class.getDeclaredField("target");
                field.setAccessible(true);
                Object targetValue = field.get(thread);
                if (targetValue instanceof Runnable) {
                    Runnable runnable = (Runnable) targetValue;
                    field.set(thread, new CustomeRunnable(runnable));
                }
            } catch (Throwable e) {
                System.out.println(e);;
            }
        }
    }
    

    首先打印出"welcome to my custom thread"标志走进了增强后的start方法,随后通过反射获取到thread的target属性,最后使用我们自己的CustomeRunnable对Runnable类型的target属性再做一次封装:

    public class CustomeRunnable implements Runnable {
    
        Runnable delegate;
    
        public CustomeRunnable(Runnable delegate) {
            this.delegate = delegate;
        }
    
        @Override
        public void run() {
            System.out.println("welcome to my custom thread");
            delegate.run();
        }
    }
    

    为了演示,CustomeRunnable做的事情和增强的start方法差不多,也只是简单打印”welcome to my custom thread“

    现在我们对增强的Thread的期望是在执行runnable的逻辑前先打印一下当前线程名和时间戳,随后打印两次welcome to my custom thread,接着执行增强前的逻辑,最后退出线程打印当前线程名和时间戳,我们启动一个线程观察增强结果:

    public static void main(String[] args) {
            new Thread(() -> {
                System.out.println("hello bytebuddy agent");
                for (int i=0; i < 10000L; i++) {
    
                }
            }, "tinysakura").start();
        }
    

    执行结果:
    在这里插入图片描述

    展开全文
  • ByteBuddy基本用法

    2021-06-20 21:48:57
    ByteBuddy是一种字节码技术框架,其广泛用于中间件
  • PowerMock and ByteBuddy

    2021-01-10 04:29:49
    ve tried implement some of PowerMock feature with using ByteBuddy and come to conclusion that it will be better to ask you first instead wasting time and searching what possible implement with Byte...
  • bytebuddy 匹配方法demo

    2019-10-11 11:19:36
    参考 https://www.programcreek.com/java-api-examples/?class=net.bytebuddy.matcher.ElementMatcher&method=Junction
  • bytebuddy简单入门

    千次阅读 2020-06-04 15:21:38
    bytebuddy介绍一、描述二、总览三、细节3.1 glance 一撇3.2 各类类增强工具对比3.3 命名策略3.4 classLoader策略Unloaded 未加载的类的处理ClassReloadingStrategywrapperchild-firstinject3.5 subclass &...
  • 字节码增强技术-Byte Buddy 目录简介性能使用installHello World创建动态类加载类拦截方法通过匹配模式拦截方法委托参数绑定AgentElementMatcherTransformerEND 简介 Byte Buddy是一个字节码生成和操作库,用于在...
  • 使用bytebuddy实现接口

    2019-11-27 21:54:38
    bytebuddy依赖接口代码 依赖 <dependency> <groupId>net.bytebuddy</groupId> <artifactId>byte-buddy</artifactId> <version>1.10.2</version> </dependency>....
  • bytebuddy源码解析

    2020-06-06 21:45:14
    bytebuddy 进阶一、ByteBuddyAgent示例1.1 Attacher 负责启动agent.jar1.2 Intaller Instrumention对象的载体很简单的getInstrumentionpremain agentmain1.3 VirtualMachine jvm平台的支持Resover获取 jvm平台信息...
  • bytebuddy的开发指南-翻译入门架构概览编码约定贡献路线图类型推断 原文链接 翻译这个的目的是,简述了源码的结构 入门 首先,您需要在本地计算机上创建Byte Buddy的副本。克隆存储库后,您可以使用Maven构建项目...
  • ASM、Javassist 系列,Byte Buddy 玩法上更加高级,你可以完全不需要了解一个类和方法块是如何通过 指令码 LDC、LOAD、STORE、IRETURN… 生成出来的。就像它的官网介绍; Byte Buddy 是一个代码生成和操作库,用于...
  • 使用 Byte Buddy 运行时生成泛型子类

    千次阅读 2017-03-23 13:14:18
    就是运用 Byte Buddy 在运行时生成一个类的子类,带泛型的,给类加上一个注解,可生成类文件或 Class 实例,不过这里更进一步,实现的方法是带参数的。 用 Byte Buddy 操作起来更简单,根本不需要接触任何字节
  • 目录 前言 一、Bety Buddy是什么? 1.Bety Buddy 定义 2.代码生成的其他的库类 ...3.代码生产代理库类性能指标 ...在研发生产或运维中经常会有系统...Byte Buddy 是基于ASM的一种通过领域特定语言(DSL)和注解的针对 J...
  • springcloud微服务报错Cannot resolve net.bytebuddy:byte-buddy-agent:1.7.11 点击idea右侧Maven,沿着红线打开 发现报错的是测试的包 应该是springboot测试启动器包版本问题,直接把该依赖注释掉就行,或者更换...
  • 前言bytebuddy是一个提供了一个API用于生成任意的Java类工具包,给需要编写javaagent的代码用户提供了一个很方便的工具。一般来说,如果要编写agent代码一般都是从premain函数开始,然后在启动的时候通过-javaagent...
  • 本文介绍byte buddy的源码。 byte buddy 是 新一代用于动态修改字节码的工具。 官方文档过于老旧和简陋,想要更好的使用 byte buddy 需要阅读源码。 dep 是byte buddy的开发包,实现都在里面。 码非常复杂。源码的...
  • 使用bytebuddy构建agent

    2016-10-23 23:08:41
    使用固定agent 引入pom <dependency> <groupId>net.bytebuddy</groupId> <artifactId>byte-buddy</artifactId> <v...
  • 在本文中,我们将从基础知识开始,使用字节码操作工具Byte Buddy实现高级代理。 在最基本的用例中,Java agent设置应用程序属性或配置特定的环境状态,从而使代理能够充当可重用和可插入的组件。下面的示例描述了...
  • Java字节码工程库Byte Buddy最新版本完全支持Java 11以及自Java 8以来引入的...\\InfoQ采访了ByteBuddy的作者Rafael Winterhalter,以了解更多信息。\\InfoQ:感谢你抽出宝贵时间与我们交谈。你能先介绍一下Byte ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,594
精华内容 1,437
关键字:

bytebuddy