Spring Shell和使用Attach API

2022-01-21  本文已影响0人  东南枝下

Spring Shell简单使用

spring-shell中已经包含了spring-boot-starter,直接依赖就可以使用

  1. 引入依赖
        <dependency>
            <groupId>org.springframework.shell</groupId>
            <artifactId>spring-shell-starter</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>
  1. 启动类
/**
 * @author Jenson
 */
@SpringBootApplication
public class SpringShellApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringShellApplication.class, args);
    }
}
  1. 直接启动就可以使用
    使用help命令可以看到,有一些基础命令
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.4.RELEASE)

2022-01-21 17:37:10.184  INFO 73896 --- [           main] com.jenson.SpringShellApplication        : Starting SpringShellApplication on localhost with PID 73896 (started by Jenson in /Users/gy/MyCode/write-own-code-to-paly/java代码/hotchpotch)
2022-01-21 17:37:10.190  INFO 73896 --- [           main] com.jenson.SpringShellApplication        : No active profile set, falling back to default profiles: default
2022-01-21 17:37:10.315  INFO 73896 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@77e4c80f: startup date [Fri Jan 21 17:37:10 CST 2022]; root of context hierarchy
2022-01-21 17:37:12.617  WARN 73896 --- [           main] org.jline                                : Unable to create a system terminal, creating a dumb terminal (enable debug logging for more information)
2022-01-21 17:37:12.957  INFO 73896 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2022-01-21 17:37:12.991  INFO 73896 --- [           main] com.jenson.SpringShellApplication        : Started SpringShellApplication in 3.561 seconds (JVM running for 4.363)
shell:>
shell:>
shell:>
shell:>help
AVAILABLE COMMANDS

Built-In Commands
        clear: Clear the shell screen.
        exit, quit: Exit the shell.
        help: Display help about available commands.
        script: Read and execute commands from a file.
        stacktrace: Display the full stacktrace of the last error.
  1. 创建命令
    创建一个查询java进程和使用attach-api把agent的jar包添加到指定进程号到Java程序中的命令
package com.jenson.command;

import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;
import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellMethod;

import java.io.IOException;
import java.util.List;

/**
 * @author Jenson
 */
@ShellComponent
public class VmCommand {

    @ShellMethod(value = "查询所有java进程号")
    public void pid() {
        List<VirtualMachineDescriptor> vmList = VirtualMachine.list();
        vmList.forEach(vm->{
            System.out.println(vm.id() + " " +vm.displayName());
        });
    }

