Instrumentation增强部分rt.jar中的类

2018-11-12  本文已影响0人  全都是泡沫啦

类加载的步骤:1.加载 2.校验 3.准备 4.解析(不固定:对于动态调用可能在初始化后解析,例如多态的实现,java8的lambda语法) 5.初始化 6.使用 7.卸载

Instrumentation原理:在类加载器加载过程中对class文件流进行拦截替换,
Instrumentation提供了获取对象大小的方法:getObjectSize

META-INF/MANIFEST.MF文件内容

Manifest-Version: 1.0
Premain-Class: com.paulzhangcc.InstrumentationHolder
Can-Redefine-Classes: true
Can-Retransform-Classes: true

pom.xml

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.paulzhangcc</groupId>
    <artifactId>agent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifestFile>
                            src/main/resources/META-INF/MANIFEST.MF
                        </manifestFile>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
package com.paulzhangcc;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import java.util.Properties;

/**
 * @author paul
 * @description
 * @date 2018/8/7
 */
public class InstrumentationHolder {
    public static final String default_config_path = "/opt/java-agent.properties";
    public static Instrumentation instrumentation = null;
    public static void premain(String agentArgs, Instrumentation instrumentationTemp) {
        instrumentation = instrumentationTemp;
        System.out.println("[premain][init ]:agentArgs=" + agentArgs + ",instrumentationName="+instrumentationTemp.getClass().getName());
        Class[] allLoadedClasses = instrumentationTemp.getAllLoadedClasses();
        for (Class _class:allLoadedClasses){
            System.out.println("[premain][LoadedClasses]:className="+_class.getName());
        }
        Properties properties = new Properties();
        try {
            if (agentArgs != null && agentArgs.length() != 0) {
                System.out.println("[premain][read ]:config_path="+agentArgs);
                properties.load(new FileInputStream(agentArgs));
            } else {
                System.out.println("[premain][read ]:default_config_path="+default_config_path);
                properties.load(new FileInputStream(default_config_path));
            }

        } catch (Exception e) {
            if (e instanceof FileNotFoundException) {
                try {
                    properties.load(new FileInputStream(default_config_path));
                } catch (IOException e1) {
                    System.out.println("[premain][error]:how to use:java -javaagent:{1}={2}  {1} is agent jar ,{2} is conf properties , default {2} is /opt/java-agent.properties");
                    System.out.println("[premain][error]:/opt/java-agent.properties for example sun/security/util/HostnameChecker=C:/Users/paul/Desktop/HostnameChecker.class");
                }
            } else {
                e.printStackTrace();
            }
        }
        if (properties.isEmpty()){
            System.out.println("[premain][info ]:config properties is empty,so do not transform Class");
            return;
        }
        instrumentationTemp.addTransformer(new ClassFileTransformer() {
            @Override
            public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
                //className 对于类     :java/lang/Void
                //className 对于内部类 :java/lang/Class$MethodArray
                String property = properties.getProperty(className);
                if (property != null) {
                    byte[] fileBytes = getFileBytes(property);
                    if (fileBytes != null) {
                        System.out.println("[premain][replace]:className=" + className + ",fileName="+property);
                        return fileBytes;
                    }
                }
                return null;
            }
        }, true);
    }

    public static byte[] getFileBytes(String fileName) {
        try {
            File file = new File(fileName);
            if (!file.exists()) {
                return null;
            }
            long fileSize = file.length();
            FileInputStream fi = new FileInputStream(file);
            byte[] buffer = new byte[(int) fileSize];
            int offset = 0;
            int numRead = 0;
            while (offset < buffer.length
                    && (numRead = fi.read(buffer, offset, buffer.length - offset)) >= 0) {
                offset += numRead;
            }
            if (offset != buffer.length) {
                return null;
            }
            fi.close();
            return buffer;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

测试类

import java.lang.instrument.Instrumentation;
import java.lang.reflect.Field;

/**
 * @author paul
 * @description
 * @date 2018/11/12
 */
public class Test {

    public static Instrumentation getInstrumentation(){
        try {
            Class<?> aClass = Class.forName("com.paulzhangcc.InstrumentationHolder");
            Field instrumentation = aClass.getField("instrumentation");
            return (Instrumentation) instrumentation.get(null);
        }catch (Exception e){
        }
        return null;
    }
    public static void main(String[] args) throws Exception {
        Instrumentation instrumentation = getInstrumentation();
        if (instrumentation != null){
            //查看Test对象的大小
            long objectSize = instrumentation.getObjectSize(new Test());
            System.out.println("Object Test size is "+objectSize +" Byte");
        }
    }
}

运行Test时:java -javaagent:agent-1.0-SNAPSHOT.jar=/test/conf.properties Test
/test/conf.properties配置类似如下:(注意String类无法覆盖由于系统在使用Instrumentation前就已经加载了String类,日志中:[premain][LoadedClasses]的类提前加载到内存都无法进行覆盖)

java/lang/String=/opt/class/String.class   #只作为格式参考

对于提前加载的类可以使用Instrumentation#redefineClasses进行修改,但是有他的局限性
1.不允许新增加field/method 2.正在跑的函数,没有退出不能生效

上一篇下一篇

猜你喜欢

热点阅读