Arthas中ognl表达式使用详解,以及通过ognl表达式获取Spring项目上下文中对象参数信息
Arthas中ognl表达式使用详解,以及通过ognl表达式获取Spring项目上下文中对象参数信息,获取Nacos服务列表
前言
在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 |
一、基础操作
在基础操作中会说明如果操作静态属性和方法,以及通过静态属性对象操作成员属性和方法。
1.1、使用ognl
表达式获取静态属性和调用静态方法
1.1.1、测试代码
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;
}
}
1.1.2、获取静态属性ognl
表达式
- 1、获取公有静态属性
[arthas@1176]$ ognl '@com.kerwin.arthas.demo.OgnlDemo01@s1'
@String[s1-public-static-v]
- 2、获取私有静态属性(私有属性同样可以获取到)
[arthas@1176]$ ognl '@com.kerwin.arthas.demo.OgnlDemo01@s2'
@String[s2-private-static-v]
- 3、获取公有静态属性(自定义对象)
[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()],
]
1.1.3、调用静态方法ognl
表达式
- 1、调用静态方法(无返回值):可以在控制台看见输出打印
[arthas@1176]$ ognl '@com.kerwin.arthas.demo.OgnlDemo01@printS12()'
null
- 2、调用静态方法(有返回值)
[arthas@1176]$ ognl '@com.kerwin.arthas.demo.OgnlDemo01@getS12()'
@String[s1-public-static-v----s2-private-static-v]
- 3、调用静态方法(有返回值,且有入参):调用方法后属性值被改变
[arthas@6672]$ ognl '@com.kerwin.arthas.demo.OgnlDemo01@setS12("my-s1","my-s2")'
@String[my-s1----my-s2]
1.2、使用ognl
表达式操作对象的非静态属性和非静态方法
要操作非静态属性前提一定是对象已经被new出来,我们通过一个入口找到这个被创建的对象从而操作对象的属性和方法,就比如Spring中可以通过上下文对象获取到一个指定的对象,从而进行操作,又或者通过ognl
表达式直接创建一个对象,然后对这个对象进行操作,又或者使用静态属性对象操作,这里演示会使用静态属性对象ognlDemo02
操作方式都差不多,在后续会对其它方式也做讲解。
1.2.1、测试代码
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;
}
}
1.2.2、获取非静态属性ognl
表达式
- 1、通过静态属性
ognlDemo02
对象直接获取公有非静态属性值(这种方法没法获取静态属性)
[arthas@1176]$ ognl '@com.kerwin.arthas.demo.OgnlDemo02@ognlDemo02.s2'
@String[s2-public-v]
- 2、通过静态属性
ognlDemo02
对象直接获取私有非静态属性值(这种方法没法获取静态属性)
[arthas@1176]$ ognl '@com.kerwin.arthas.demo.OgnlDemo02@ognlDemo02.s3'
@String[s3-private-v]
- 3、通过静态属性
ognlDemo02
对象的成员方法获取对应属性值
[arthas@1176]$ ognl '@com.kerwin.arthas.demo.OgnlDemo02@ognlDemo02.getS1()'
@String[s1-public-static-v]
[arthas@1176]$ ognl '@com.kerwin.arthas.demo.OgnlDemo02@ognlDemo02.getS2()'
@String[s2-public-v]
1.2.3、调用非静态方法ognl
表达式
- 1、通过静态属性
ognlDemo02
对象调用非静态方法
[arthas@1176]$ ognl '@com.kerwin.arthas.demo.OgnlDemo02@ognlDemo02.getS1()'
@String[s1-public-static-v]
[arthas@1176]$ ognl '@com.kerwin.arthas.demo.OgnlDemo02@ognlDemo02.getS2()'
@String[s2-public-v]
[arthas@1176]$ ognl '@com.kerwin.arthas.demo.OgnlDemo02@ognlDemo02.setS2("my-s2")'
null
[arthas@1176]$ ognl '@com.kerwin.arthas.demo.OgnlDemo02@ognlDemo02.getS2()'
@String[my-s2]
二、进阶操作
2.1、使用ognl
表达式创建一个对象,并对这个对象进行操作
2.1.1、测试代码
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;
}
}
2.1.2、ognl
表达式
- 1、创建一个对象
# 创建一个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],
]
- 2、调用创建出来的对象中的成员方法
[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]
]
2.2、方法复杂入参ognl
表达式
2.2.1、测试代码
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;
}
}
2.2.2、自定义对象入参
- 1、创建一个
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],
]
- 2、创建一个
User
对象,将这个对象作为参数传入静态属性ognlDemo03
对象setUser(user)
方法中
[arthas@24600]$ ognl '#user = new com.kerwin.arthas.demo.User(10001,"kerwin"),@com.kerwin.arthas.demo.OgnlDemo03@ognlDemo03.setUser(#user)'
@User[
uid=@Long[10001],
nickName=@String[kerwin],
]
- 3、创建一个
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],
]
- 4、调用
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],
],
]
2.2.3、数组入参
[arthas@18904]$ ognl '@com.kerwin.arthas.demo.OgnlDemo03@setLists({"k1","k2","k3"})'
@ArrayList[
@String[k1],
@String[k2],
@String[k3],
]
2.2.4、Map入参
[arthas@18904]$ ognl '#map = #{"id":10003L,"nickName":"k3"},@com.kerwin.arthas.demo.OgnlDemo03@setMaps(#map)'
@LinkedHashMap[
@String[id]:@Long[10003],
@String[nickName]:@String[k3],
]
三、实践操作
3.1、使用ognl
表达式获取Spring上下文中对象,并且进行操作
3.1.1、测试代码
- 需要注入IOC容器的Service
@Service
public class OgnlDemoService {
private String description;
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
- 自定义一个获取Spring上下文中对象的工具类
@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);
}
}
3.1.2、ognl
表达式
- 1、获取Spring上下文中的
OgnlDemoService
对象
# 通过beanName获取
[arthas@30000]$ ognl '@com.kerwin.arthas.utils.SpringApplicationContext@getBean("ognlDemoService")'
@OgnlDemoService[
description=null,
]
# 通过class获取
[arthas@30000]$ ognl '#OgnlDemoServiceClass =@com.kerwin.arthas.service.OgnlDemoService@class,@com.kerwin.arthas.utils.Sp
ringApplicationContext@getBean(#OgnlDemoServiceClass)'
@OgnlDemoService[
description=null,
]
- 2、操作
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]
3.2、使用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],
]
3.3、注意事项
如果项目打包后使用Arthas的ognl命令执行相关操作出现ClassNotFoundException
异常,是由于classloader不是默认加载器导致,这里需要自己指定一个类加载器的hash值,这里可以使用sc -d 类路径
获取。
[arthas@18316]$ sc -d com.kerwin.arthas.utils.SpringApplicationContext
class-info com.kerwin.arthas.utils.SpringApplicationContext
code-source file:/data/apps/user-service.jar!/BOOT-INF/classes!/
name com.kerwin.arthas.utils.SpringApplicationContext
isInterface false
isAnnotation false
isEnum false
isAnonymousClass false
isArray false
isLocalClass false
isMemberClass false
isPrimitive false
isSynthetic false
simple-name SpringUtils
modifier final,public
annotation org.springframework.stereotype.Component
interfaces org.springframework.beans.factory.config.BeanFactoryPostProcessor
super-class +-java.lang.Object
class-loader +-org.springframework.boot.loader.LaunchedURLClassLoader@6d5380c2
+-jdk.internal.loader.ClassLoaders$AppClassLoader@8bcc55f
+-jdk.internal.loader.ClassLoaders$PlatformClassLoader@6296474f
classLoaderHash 6d5380c2
Affect(row-cnt:1) cost in 212 ms.
这里可以看到classLoaderHash 6d5380c2
,这个6d5380c2
也就是加载这个类的类加载器hash值,然后再执行ognl命令是通过ognl -c 类加载器hash值
来指定即可。
[arthas@18316]$ ognl -c 6d5380c2 '@com.kerwin.arthas.utils.SpringApplicationContext@getBean("springClientFactory").getLoadBalancer("shopping-order").allServerList'
@ArrayList[
@NacosServer[172.16.8.106:49953],
@NacosServer[172.16.8.106:8889],
]
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)