-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
浅尝Java Instrumentation #9
Comments
这个问题的重点在于: 如何使用最小的代价替换程序类库已有的功能实现. 看到这个问题的第一眼, 我最早想起的是Objective-C的一个黑魔法, 或者说是"奇技淫巧":
所谓Method Swizzling, 或者称之为方法调配, 其实是Objective-C设计者预留的一个工具, 通过修改函数指针和函数实现的映射关系, 使调用A函数的命令触发B函数的执行, 进而能够使开发者可以修改程序类库已有的功能实现, 达到自己"不可告人"的目的. GitHub上的开源Method Swizzling工具库有: 但是, Method Swizzling被称为"奇技淫巧"也是有原因的. |
话题转回: 如何替换Java程序类库已有的功能实现. 类似前文提到的Method Swizzling是Objective-C设计者预留的一个工具, 在Java设计者留给开发者的工具中, 就有这样一个独立于应用程序之上, 可以协助JVM完成一些特殊工作的入口:
复制一段Instrumentation的简介: 利用 在Java SE 6里面,instrumentation包被赋予了更强大的功能:启动后的instrument、本地代码(native code)instrument,以及动态改变classpath等等。这些改变,意味着Java具有了更强的动态控制、解释能力,它使得Java语言变得更加灵活多变。 |
在以往的开发中, 其实已经接触过一些Java Instrumentation的实现, 例如: 同时, 参考一些博客: 其中, 有一些需要注意的关键点:
|
回到最初的问题:
参考通过Java Agent的redefineClasses实现Mock功能 解答如下: |
Agent Jar包
<groupId>com.github.charlemaznable</groupId>
<artifactId>array-list-instrumentation</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<appendAssemblyId>true</appendAssemblyId>
<archive>
<manifestEntries>
<Premain-Class>com.github.charlemaznable.instrument.Agent</Premain-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
package com.github.charlemaznable.instrument;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public class ClassesLoadUtil {
private static final Map<String, byte[]> path2Classes = new ConcurrentHashMap<>();
private static final Map<String, byte[]> className2Classes = new ConcurrentHashMap<>();
private static boolean havaLoaded = false;
private static void loadFromZipFile(String jarPath) {
try {
ZipFile zipFile = new ZipFile(jarPath);
Enumeration<? extends ZipEntry> entrys = zipFile.entries();
while (entrys.hasMoreElements()) {
ZipEntry zipEntry = entrys.nextElement();
entryRead(jarPath, zipEntry);
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static boolean entryRead(String jarPath, ZipEntry ze) throws IOException {
if (ze.getSize() > 0) {
String fileName = ze.getName();
if (!fileName.endsWith(".class")) {
return true;
}
try (ZipFile zf = new ZipFile(jarPath); InputStream input = zf.getInputStream(ze);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
if (input == null) {
return true;
}
int b = 0;
while ((b = input.read()) != -1) {
byteArrayOutputStream.write(b);
}
byte[] bytes = byteArrayOutputStream.toByteArray();
path2Classes.put(fileName, bytes);
String name1 = fileName.replaceAll("\\.class", "");
String name2 = name1.replaceAll("/", ".");
className2Classes.put(name2, bytes);
System.out.println("加载文件: fileName : " + fileName + ". className:" + name2);
}
}
return false;
}
public static Map<String, byte[]> getRewriteClasses(String agentArgs) {
synchronized (className2Classes) {
if (!havaLoaded) {
loadFromZipFile(agentArgs);
havaLoaded = true;
}
}
return className2Classes;
}
}
package com.github.charlemaznable.instrument;
import lombok.val;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.util.HashMap;
import java.util.Map;
public class Agent {
public static void premain(String agentArgs, Instrumentation instrumentation) {
val allLoadedClasses = instrumentation.getAllLoadedClasses();
val allLoadedClassesMap = new HashMap<String, Class>();
try {
for (val loadedClass : allLoadedClasses) {
if (loadedClass == null) continue;
if (loadedClass.getCanonicalName() == null) continue;
allLoadedClassesMap.put(loadedClass.getCanonicalName(), loadedClass);
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(allLoadedClassesMap);
Map<String, byte[]> rewriteClasses = ClassesLoadUtil.getRewriteClasses(agentArgs);
if (allLoadedClassesMap.size() == 0 || rewriteClasses.size() == 0) {
return;
}
for (String className : rewriteClasses.keySet()) {
byte[] classBytes = rewriteClasses.get(className);
if (classBytes == null || classBytes.length == 0) {
System.out.println("从 rewriteClasses 找不到class: " + className);
continue;
}
Class redefineClass = allLoadedClassesMap.get(className);
if (redefineClass == null) {
System.out.println("从 allLoadedClassesMap 找不到class: " + className);
continue;
}
System.out.println("开始redefineClasses: " + className);
ClassDefinition classDefinition = new ClassDefinition(redefineClass, classBytes);
try {
instrumentation.redefineClasses(classDefinition);
System.out.println("结束redefineClasses: " + className);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
获取 |
Client Jar包
<groupId>com.github.charlemaznable</groupId>
<artifactId>array-list-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
if (size + 1 > 10000) {
throw new IllegalStateException();
}
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
获取 |
执行演示Demo
|
整成一个github工程吧 |
已上传 instrument-demo |
昨天晚上, 老大 @bingoohuang提了个问题:
略微研究, 浅尝辄止, 在这里分享一下答案.
The text was updated successfully, but these errors were encountered: