




























SkyWalking 提供了一种简便的方式来清晰地观测分布式系统,甚至横跨多个云平台。SkyWalking 更是一个现代化的应用程序性能监控(Application Performance Monitoring)系统,尤其专为云原生、基于容器的分布式系统设计。
概念讲解:
服务(service):表示对请求提供相同行为的一组工作负载。在使用打点代理或 SDK 的时候,你可以定义服务的名字。SkyWalking 还可以使用在 Istio 等平台中定义的名称。
服务实例(Service Instance):上述的一组工作负载中的每一个工作负载称为一个实例。就像 Kubernetes 中的 pods 一样,服务实例未必就是操作系统上的一个进程。 但当你在使用打点代理的时候,一个服务实例实际就是操作系统上的一个真实进程。
在 SkyWalking中,探针表示集成到目标系统中的代理或SDK库,它负责收集遥测数据,包括链路追踪和性能指标。根据目标系统的技术栈,探针可能有差异巨大的方式来达到以上功能。但从根本上来说都是一样的,即收集并格式化数据,并发送到后端。
对于最终用户来说他们不需要修改代码(至少在绝大多数情况下),只是被代理给修改了,这种做法通常叫做"在运行时操作代码"。底层原理就是自动打点代理利用了虚拟机提供的用于修改代码的接口来动态加入打点的代码,如通过 javaagent premain 来修改 Java 类。
服务网格通常用于描述组成此类应用程序的微服务网络以及它们之间的交互。随着服务网格的大小和复杂性的增长,它会变得更难理解和管理。它需要包括发现、负载平衡、故障恢复、度量和监视以及更复杂的操作需求A/B测试、金丝雀发布、限流、访问控制和端到端身份验证。
服务网格探针可以选择从 控制平面 和 数据平面 采集数据。在 Istio 中,指的是从 Mixer(Control Panel) 或者 Envoy sidecar(Data Panel) 中采集遥测数据。探针从客户端和服务器端收集每个请求的两个遥测实体,它们其实是相同的数据。
服务网格探针从每个请求收集遥测数据,因此它知道源、目标、端点、延迟和状态。通过这些,后端可以通过将这些调用合并为行来描述整个拓扑图,以及每个节点通过传入请求的度量。后端解析跟踪数据,请求相同的度量数据。因此,正确的表述是:
现在系统基本都是微服务架构,对于复杂微服务链路调用如下问题如何解决?
为了解决分布式应用、微服务系统面临的这些挑战,APM系统(Application Performance Management,即应用性能管理,简单来说就是应用监控)为之诞生,核心满足微服务系统监控的三要素如下:
早在在 2010 年 4 月谷歌发表了一篇论文《Dapper, a Large-Scale Distributed Systems TracingInfrastructure》阐述分布式追踪的概念,OpenTracing用于分布式跟踪和上下文传播的一致的、表达的、提供了一个标准的与供应商无关的api框架,这意味着如果开发者想要尝试一种不同的分布式追踪系统,开发者只需要简单地修改Tracer配置即可,而不需要替换整个分布式追踪系统;OpenTracing API目前也支持众多语言。了解OpenTracing API可以有利于更好学习本篇的主角SkyWalking。
目前市面上开源的APM系统主要有CAT、Zipkin、Pinpoint,大都是参考Google的Dapper实现的
Apache SkyWalking 官网地址 https://skywalking.apache.org/ 最新版本9.1.0
Apache SkyWalking 文档地址 https://skywalking.apache.org/docs/
Apache SkyWalking v9.1.0文档地址 https://skywalking.apache.org/docs/main/v9.1.0/readme/
Apache SkyWalking GitHub源码地址 https://github.com/apache/skywalking
Apache SkyWalking用于分布式系统的应用程序性能监控工具,特别为微服务、云本地和基于容器(Kubernetes)架构设计。Service Mesh和FaaS已就绪,内置服务网格和FaaS可观察性,收集和分析Istio + Envoy Service Mesh和OpenFunction作为FaaS平台的数据。
SkyWalking基本可以满足对于分布式系统APM的所有需要的功能,功能非常强大、性能表现优秀、对业务代码无侵入, 增长势头强劲,社区活跃,中文文档齐全,支持多语言探针, SkyWalking 支持Dubbo、gRPC、SOFARPC 等很多框架,包含了云原生架构下的分布式系统的监控、跟踪、诊断、日志记录功能,可以在浏览器上观察分布式系统应用程序发生的一切。
核心功能
特点
SkyWalking是一个开源的可观测平台的APM系统,用于收集、分析、聚合和可视化来自服务和云原生基础设施的数据。SkyWalking提供了一种简单的方式来维护分布式系统的清晰视图,甚至跨云。它是一个现代化的APM,专门为本地云、基于容器的分布式系统设计。
SkyWalking涵盖了所有3个可观察性领域,包括跟踪、指标和日志:
使用skywalk,用户可以了解服务和端点之间的拓扑关系,查看每个服务/服务实例/端点的指标,设置告警规则。SkyWalking逻辑上分为四个部分:探针、平台后端、存储和UI。


