Java重新加载类之Attach Tools

395 阅读1分钟

通过上一篇文章Java方法日志打印之javaagent我们了解了在应用启动前怎样去修改类

但有时应用在运行中,需要给方法加日志的输出,这时需要对运行中的类进行类的重新加载。

那通过-javaagent命令是无法实现的,只能通过Attach Tools的API来重新加载类。

agent项目改造

  • premain方法改为agentmain方法
  • 调用retransformClasses来重加载类
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.util.Arrays;

public class Javaagent {
    public static void agentmain(String arg, Instrumentation instrumentation) throws UnmodifiableClassException {
        System.out.println("agentmain...");
        CusClassFileTransformer transformer = new CusClassFileTransformer();
        instrumentation.addTransformer(transformer, true);
        Class<?>[] allLoadedClasses = Arrays.stream(instrumentation.getAllLoadedClasses())
                .filter(item -> !item.getName().startsWith("java"))
                .filter(item -> !item.getName().startsWith("sun"))
                .filter(item -> !item.getName().startsWith("[L"))
                .toArray(Class[]::new);
        System.out.println("allLoadedClasses:" + allLoadedClasses.length);
        for (Class<?> aClass : allLoadedClasses) {
            if ("UserService".equals(aClass.getName())) {
                System.out.println("retransformClasses : " + aClass.getName());
                instrumentation.retransformClasses(aClass);
            }
        }

        instrumentation.removeTransformer(transformer);


    }
}
  • pom.xml修改
<build>
    <plugins>
        <plugin>
            <artifactId>maven-assembly-plugin</artifactId>
            <configuration>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
                <archive>
                    <manifestEntries>
                        <Agent-Class>Javaagent</Agent-Class>
                        <Can-Retransform-Classes>true</Can-Retransform-Classes>
                    </manifestEntries>
                </archive>
            </configuration>
            <executions>
                <execution>
                    <id>make-assembly</id>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

改造一下App

  • 使用while循环来调用方法
  • 打印输出结果,观察控制台输出
import java.util.concurrent.atomic.AtomicInteger;

public class App {
    public static void main(String[] args) throws InterruptedException {
        UserService userService = new UserService();
        AtomicInteger count = new AtomicInteger(0);
        Thread thread = new Thread(() -> {
            while (true) {
                System.out.println(count.incrementAndGet() + ":" + userService.queryUserByName("MinXie"));
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
        thread.join();
        System.out.println("end");
    }
}

使用Attach工具

import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;

import java.io.IOException;

public class Attach {
    public static void main(String[] args) throws IOException, AttachNotSupportedException {
        attachPid("39984", "/Users/user/IdeaProjects/javaagent/target/javaagent-1.0-SNAPSHOT-jar-with-dependencies.jar");
    }

    private static void attachPid(String pid, String jarPath) throws IOException, AttachNotSupportedException {
        VirtualMachine vm = VirtualMachine.attach(pid);
        System.out.println("attach:" + vm.id());
        try {
            //加载
            vm.loadAgent(jarPath, null);
        } catch (AgentLoadException | AgentInitializationException e) {
            e.printStackTrace();
        } finally {
            vm.detach();
        }
    }
}
  • 调用loadAgent来触发agent
  • 这里的pid可以通过jps来输出

image.png

启动App,再启动Attach

看控制台输出,看到第八次调用方法之后是有打印方法调用日志的

成功对运行中的类进行字节码插桩 image.png