Java调用链跟踪关键技术(二)Javaagent

2,925 阅读3分钟

banner窄.png

铿然架构  |  作者  /  铿然一叶 这是铿然架构的第 4 篇原创文章

相关阅读:

萌新快速成长之路
如何编写软件设计文档
JAVA编程思想(一)通过依赖注入增加扩展性
JAVA编程思想(二)如何面向接口编程
JAVA编程思想(三)去掉别扭的if,自注册策略模式优雅满足开闭原则
JAVA编程思想(四)Builder模式经典范式以及和工厂模式如何选?
Java编程思想(七)使用组合和继承的场景
JAVA基础(一)简单、透彻理解内部类和静态内部类
JAVA基础(二)内存优化-使用Java引用做缓存
JAVA基础(三)ClassLoader实现热加载
JAVA基础(四)枚举(enum)和常量定义,工厂类使用对比
JAVA基础(五)函数式接口-复用,解耦之利刃
Seata源码(一)初始化
Seata源码(二)事务基础对象
Seata源码(三)事务处理类结构和流程
Seata源码(四)全局锁GlobalLock
Seata源码(五)Seata数据库操作
Seata源码(六)Seata的undo日志操作
Seata源码(七)Seata事务故障处理
Seata源码(八)Seata事务生命周期hook
Seata源码(九)TCC核心类和处理逻辑
Seata源码(十)RM接收到请求后的调用过程
Seata源码(十一)TC接收到请求后的处理过程\


一、Javaagent

网上关于Javaagent的介绍很多,请找度娘和谷兄。唯一提的一点是字节码注入比较好用的是bytebuddy,封装度很高,使用简单。

二、代码样例

以下为关键代码样例,可以依样画瓢自行改造。
1.编写agent入口

package com.javashizhan.trace;

import static net.bytebuddy.matcher.ElementMatchers.isInterface;
import static net.bytebuddy.matcher.ElementMatchers.isSetter;
import static net.bytebuddy.matcher.ElementMatchers.nameContainsIgnoreCase;
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWithIgnoreCase;
import static net.bytebuddy.matcher.ElementMatchers.not;

import java.lang.instrument.Instrumentation;

import com.javashizhan.trace.interceptor.AbstractJunction;
import com.javashizhan.trace.interceptor.ProtectiveShieldMatcher;
import com.javashizhan.trace.interceptor.TraceInterceptor;

import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.description.NamedElement;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;

public class TraceAgent {
	
	public static void premain(String arguments, Instrumentation instrumentation) { 
		new AgentBuilder.Default()
		.type(buildMatch())
		.transform((builder, type, classLoader, module) ->
		builder.method(ElementMatchers.any())
						.intercept(MethodDelegation.to(TraceInterceptor.class)) // 拦截器
		).installOn(instrumentation);
	}
	
	public static ElementMatcher<? super TypeDescription> buildMatch() {
		ElementMatcher.Junction judge = new AbstractJunction<NamedElement>() {
            @Override
            public boolean matches(NamedElement target) {
                return true;
            }
        };
        judge = judge.and(not(isInterface())).and(not(isSetter()))
        		.and(nameStartsWithIgnoreCase("io.spring"))
				.and(not(nameContainsIgnoreCase("util")))
        		.and(not(nameContainsIgnoreCase("interceptor")));
        judge = judge.and(not(isSetter()));
        return new ProtectiveShieldMatcher(judge);
	}

}

2.拦截器类TraceInterceptor.java

package com.javashizhan.trace.interceptor;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;

import com.javashizhan.trace.domain.CallMethod;
import com.javashizhan.trace.TraceWrapper;
import com.javashizhan.trace.collector.DBCollector;
import com.javashizhan.trace.domain.Trace;
import com.javashizhan.trace.domain.TraceRecord;

import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;

public class TraceInterceptor {
	
	@RuntimeType
    public static Object intercept(@Origin Method method,
        @SuperCall Callable<?> callable) throws Exception {
		
		before(method);
		
        try {
            return callable.call();
        } finally {
            after();
        }
    }
	
	public static void after() {
		Trace trace = TraceWrapper.getTrace(); //Trace类,可自行实现,不是关键
		
		if (null != trace) {
			if (trace.callMethodSize() > 0) {
				
				CallMethod callMethod = trace.pop();
				
				if (null != callMethod && callMethod.isTraceFlag()) {
					
					callMethod.calculateCostTime();
					trace.addTraceRecord(new TraceRecord(callMethod));
					
				}
				
				if (trace.callMethodSize() == 0) {
					List<TraceRecord> traceRecordList = trace.getAllTraceRecord();
					if (null != traceRecordList && traceRecordList.size() > 0) {
						DBCollector collector = new DBCollector(traceRecordList);
						new Thread(collector).start();
						TraceWrapper.destory();
					}
				}
			}
		}
	}
	
