乐趣区

关于java:探针技术JavaAgent-和字节码增强技术Byte-Buddy

  • 可能基于 Java Agent 编写出一般类的代理
  • 了解 Byte Buddy 的作用
  • 可能基于 Byte Buddy 编写动静代理

1 Byte Buddy

Byte Buddy 是一个代码生成和操作库,用于在 Java 利用程序运行时创立和批改 Java 类,而无需编译器的帮忙。除了 Java 类库附带的代码生成实用程序外,Byte Buddy 还容许创立任意类,并且不限于实现用于创立运行时代理的接口。此外,Byte Buddy 提供了一种不便的 API,能够应用 Java 代理或在构建过程中手动更改类。

  • 无需了解字节码指令,即可应用简略的 API 就能很容易操作字节码,管制类和办法。
  • 已反对 Java 11,库轻量,仅取决于 Java 字节代码解析器库 ASM 的访问者 API,它自身不须要任何其余依赖项。
  • 比起 JDK 动静代理、cglib、Javassist,Byte Buddy 在性能上具备肯定的劣势。

官网:https://bytebuddy.net

1.1 Byte Buddy 利用场景

Java 是一种强类型的编程语言,即要求所有变量和对象都有一个确定的类型,如果在赋值操作中呈现类型不兼容的状况,就会抛出异样。强类型查看在大多数状况下是可行的,然而在某些非凡场景下,强类型查看则成了微小的阻碍。

咱们在做一些通用工具封装的时候,类型查看就成了很大阻碍。比方咱们编写一个通用的 Dao 实现数据操作,咱们基本不晓得用户要调用的办法会传几个参数、每个参数是什么类型、需要变更又会呈现什么类型,简直没法在办法中援用用户办法中定义的任何类型。咱们绝大多数通用工具封装都采纳了反射机制,通过反射能够晓得用户调用的办法或字段,然而 Java 反射有很多缺点:

1: 反射性能很差
2: 反射能绕开类型安全检查,不平安,比方权限暴力破解

java 编程语言代码生成库不止 Byte Buddy 一个,以下代码生成库在 Java 中也很风行:

  • Java Proxy

Java Proxy 是 JDK 自带的一个代理工具,它容许为实现了一系列接口的类生成代理类。Java Proxy 要求指标类必须实现接口是一个十分大限度,例如,在某些场景中,指标类没有实现任何接口且无奈批改指标类的代码实现,Java Proxy 就无奈对其进行扩大和加强了。

  • CGLIB

CGLIB 诞生于 Java 初期,但可怜的是没有跟上 Java 平台的倒退。尽管 CGLIB 自身是一个相当弱小的库,但也变得越来越简单。鉴于此,导致许多用户放弃了 CGLIB。

  • Javassist

Javassist 的应用对 Java 开发者来说是十分敌对的,它应用 Java 源代码字符串和 Javassist 提供的一些简略 API,独特拼凑出用户想要的 Java 类,Javassist 自带一个编译器,拼凑好的 Java 类在程序运行时会被编译成为字节码并加载到 JVM 中。Javassist 库简略易用,而且应用 Java 语法构建类与平时写 Java 代码相似,然而 Javassist 编译器在性能上比不了 Javac 编译器,而且在动静组合字符串以实现比较复杂的逻辑时容易出错。

  • Byte Buddy

Byte Buddy 提供了一种非常灵活且弱小的畛域特定语言,通过编写简略的 Java 代码即可创立自定义的运行时类。与此同时,Byte Buddy 还具备十分凋谢的定制性,可能应酬不同复杂度的需要。

下面所有代码生成技术中,咱们举荐应用 Byte Buddy,因为 Byte Buddy 代码生成可的性能最高,Byte Buddy 的次要侧重点在于生成更疾速的代码,如下图:

1.2 Byte Buddy 学习

咱们接下来具体解说一下 Byte Buddy Api,对重要的办法和类进行深度分析。