# 官网下载最新版本9.1.0
wget https://dlcdn.apache.org/skywalking/9.1.0/apache-skywalking-apm-9.1.0.tar.gz
# 接下文件
tar -xvf apache-skywalking-apm-9.1.0.tar.gz
# 进入目录
cd apache-skywalking-apm-bin/
持久化存储我们选择ES,由于前面有两篇文章都讲过ES部署,一篇使用docker部署,一篇使用二进制文件部署,有兴趣可以前往去看。这里我们就直接使用
SkyWalk部署很简单,部署方式有很多,官方提供二进制、Docker、K8S的部署指引,Docker和K8s部署详细可以查阅官网
# oap-server
docker run --name oap --restart always -d -e SW_STORAGE=elasticsearch -e SW_STORAGE_ES_CLUSTER_NODES=elasticsearch:9200 apache/skywalking-oap-server:9.1.0
# UI
docker run --name oap --restart always -d -e SW_OAP_ADDRESS=http://oap:12800 apache/skywalking-ui:9.1.0
学习则选择二进制部署方式,默认配置可以直接运行,但使用的存储是H2内存数据库,重启后数据丢失,修改oap-server配置文件中存储模式即可, vi config/application.yml

SkyWalk的webapp也即是UI默认的8080端口,这里就不修改

# 启动
sh ./bin/startup.sh
# 启动后skywalking-oap-server会先判断是否有相应的表,没有则创建,创建的表也比较多,需要稍等一会,下面日志已经使用elasticsearch做持久化存储
2022-07-23 10:46:17,966 - org.apache.skywalking.oap.server.starter.config.ApplicationConfigLoader - 118 [main] INFO [] - Provider=elasticsearch config=clusterNodes has been set as 192.168.5.52:9200
访问http://192.168.5.52:8080/ ,出现UI界面,当然9.x比8.x多出很多功能,包括服务网格、函数,可视化能力也是越来越强

# 选择目前最新版本8.11.0
wget https://dlcdn.apache.org/skywalking/java-agent/8.11.0/apache-skywalking-java-agent-8.11.0.tgz
# 解压后skywalk-agent.jar在根目录下,agent.config在config目录下

Agent包目录如下:

使用前面的库存微服务和订单微服务模块,将agent.config 拷贝到两个项目的resources目录下
库存微服务修改agent.config下面两个配置
agent.service_name=${SW_AGENT_NAME:ecom-storage-service}
# Backend service addresses.
collector.backend_service=${SW_AGENT_COLLECTOR_BACKEND_SERVICES:192.168.5.52:11800}
启动jvm参数增加
-javaagent:F:\commoms\skywalking-agent\skywalking-agent.jar
-Dskywalking_config=F:\dev\simple-ecommerce\ecom-storage-service\src\main\resources\agent.config

