1. ognl获取bean
SpringContextUtil,通常代码中会有类似这样的工具类用来获取 bean 实例
@Component
public class SpringContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtil.applicationContext = applicationContext;
}
public static Object getBean(String beanName) {
return applicationContext.getBean(beanName);
}
public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
}
UserController
@RestController
public class UserController {
private static final Logger LOGGER = LoggerFactory.getLogger(UserController.class);
@GetMapping("/user/{id}")
public User getUser(@PathVariable Integer id) {
if (null == id) {
throw new IllegalArgumentException("id can not be null");
}
if (id < 1) {
throw new IllegalArgumentException("id must be greater than 1");
}
return new User(id, "zhangsan");
}
}
使用 arthas 连接 spring 应用,执行如下操作:
查找全类名
sc *SpringContextUtil
查找类加载器
sc -d *SpringContextUtil | grep classLoaderHash
使用ognl表达式获取bean,并调用方法
> ognl -c 18b4aac2 '@com.soulballad.usage.arthasdemo.util.SpringContextUtil@getBean("userController").getUser(2)'
2. watch观测方法调用
# 查看 UserController 下所有方法的 参数、对象、返回值
watch com.soulballad.usage.arthasdemo.web.UserController * '{params,target,returnObj}'
watch 支持方法调用前、调用后、异常抛出等多个场景观测,同时还可以在第四个参数中使用条件进行过滤,比如:
watch com.soulballad.usage.arthasdemo.web.UserController * '{returnObj}' 'params[0]>10'
watch com.soulballad.usage.arthasdemo.web.UserController * '{returnObj}' '#cost>10'
3. 热更新
步骤:使用jad反编译 -> 修改文件 -> 使用mc重新编译修改后的文件->使用redefine加载重新编译后的类
上述 UserController 访问 user/0
,会出现如下错误:
There was an unexpected error (type=Internal Server Error, status=500).
id must be greater than 1
现对其进行热更新
反编译 UserController
# --source-only 只输出源码 jad --source-only com.soulballad.usage.arthasdemo.web.UserController > UserController.java
修改编译后的文件
package com.soulballad.usage.arthasdemo.web; import com.soulballad.usage.arthasdemo.model.User; import com.soulballad.usage.arthasdemo.util.SpringContextUtil; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @RestController public class UserController { private static final Logger LOGGER = LoggerFactory.getLogger(UserController.class); @GetMapping(value={"/user/{id}"}) public User getUser(@PathVariable Integer id) { if (null == id) { throw new IllegalArgumentException("id can not be null"); } if (id < 1) { // throw new IllegalArgumentException("id must be greater than 1"); return new User(id, "lisi"+id); } return new User(id, "zhangsan"); } }
重新编译
# 使用mc重新编译修改后的文件,这里需要使用 -c 指定类加载器 sc -d com.soulballad.usage.arthasdemo.web.UserController | grep classLoaderHash mc -c 18b4aac2 UserController.java
编译完成会出现一个路径,这个路径就是编译后class文件的位置
使用redefine重新加载
# redefine 后面使用上一步的路径,需要将 \ 转成 / redefine ../UserController.class
更新后结果
4. 更新日志级别
查找类加载器
sc -d *UserController | grep classLoaderHash
查看更新前日志级别
ognl -c 18b4aac2 '@com.soulballad.usage.arthasdemo.web.UserController@LOGGER'
更新日志级别为 DEBUG
ognl -c 18b4aac2 '@com.soulballad.usage.arthasdemo.web.UserController@LOGGER.setLevel(@ch.qos.logback.classic.Level@DEBUG)'
查看更新后日志级别
5. tt获取spring上下文
执行 tt
命令来记录 RequestMappingHandlerAdapter#invokeHandlerMethod
的请求
tt -t org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter invokeHandlerMethod
然后访问 user/1
,arthas 会记录访问时间片(time fragment)
可以用 tt
命令的 -i
参数来指定index,并且用 -w
参数来执行ognl表达式来获取spring context:
tt -i 1000 -w 'target.getApplicationContext()'
可以从 applicationContext 中获取 bean,触发方法调用
tt -i 1000 -w 'target.getApplicationContext().getBean("userController").getUser(2)'