1.2.1 ByteBuddy 语法

任何一个由 Byte Buddy 创立 / 加强的类型都是通过 ByteBuddy 类的实例来实现的,咱们先来学习一下 ByteBuddy 类,如下代码:

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

Byte Buddy 动静加强代码总共有三种形式:

subclass: 对应 ByteBuddy.subclass() 办法。这种形式比拟好了解,就是为指标类(即被加强的类)生成一个子类,在子类办法中插入动静代码。rebasing: 对应 ByteBuddy.rebasing() 办法。当应用 rebasing 形式加强一个类时,Byte Buddy 保留指标类中所有办法的实现,也就是说,当 Byte Buddy 遇到抵触的字段或办法时,会将原来的字段或办法实现复制到具备兼容签名的重新命名的公有办法中,而不会摈弃这些字段和办法实现。从而达到不失落实现的目标。这些重命名的办法能够持续通过重命名后的名称进行调用。redefinition: 对应 ByteBuddy.redefine() 办法。当重定义一个类时,Byte Buddy 能够对一个已有的类增加属性和办法,或者删除曾经存在的办法实现。如果应用其余的办法实现替换曾经存在的办法实现,则原来存在的办法实现就会隐没。

通过上述三种形式实现类的加强之后,咱们失去的是 DynamicType.Unloaded 对象,示意的是一个未加载的类型,咱们能够应用 ClassLoadingStrategy 加载此类型。Byte Buddy 提供了几品种加载策略,这些加载策略定义在 ClassLoadingStrategy.Default 中,其中:

  • WRAPPER 策略:创立一个新的 ClassLoader 来加载动静生成的类型。
  • CHILD_FIRST 策略:创立一个子类优先加载的 ClassLoader,即突破了双亲委派模型。
  • INJECTION 策略:应用反射将动静生成的类型间接注入到以后 ClassLoader 中。

实现如下:

Class<?> dynamicClazz = new ByteBuddy()
        // 生成 Object 的子类
        .subclass(Object.class)
        // 生成类的名称为 "com.itheima.Type"
        .name("com.itheima.Type")
        .make()
        .load(Demo.class.getClassLoader(),
                // 应用 WRAPPER 策略加载生成的动静类型
                ClassLoadingStrategy.Default.WRAPPER)
        .getLoaded();

后面动静生成的 com.itheima.Type 类型只是简略的继承了 Object 类,在理论利用中动静生成新类型的个别目标就是为了加强原始的办法,上面通过一个示例展现 Byte Buddy 如何加强 toString() 办法:

// 创立 ByteBuddy 对象
String str = new ByteBuddy()
        // subclass 加强形式
        .subclass(Object.class)
        // 新类型的类名
        .name("com.itheima.Type") 
        // 拦挡其中的 toString()办法
        .method(ElementMatchers.named("toString"))
        // 让 toString()办法返回固定值
        .intercept(FixedValue.value("Hello World!"))
        .make()
        // 加载新类型,默认 WRAPPER 策略
        .load(ByteBuddy.class.getClassLoader())
        .getLoaded()
        // 通过 Java 反射创立 com.xxx.Type 实例
        .newInstance()
        // 调用 toString()办法
        .toString(); 

首先须要关注这里的 method() 办法,method() 办法能够通过传入的 ElementMatchers 参数匹配多个须要批改的办法,这里的 ElementMatchers.named("toString") 即为依照办法名匹配 toString() 办法。如果同时存在多个重载办法,则能够应用 ElementMatchers 其余 API 形容办法的签名,如下所示:

// 指定办法名称
ElementMatchers.named("toString")
    // 指定办法的返回值
    .and(ElementMatchers.returns(String.class))
    // 指定办法参数
    .and(ElementMatchers.takesArguments(0));

接下来须要关注的是 intercept() 办法,通过 method() 办法拦挡到的所有办法会由 Intercept() 办法指定的 Implementation 对象决定如何加强。这里的 FixValue.value() 会将办法的实现批改为固定值,上例中就是固定返回 “Hello World!” 字符串。

