首頁>技術>

一、簡介

java agent是獨立於應用程式外的代理程式,可以在應用程式啟動前或執行中,修改類位元組碼資訊,改變類的行為。這裡對應用程式啟動前和執行中的agent使用分別介紹。

二、應用程式啟動前的agent使用

應用程式啟動前agent使用,是透過在應用程式啟動時新增-javaagent引數(可多個-javaagent引數)實現的。

2.1 javaagent引數格式

javaagent引數使用格式如下:

java -javaagent:/xx/agent.jar[=引數] -jar xx.jar

2.2 開發步驟

應用程式啟動前agent開發,包含agent程式開發、MENIFEST.MF配置檔案定義、maven中maven-jar-plugin外掛修改、打jar包、主程式呼叫,具體開發步驟如下:

定義agent程式,需包含方法名為premain的靜態方法,同時實現ClassFileTransformer介面對特定類位元組碼修改(結合javassist工具);

定義MENIFEST.MF配置檔案,位於resources/META-INF目錄下,內容類似:

Manifest-Version: 1.0.1

Premain-Class: com.dragon.study.spring_boot_pre_agent.PreAgentMain

Can-Redefine-Classes: true

其中Premain-Class為前面定義的agent類,且配置檔案最要空一行。

修改mava的pom.xml中的外掛配置,類似於:

<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-jar-plugin</artifactId>

<version>3.2.0</version>

<configuration>

<archive>

<manifest>

<addClasspath>true</addClasspath>

</manifest>

<manifestEntries>

<Premain-Class>

com.dragon.study.spring_boot_pre_agent.PreAgentMain

</Premain-Class>

</manifestEntries>

</archive>

</configuration>

</plugin>

將前面定義的agent程式打成jar包;

在目標主程式上,新增-javaagent引數及前面的agent的jar包,再執行目標主程式,類似於:

java -javaagent:/xx/agent.jar=agentArgs -cp /xx.jar xx.Main

2.3 示例

這是以spring_boot_pre_agent專案建立agent的jar包,以spring_boot_main專案中的PreAgentTargetMain為目標類為例。

2.3.1 agent專案spring_boot_pre_agent

2.3.1.1 agent專案中的maven依賴

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

<parent>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-parent</artifactId>

<version>2.3.3.RELEASE</version>

</parent>

<groupId>com.dragon.study</groupId>

<artifactId>spring_boot_pre_agent</artifactId>

<version>0.0.1-SNAPSHOT</version>

<name>spring_boot_pre_agent</name>

<description>Demo project for Spring Boot</description>

<packaging>jar</packaging>

<properties>

<java.version>1.8</java.version>

</properties>

<dependencies>

<dependency>

<groupId>org.javassist</groupId>

<artifactId>javassist</artifactId>

<version>3.26.0-GA</version>

</dependency>

</dependencies>

<build>

<plugins>

<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-jar-plugin</artifactId>

<version>3.2.0</version>

<configuration>

<archive>

<manifest>

<addClasspath>true</addClasspath>

</manifest>

<manifestEntries>

<Premain-Class>

com.dragon.study.spring_boot_pre_agent.PreAgentMain

</Premain-Class>

</manifestEntries>

</archive>

</configuration>

</plugin>

</plugins>

</build>

</project>

2.3.1.2 agent專案中的agent相關類

這裡agent相關類包含自定義位元組碼編輯類ConfigTransformer.java和agent主類PreAgentMain.java,如下: ConfigTransformer.java類如下:

package com.dragon.study.spring_boot_pre_agent;

import javassist.ClassPool;

import javassist.CtClass;

import javassist.CtMethod;

import java.lang.instrument.ClassFileTransformer;

import java.lang.instrument.IllegalClassFormatException;

import java.security.ProtectionDomain;

//修改類的位元組碼

public class ConfigTransformer implements ClassFileTransformer {

private static ClassPool classPool = ClassPool.getDefault();

@Override

public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {

String target = "com.dragon.study.spring_boot_main.StaticConfig";

className = className.replaceAll("/", ".");

if(className.contains(target)){

try {

CtClass ctClass = classPool.getCtClass(target);

CtMethod ctMethod = ctClass.getDeclaredMethod("sayHello");

//指定方法新增一行自定義輸出

ctMethod.insertBefore("System.out.println(\"pre inject, configName:\"+configName);");

//返回修改後的位元組碼

return ctClass.toBytecode();

} catch (Exception e) {

}

}

//返回原類位元組碼

return classfileBuffer;

}

}

