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方法前会打印如下截图内容:
前面我们只是打印一句话,我们还可以做些什么呢?继续看后面的示例。
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目标项目,可以看到如下信息:
一句话总结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目标项目,启动成功后效果如下:
7、然后通过浏览器访问控制器 /test1 /test2 /test3,然后打开D盘的time.log文件,可以看到如下内容:
---------------------- 正文结束 ------------------------
长按扫码关注微信公众号
Java软件编程之家