Byte Buddy 中能够设置多个 method()Intercept() 办法进行拦挡和批改,Byte Buddy 会依照栈的程序来进行拦挡。

1.2.2 ByteBuddy 案例

创立一个我的项目agent-demo,增加如下坐标

<dependencies>
    <dependency>
        <groupId>net.bytebuddy</groupId>
        <artifactId>byte-buddy</artifactId>
        <version>1.9.2</version>
    </dependency>
    <dependency>
        <groupId>net.bytebuddy</groupId>
        <artifactId>byte-buddy-agent</artifactId>
        <version>1.9.2</version>
    </dependency>
</dependencies>

咱们先创立一个一般类,再为该类创立代理类,创立代理对办法进行拦挡做解决。

1)一般类

创立com.itheima.service.UserService

package com.itheima.service;

public class UserService {

    // 办法 1
    public String username(){System.out.println("username().....");
        return "张三";
    }

    // 办法 2
    public String address(String username){System.out.println("address(String username).....");
        return username+"来自【湖北省 - 仙居 - 恩施土家族苗族自治州】";
    }

    // 办法 3
    public String address(String username,String city){System.out.println("address(String username,String city).....");
        return username+"来自【湖北省"+city+"】";
    }
}

2)代理测试com.itheima.service.UserServiceTest

public static void main(String[] args) throws IllegalAccessException, InstantiationException {Class<? extends UserService> aClass = new ByteBuddy()
                // 创立一个 UserService 的子类
                .subclass(UserService.class)
                // 指定类的名称
                .name("com.itheima.service.UserServiceImpl")
                // 指定要拦挡的办法
                //.method(ElementMatchers.isDeclaredBy(UserService.class))
                .method(ElementMatchers.named("address").and(ElementMatchers.returns(String.class).and(ElementMatchers.takesArguments(1))))
                // 为办法增加拦截器 如果拦截器办法是动态的 这里能够传 LogInterceptor.class
                .intercept(MethodDelegation.to(new LogInterceptor()))
                // 动静创建对象,但还未加载
                .make()
                // 设置类加载器 并指定加载策略(默认 WRAPPER)
                .load(ByteBuddy.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
                // 开始加载失去 Class
                .getLoaded();
        UserService userService = aClass.newInstance();

        System.out.println(userService.username());
        System.out.println(userService.address("唐僧老师"));
        System.out.println(userService.address("唐僧老师","仙居恩施"));
    }

3)创立拦截器,编写拦截器办法:com.itheima.service.LogInterceptor

package com.itheima.service;

import net.bytebuddy.implementation.bind.annotation.*;

import java.lang.reflect.Method;
import java.util.concurrent.Callable;

public class LogInterceptor {

    @RuntimeType // 将返回值转换成具体的办法返回值类型, 加了这个注解 intercept 办法才会被执行
    public  Object intercept(
            // 被拦挡的指标对象(动静生成的指标对象)@This  Object target,
            // 正在执行的办法 Method 对象(指标对象父类的 Method)@Origin Method method,
            // 正在执行的办法的全副参数
            @AllArguments Object[] argumengts,
            // 指标对象的一个代理
            @Super  Object delegate,
            // 办法的调用者对象 对原始办法的调用依附它
            @SuperCall Callable<?> callable) throws Exception {
        // 指标办法执行前执行日志记录
        System.out.println("筹备执行 Method="+method.getName());
        // 调用指标办法
        Object result = callable.call();
        // 指标办法执行后执行日志记录
        System.out.println("办法执行实现 Method="+method.getName());
        return result;
    }

}