PreAgentMain類如下:

package com.dragon.study.spring_boot_pre_agent;

import java.lang.instrument.Instrumentation;

public class PreAgentMain {

//主程式執行前執行自定義操作

public static void premain(String agentArgs, Instrumentation inst) {

//這裡示例列印傳入引數

System.out.println("agentArgs:"+agentArgs);

//修改指定類行為

inst.addTransformer(new ConfigTransformer());

}

}

2.3.1.3 agent專案中的MENIFEST.MF配置檔案

MENIFEST.MF配置檔案位於resources/META-INF目錄下,內容為:

Manifest-Version: 1.0.1

Premain-Class: com.dragon.study.spring_boot_pre_agent.PreAgentMain

Can-Redefine-Classes: true

2.3.1.4 agent專案打為jar包

這裡透過maven打jar包為:spring_boot_pre_agent-0.0.1-SNAPSHOT.jar

2.3.2 agent目標專案spring_boot_main

spring_boot_main示例包含目標相關類,以及最終新增agent啟動。

2.3.2.1 目標相關類

目標相關類包含靜態配置類StaticConfig.java和目標類PreAgentTargetMain.java如下: StaticConfig.java:

package com.dragon.study.spring_boot_main;

public class StaticConfig {

//自定義靜態屬性

public static String configName="apple";

//自定義靜態方法

public static String sayHello(){

return "hello " + configName;

}

}

PreAgentTargetMain.java:

package com.dragon.study.spring_boot_main.agent;

import com.dragon.study.spring_boot_main.StaticConfig;

public class PreAgentTargetMain {

public static void main(String[] args) {

System.out.println("main");

//呼叫指定方法

StaticConfig.sayHello();

}

}

2.3.3 測試

應用程式啟動前使用agent,是透過啟動時新增-javaagent引數實現的。測試呼叫如下:

java -javaagent:/xx/spring_boot_pre_agent-0.0.1-SNAPSHOT.jar=agentArgs -cp /xx/spring_boot_main-0.0.1-SNAPSHOT.jar com.dragon.study.spring_boot_main.agent.PreAgentTargetMain

輸出:

agentArgs:agentArgs

main

pre inject, configName:apple

總結分析,藉助應用程式啟動前agent的使用,在PreAgentTargetMain啟動前改變了其行為。

三、應用程式執行中的agent使用

應用程式執行中的agent使用,是透過第三方程式,藉助VirtualMachine將自定義agent新增到目標程式(透過程序號pid)上。

3.1 開發步驟

應用程式執行中的agent開發和執行前類似,只是啟動方式不同,包含agent程式開發、MENIFEST.MF配置檔案定義、maven中maven-jar-plugin外掛修改、打jar包、agent使用的目標程式、第三方啟動程式,具體開發步驟如下:

定義agent程式,需包含方法名為agentmain的靜態方法,同時實現ClassFileTransformer介面對特定類位元組碼修改(結合javassist工具);

定義MENIFEST.MF配置檔案,位於resources/META-INF目錄下,內容類似:

Manifest-Version: 1.0.1

Agent-Class: com.dragon.study.spring_boot_post_agent.PostAgentMain

Can-Retransform-Classes: true

Can-Redefine-Classes: true

其中Agent-Class為前面定義的agent類,且配置檔案最要空一行。

修改mava的pom.xml中的外掛配置,類似於:

<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-jar-plugin</artifactId>

<version>3.2.0</version>

<configuration>

<archive>

<manifest>

<addClasspath>true</addClasspath>

</manifest>

<manifestEntries>

<Agent-Class>

com.dragon.study.spring_boot_post_agent.PostAgentMain

</Agent-Class>

<Can-Redefine-Classes>true</Can-Redefine-Classes>

<Can-Retransform-Classes>true</Can-Retransform-Classes>

</manifestEntries>

</archive>

</configuration>

</plugin>

將前面定義的agent程式打成jar包;

透過第三方程式將agent程式新增到目標程式上,類似:

//獲取指定專案執行的pid

String targetPid = "xx"

//執行期,對指定pid程式新增agent,動態改變程式行為

