在Arthas中可以通过ognl
表达式在Java程序的运行阶段获取Java类的静态属性值、调用静态方法、new出对象操作成员属性和方法等操作。
这些能力可以用于排查线上一些奇怪问题,比如感觉线上某个静态属性值不对,可以通过ognl
表达式获取对应静态属性值查看,又或者在Spring项目中注入的对象都是单例的,可以通过getBean(name)
的方式获取到具体的单例对象,然后对这个单例对象进行操作,同时也能调用对象中的一些方法,在不提供http接口的情况下实现某些特定的线上测试,本文会对ognl
表达式的一些用法做详细说明。
PS:我这里会使用到SpringBoot
工程,在后面会演示一个如何通过ognl
表达式获取Nacos
服务列表的操作。
官网地址:https://arthas.gitee.io/doc
ognl express -c {hashCode} --classLoaderClass {当前的全路径 ClassLoader 信息} -x {number}
参数名称 | 参数说明 |
---|---|
express | 执行的表达式 |
[c:] | 执行表达式的 ClassLoader 的 hashcode,默认值是 SystemClassLoader |
[classLoaderClass:] | 指定执行表达式的 ClassLoader 的 class name |
[x] | 结果对象的展开层次,默认值 1 |
在基础操作中会说明如果操作静态属性和方法,以及通过静态属性对象操作成员属性和方法。
ognl
表达式获取静态属性和调用静态方法public class OgnlDemo01 {
public static String s1 = "s1-public-static-v";
private static String s2 = "s2-private-static-v";
public static OgnlDemo01 ognlDemo01 = new OgnlDemo01();
public static void printS12() {
System.out.println( s1 + "----" + s2);
}
public static String getS12() {
return s1 + "----" + s2;
}
public static String setS12(String s1, String s2) {
OgnlDemo01.s1 = s1;
OgnlDemo01.s2 = s2;
return s1 + "----" + s2;
}
}
ognl
表达式[arthas@1176]$ ognl '@com.kerwin.arthas.demo.OgnlDemo01@s1'
@String[s1-public-static-v]
[arthas@1176]$ ognl '@com.kerwin.arthas.demo.OgnlDemo01@s2'
@String[s2-private-static-v]
[arthas@1176]$ ognl '@com.kerwin.arthas.demo.OgnlDemo01@ognlDemo01'
@OgnlDemo01[
s1=@String[s1-public-static-v],
s2=@String[s2-private-static-v],
ognlDemo01=@OgnlDemo01[OgnlDemo01()],
]
ognl
表达式[arthas@1176]$ ognl '@com.kerwin.arthas.demo.OgnlDemo01@printS12()'
null
[arthas@1176]$ ognl '@com.kerwin.arthas.demo.OgnlDemo01@getS12()'
@String[s1-public-static-v----s2-private-static-v]
[arthas@6672]$ ognl '@com.kerwin.arthas.demo.OgnlDemo01@setS12("my-s1","my-s2")'
@String[my-s1----my-s2]
ognl
表达式操作对象的非静态属性和非静态方法 要操作非静态属性前提一定是对象已经被new出来,我们通过一个入口找到这个被创建的对象从而操作对象的属性和方法,就比如Spring中可以通过上下文对象获取到一个指定的对象,从而进行操作,又或者通过ognl
表达式直接创建一个对象,然后对这个对象进行操作,又或者使用静态属性对象操作,这里演示会使用静态属性对象ognlDemo02
操作方式都差不多,在后续会对其它方式也做讲解。
public class OgnlDemo02 {
public static String s1="s1-public-static-v";
public String s2="s2-public-v";
private String s3="s3-private-v";
public static OgnlDemo02 ognlDemo02=new OgnlDemo02();
public String getS1() {
return OgnlDemo02.s1;
}
public void setS1(String s1) {
OgnlDemo02.s1 = s1;
}
public String getS2() {
return s2;
}
public void setS2(String s2) {
this.s2 = s2;
}
public String getS3() {
return s3;
}
public void setS3(String s3) {
this.s3 = s3;
}
}
ognl
表达式ognlDemo02
对象直接获取公有非静态属性值(这种方法没法获取静态属性)[arthas@1176]$ ognl '@[email protected]'
@String[s2-public-v]
ognlDemo02
对象直接获取私有非静态属性值(这种方法没法获取静态属性)[arthas@1176]$ ognl '@[email protected]'
@String[s3-private-v]
ognlDemo02
对象的成员方法获取对应属性值[arthas@1176]$ ognl '@[email protected]()'
@String[s1-public-static-v]
[arthas@1176]$ ognl '@[email protected]()'
@String[s2-public-v]
ognl
表达式ognlDemo02
对象调用非静态方法[arthas@1176]$ ognl '@[email protected]()'
@String[s1-public-static-v]
[arthas@1176]$ ognl '@[email protected]()'
@String[s2-public-v]
[arthas@1176]$ ognl '@[email protected]("my-s2")'
null
[arthas@1176]$ ognl '@[email protected]()'
@String[my-s2]
ognl
表达式创建一个对象,并对这个对象进行操作public class User {
private Long uid;
private String nickName;
public User(Long uid, String nickName) {
this.uid = uid;
this.nickName = nickName;
}
public Long getUid() {
return uid;
}
public void setUid(Long uid) {
this.uid = uid;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
}
ognl
表达式# 创建一个User对象什么也不做
[arthas@27880]$ ognl 'new com.kerwin.arthas.demo.User(100,"kerwin")'
@User[
uid=@Long[100],
nickName=@String[kerwin],
]
# 创建一个User对象赋值给user,后续可以在别的地方对创建出来的对象进行操作
[arthas@27880]$ ognl '#user = new com.kerwin.arthas.demo.User(100,"kerwin")'
@User[
uid=@Long[100],
nickName=@String[kerwin],
]
[arthas@27880]$ ognl 'new com.kerwin.arthas.demo.User(10001,"kerwin").getNickName()'
@String[kerwin]
]
[arthas@27880]$ ognl '#user = new com.kerwin.arthas.demo.User(10001,"kerwin").getNickName()'
@String[kerwin]
]
[arthas@27880]$ ognl '#user = new com.kerwin.arthas.demo.User(10001,"kerwin"),#user.getNickName()'
@String[kerwin]
]
ognl
表达式public class OgnlDemo03 {
public static OgnlDemo03 ognlDemo03 = new OgnlDemo03();
private User user;
private static User staticUser;
private static List<String> lists;
private static Map<String,String> maps;
public User setUser(User user){
this.user = user;
return user;
}
public static User setStaticUser(User user){
OgnlDemo03.staticUser = user;
return user;
}
public static User getMyUser(){
return new User(10002L,"kerwin2");
}
public static User changeUser(User user){
return new User(user.getUid(),user.getNickName()+"---changeUser");
}
public static List<String> setLists(List<String> lists){
OgnlDemo03.lists = lists;
return lists;
}
public static Map<String,String> setMaps(Map<String,String> maps){
OgnlDemo03.maps = maps;
return maps;
}
}
User
对象将这个对象作为参数传入静态方法中[arthas@24600]$ ognl '#user = new com.kerwin.arthas.demo.User(10001,"kerwin"),@com.kerwin.arthas.demo.OgnlDemo03@setStaticUser(#user)'
@User[
uid=@Long[10001],
nickName=@String[kerwin],
]
User
对象,将这个对象作为参数传入静态属性ognlDemo03
对象setUser(user)
方法中[arthas@24600]$ ognl '#user = new com.kerwin.arthas.demo.User(10001,"kerwin"),@[email protected](#user)'
@User[
uid=@Long[10001],
nickName=@String[kerwin],
]
User
对象,在创建一个OgnlDemo03
对象,将创建的User
对象传入OgnlDemo03
对象的setUser(user)
方法[arthas@24600]$ ognl '#user = new com.kerwin.arthas.demo.User(10001,"kerwin"),new com.kerwin.arthas.demo.OgnlDemo03().setUser(#user)'
@User[
uid=@Long[10001],
nickName=@String[kerwin],
]
getMyUser()
方法获取User
对象作为changeUser(user)
的入参# {#user1,#user2} 代表将user1、user2这两个对象作为数组输出在控制台,因为这里没有加-x 2默认展开层级为1所以输出的是对象内存地址
[arthas@18904]$ ognl '#user1 = @com.kerwin.arthas.demo.OgnlDemo03@getMyUser(),#user2 = @com.kerwin.arthas.demo.OgnlDemo03@changeUser(#user1),{#user1,#user2}'
@ArrayList[
@User[com.kerwin.arthas.demo.User@30833e75],
@User[com.kerwin.arthas.demo.User@70ca5419],
]
# 加上-x 2 可以展开数组内部对象
[arthas@18904]$ ognl '#user1 = @com.kerwin.arthas.demo.OgnlDemo03@getMyUser(),#user2 = @com.kerwin.arthas.demo.OgnlDemo03@changeUser(#user1),{#user1,#user2}' -x 2
@ArrayList[
@User[
uid=@Long[10002],
nickName=@String[kerwin2],
],
@User[
uid=@Long[10002],
nickName=@String[kerwin2---changeUser],
],
]
[arthas@18904]$ ognl '@com.kerwin.arthas.demo.OgnlDemo03@setLists({"k1","k2","k3"})'
@ArrayList[
@String[k1],
@String[k2],
@String[k3],
]
[arthas@18904]$ ognl '#map = #{"id":10003L,"nickName":"k3"},@com.kerwin.arthas.demo.OgnlDemo03@setMaps(#map)'
@LinkedHashMap[
@String[id]:@Long[10003],
@String[nickName]:@String[k3],
]
ognl
表达式获取Spring上下文中对象,并且进行操作@Service
public class OgnlDemoService {
private String description;
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
@Component
public class SpringApplicationContext implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringApplicationContext.applicationContext = applicationContext;
}
/**
* 通过class获取Bean
*/
public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
/**
* 通过name获取 Bean.
*/
public static Object getBean(String name) {
return applicationContext.getBean(name);
}
}
ognl
表达式OgnlDemoService
对象# 通过beanName获取
[arthas@30000]$ ognl '@com.kerwin.arthas.utils.SpringApplicationContext@getBean("ognlDemoService")'
@OgnlDemoService[
description=null,
]
# 通过class获取
[arthas@30000]$ ognl '#OgnlDemoServiceClass [email protected]@class,@com.kerwin.arthas.utils.Sp
ringApplicationContext@getBean(#OgnlDemoServiceClass)'
@OgnlDemoService[
description=null,
]
OgnlDemoService
对象中变量和方法[arthas@30000]$ ognl '@com.kerwin.arthas.utils.SpringApplicationContext@getBean("ognlDemoService").description'
null
[arthas@30000]$ ognl '@com.kerwin.arthas.utils.SpringApplicationContext@getBean("ognlDemoService").setDescription("HelloW orld")'
null
[arthas@30000]$ ognl '@com.kerwin.arthas.utils.SpringApplicationContext@getBean("ognlDemoService").getDescription()'
@String[Hello World]
ognl
表达式获取Nacos
服务列表 我这里负载均衡器用的是Ribbon
,而Ribbon
会使用SpringClientFactory
来加载每个服务名称独立的上下文信息,并且会存储在NamedContextFactory
中,比如我这里想看当前shopping-order
服务在内存中有那些服务地址,先获取到Spring上下文中的SpringClientFactory
对象,然后调用getLoadBalancer(name)
方法获取到对应服务的负载均衡器,在每个负载均衡器中都实现了BaseLoadBalancer
,BaseLoadBalancer
中的allServerList
变量就存储了内存中的服务列表。
[arthas@18316]$ ognl '@com.kerwin.arthas.utils.SpringApplicationContext@getBean("springClientFactory").getLoadBalancer("shopping-order").allServerList'
@ArrayList[
@NacosServer[172.16.8.106:49953],
@NacosServer[172.16.8.106:8889],
]