在程序中咱们 用到 ByteBuddyMethodDelegation对象,它能够将拦挡的指标办法委托给其余对象解决,这里有几个注解咱们先进行阐明:

  • @RuntimeType:不进行严格的参数类型检测,在参数匹配失败时,尝试应用类型转换形式(runtime type casting)进行类型转换,匹配相应办法。
  • @This:注入被拦挡的指标对象(动静生成的指标对象)。
  • @Origin:注入正在执行的办法 Method 对象(指标对象父类的 Method)。如果拦挡的是字段的话,该注解应该标注到 Field 类型参数。
  • @AllArguments:注入正在执行的办法的全副参数。
  • @Super:注入指标对象的一个代理
  • @SuperCall:这个注解比拟非凡,咱们要在 intercept() 办法中调用 被代理 / 加强 的办法的话,须要通过这种形式注入,与 Spring AOP 中的 ProceedingJoinPoint.proceed() 办法有点相似,须要留神的是,这里不能批改调用参数,从下面的示例的调用也能看进去,参数不必独自传递,都蕴含在其中了。另外,@SuperCall 注解还能够润饰 Runnable 类型的参数,只不过指标办法的返回值就拿不到了。

运行测试后果:

筹备执行 Method=username
username().....
办法执行实现 Method=username
张三
筹备执行 Method=address
address(String username).....
办法执行实现 Method=address
唐僧老师来自【湖北省 - 仙居 - 恩施土家族苗族自治州】筹备执行 Method=address
address(String username,String city).....
办法执行实现 Method=address
唐僧老师来自【湖北省仙居恩施】

2 探针技术 -javaAgent

应用 Skywalking 的时候,并没有批改程序中任何一行 Java 代码,这里便应用到了 Java Agent 技术,咱们接下来开展对 Java Agent 技术的学习。

2.1 javaAgent 概述

Java Agent 这个技术对大多数人来说都比拟生疏,然而大家都都多多少少接触过一些,实际上咱们平时用过的很多工具都是基于 java Agent 来实现的,例如:热部署工具 JRebel,springboot 的热部署插件,各种线上诊断工具(btrace, greys),阿里开源的 arthas 等等。

其实 java Agent 在 JDK1.5 当前,咱们能够应用 agent 技术构建一个独立于应用程序的代理程序(即 Agent),用来帮助监测、运行甚至替换其余 JVM 上的程序。应用它能够实现虚拟机级别的 AOP 性能,并且这种形式一个典型的劣势就是无代码侵入。

Agent 分为两种:

1、在主程序之前运行的 Agent,

2、在主程序之后运行的 Agent(前者的升级版,1.6 当前提供)。

2.2 javaAgent 入门

2.2.1 premain

premain:主程序之前运行的 Agent

在理论应用过程中,javaagent 是 java 命令的一个参数。通过 java 命令启动咱们的应用程序的时候,可通过参数 -javaagent 指定一个 jar 包(也就是咱们的代理 agent),可能实现在咱们应用程序的主程序运行之前来执行咱们指定 jar 包中的特定办法,在该办法中咱们可能实现动静加强 Class 等相干性能,并且该 jar 包有 2 个要求:

  1. 这个 jar 包的 META-INF/MANIFEST.MF 文件必须指定 Premain-Class 项,该选项指定的是一个类的全门路
  2. Premain-Class 指定的那个类必须实现 premain() 办法。

    META-INF/MANIFEST.MF

    Manifest-Version: 1.0
    Can-Redefine-Classes: true
    Can-Retransform-Classes: true
    Premain-Class: com.itheima.PreMainAgent
    

    留神:最初须要多一行空行

    Can-Redefine-Classes:true 示意能重定义此代理所需的类,默认值为 false(可选)

    Can-Retransform-Classes:true 示意能重转换此代理所需的类,默认值为 false(可选)

    Premain-Class:蕴含 premain 办法的类(类的全路径名)

从字面上了解,Premain-Class 就是运行在 main 函数之前的的类。当 Java 虚拟机启动时,在执行 main 函数之前,JVM 会先运行 -javaagent 所指定 jar 包内 Premain-Class 这个类的 premain 办法。

