Arthas的奇技淫巧-常用高级技巧(spring获取bean,ognl表达式,命令别名,调用方法,对象传参)

123 阅读3分钟

Arthas常用高级用法

视频地址:

www.bilibili.com/video/BV16K…

获取Spring Bean

tt命令

  • 场景

    • 执行Spring中某个Bean的特定方法
    • 对某段代码做测试,但当前方法不能直接被接口访问
    • Dubbo Service中的代码,不想使用telent invoke调用
    • 线上观察Bean内存中的数据
  • 释义

    • 理解为watch命令的变体,watch命令是实时监听,而tt命令可以将观测到的数据记录下来
  • 使用示例

    • 记录任意地址的调用

      tt -t org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter invokeHandlerMethod -n 1
      
      • -n指定记录次数,否则可能导致OOM
      • 直接访问localhost:应用端口即可触发
      • 监听的是Spring MVC中的处理适配器,所有接口调用都会被触发
      • 成功后,会退出tt命令的监听,并输出一个记录ID,第一次记录为1000
    • 获取Spring容器并调用方法

      tt -i 1000 -w 'target.getApplicationContext().getBean("testBean").test()'
      
      • 使用-w以及OGNL表达式获取记录的目标对象并调用目标对象的getApplicationContext方法
      • 获取到Spring容器后,调用getBean方法,填入BeanName,后面输入要调用的方法

命令别名

  • 问题

    • 命令太长记不住
    • 每次都要打开文档查看命令
  • 解决方案

    • 给命令取一个别名,输入别名直接替换为命令
    • 使用AutoHotKey(V2)创建别名
  • 别名示例

    • att(记录调用)
      ::att::tt -t org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter invokeHandlerMethod -n 1
      
    • agb(参数1:BeanName;参数2:tt索引,默认值为1000)
      ::agb::
      {
      args := InputBox("请输入tt -i 参数", "tt -i [number:1000] -w 'target.getApplicationContext().getBean(beanName)'", "w520 h100").value
      tti := "1000"
      SplitArgs := StrSplit(args, " ")
      if (SplitArgs.Length >= 2)
          tti := SplitArgs[2]
      newArgs := 'tt -i ' tti ' -w `'target.getApplicationContext().getBean("' SplitArgs[1] '\`")\`''
      Send newArgs
      }
      
  • 使用方法

    • 创建aa.ank文件,将上述脚本内容复制进去,保存退出
    • aa.ank文件创建快捷方式,移动到C:\Users\你的用户名\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup

调用静态方法

  • OGNL表达式

    • 被调用者为静态
      ognl '@java.util.Objects@hashCode(1)'
      ognl '@java.lang.Thread@currentThread()'
      ognl '@java.util.ArrayList@DEFAULT_CAPACITY'
      
    • 被调用者为实例
      ognl '@java.lang.System@out.println("hello world")'
      ognl '@java.lang.Runtime@getRuntime().gc()'
      
  • 规则

    • 访问类:@全类名
      • 例:@java.lang.System
    • 访问类对象:@全类名@class
      • 例:@java.lang.System@class
    • 静态字段(方法):@静态字段(方法)
      • 例:ognl '@java.lang.Thread@currentThread()
    • 实例字段(方法):.实例字段(方法)
      • 例:ognl '@java.lang.Thread@currentThread().name

参数为对象类型的方法调用

  • 痛点

    • string类型和数字类型可以直接传
    • 但是参数为对象接口不可以直接传参,并且也不能支持JSON写法
  • 场景

    • 使用tt命令调用方法参数为对象时
    • 使用OGNL调用静态方法时
  • 解决方案

    • 使用JSON库将JSON字符串解析为实体对象
      @com.alibaba.fastjson.JSON@parseObject("{\\"name\\":\\"a\\",\\"age\\":1}", @com.maple.spring.Student@class)
      
    • 示例
      tt -i 1000 -w 'target.getApplicationContext().getBean("springBootApplication").test03(@com.alibaba.fastjson.JSON@parseObject("{\\"name\\":\\"name_babb0dc2c3cb\\",\\"age\\":1}", @com.maple.spring.Student@class))'
      
    • 注意:是否有getter和setter方法

查看内存数据

  • vmtool
    • 通过类型找到内存对象(谨慎使用)
    • 获取对象
      vmtool --action getInstances --className com.maple.spring.SpringBootApplication --limit 1
      
    • 执行方法
      vmtool --action getInstances --className com.maple.spring.SpringBootApplication --limit 1 --express 'instances[0].test03(null)'
      

找出忙碌线程

  • thread命令
    • 输出最忙的前3个线程并打印堆栈
      thread -n 3