Comments (9)
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>
- 新建ClassesLoadUtil类
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;
}
}
- 新建Agent类
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();
}
}
}
}
- 打包
mvn clean package
获取target/array-list-instrumentation-0.0.1-SNAPSHOT-jar-with-dependencies.jar
from blog.
执行演示Demo
java -javaagent:/path/to/array-list-instrumentation-0.0.1-SNAPSHOT-jar-with-dependencies.jar=/path/to/array-list-client-0.0.1-SNAPSHOT.jar ...
from blog.
这个问题的重点在于: 如何使用最小的代价替换程序类库已有的功能实现.
看到这个问题的第一眼, 我最早想起的是Objective-C的一个黑魔法, 或者说是"奇技淫巧":
Method Swizzling
所谓Method Swizzling, 或者称之为方法调配, 其实是Objective-C设计者预留的一个工具, 通过修改函数指针和函数实现的映射关系, 使调用A函数的命令触发B函数的执行, 进而能够使开发者可以修改程序类库已有的功能实现, 达到自己"不可告人"的目的.
GitHub上的开源Method Swizzling工具库有:
jrswizzle
RSSwizzle
但是, Method Swizzling被称为"奇技淫巧"也是有原因的.
就好比语法糖的滥用会变成糖衣炮弹, 任何技巧的滥用都会变成凶残恐怖的怪物和深不见底的深渊.
参考: iOS界的毒瘤-MethodSwizzling
from blog.
话题转回: 如何替换Java程序类库已有的功能实现.
类似前文提到的Method Swizzling是Objective-C设计者预留的一个工具, 在Java设计者留给开发者的工具中, 就有这样一个独立于应用程序之上, 可以协助JVM完成一些特殊工作的入口:
Instrumentation
复制一段Instrumentation的简介:
利用java.lang.instrument
做动态Instrumentation是Java SE 5的新特性,它把Java的instrument功能从本地代码中解放出来,使之可以用Java代码的方式解决问题。使用Instrumentation,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在JVM上的程序,甚至能够替换和修改某些类的定义。有了这样的功能,开发者就可以实现更为灵活的运行时虚拟机监控和Java类操作了,这样的特性实际上提供了一种虚拟机级别支持的AOP实现方式,使得开发者无需对JDK做任何升级和改动,就可以实现某些AOP的功能了。
在Java SE 6里面,instrumentation包被赋予了更强大的功能:启动后的instrument、本地代码(native code)instrument,以及动态改变classpath等等。这些改变,意味着Java具有了更强的动态控制、解释能力,它使得Java语言变得更加灵活多变。
from blog.
在以往的开发中, 其实已经接触过一些Java Instrumentation的实现, 例如:
blackcat-instrument
jmockit
instrumentation
logging-instrumentation
同时, 参考一些博客:
Java Instrument (六) 动态重定义Class
Instrumentation增强部分rt.jar中的类
通过Java Agent的redefineClasses实现Mock功能
其中, 有一些需要注意的关键点:
- 使用premain实现Agent时, 需要添加配置
MANIFEST.MF
文件内容:
Premain-Class: your.premain.class.path
- 而使用agentmain实现Agent时, 需要添加配置
MANIFEST.MF
文件内容:
Agent-Class: your.agentmain.class.path
- Agent也是基于JVM执行的, 所以在premain/agentmain执行前, 已有部分Java类被加载, 这些被提前加载的类需要使用Instrumentation#redefineClasses进行修改
from blog.
回到最初的问题:
提取java中的ArrayList类的源代码
在add方法中,添加最大容器数量限制I(比如最大1万个),超过则抛出异常
替换系统类库中的实现
参考通过Java Agent的redefineClasses实现Mock功能
解答如下:
from blog.
Client Jar包
- 新建工程
<groupId>com.github.charlemaznable</groupId>
<artifactId>array-list-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
- 复制java.util.ArrayList类源码, 新建同名类路径下的同名类
- 修改ArrayList#add方法实现
/**
* 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;
}
- 打包
mvn clean package
获取target/array-list-client-0.0.1-SNAPSHOT.jar
from blog.
整成一个github工程吧
from blog.
整成一个github工程吧
已上传 instrument-demo
from blog.
Related Issues (20)
- Maven的多继承 HOT 2
- 浅尝Java ClassLoader HOT 6
- SimpleDateFormat的隐藏技能
- 浅尝Cobra HOT 1
- 做一些摘抄 HOT 14
- 浅尝Kotlin HOT 3
- 领域实体类
- PECS - producer-extends, consumer-super HOT 5
- CircularFifoQueue vs EvictingQueue HOT 1
- 浅尝Hazelcast HOT 14
- 浅尝HyperLogLog HOT 6
- 信息熵 HOT 1
- Go 开发笔记 HOT 2
- 0x5f375a86
- 空方法编译报错?!
- Lombok it! HOT 4
- 注解到底是什么
- 缓存风险
- 日常学习笔记 2024
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from blog.