咱们能够通过在命令行输出 java 看到相应的参数,其中就有和 java agent 相干的

在下面 -javaagent 参数中提到了参阅java.lang.instrument,这是在rt.jar 中定义的一个包,该门路下有两个重要的类:

该包提供了一些工具帮忙开发人员在 Java 程序运行时,动静批改零碎中的 Class 类型。其中,应用该软件包的一个要害组件就是 Javaagent,如果从实质上来讲,Java Agent 是一个遵循一组严格约定的惯例 Java 类。下面说到 javaagent 命令要求指定的类中必须要有 premain()办法,并且对 premain 办法的签名也有要求,签名必须满足以下两种格局:

public static void premain(String agentArgs, Instrumentation inst)
    
public static void premain(String agentArgs)

JVM 会优先加载 带 Instrumentation 签名的办法,加载胜利疏忽第二种,如果第一种没有,则加载第二种办法

demo:

1、在 agent-demo 中增加如下坐标

<build>
    <plugins>
        <plugin>
            <artifactId>maven-assembly-plugin</artifactId>
            <configuration>
                <appendAssemblyId>false</appendAssemblyId>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
                <archive> <!-- 主动增加 META-INF/MANIFEST.MF -->
                    <manifest>
                        <!-- 增加 mplementation-* 和 Specification-* 配置项 -->
                        <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                        <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
                    </manifest>
                    <manifestEntries>
                        <!-- 指定 premain 办法所在的类 -->
                        <Premain-Class>com.itheima.agent.PreMainAgent</Premain-Class>
                        <Can-Redefine-Classes>true</Can-Redefine-Classes>
                        <Can-Retransform-Classes>true</Can-Retransform-Classes>
                    </manifestEntries>
                </archive>
            </configuration>
            <executions>
                <execution>
                    <id>make-assembly</id>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

2、编写一个 agent 程序:com.itheima.agent.PreMainAgent,实现 premain 办法的签名,先做一个简略的输入

package com.itheima.agent;

import java.lang.instrument.Instrumentation;

public class PreMainAgent {public static void premain(String agentArgs, Instrumentation inst) {System.out.println("我的 agent 程序跑起来啦!");
        System.out.println("收到的 agent 参数是:"+agentArgs);
    }
}

上面先来简略介绍一下 Instrumentation 中的外围 API 办法:

  • addTransformer()/removeTransformer() 办法:注册 / 登记一个 ClassFileTransformer 类的实例,该 Transformer 会在类加载的时候被调用,可用于批改类定义(批改类的字节码)。
  • redefineClasses() 办法:该办法针对的是曾经加载的类,它会对传入的类进行从新定义。
  • getAllLoadedClasses()办法:返回以后 JVM 已加载的所有类。
  • getInitiatedClasses() 办法:返回以后 JVM 曾经初始化的类。
  • getObjectSize()办法:获取参数指定的对象的大小。

3、对 agent-demo 我的项目进行打包,失去 agent-demo-1.0-SNAPSHOT.jar

4、创立 agent-test 我的项目,编写一个类:com.itheima.Application

package com.itheima;

public class Application {public static void main(String[] args) {System.out.println("main 函数 运行了");
    }
}

5、启动运行,增加 -javaagent 参数

-javaagent:/xxx.jar=option1=value1,option2=value2

运行后果为:

我的 agent 程序跑起来啦!
收到的 agent 参数是:k1=v1,k2=v2
main 函数 运行了 

总结:

这种 agent JVM 会先执行 premain 办法,大部分类加载都会通过该办法,留神:是大部分,不是所有。当然,脱漏的次要是零碎类,因为很多零碎类先于 agent 执行,而用户类的加载必定是会被拦挡的。也就是说,这个办法是在 main 办法启动前拦挡大部分类的加载流动,既然能够拦挡类的加载,那么就能够去做重写类这样的操作,联合第三方的字节码编译工具,比方 ASM,bytebuddy,javassist,cglib 等等来改写实现类。

