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

那通过-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来输出
  • 启动App,再启动Attach

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

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

    分类:
    后端
  •