ngrok工具

ngrok 是一个反向代理,通过在公共的端点和本地运行的Web 服务器之间建立一个安全的通道。 ngrok 可捕获和分析所有通道上的流量,便于后期分析和重放。 反向代理在计算机网络中是代理服务器的一种。

相关科普文章:https://zhuanlan.zhihu.com/p/43628167 https://www.xncoding.com/2017/12/29/web/ngrok.html

kt-connect

https://github.com/alibaba/kt-connect

研发侧利器,本地连通 Kubernetes 集群内网

相关介绍文档 https://developer.aliyun.com/article/751321

spring xsd和dtd区别

可参考:https://blog.csdn.net/win7system/article/details/91553401

java agent

java agent本质上可以理解为一个插件,该插件就是一个精心提供的jar包,这个jar包通过JVMTI(JVM Tool Interface)完成加载,最终借助JPLISAgent(Java Programming Language Instrumentation Services Agent)完成对目标代码的修改。

可以在加载java文件之前做拦截把字节码做修改

可以在运行期将已经加载的类的字节码做变更

还有其他的一些小众的功能

  • 获取所有已经被加载过的类
  • 获取所有已经被初始化过了的类
  • 获取某个对象的大小
  • 将某个jar加入到bootstrapclasspath里作为高优先级被bootstrapClassloader加载
  • 将某个jar加入到classpath里供AppClassloard去加载
  • 设置某些native方法的前缀,主要在查找native方法的时候做规则匹配

通过java agent技术进行类的字节码修改最主要使用的就是Java Instrumentation API。下面将介绍如何使用Java Instrumentation API进行字节码修改。

1.实现java agent启动的方法

Java Agent支持目标JVM启动时加载,也支持在目标JVM运行时加载,这两种不同的加载模式会使用不同的入口函数,如果需要在目标JVM启动的同时加载Agent,那么可以选择实现下面的方法:

1
2
[1] public static void premain(String agentArgs, Instrumentation inst); 
[2] public static void premain(String agentArgs);

JVM将首先寻找[1],如果没有发现[1],再寻找[2]。如果希望在目标JVM运行时加载Agent,则需要实现下面的方法:

1
2
[1] public static void agentmain(String agentArgs, Instrumentation inst); 
[2] public static void agentmain(String agentArgs);

这两组方法的第一个参数AgentArgs是随同 “–javaagent”一起传入的程序参数,如果这个字符串代表了多个参数,就需要自己解析这些参数。inst是Instrumentation类型的对象,是JVM自动传入的,我们可以拿这个参数进行类增强等操作。

2. 指定Main-Class

Agent需要打包成一个jar包,在ManiFest属性中指定“Premain-Class”或者“Agent-Class”,且需根据需求定义Can-Redefine-Classes和Can-Retransform-Classes:

1
2
3
4
5
6
7
8
Manifest-Version: 1.0
preMain-Class: com.test.AgentClass
Archiver-Version: Plexus Archiver
Agent-Class: com.test.AgentClass
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Created-By: Apache Maven 3.3.9
Build-Jdk: 1.8.0_112

3.agent加载

  • 启动时加载
    • 启动参数增加-javaagent:[path],其中path为对应的agent的jar包路径
  • 运行中加载
    • 使用com.sun.tools.attach.VirtualMachine加载
1
2
3
4
5
6
7
8
9
10
try {
String jvmPid = 目标进行的pid;
logger.info("Attaching to target JVM with PID: " + jvmPid);
VirtualMachine jvm = VirtualMachine.attach(jvmPid);
jvm.loadAgent(agentFilePath);//agentFilePath为agent的路径
jvm.detach();
logger.info("Attached to target JVM and loaded Java agent successfully");
} catch (Exception e) {
throw new RuntimeException(e);
}

4. Instrument

instrument是JVM提供的一个可以修改已加载类的类库,专门为Java语言编写的插桩服务提供支持。它需要依赖JVMTI的Attach API机制实现。在JDK 1.6以前,instrument只能在JVM刚启动开始加载类时生效,而在JDK 1.6之后,instrument支持了在运行时对类定义的修改。要使用instrument的类修改功能,我们需要实现它提供的ClassFileTransformer接口,定义一个类文件转换器。接口中的transform()方法会在类文件被加载时调用,而在transform方法里,我们可以利用上文中的ASM或Javassist对传入的字节码进行改写或替换,生成新的字节码数组后返回。

首先定义类转换器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class TestTransformer implements ClassFileTransformer {
//目标类名称, .分隔
private String targetClassName;
//目标类名称, /分隔
private String targetVMClassName;
private String targetMethodName;


public TestTransformer(String className,String methodName){
this.targetVMClassName = new String(className).replaceAll("\\.","\\/");
this.targetMethodName = methodName;
this.targetClassName=className;
}
//类加载时会执行该函数,其中参数 classfileBuffer为类原始字节码,返回值为目标字节码,className为/分隔
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
//判断类名是否为目标类名
if(!className.equals(targetVMClassName)){
return classfileBuffer;
}
try {
ClassPool classPool = ClassPool.getDefault();
CtClass cls = classPool.get(this.targetClassName);
CtMethod ctMethod = cls.getDeclaredMethod(this.targetMethodName);
ctMethod.insertBefore("{ System.out.println(\"start\"); }");
ctMethod.insertAfter("{ System.out.println(\"end\"); }");
return cls.toBytecode();
} catch (Exception e) {

}
return classfileBuffer;
}
}

类转换器定义完毕后,需要将定义好的类转换器添加到对应的instrumentation中,对于已经加载过的类使用retransformClasses对类进行重新加载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class AgentDemo {

private static String className = "hello.GreetingController";
private static String methodName = "getDomain";

public static void agentmain(String args, Instrumentation instrumentation) {

try {
List<Class> needRetransFormClasses = new LinkedList<>();
Class[] loadedClass = instrumentation.getAllLoadedClasses();
for (int i = 0; i < loadedClass.length; i++) {
if (loadedClass[i].getName().equals(className)) {
needRetransFormClasses.add(loadedClass[i]);
}
}

instrumentation.addTransformer(new TestTransformer(className, methodName));
instrumentation.retransformClasses(needRetransFormClasses.toArray(new Class[0]));
} catch (Exception e) {

}
}

public static void premain(String args, Instrumentation instrumentation) {
instrumentation.addTransformer(new TestTransformer(className, methodName));
}
}

从上图的代码可以看出,主方法实现了两个,分别为agentmain和premain,其中

  • premain

    • 用于在启动时,类加载前定义类的TransFormer,在类加载的时候更新对应的类的字节码
  • agentmain

    • 用于在运行时进行类的字节码的修改,步骤整体分为两步
    • 注册类的TransFormer
    • 调用retransformClasses函数进行类的重加载

参考文档

作者:kokov
链接:https://www.jianshu.com/p/d573456401eb
来源:简书

5 美团相关技术文章参考