2.2.2 agentmain(自学)

agentmain:主程序之后运行的 Agent

下面介绍的是在 JDK 1.5 中提供的,开发者只能在 main 加载之前增加手脚,在 Java SE 6 中提供了一个新的代理操作方法:agentmain,能够在 main 函数开始运行之后再运行。

premain 函数一样,开发者能够编写一个含有 agentmain 函数的 Java 类,具备以下之一的办法即可

public static void agentmain (String agentArgs, Instrumentation inst)

public static void agentmain (String agentArgs)

同样须要在 MANIFEST.MF 文件外面设置“Agent-Class”来指定蕴含 agentmain 函数的类的全门路。

1:在 agentdemo 中创立一个新的类:com.itheima.agent.AgentClass,并编写办法 agenmain

/**
 * Created by 传智播客 * 黑马程序员.
 */
public class AgentClass {public static void agentmain (String agentArgs, Instrumentation inst){System.out.println("agentmain runing");
    }
}

2:在 pom.xml 中增加配置如下

<plugin>
    <artifactId>maven-assembly-plugin</artifactId>
    <configuration>
        <appendAssemblyId>false</appendAssemblyId>
        <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
        </descriptorRefs>
        <archive> <!-- 主动增加 META-INF/MANIFEST.MF -->
            <manifest>
                <!-- 增加 mplementation-* 和 Specification-* 配置项 -->
                <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
            </manifest>
            <manifestEntries>
                <!-- 指定 premain 办法所在的类 -->
                <Premain-Class>com.itheima.agent.PreMainAgent</Premain-Class>
                <!-- 增加这个即可 -->
                <Agent-Class>com.itheima.agent.AgentClass</Agent-Class>
                <Can-Redefine-Classes>true</Can-Redefine-Classes>
                <Can-Retransform-Classes>true</Can-Retransform-Classes>
            </manifestEntries>
        </archive>
    </configuration>
    <executions>
        <execution>
            <id>make-assembly</id>
            <phase>package</phase>
            <goals>
                <goal>single</goal>
            </goals>
        </execution>
    </executions>
</plugin>

3:对 agent-demo 从新打包

4:找到 agent-test 中的 Application,批改如下:

public class Application {public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {System.out.println("main 函数 运行了");

        // 获取以后零碎中所有 运行中的 虚拟机
        List<VirtualMachineDescriptor> list = VirtualMachine.list();
        for (VirtualMachineDescriptor vm : list) {if (vm.displayName().endsWith("com.itheima.Application")) {VirtualMachine virtualMachine = VirtualMachine.attach(vm.id());
                virtualMachine.loadAgent("D:/agentdemo.jar");
                virtualMachine.detach();}
        }
    }
}

list()办法会去寻找以后零碎中所有运行着的 JVM 过程,你能够打印 vmd.displayName() 看到以后零碎都有哪些 JVM 过程在运行。因为 main 函数执行起来的时候过程名为以后类名,所以通过这种形式能够去找到以后的过程 id。

留神:在 mac 上装置了的 jdk 是能间接找到 VirtualMachine 类的,然而在 windows 中装置的 jdk 无奈找到,如果你遇到这种状况,请手动将你 jdk 装置目录下:lib 目录中的 tools.jar 增加进以后工程的 Libraries 中。

之所以要这样写是因为:agent 要在主程序运行后加载,咱们不可能在主程序中编写加载的代码,只能另写程序,那么另写程序如何与主程序进行通信?这里用到的机制就是 attach 机制,它能够将 JVM A 连贯至 JVM B,并发送指令给 JVM B 执行。

总结:

以上就是 Java Agent 的俩个简略小栗子了,Java Agent 非常弱小,它能做到的不仅仅是打印几个监控数值而已,还包含应用 Transformer 等高级性能进行类替换,办法批改等,要应用 Instrumentation 的相干 API 则须要对字节码等技术有较深的意识。

2.3 agent 案例

