Java Agent, the Key Technology of Java Call Chain Tracking

I. Java agent

There are many introductions about Java agent on the internet. Please find Du Niang and Gu Brother. The only point to mention is that byte code injection is more useful than byte buddy, which is highly encapsulated and easy to use.

Code Samples

The following is a sample of key code that can be adapted by itself.
1. Writing agent Entry

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)) // Interceptor
        ).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. Interceptor class 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 class, self-implemented, is not the key
        
        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)) {    //There are many internal classes in spring that can be removed
            callMethod.setTraceFlag(false);
        } else {
            callMethod.setTraceFlag(true);
        }
        
        //Put it in, whether it's tracked or not.
        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;
        }
    }
}

II. pom Files

<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>

3. Adding Startup Parameters to Java Applications

1. Firstly, the agent project is packaged into jar package.
2. Add the following VM startup parameters to the Java application using agent

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

Note replacing the jar package path by yourself.

end.

Join the "Java Stack Camp" knowledge planet, participate in discussions, more real code sharing, not just a few kilograms of apples, a few glorious props!


https://t.zsxq.com/RNzfi2j

Tags: Java Maven Apache Spring

Posted on Thu, 10 Oct 2019 10:02:34 -0700 by jwer78