订单微服务修改agent.config下面两个配置
agent.service_name=${SW_AGENT_NAME:ecom-order-service}
# Backend service addresses.
collector.backend_service=${SW_AGENT_COLLECTOR_BACKEND_SERVICES:192.168.5.52:11800}
启动jvm参数增加
-javaagent:F:\commoms\skywalking-agent\skywalking-agent.jar
-Dskywalking_config=F:\dev\simple-ecommerce\ecom-order-service\src\main\resources\agent.config
启动库存微服务和订单微服务,启动日志中加载agent.config和使用skywalk-agent.jar

访问订单接口,http://localhost:4070/order/create/1000/1001/2 ,访问库存接口http://localhost:4080/list ,在普通服务的Service 页面中查看到两个微服务
在Topology查看微服务之间拓扑关系,可以设置查询的深度

在跟Trace查看跟踪列表信息,选择查询条件,可以切换树结构、表格、统计

点进列表的记录后还可以查看到详细信息,包括使用那个组件都有,但识别不一定很准确

普通服务的Service 页面选择服务如订单服务,显示订单服务的各项概览信息,可切换到实例、断点、拓扑、追踪、性能分析、日志等功能页面
如果想要对项目里的业务方法实现链路追踪,方便排查问题,做法如下,先引入依赖
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-trace</artifactId>
<version>8.11.0</version>
</dependency>
然后在业务方法加上@Trace+@Tags注解,如果只需要一个Tag也可以直接@Trace+@Tag
@Override
@GlobalTransactional
@Trace
@Tags({@Tag(key = "order",value = "returnedObj"),
@Tag(key = "param1", value = "arg[0]"),
@Tag(key = "param2", value = "arg[1]")})
public Order create(String userId, String commodityCode, int orderCount) {
String xid = RootContext.getXID();
log.info("order xid:{}",xid);
storageFeignService.deduct(commodityCode,orderCount);

重新启动库存和订单微服务,再次访问订单接口,http://localhost:4070/order/create/1000/1001/2

选择服务后再选择相应的功能页面

点击查询详细,这时候就可以查看业务方法的参数值和返回值信息

在系统性能监控方法上,Skywalking 提出了代码级性能剖析这种在线诊断方法。这种方法基于一个高级语言编程模型共性,即使再复杂的系统,再复杂的业务逻辑,都是基于线程去进行执行的,而且多数逻辑是在单个线程状态下执行的;代码级性能剖析就是利用方法栈快照,并对方法执行情况进行分析和汇总;并结合有限的分布式追踪 span 上下文,对代码执行速度进行估算。有如下优势:
SkyWalking的跟踪或者说性能剖析,选择某个服务

根据选择端点的名称及相应的规则建立任务,后续再调用任务列表的端口会自动记录剖析剖析当前端口数据并生成剖析结果

为了更好演示在库存微服务的创建订单方法中增加一个睡眠3秒,然后重新启动订单微服务

再次多次访问创建订单接口 http://localhost:4070/order/create/1000/1001/2 ,需要连续执行多次请求,因为存在采样设置。如果执行次数少,可能不会出现采样数据,每个服务,相同时间只能添加一个任务,添加的任务不能更改,也不能删除,只能等待过期后自动删除。
在库存和订单微服务中引入依赖
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-logback-1.x</artifactId>
<version>8.11.0</version>
</dependency>
在库存和订单微服务中,增加分布式链路追踪ID在logback.xml加入如下配置,[%tid]
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%tid] [%thread] %-5level %logger{36} -%msg%n</Pattern>
</layout>
</encoder>
</appender>
gRPC reporter上报日志在logback.xml加入如下配置:
<appender name="grpc-log" class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.log.GRPCLogClientAppender">
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.mdc.TraceIdMDCPatternLogbackLayout">
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{tid}] [%thread] %-5level %logger{36} -%msg%n</Pattern>
</layout>
</encoder>
</appender>