需要:通过 java agent 技术实现一个统计办法耗时的案例

1、在 agent-test 我的项目中增加办法:com.itheima.driver.DriverService

package com.itheima.driver;

import java.util.concurrent.TimeUnit;

public class DriverService {public void didi() {System.out.println("di di di ------------");
        try {TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {e.printStackTrace();
        }
    }

    public void dada() {System.out.println("da da da ------------");
        try {TimeUnit.SECONDS.sleep(6);
        } catch (InterruptedException e) {e.printStackTrace();
        }
    }
}

并在 com.itheima.Application 进行办法的调用

package com.itheima;

import com.itheima.service.DriverService;

public class Application {public static void main(String[] args) {System.out.println("main 函数 运行了");
        DriverService driverService = new DriverService();
        driverService.didi();
        driverService.dada();}
}

2、在 agent-demo 中革新com.itheima.agent.PreMainAgent

public class PreMainAgent {

    /***
     * 执行办法拦挡
     * @param agentArgs:-javaagent 命令携带的参数。在后面介绍 SkyWalking Agent 接入时提到
     *                 agent.service_name 这个配置项的默认值有三种笼罩形式,*                 其中,应用探针配置进行笼罩,探针配置的值就是通过该参数传入的。* @param inst:java.lang.instrumen.Instrumentation 是 Instrumention 包中定义的一个接口,它提供了操作类定义的相干办法。*/
    public static void premain(String agentArgs, Instrumentation inst){
        // 动静构建操作,依据 transformer 规定执行拦挡操作
        AgentBuilder.Transformer transformer = new AgentBuilder.Transformer() {
            @Override
            public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,
                                                    // 匹配上的具体的类型形容
                                                    TypeDescription typeDescription,
                                                    ClassLoader classLoader,
                                                    JavaModule javaModule) {
                // 构建拦挡规定
                return builder
                        //method()指定哪些办法须要被拦挡,ElementMatchers.any()示意拦挡所有办法
                        .method(ElementMatchers.<MethodDescription>any())
                        //intercept()指定拦挡上述办法的拦截器
                        .intercept(MethodDelegation.to(TimeInterceptor.class));
            }
        };

        // 采纳 Byte Buddy 的 AgentBuilder 联合 Java Agent 处理程序
        new AgentBuilder
                // 采纳 ByteBuddy 作为默认的 Agent 实例
                .Default()
                // 拦挡匹配形式:类以 com.itheima.driver 开始(其实就是 com.itheima.driver 包下的所有类).type(ElementMatchers.nameStartsWith("com.itheima.driver"))
                // 拦挡到的类由 transformer 解决
                .transform(transformer)
                // 装置到 Instrumentation
                .installOn(inst);
    }
}

agent-demo 我的项目中,创立 com.itheima.service.TimeInterceptor 实现统计拦挡,代码如下:

public class TimeInterceptor {

    /***
     * 拦挡办法
     * @param method:拦挡的办法
     * @param callable:调用对象的代理对象
     * @return
     * @throws Exception
     */
    @RuntimeType // 申明为 static
    public static Object intercept(@Origin Method method,
                                   @SuperCall Callable<?> callable) throws Exception {
        // 工夫统计开始
        long start = System.currentTimeMillis();
        // 执行原函数
        Object result = callable.call();
        // 执行工夫统计
        System.out.println(method.getName() + ": 执行耗时" + (System.currentTimeMillis() - start) + "ms");
        return result;
    }
}

3、从新打包 agent-demo,而后再次测试运行agent-test 中的主类 Application

试成果如下:

 premain 执行了
main 函数 运行了 
di di di ------------
didi: 执行耗时 5009ms
da da da ------------
dada: 执行耗时 6002ms

本文由传智教育博学谷 – 狂野架构师教研团队公布,转载请注明出处!

如果本文对您有帮忙,欢送关注和点赞;如果您有任何倡议也可留言评论或私信,您的反对是我保持创作的能源

退出移动版