VirtualMachine vm = VirtualMachine.attach(targetPid);

//新增指定agent的jar包

vm.loadAgent("/xx/agent.jar");

vm.detach();

3.2 示例

這是以spring_boot_post_agent專案建立agent的jar包,以spring_boot_main專案為目標專案,以PostAgentTargetMain.java為第三方新增程式為例。

3.2.1 agent專案spring_boot_post_agent

3.2.1.1 agent專案中的maven依賴

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

<parent>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-parent</artifactId>

<version>2.3.3.RELEASE</version>

</parent>

<groupId>com.dragon.study</groupId>

<artifactId>spring_boot_post_agent</artifactId>

<version>0.0.1-SNAPSHOT</version>

<name>spring_boot_post_agent</name>

<description>Demo project for Spring Boot</description>

<properties>

<java.version>1.8</java.version>

</properties>

<dependencies>

<dependency>

<groupId>org.javassist</groupId>

<artifactId>javassist</artifactId>

<version>3.26.0-GA</version>

</dependency>

</dependencies>

<build>

<plugins>

<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-jar-plugin</artifactId>

<version>3.2.0</version>

<configuration>

<archive>

<manifest>

<addClasspath>true</addClasspath>

</manifest>

<manifestEntries>

<Agent-Class>

com.dragon.study.spring_boot_post_agent.PostAgentMain

</Agent-Class>

<Can-Redefine-Classes>true</Can-Redefine-Classes>

<Can-Retransform-Classes>true</Can-Retransform-Classes>

</manifestEntries>

</archive>

</configuration>

</plugin>

</plugins>

</build>

</project>

3.2.1.2 agent專案中的agent相關類

這裡agent相關類包含自定義位元組碼編輯類ConfigTransformer.java和agent主類PreAgentMain.java,如下: ConfigTransformer.java類如下:

package com.dragon.study.spring_boot_post_agent;

import javassist.ClassPool;

import javassist.CtClass;

import javassist.CtMethod;

import javassist.Loader;

import java.lang.instrument.ClassFileTransformer;

import java.lang.instrument.IllegalClassFormatException;

import java.security.ProtectionDomain;

public class ConfigTransformer implements ClassFileTransformer {

private static ClassPool classPool = ClassPool.getDefault();

@Override

public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {

String target = "com.dragon.study.spring_boot_main.StaticConfig";

className = className.replaceAll("/", ".");

if(className.contains(target)){

try {

CtClass ctClass = classPool.getCtClass(target);

CtMethod ctMethod = ctClass.getDeclaredMethod("sayHello");

//指定方法新增一行自定義輸出

ctMethod.insertBefore("System.out.println(\"post inject, configName:\"+configName);");

//返回修改後的位元組碼

return ctClass.toBytecode();

} catch (Exception e) {

}

}

//返回原類位元組碼

return classfileBuffer;

}

}

PostAgentMain類如下:

package com.dragon.study.spring_boot_post_agent;

import java.lang.instrument.Instrumentation;

import java.lang.instrument.UnmodifiableClassException;

import java.util.stream.Stream;

public class PostAgentMain {

public static void agentmain(String agentArgs, Instrumentation inst) {

//這裡示例列印傳入引數

System.out.println("agentArgs:" + agentArgs);

//列印載入的所有類

Class<?>[] clazzArr = inst.getAllLoadedClasses();

// Stream.of(clazzArr).forEach(System.out::println);

//列印目標專案中指定欄位的記憶體值

Stream.of(clazzArr).filter(t->t.getName().contains("StaticConfig")).forEach(t->{

try {

System.out.println("configName:"+t.getDeclaredField("configName").get(null));

} catch (Exception e) {

e.printStackTrace();

}

});

//修改目標專案中指定欄位的記憶體值

Stream.of(clazzArr).filter(t->t.getName().contains("StaticConfig")).forEach(t->{

try {

t.getDeclaredField("configName").set(null, "banana");

} catch (Exception e) {

e.printStackTrace();

}

});

//執行期修改指定類行為

Stream.of(clazzArr).filter(t->t.getName().contains("StaticConfig")).forEach(t->{

try {

inst.addTransformer(new ConfigTransformer(), true);

inst.retransformClasses(t);

} catch (UnmodifiableClassException e) {

e.printStackTrace();

}

});

}

}

