欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java Agent - Java的黑科技

程序员文章站 2022-06-09 23:11:09
...

Java Agent - Java的黑科技

文末欢迎扫码关注微信公众号!

今天给大家分享一个Java方面的黑科技:Java Agent技术!使用方式如下:

1、编写一个Agent类,键入如下代码:

package com.lazy.agent;


import java.lang.instrument.Instrumentation;


public class MyAgent {


  /**
   * 执行main方法前,会执行该签名方法,这是由-javaagent参数决定
   * @param arguments
   * @param instrumentation
   */
  public static void premain(String arguments, Instrumentation instrumentation) {


    System.out.println("hello java agent");
  }
}

2、pom.xml配置如下:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>


  <groupId>com.lazy.agent</groupId>
  <artifactId>lazy-agent-core</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>lazy-agent-core</name>


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


  <dependencies>
    <!--引入JDK8 tools。jar文件 -->
    <dependency>
      <groupId>com.sun</groupId>
      <artifactId>tools</artifactId>
      <version>1.8.0</version>
      <scope>system</scope>
      <systemPath>${JAVA_HOME}/lib/tools.jar</systemPath>
    </dependency>
    <dependency>
      <groupId>net.bytebuddy</groupId>
      <artifactId>byte-buddy-agent</artifactId>
      <version>1.10.1</version>
    </dependency>
    <dependency>
      <groupId>net.bytebuddy</groupId>
      <artifactId>byte-buddy</artifactId>
      <version>1.10.1</version>
    </dependency>
  </dependencies>


  <build>
    <finalName>lazy-agent-core</finalName>
    <pluginManagement>
      <plugins>
        <plugin>
          <artifactId>maven-jar-plugin</artifactId>
          <version>3.0.2</version>
          <configuration>
            <archive>
              <manifest>
                <!--配置jar包内创建MANIFEST。MF文件 -->
                <addClasspath>true</addClasspath>
              </manifest>
              <manifestEntries>
                <!--在MANIFEST。MF文件配置Preamin-Class:com.lazy.agent.MyAgent -->
                <Premain-Class>com.lazy.agent.MyAgent</Premain-Class>
              </manifestEntries>
            </archive>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

3、在需要使用的项目上配置如下VM参数:

-javaagent:xxxxxxx\target\lazy-agent-core.jar

4、启动agent目标项目(其它业务项目,可以自行编写一个demo项目),然后目标项目在运行main方法前会打印如下截图内容:

Java Agent - Java的黑科技

前面我们只是打印一句话,我们还可以做些什么呢?继续看后面的示例。

1、在agent的项目继续编写下面的类

public class Transformer implements ClassFileTransformer {




    public byte[] transform(ClassLoader loader, String className, Class<?> c,
                            ProtectionDomain pd, byte[] bytes) throws IllegalClassFormatException {




      //这里可以做很多事情,比如解密class文件,替换class文件,或者结合字节码框架对类进行修改
      System.out.println(className);


      return null;
    }
}

2、修改MyAgent类,修改后的代码如下:

package com.lazy.agent;


import java.lang.instrument.Instrumentation;


public class MyAgent {


  /**
   * 执行main方法前,会执行该参数
   * 
   * @param arguments
   * @param instrumentation
   */
  public static void premain(String arguments, Instrumentation instrumentation) {


    System.out.println("hello java agent");


    instrumentation.addTransformer(new Transformer());
  }


}

3、重新打包构建lazy-agent-core.jar,让启动agent目标项目,可以看到如下信息:

Java Agent - Java的黑科技

一句话总结JavaAgent黑科技的力量:

我们可以借助javaagent技术,在启动main方法前面修改class字节码信息。

比如微服务的链路追踪框架SkyWalking便是使用Java Agent技术,结合Byte Buddy字节码框架来织入OpenTracing(开放式分布式追踪)规范逻辑的。

下面我们通过Java Agent集成Byte Buddy的方式来实现拦截指定包任何方法执行耗时日志记录的功能。

