美文网首页
Spring Shell和使用Attach API

Spring Shell和使用Attach API

作者: 东南枝下 | 来源:发表于2022-01-21 18:18 被阅读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

    相关文章

      网友评论

          本文标题:Spring Shell和使用Attach API

          本文链接:https://www.haomeiwen.com/subject/saoehrtx.html