	private static void before(Method method) {
		Trace trace = TraceWrapper.getTrace();
		
		CallMethod callMethod = new CallMethod(method);
		if (isInnerClass(callMethod)) {    //spring中有很多内部类,可以去掉
			callMethod.setTraceFlag(false);
		} else {
			callMethod.setTraceFlag(true);
		}
		
		//不管是否跟踪都放进去
		trace.push(callMethod);
	}
	
	private static boolean isInnerClass(CallMethod callMethod) {
		 return callMethod.getClassName().indexOf('$') > -1;
	}
	
	
}

3.AbstractJunction.java

package com.javashizhan.trace.interceptor;

import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatcher.Junction;
import net.bytebuddy.matcher.ElementMatcher.Junction.Conjunction;
import net.bytebuddy.matcher.ElementMatcher.Junction.Disjunction;

public abstract class AbstractJunction<V> implements ElementMatcher.Junction<V> {
    @Override
    public <U extends V> Junction<U> and(ElementMatcher<? super U> other) {
        return new Conjunction<U>(this, other);
    }

    @Override
    public <U extends V> Junction<U> or(ElementMatcher<? super U> other) {
        return new Disjunction<U>(this, other);
    }
}

4.ProtectiveShieldMatcher.java

package com.javashizhan.trace.interceptor;

import net.bytebuddy.matcher.ElementMatcher;

public class ProtectiveShieldMatcher<T> extends ElementMatcher.Junction.AbstractBase<T> {

    private final ElementMatcher<? super T> matcher;

    public ProtectiveShieldMatcher(ElementMatcher<? super T> matcher) {
        this.matcher = matcher;
    }

    public boolean matches(T target) {
        try {
            return this.matcher.matches(target);
        } catch (Throwable t) {
            //logger.warn(t, "Byte-buddy occurs exception when match type.");
            return false;
        }
    }
}

三、pom文件

<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>trace</groupId>
  <artifactId>chain</artifactId>
  <version>0.0.1-SNAPSHOT</version>
	
  <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <java.version>1.8</java.version>
		<!-- <spring-cloud.version>Finchley.SR1</spring-cloud.version>  -->
    </properties>

    <dependencies>
        <dependency>
            <groupId>net.bytebuddy</groupId>
            <artifactId>byte-buddy</artifactId>
            <version>1.9.6</version>
        </dependency>
        <dependency>
            <groupId>net.bytebuddy</groupId>
            <artifactId>byte-buddy-agent</artifactId>
            <version>1.9.6</version>
        </dependency>
        <dependency>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
            <version>2.7.9</version>
        </dependency>
        <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>8.0.16</version>
        </dependency>
		
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.0.0</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer
                                  implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <manifestEntries>
                                        <!--  <Premain-Class>com.undergrowth.secure.SecurityAgent</Premain-Class> -->
                                       <!-- <Premain-Class>com.undergrowth.agent.AgentToString</Premain-Class>-->
                                        <Premain-Class>com.javashizhan.trace.TraceAgent</Premain-Class>
                                    </manifestEntries>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
        <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
            <plugins>
                <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
                <plugin>
                    <artifactId>maven-clean-plugin</artifactId>
                    <version>3.1.0</version>
                </plugin>
                <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
                <plugin>
                    <artifactId>maven-resources-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.0</version>
                </plugin>
                <plugin>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <version>2.22.1</version>
                </plugin>
                <plugin>
                    <artifactId>maven-jar-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-install-plugin</artifactId>
                    <version>2.5.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-deploy-plugin</artifactId>
                    <version>2.8.2</version>
                </plugin>
                <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
                <plugin>
                    <artifactId>maven-site-plugin</artifactId>
                    <version>3.7.1</version>
                </plugin>
                <plugin>
                    <artifactId>maven-project-info-reports-plugin</artifactId>
                    <version>3.0.0</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

四、在Java应用中添加启动参数

1.先将agent工程打成jar包
2.在要使用agent的Java应用中添加如下VM启动参数

-javaagent:D:\MyApp\apache-skywalking-apm-bin\agent\chain-0.0.1-SNAPSHOT.jar

注意自行替换jar包路径。

end.