1、在agent项目(lazy-agent-core.jar所在的项目)引入Byte Buddy Mave依赖:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>


  <groupId>com.lazy.agent</groupId>
  <artifactId>lazy-agent-core</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>lazy-agent-core</name>


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


  <dependencies>
    <!--引入JDK8 tools。jar文件 -->
    <dependency>
      <groupId>com.sun</groupId>
      <artifactId>tools</artifactId>
      <version>1.8.0</version>
      <scope>system</scope>
      <systemPath>${JAVA_HOME}/lib/tools.jar</systemPath>
    </dependency>
    <!--引入byte buddy依赖 -->
    <dependency>
      <groupId>net.bytebuddy</groupId>
      <artifactId>byte-buddy-agent</artifactId>
      <version>1.10.1</version>
    </dependency>
    <dependency>
      <groupId>net.bytebuddy</groupId>
      <artifactId>byte-buddy</artifactId>
      <version>1.10.1</version>
    </dependency>
  </dependencies>


  <build>
    <finalName>lazy-agent-core</finalName>
    <pluginManagement>
      <plugins>
        <plugin>
          <artifactId>maven-jar-plugin</artifactId>
          <version>3.0.2</version>
          <configuration>
            <archive>
              <manifest>
                <!--配置jar包内创建MANIFEST。MF文件 -->
                <addClasspath>true</addClasspath>
              </manifest>
              <manifestEntries>
                <!--在MANIFEST。MF文件配置Preamin-Class:com.lazy.agent.MyAgent -->
                <Premain-Class>com.lazy.agent.TimeAgent</Premain-Class>
              </manifestEntries>
            </archive>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

注意<Premain-Class>com.lazy.agent.TimeAgent</Premain-Class>内容的修改。

2、继续编写TimeAgent类,代码如下:

package com.lazy.agent;


import java.lang.instrument.Instrumentation;


import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;


public class TimeAgent {


  public static void premain(String arguments, Instrumentation instrumentation) {


    System.out.println("当前配置参数信息为:" + arguments);


    if (arguments == null) {
      System.out.println("请正确配置包前缀和日志文件全路径参数,配置示例:-javaagent:xxxx.jar=com.xxx,d:\time.log");
    }


    String[] toArray = arguments.split(",");


    if (toArray.length != 2) {
      System.out.println("请正确配置包前缀和日志文件全路径参数,配置示例:-javaagent:xxxx.jar=com.xxx,d:\time.log");
    }


    TimeInterceptor.LOG_FILE = toArray[1];
 
    // 这里是关键,表示配置byte buddy agent的方式来拦截类方法
    new AgentBuilder.Default()


        // 表示拦截传入的第一个参数指定的包前缀类
        .type(ElementMatchers.nameStartsWith(toArray[0]))


        .transform((builder, type, classLoader, module) -> builder


            //表示拦截对应的任何方法,然后交给自定义的TimeInterceptor类去处理
            .method(ElementMatchers.any()).intercept(MethodDelegation.to(TimeInterceptor.class)))
        .installOn(instrumentation);
  }


}


3、TimeInterceptor类代码如下:

package com.lazy.agent;


import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.concurrent.Callable;


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


public class TimeInterceptor {


  public static String LOG_FILE = "";


  @RuntimeType
  public static Object intercept(@Origin Method method, @This Object object, @SuperCall Callable<?> callable)
      throws Exception {


    long start = System.currentTimeMillis();
    try {


      // 调用拦截的目标方法
      return callable.call();
    } finally {


      long took = System.currentTimeMillis() - start;
      String content = object.getClass().getName() + " " + method.getName() + " 耗时  " + took + " ms"
          + System.getProperty("line.separator");
      FileWriter writer = null;
      try {
        // 写入传入第二个参数指定的文件
        writer = new FileWriter(LOG_FILE, true);
        writer.write(content);
      } catch (IOException e) {
        e.printStackTrace();
      } finally {
        try {
          if (writer != null) {
            writer.close();
          }
        } catch (IOException e) {
          e.printStackTrace();
        }


      }
    }
  }
}


4、重新打包构建lazy-agent-core.jar包,然后修改agent目标项目VM参数如下:

-javaagent:E:\Git\Repository\lazy-opensource\lazy-develop-platform\lazy-agent\lazy-agent-core\target\lazy-agent-core.jar=com.lazy,d:\time.log

注意后面的=com.lazy,d:\time.log,等于号后面表示参数,这里通过逗号分割方式来传入两个参数,笔者这里参数含义如下:

  • com.lazy:表示拦截的包前缀参数

  • d:\time.log:表示耗时写入的日志文件

5、为了方便测试,编写agent目标项目控制器,代码如下:

package com.lazy.agent.example;


import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;


/**
 *
 * @author laizhiyuan
 * @since 2020/4/15.
 */
@RestController
public class HelloController {


    @GetMapping("/test1")
    public String test1() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


        return "ok";
    }


    @GetMapping("/test2")
    public String test2() {
        try {
            Thread.sleep(8000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


        return "ok";
    }


    @GetMapping("/test3")
    public String test3() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


        return "ok";
    }
}


6、启动agent目标项目,启动成功后效果如下:

Java Agent - Java的黑科技

7、然后通过浏览器访问控制器 /test1  /test2  /test3,然后打开D盘的time.log文件,可以看到如下内容:

Java Agent - Java的黑科技

---------------------- 正文结束 ------------------------

长按扫码关注微信公众号

Java Agent - Java的黑科技

Java软件编程之家