3.2.1.3 agent專案中的MENIFEST.MF配置檔案

MENIFEST.MF配置檔案位於resources/META-INF目錄下,內容為:

Manifest-Version: 1.0.1

Agent-Class: com.dragon.study.spring_boot_post_agent.PostAgentMain

Can-Retransform-Classes: true

Can-Redefine-Classes: true

3.2.1.4 agent專案打為jar包

這裡透過maven打jar包為:spring_boot_post_agent-0.0.1-SNAPSHOT.jar

3.2.2 agent目標專案spring_boot_main

spring_boot_main為完整常規專案。

3.2.2.1 新增maven依賴

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

<parent>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-parent</artifactId>

<version>2.3.3.RELEASE</version>

</parent>

<groupId>com.dragon.study</groupId>

<artifactId>spring_boot_main</artifactId>

<version>0.0.1-SNAPSHOT</version>

<name>spring_boot_main</name>

<description>Demo project for Spring Boot</description>

<properties>

<java.version>1.8</java.version>

</properties>

<dependencies>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-web</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-devtools</artifactId>

<scope>runtime</scope>

<optional>true</optional>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-test</artifactId>

<scope>test</scope>

</dependency>

<dependency>

<groupId>com.alibaba</groupId>

<artifactId>fastjson</artifactId>

<version>1.2.62</version>

</dependency>

<dependency>

<groupId>org.javassist</groupId>

<artifactId>javassist</artifactId>

<version>3.26.0-GA</version>

</dependency>

</dependencies>

<build>

<plugins>

<plugin>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-maven-plugin</artifactId>

</plugin>

</plugins>

</build>

</project>

3.2.2.2 application.yaml配置

server:

port: 10013

spring:

application:

name: spring-boot-main

3.2.2.3 關鍵類

目標相關類包含靜態配置類StaticConfig.java和啟動類: StaticConfig.java:

package com.dragon.study.spring_boot_main;

public class StaticConfig {

//自定義靜態屬性

public static String configName="apple";

//自定義靜態方法

public static String sayHello(){

return "hello " + configName;

}

}

啟動類SpringBootMainApplication.java:

package com.dragon.study.spring_boot_main;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication

public class SpringBootMainApplication {

public static void main(String[] args) throws Exception {

SpringApplication.run(SpringBootMainApplication.class, args);

//主動載入靜態類

Class.forName("com.dragon.study.spring_boot_main.StaticConfig");

}

}

3.3.3 第三方新增類

第三方新增類是透過VirtualMachine將agent和目標專案關聯起來,示例PostAgentTargetMain如下:

package com.dragon.study.spring_boot_main.agent;

import com.sun.tools.attach.VirtualMachine;

import com.sun.tools.attach.VirtualMachineDescriptor;

import java.util.List;

public class PostAgentTargetMain {

public static void main(String[] args) throws Exception {

List<VirtualMachineDescriptor> vmList = VirtualMachine.list();

//獲取指定專案執行的pid

String targetPid = vmList.stream().filter(t->t.displayName().endsWith("com.dragon.study.spring_boot_main.SpringBootMainApplication")).findFirst().map(t->t.id()).get();

//執行期,對指定pid程式新增agent,動態改變程式行為

VirtualMachine vm = VirtualMachine.attach(targetPid);

//新增指定agent的jar包

vm.loadAgent("/xx/spring_boot_post_agent-0.0.1-SNAPSHOT.jar");

vm.detach();

}

}

3.3.4 測試

啟動spring_boot_main專案,執行第三方新增類PostAgentTargetMain。

java -javaagent:/xx/spring_boot_pre_agent-0.0.1-SNAPSHOT.jar=agentArgs -cp /xx/spring_boot_main-0.0.1-SNAPSHOT.jar com.dragon.study.spring_boot_main.agent.PreAgentTargetMain

spring_boot_main輸出:

agentArgs:null

configName:apple

再次透過http檢視:

GET http://localhost:10013/hello/sayHello

Accept: application/json

http結果輸出:

hello banana

同時spring_boot_main輸出:

post inject, configName:banana

分析總結: 分析前面測試結果,可以發現,藉助執行期agent的使用,實現了動態獲取、修改、新增執行期StaticConfig.java(spring_boot_main專案)類的值和行為。

8
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • java中加密演算法AES和RSA