后端 Java Java-JavaAgent cmyang 2022-10-28 2023-05-14 Skywalking实现 如果需要我们对每个方法的执行添加打印执行时间,一般可以通过AOP的方式来实现,对每个方法做环绕通知的切面,这种方式有一个问题就是,如果后续还有一些其他需求需要在方法执行前做统计,比如打印HTTPRequest中的请求地址,请求参数等信息,就需要重新编写aop,编译部署上线。
skywalking使用0侵入的方式来扩展Java程序的功能,具体实现就是使用了Java插桩技术。
字节码插桩 字节码插桩实际就是在代码运行期间对字节码进行修改,比如动态代理,或者java Agent
skywalking就是通过javaAgent实现的字节码增强,启动方式:
启动jar包时,需要添加额外的运行参数
1 -javaagent:D:\tool\apache-skywalking-apm-bin-es7\agent\skywalking-agent.jar -Dskywalking.agent.service_name=skywalking-learn -Dskywalking.collector.backend_service=192.168 .80 .128 :11800
skywalking-agent.jar就是修改字节码的具体实现的包
JavaAgent jdk1.5引入的新特性,在加载class前进行拦截,我们可以对拦截的class做一些修改操作,然后再加载这个class。
agent的启动方式
静态启动
使用-javaagent的方式,在应用启动时挂载agent
主方法入口:premain()
对字节码操作自由度高,只要符合字节码规范,JVM可以验证通过,可以对字节码做任何修改
SkyWalking
动态附加
在应用运行过程中通过attach API挂载Agent
主方法入口:agentmain()
对字节码操作自由度低,不能创建父类,接口,字段…
Arthas
javaAgent探针包要求在META-INF/MANIFEST.MF文件中,要有一个包含premain方法的入口类
public static void premain(String agentArgs, Instrumentation inst)
agentArgs:挂载agent是设置的参数
Instrumentation :可以获取一些类信息
addTransformer()/removeTransformer() :注册/注销一个 ClassFileTransformer 类的实例,该 Transformer 会在类加载的时候被调用,可用于修改类定义(修改类的字节码)。
redefineClasses() :重新定义传入已经加载的类。
getAllLoadedClasses():返回 JVM 已加载的所有类。
getInitiatedClasses() :返回 JVM 已经初始化的类。
getObjectSize():获取参数指定的对象的大小。
字节码操作工具 JavaAgent就是一个插桩入口,主要是做代理拦截的,真正做字节码修改的,是有一套操作字节码的工具,目前主要有:
asm,javassist,bytebuddy
asm
支持任意字节码插入,功能强大,比较偏底层,学习难度大
javassist
java原始语法,支持jdk1.5的语法,后面的一些其他语法比如泛型等不支持
bytebuddy
基于asm实现,通过一些API操作class,支持java任意版本,性能比javassist高
skywalking就是使用bytebuddy来实现的修改字节码
bytebuddy 定义一个最简单的探针包主要分为如下几个步骤
创建一个探针maven项目
编写探针入口类:public static void premain(String agentArgs, Instrumentation inst)
编写拦截配置,需要拦截哪些类,可以根据全类名前缀,后缀,标记的注解等等
编写拦截后的处理类
处理类的方法参数中可以获取当前执行类的method,参数,调用类,父类对象等等
在方法中编写需要增强的代码
使用maven插件,生成MANIFEST.MF文件,打包
DEMO实例:
(1)引入maven依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <groupId > cn.aacopy.learn</groupId > <artifactId > agent-learn</artifactId > <version > 1.0-SNAPSHOT</version > <properties > <maven.compiler.source > 8</maven.compiler.source > <maven.compiler.target > 8</maven.compiler.target > </properties > <dependencies > <dependency > <groupId > net.bytebuddy</groupId > <artifactId > byte-buddy</artifactId > <version > 1.12.12</version > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-assembly-plugin</artifactId > <version > 3.3.0</version > <configuration > <descriptorRefs > <descriptorRef > jar-with-dependencies</descriptorRef > </descriptorRefs > <archive > <manifest > <addDefaultImplementationEntries > true</addDefaultImplementationEntries > <addDefaultSpecificationEntries > true</addDefaultSpecificationEntries > </manifest > <manifestEntries > <Premain-Class > cn.aacopy.learn.agent.AgentMainTest</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 > </project >
(2)创建探针入口premain方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package cn.aacopy.learn.agent;import net.bytebuddy.agent.builder.AgentBuilder;import net.bytebuddy.implementation.MethodDelegation;import net.bytebuddy.matcher.ElementMatchers;import java.lang.instrument.Instrumentation;public class AgentMainTest { public static void premain (String agentArgs, Instrumentation inst) { System.out.println("执行agent拦截,参数:" + agentArgs); AgentBuilder.Identified.Extendable agentBuilder = new AgentBuilder .Default() .type(ElementMatchers.nameStartsWith(agentArgs)) .transform((builder, typeDescription, classLoader, javaModule) -> builder.method(ElementMatchers.isPublic().and(ElementMatchers.nameEndsWith("Haha" ))) .intercept(MethodDelegation.to(TimeInterceptor.class))); agentBuilder.installOn(inst); } }
(3)编写一个拦截后的方法处理类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package cn.aacopy.learn.agent;import net.bytebuddy.implementation.bind.annotation.AllArguments;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.Arrays;import java.util.concurrent.Callable;public class TimeInterceptor { @RuntimeType public static Object intercept (@Origin Method method, @AllArguments Object[] args, @SuperCall Callable<?> callable) throws Exception { long l1 = System.currentTimeMillis(); Object result = callable.call(); System.out.println(method.getName() + ":执行耗时" + (System.currentTimeMillis() - l1) + "ms" ); System.out.println("参数列表:" +Arrays.toString(args)); System.out.println("返回类型:" +method.getReturnType()); System.out.println("返回值:" +result); return result; } }
(4)随便写一个应用程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package cn.aacopy.test.simpleboot.controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;@RestController public class TestController { @GetMapping("/test1") public String test1Haha () { System.out.println("hahaha" ); try { Thread.sleep(100 ); } catch (InterruptedException e) { e.printStackTrace(); } return "hahah" ; } @GetMapping("/test2") public String test2Haha (@RequestParam String name) { System.out.println("hahaha2sssssssss" ); try { Thread.sleep(300 ); } catch (InterruptedException e) { e.printStackTrace(); } return "hihei" + name; } @GetMapping("/test3") public String test3Haha1 () { System.out.println("hihei123123213" ); try { Thread.sleep(300 ); } catch (InterruptedException e) { e.printStackTrace(); } return "hihei1232312322" ; } }
(5)修改启动jvm配置:
1 -javaagent:D:\code\mine\agent-learn\target\agent-learn-1.0 -SNAPSHOT-jar-with-dependencies.jar=cn.aacopy.test.simpleboot.controller
(6)启动服务,调用接口
启动服务前会打印日志
1 执行agent拦截,参数:cn.aacopy.test.simpleboot.controller
调用接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 hihei123123213 hahaha test1Haha:执行耗时100ms 参数列表:[] 返回类型:class java.lang.String 返回值:hahah hahaha2sssssssss test2Haha:执行耗时306ms 参数列表:[wwwww] 返回类型:class java.lang.String 返回值:hiheiwwwww
Skywalking的插桩实现,静态方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 public abstract class ClassEnhancePluginDefine extends AbstractClassEnhancePluginDefine { protected DynamicType.Builder<?> enhanceClass(TypeDescription typeDescription, DynamicType.Builder<?> newClassBuilder, ClassLoader classLoader) throws PluginException { StaticMethodsInterceptPoint[] staticMethodsInterceptPoints = getStaticMethodsInterceptPoints(); String enhanceOriginClassName = typeDescription.getTypeName(); if (staticMethodsInterceptPoints == null || staticMethodsInterceptPoints.length == 0 ) { return newClassBuilder; } for (StaticMethodsInterceptPoint staticMethodsInterceptPoint : staticMethodsInterceptPoints) { String interceptor = staticMethodsInterceptPoint.getMethodsInterceptor(); if (StringUtil.isEmpty(interceptor)) { throw new EnhanceException ("no StaticMethodsAroundInterceptor define to enhance class " + enhanceOriginClassName); } if (staticMethodsInterceptPoint.isOverrideArgs()) { if (isBootstrapInstrumentation()) { newClassBuilder = newClassBuilder.method(isStatic().and(staticMethodsInterceptPoint.getMethodsMatcher())) .intercept(MethodDelegation.withDefaultConfiguration() .withBinders(Morph.Binder.install(OverrideCallable.class)) .to(BootstrapInstrumentBoost.forInternalDelegateClass(interceptor))); } else { newClassBuilder = newClassBuilder.method(isStatic().and(staticMethodsInterceptPoint.getMethodsMatcher())) .intercept(MethodDelegation.withDefaultConfiguration() .withBinders(Morph.Binder.install(OverrideCallable.class)) .to(new StaticMethodsInterWithOverrideArgs (interceptor))); } } else { if (isBootstrapInstrumentation()) { newClassBuilder = newClassBuilder.method(isStatic().and(staticMethodsInterceptPoint.getMethodsMatcher())) .intercept(MethodDelegation.withDefaultConfiguration() .to(BootstrapInstrumentBoost.forInternalDelegateClass(interceptor))); } else { newClassBuilder = newClassBuilder.method(isStatic().and(staticMethodsInterceptPoint.getMethodsMatcher())) .intercept(MethodDelegation.withDefaultConfiguration() .to(new StaticMethodsInter (interceptor))); } } } return newClassBuilder; }
(增强类)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 public class StaticMethodsInter { private static final ILog LOGGER = LogManager.getLogger(StaticMethodsInter.class); private String staticMethodsAroundInterceptorClassName; public StaticMethodsInter (String staticMethodsAroundInterceptorClassName) { this .staticMethodsAroundInterceptorClassName = staticMethodsAroundInterceptorClassName; } @RuntimeType public Object intercept (@Origin Class<?> clazz, @AllArguments Object[] allArguments, @Origin Method method, @SuperCall Callable<?> zuper) throws Throwable { StaticMethodsAroundInterceptor interceptor = InterceptorInstanceLoader.load(staticMethodsAroundInterceptorClassName, clazz .getClassLoader()); MethodInterceptResult result = new MethodInterceptResult (); try { interceptor.beforeMethod(clazz, method, allArguments, method.getParameterTypes(), result); } catch (Throwable t) { LOGGER.error(t, "class[{}] before static method[{}] intercept failure" , clazz, method.getName()); } Object ret = null ; try { if (!result.isContinue()) { ret = result._ret(); } else { ret = zuper.call(); } } catch (Throwable t) { try { interceptor.handleMethodException(clazz, method, allArguments, method.getParameterTypes(), t); } catch (Throwable t2) { LOGGER.error(t2, "class[{}] handle static method[{}] exception failure" , clazz, method.getName(), t2.getMessage()); } throw t; } finally { try { ret = interceptor.afterMethod(clazz, method, allArguments, method.getParameterTypes(), ret); } catch (Throwable t) { LOGGER.error(t, "class[{}] after static method[{}] intercept failure:{}" , clazz, method.getName(), t.getMessage()); } } return ret; } }