访问订单接口 http://localhost:4070/order/create/1000/1001/2,查看订单和库存微服务的日志中已带有 TID

也通过GRPC上传到SkyWalking后端,通过Log页面可以查看日志信息

可以通过TID查询对应日志详细信息

在config/alarm-settings.yml ,已经默认若干项告警,我们简单修改告警信息内容,增加一串标识"Itxs Alarm"例如,配置webhooks
rules:
# Rule unique name, must be ended with `_rule`.
service_instance_resp_time_rule:
metrics-name: service_instance_resp_time
op: ">"
threshold: 1000
period: 10
count: 2
silence-period: 5
message: Itxs Alarm esponse time of service instance {name} is more than 1000ms in 2 minutes of last 10 minutes
endpoint_relation_resp_time_rule:
metrics-name: endpoint_relation_resp_time
threshold: 1000
op: ">"
period: 10
count: 2
message: Itxs Alarm esponse time of endpoint relation {name} is more than 1000ms in 2 minutes of last 10 minutes
webhooks:
- http://192.168.4.210:8080/alarm/
新建一个webhooks接口服务端,创建AlarmMessage实体类
package com.aotain.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AlarmMessage implements Serializable {
private String scopeId;
private String scope;
private String name;
private String id0;
private String id1;
private String ruleName;
创建一个控制器AlarmController,提供/alarm接口,这里简单就显示信息,后续可以根据实际调用微信、钉钉告警之类。
package com.aotain.controller;
import com.aotain.entity.AlarmMessage;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class AlarmController {
@PostMapping("/alarm")
public String alarm(@RequestBody List<AlarmMessage> alarmMessageList) throws Exception {
System.out.println(alarmMessageList);
return "ok";
}
}
这里使用ApiFox多线程访问http://localhost:4070/order/create/1000/1001/2

查看告警,已经为我们修改后告警信息,含有Itxs Alarm的前缀字符串

查看事件

查看webhooks调用接口,已经收到SkyWalking调用过来的数据,这里后续可扩展为实际的告警方式处理。


上面使用Skywalking并没有修改程序中任何一行 Java 代码,这里便是使用到了 Java Agent 技术,如果平常基于增删改查业务逻辑那就基本不会使用到Java Agent,但我们平时用过的不少工具如热部署工具JRebel,SpringBoot的热部署插件,各种线上诊断工具(btrace, greys),阿里开源的arthas都是基于java Agent来实现的。在JDK1.5以后就有java Agent,使用agent技术构建一个独立于应用程序的代理程序(即Agent),用来协助监测、运行甚至替换其他JVM上的程序。使用它可以实现虚拟机级别的AOP功能,典型的优势就是无代码侵入。Agent大体可分为两种:
premain为主程序之前运行的Agent,在实际使用过程中,javaagent是java命令的一个参数。通过java 命令启动我们的应用程序的时候,可通过参数 -javaagent 指定一个 jar 包(也就是我们的代理agent),能够实现在我们应用程序的主程序运行之前来执行我们指定jar包中的特定方法,在该方法中我们能够实现动态增强Class等相关功能,并且该 jar包有2个要求:
从字面上理解,Premain-Class 就是运行在 main 函数之前的的类。当Java 虚拟机启动时,在执行 main 函数之前,JVM 会先运行-javaagent所指定 jar 包内 Premain-Class 这个类的 premain 方法 。
-javaagent所在包java.lang.instrument,是rt.jar 中定义的一个包,有两个重要的类:

java.lang.instrument包提供了一些工具帮助开发人员在 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 签名的方法,加载成功忽略第二种,如果第一种没有,则加载第二种方法。
创建PreAgentDemo的maven项目,编写一个agent程序com.itxs.agent.PreAgentDemo,完成premain方法的签名,这里先做一个简单的日志输出。
package com.itxs.agent;
import java.lang.instrument.Instrumentation;
public class PreAgentDemo {
public static void premain(String agentArgs, Instrumentation instrumentation) {
System.out.println("PreAgentDemo run");
System.out.println("PreAgentDemo receive params agentArgs=" + agentArgs);
}
}
maven项目pom文件增加如下坐标
<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.itxs.agent.PreAgentDemo</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>
对PreAgentDemo项目进行打包,得到 PreAgentDemo-1.0.jar,放在G:\other下,查看jar包中的MANIFEST.MF文件

Can-Redefine-Classes :true表示能重定义此代理所需的类,默认值为 false(可选)
Can-Retransform-Classes :true 表示能重转换此代理所需的类,默认值为 false (可选)
Premain-Class :包含 premain 方法的类(类的全路径名)
接着创建一个test-demo项目,编写一个简单测试类App,运行JVM参数添加
-javaagent:G:\other\PreAgentDemo-1.0.jar=param1=value1,param2=value2,param3=value3

上运行结果可以看到在测试程序main函数启动前先输出premain方法打印的日志。实际开发中大部分类加载都会通过该方法。当然,遗漏的主要是系统类,因为很多系统类先于 agent 执行,而用户类的加载肯定是会被拦截的。也就是说,这个方法是在 main 方法启动前拦截大部分类的加载活动,既然可以拦截类的加载,那么就可以去做重写类这样的操作,结合第三方的字节码编译工具,比如ASM,bytebuddy,javassist,cglib等等来改写实现类。
Instrumentation 中的核心 API 方法:
agentmain,可以在 main 函数开始运行之后再运行。跟premain函数一样, 开发者可以编写一个含有agentmain函数的 Java 类。
public static void agentmain (String agentArgs, Instrumentation inst)
public static void agentmain (String agentArgs)
同样需要在MANIFEST.MF文件里面设置“Agent-Class”来指定包含 agentmain 函数的类的全路径。在前面工程基础上增加com.itxs.agent.AgentDemo文件,也是简单打印日志。
package com.itxs.agent;
import java.lang.instrument.Instrumentation;
public class AgentDemo {
public static void agentmain(String agentArgs, Instrumentation instrumentation) {
System.out.println("AgentDemo run");
}
}

在pom.xml中添加配置如下
<Agent-Class>com.itxs.agent.AgentDemo</Agent-Class>

重新打包 PreAgentDemo-1.0.jar并覆盖到G:\other下,在测试类App修改如下代码
package com.itxs;
import com.sun.tools.attach.*;
import java.io.IOException;
import java.util.List;
public class App
{
public static void main( String[] args ) throws IOException, AgentLoadException, AgentInitializationException, AttachNotSupportedException {
System.out.println( "itxs app main run!" );
list()方法会去寻找当前系统中所有运行着的JVM进程,你可以打印vmd.displayName()看到当前系统都有哪些JVM进程在运行。因为main函数执行起来的时候进程名为当前类名,所以通过这种方式可以去找到当前的进程id。在windows中安装的jdk无法找到,如遇到这种情况手动将你jdk安装目录下:lib目录中的tools.jar添加进当前工程的Libraries中。
agent要在主程序运行后加载,我们不可能在主程序中编写加载的代码,只能另写程序,那么另写程序如何与主程序进行通信?这里用到的机制就是attach机制,它可以将JVM A连接至JVM B,并发送指令给JVM B执行。
Byte Buddy是一个代码生成和操作库,用于在Java应用程序运行时创建和修改Java类,并且不需要编译器的帮助。与Java类库附带的代码生成实用程序不同,Byte Buddy允许创建任意类,并且不局限于为创建运行时代理实现接口。此外,Byte Buddy提供了一个方便的API,可以手动更改类,可以使用Java代理,也可以在构建期间更改类。
反射机制可以知道调用的方法或字段,但反射性能很差,反射能绕开类型安全检查,不安全,比如权限暴力破解;java编程语言代码生成库也有多种:
上面所有代码生成技术中推荐使用Byte Buddy,因为Byte Buddy代码生成可的性能最高;Byte Buddy 的主要侧重点在于生成更快速的代码,如下图

Class<?> dynamicType = new ByteBuddy()
// 生成 Object的子类
.subclass(Object.class)
// 生成类的名称
.name("com.itxs.type")
Byte Buddy 动态增强代码总有如下三种方式:
上面三种增强代码后得到的是 DynamicType.Unloaded 对象,表示的是一个未加载的类型,可以使用 ClassLoadingStrategy加载此类型;Byte Buddy 提供了几种类加载策略,这些加载策略定义在 ClassLoadingStrategy.Default 中:
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 会按照栈的顺序来进行拦截。
在test-demo项目中添加ByteBuddy的依赖
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.12.12</version>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.12.12</version>
<scope>test</scope>
</dependency>
创建普通类OrderService
package com.itxs.service;
public class OrderService {
public String addOrder(){
System.out.println("=====do addOrder==========");
return "1000000001";
}
public String getOrder(String orderId){
System.out.println("=====do getOrder==========");
return orderId;
}
public String getOrder(String orderId,String status){
System.out.println("=====do getOrder two params==========");
return orderId+status;
}
}
创建拦截器类TestInterceptor
package com.itxs.interceptor;
import net.bytebuddy.implementation.bind.annotation.*;
import java.lang.reflect.Method;
import java.util.concurrent.Callable;
public class TestInterceptor {
@RuntimeType
创建普通类代理测试类ByteBuddyTest
package com.itxs;
import com.itxs.interceptor.TestInterceptor;
import com.itxs.service.OrderService;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;
public class ByteBuddyTest {
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
Class<? extends OrderService> generateClass = new ByteBuddy()
在程序中用到ByteBuddy的MethodDelegation对象,它可以将拦截的目标方法委托给其他对象处理,注解使用说明如下:
运行ByteBuddyTest,增强的方法输出就是上面代码中方法匹配名称为getOrder且返回值为String且有两个入参的结果。

Java Agent十分强大,使用Transformer等高级功能进行类替换,方法修改等,要使用Instrumentation的相关API则需要对字节码等技术有较深的认识。接下来ByteBuddy结合Java Agent技术实现一个统计方法耗时的示例。
在上面的PreAgentDemo项目中加入依赖byte-buddy和byte-buddy-agent的依赖,上面测试工程Pom文件有
创建耗时统计拦截器类
package com.itxs.agent.interceptor;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
import java.lang.reflect.Method;
import java.util.concurrent.Callable;
public class TimeConsumingInterceptor {
创建JavaAgentCase的premain实现
package com.itxs.agent;
import com.itxs.agent.interceptor.TimeConsumingInterceptor;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;
import java.lang.instrument.Instrumentation;
public class JavaAgentCase {

重新打包好PreAgentDemo-1.0.jar,准备测试类UserService.java
package com.itxs.service;
import java.util.Random;
import java.util.concurrent.TimeUnit;
public class UserService {
private static Random random = new Random();
public void getUser(){
System.out.println("=====do getUser==========");
try {
TimeUnit.SECONDS.sleep(random.nextInt(5));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void updateUser(){
System.out.println("=====do updateUser==========");
try {
TimeUnit.SECONDS.sleep(random.nextInt(5));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
创建启动测试类
package com.itxs;
import com.itxs.service.UserService;
public class Application
{
public static void main( String[] args ) {
System.out.println("Application main start run-----------");
UserService service = new UserService();
service.getUser();
service.updateUser();
}
}
启动参数中jvm参数添加javaagent,可参考上面示例,执行Application的main后从日志可以看到UserService的方法被增强了

此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。