Coder Social home page Coder Social logo

浅尝Java Instrumentation about blog HOT 9 OPEN

CharLemAznable avatar CharLemAznable commented on August 30, 2024
浅尝Java Instrumentation

from blog.

Comments (9)

CharLemAznable avatar CharLemAznable commented on August 30, 2024 1

Agent Jar包

  1. 新建工程
<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>
  1. 新建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;
    }
}
  1. 新建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();
            }
        }
    }
}
  1. 打包
mvn clean package

获取target/array-list-instrumentation-0.0.1-SNAPSHOT-jar-with-dependencies.jar

from blog.

CharLemAznable avatar CharLemAznable commented on August 30, 2024 1

执行演示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.

CharLemAznable avatar CharLemAznable commented on August 30, 2024

这个问题的重点在于: 如何使用最小的代价替换程序类库已有的功能实现.

看到这个问题的第一眼, 我最早想起的是Objective-C的一个黑魔法, 或者说是"奇技淫巧":

Method Swizzling

所谓Method Swizzling, 或者称之为方法调配, 其实是Objective-C设计者预留的一个工具, 通过修改函数指针和函数实现的映射关系, 使调用A函数的命令触发B函数的执行, 进而能够使开发者可以修改程序类库已有的功能实现, 达到自己"不可告人"的目的.

GitHub上的开源Method Swizzling工具库有:
jrswizzle
RSSwizzle

但是, Method Swizzling被称为"奇技淫巧"也是有原因的.
就好比语法糖的滥用会变成糖衣炮弹, 任何技巧的滥用都会变成凶残恐怖的怪物和深不见底的深渊.
参考: iOS界的毒瘤-MethodSwizzling

from blog.

CharLemAznable avatar CharLemAznable commented on August 30, 2024

话题转回: 如何替换Java程序类库已有的功能实现.

类似前文提到的Method Swizzling是Objective-C设计者预留的一个工具, 在Java设计者留给开发者的工具中, 就有这样一个独立于应用程序之上, 可以协助JVM完成一些特殊工作的入口:

Instrumentation

复制一段Instrumentation的简介:

利用java.lang.instrument做动态InstrumentationJava SE 5的新特性,它把Java的instrument功能从本地代码中解放出来,使之可以用Java代码的方式解决问题。使用Instrumentation,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在JVM上的程序,甚至能够替换和修改某些类的定义。有了这样的功能,开发者就可以实现更为灵活的运行时虚拟机监控和Java类操作了,这样的特性实际上提供了一种虚拟机级别支持的AOP实现方式,使得开发者无需对JDK做任何升级和改动,就可以实现某些AOP的功能了。

Java SE 6里面,instrumentation包被赋予了更强大的功能:启动后的instrument、本地代码(native code)instrument,以及动态改变classpath等等。这些改变,意味着Java具有了更强的动态控制、解释能力,它使得Java语言变得更加灵活多变。

参考: Java Instrument 功能使用及原理

from blog.

CharLemAznable avatar CharLemAznable commented on August 30, 2024

在以往的开发中, 其实已经接触过一些Java Instrumentation的实现, 例如:
blackcat-instrument
jmockit
instrumentation
logging-instrumentation

同时, 参考一些博客:
Java Instrument (六) 动态重定义Class
Instrumentation增强部分rt.jar中的类
通过Java Agent的redefineClasses实现Mock功能

其中, 有一些需要注意的关键点:

  1. 使用premain实现Agent时, 需要添加配置MANIFEST.MF文件内容:
Premain-Class: your.premain.class.path
  1. 而使用agentmain实现Agent时, 需要添加配置MANIFEST.MF文件内容:
Agent-Class: your.agentmain.class.path
  1. Agent也是基于JVM执行的, 所以在premain/agentmain执行前, 已有部分Java类被加载, 这些被提前加载的类需要使用Instrumentation#redefineClasses进行修改

from blog.

CharLemAznable avatar CharLemAznable commented on August 30, 2024

回到最初的问题:

提取java中的ArrayList类的源代码
在add方法中,添加最大容器数量限制I(比如最大1万个),超过则抛出异常
替换系统类库中的实现

参考通过Java Agent的redefineClasses实现Mock功能

解答如下:

from blog.

CharLemAznable avatar CharLemAznable commented on August 30, 2024

Client Jar包

  1. 新建工程
<groupId>com.github.charlemaznable</groupId>
<artifactId>array-list-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
  1. 复制java.util.ArrayList类源码, 新建同名类路径下的同名类
  2. 修改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;
}
  1. 打包
mvn clean package

获取target/array-list-client-0.0.1-SNAPSHOT.jar

from blog.

bingoohuang avatar bingoohuang commented on August 30, 2024

整成一个github工程吧

from blog.

CharLemAznable avatar CharLemAznable commented on August 30, 2024

整成一个github工程吧

已上传 instrument-demo

from blog.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.