    @ShellMethod(key = {"attach-jar"},value = "把jar包通过attach-api加进去")
    public void attachJar(String pid,String path){
        VirtualMachine vm = null;
        try {
            //进程ID
            vm = VirtualMachine.attach(pid);
            //java agent jar包路径
            vm.loadAgent(path);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (vm != null) {
                try {
                    vm.detach();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

  1. 使用该命令
shell:>pid
58854 org.jetbrains.idea.maven.server.RemoteMavenServer36
1352 
73896 com.jenson.SpringShellApplication
74140 org.jetbrains.jps.cmdline.Launcher /Applications/...
73692 org.jetbrains.idea.maven.server.RemoteMavenServer36
74143 com.jenson.Application
74142 org.jetbrains.jps.cmdline.Launcher /Applications/...
shell:>
shell:>
shell:>attach-jar --pid 74143 --path ~/hotchpotch/attach-agent/target/attach-agent.jar

上述使用attach-agent.jar来修改进程号为74143的java程序的代码

agent.jar编写,动态修改字节码

  1. 引入依赖
    使用ASM来修改字节码
        <dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm</artifactId>
            <version>7.1</version>
        </dependency>
        <dependency>
            <artifactId>asm-commons</artifactId>
            <groupId>org.ow2.asm</groupId>
            <version>7.1</version>
        </dependency>
  1. 增加打包插件
<build>
        <finalName>attach-agent</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.2.0</version>
                <configuration>
                    <archive>
                        <!-- 打jar的文件清单,对应META-INF/MANIFEST.MF文件 -->
                        <manifestEntries>
                            <!-- 主程序启动类 -->
                            <Agent-Class>
                                com.jenson.AgentMain
                            </Agent-Class>
                            <!-- 允许重新定义类 -->
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <!-- 允许转换并重新加载类 -->
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
  1. 创建函数的visitor,修改函数返回值
package com.jenson.visitor;


import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.commons.AdviceAdapter;

/**
 * @author Jenson
 */
public class MyMethodVisitor extends AdviceAdapter {
    protected MyMethodVisitor(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) {
        super(api, methodVisitor, access, name, descriptor);
    }

    @Override
    protected void onMethodEnter() {
        //从常量池加载字符串
        mv.visitLdcInsn("------我是Agent-----");
        //返回
        mv.visitInsn(ARETURN);
    }
}
  1. 创建类的visitor,查找指定函数,调用上述方法修改函数返回值
package com.jenson.visitor;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;

/**
 * @author Jenson
 */
public class MyClassVisitor extends ClassVisitor {
    public MyClassVisitor(int api, ClassVisitor cv) {
        super(api, cv);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
                                     String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
        if ("chickList".equals(name)) {
            System.out.println("----准备修改chickList方法----");
            return new MyMethodVisitor(api,mv,access,name,descriptor);
        }
        return mv;
    }
}
  1. 定义类转化器,修改指定类的字节码
package com.jenson.transformer;

import com.jenson.visitor.MyClassVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;


/**
 * 自定义类文件转换器,通过ASM修改类字节码
 * @author Jenson
 */
public class MyClassFileTransformer implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        String finalClassName = className.replace("/", ".");
        System.out.println("----是不是没有执行下面: "+finalClassName);
        if ("com.jenson.controller.v1.ChickController".equals(finalClassName)) {
            System.out.println("----是不是没有执行这里1-----");
            //以下为ASM常规操作,详情可以查看ASM使用相关文档
            ClassReader cr = new ClassReader(classfileBuffer);
            ClassWriter cw = new ClassWriter(cr,ClassWriter.COMPUTE_FRAMES);
            ClassVisitor cv = new MyClassVisitor(Opcodes.ASM7,cw);
            cr.accept(cv,ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
            return cw.toByteArray();
        }
        return null;
    }
}
  1. 编写Agent-Class
package com.jenson;

import com.jenson.transformer.MyClassFileTransformer;

import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;

/**
 * @author Jenson
 */
public class AgentMain {
    public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
        System.out.println("---agent called---");
        //添加类文件转换器,第二个参数必须设置为true,表示可以重新转换类文件
        inst.addTransformer(new MyClassFileTransformer(),true);
        Class[] classes = inst.getAllLoadedClasses();
        for (int i = 0; i < classes.length; i++) {
            if ("com.jenson.controller.v1.ChickController".equals(classes[i].getName())) {
                System.out.println("----重新加载ChickController开始----");
                inst.retransformClasses(classes[i]);
                System.out.println("----重新加载ChickController完毕----");
                break;
            }
        }
    }
}
  1. 打包
mvn clean package
  1. 被修改字节码的Java程序需要引入依赖
        <dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm</artifactId>
            <version>7.1</version>
        </dependency>
        <dependency>
            <artifactId>asm-commons</artifactId>
            <groupId>org.ow2.asm</groupId>
            <version>7.1</version>
        </dependency>

如果没加这个依赖就会报错

spring shell 的attach报错

shell:>attach-jar --pid 74855 --path ~/hotchpotch/attach-agent/target/attach-agent.jar
shell:>com.sun.tools.attach.AgentInitializationException: Agent JAR loaded but agent failed to initialize
    at sun.tools.attach.HotSpotVirtualMachine.loadAgent(HotSpotVirtualMachine.java:121)
    at com.sun.tools.attach.VirtualMachine.loadAgent(VirtualMachine.java:540)
    at com.jenson.command.VmCommand.attachJar(VmCommand.java:32)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:223)
    at org.springframework.shell.Shell.evaluate(Shell.java:169)
    at org.springframework.shell.Shell.run(Shell.java:134)
    at org.springframework.shell.jline.InteractiveShellApplicationRunner.run(InteractiveShellApplicationRunner.java:84)
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:791)
    at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:781)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:338)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1258)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1246)
    at com.jenson.SpringShellApplication.main(SpringShellApplication.java:18)

被attach的java程序报错

Exception in thread "Attach Listener" java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:386)
    at sun.instrument.InstrumentationImpl.loadClassAndCallAgentmain(InstrumentationImpl.java:411)
Caused by: java.lang.NoClassDefFoundError: org/objectweb/asm/ClassVisitor
    at com.jenson.AgentMain.agentmain(AgentMain.java:15)
    ... 6 more
Caused by: java.lang.ClassNotFoundException: org.objectweb.asm.ClassVisitor
    at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 7 more
Agent failed to start!

  1. 测试

attach 前调用接口


图片.png

attach后调用接口


图片.png

spring-shell参考:https://www.cnblogs.com/nuccch/p/11067342.html
attach-api参考:https://www.cnblogs.com/xuxiaojian/p/14537375.html
ASM官方入门手册:https://asm.ow2.io/asm4-guide.pdf

上一篇下一篇

猜你喜欢

热点阅读