Skip to content

Conversation

taokan
Copy link

@taokan taokan commented Dec 22, 2024

背景链接:
Proposal: 表达式体验优化与引擎更换 #2849

改动实现:
新增了qlexpress自己的command,命令名称 qlexpress

参数说明

参数名称 参数说明
express 执行的表达式
[c:] 执行表达式的 ClassLoader 的 hashcode,默认值是 SystemClassLoader
[classLoaderClass:]  指定执行表达式的 ClassLoader 的 class name
[x] 结果对象的展开层次,默认值 1

使用参考:
$ qlexpress 'java.lang.System.out.println("hello~")'

通过 hashcode 指定 ClassLoader:
$classloader -t +-BootstrapClassLoader +-jdk.internal.loader.ClassLoaders$PlatformClassLoader@301ec38b +-com.taobao.arthas.agent.ArthasClassloader@472067c7 +-jdk.internal.loader.ClassLoaders$AppClassLoader@4b85612c +-org.springframework.boot.loader.LaunchedURLClassLoader@7f9a81e8
$ qlexpress -c 7f9a81e8 org.springframework.boot.SpringApplication.logger
注意 hashcode 是变化的,需要先查看当前的 ClassLoader 信息,提取对应 ClassLoader 的 hashcode。
对于只有唯一实例的 ClassLoader 可以通过 class name 指定,使用起来更加方便:
$ qlexpress --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader org.springframework.boot.SpringApplication.logger

和ognl相比语法的区别

命令 ognl qlexpress 是否相同
数组创建 new int[]{1, 2, 3} new int[]{1, 2, 3} 相同
方法调用 object.method(arg1, arg2) object.method(arg1, arg2) 相同
静态变量访问 @java.lang.Math@max(1, 2) java.lang.Math.max(1,2) 不同
非静态变量访问&修改 同样支持.访问属性,但更强调 “对象图导航”,对于根对象(Root Object)可省略变量名,非根对象需用#// 假设user是根对象(Root),直接访问属性Ognl.getValue("name", user); // 等价于user.getName()// 非根对象需用#引用上下文变量Ognl.getValue("#user.age", context, null); // 访问上下文变量的属性// 修改嵌套属性Ognl.setValue("#user.address.city = '上海'", context, null); 支持直接通过.访问属性(自动匹配 getter/setter),也支持调用方法,语法更灵活:// 访问属性(等价于user.getName())String exp1 = "user.name"; // 修改属性(等价于user.setAge(20))String exp2 = "user.age = 20;"; // 链式访问嵌套属性String exp3 = "user.address.city"; 不同
集合的操作 users[0].name users[0].name 或者 users.get(0).getName(); 不同
Lambda表达式 不支持,只有简化版lambda-like s = () -> "你好" 不同
Steam api 不支持 l.filter(i -> i.startsWith("a-")).map(i -> i.split("-")[1]) 不同
投影 users.{? #this.age >= 18} 不支持,通过steam操作实现:users.stream().filter(u -> u.getAge() >= 18).collect(Collectors.toList()) 不同
复杂-map对象 不支持直接创建map m = {"aa": 10,"bb": {"cc": "cc1","dd": "dd1"}} 不同
复杂-嵌套对象 嵌套对象创建与主对象类似,但需注意上下文变量的引用:// 先创建主对象,再创建子对象并赋值 Ognl.setValue("user = new com.example.User()", context, null);Ognl.setValue("user.address = new com.example.Address()", context, null);Ognl.setValue("user.address.city = '北京'", context, null); 支持直接链式创建嵌套对象,语法简洁:user = {'@Class': 'com.example.User','address': {'city': '北京','@Class': 'com.example.Address'} } 不同

option中新增"el"属性配置,支持切换ognl语法为qlexpress
image
使用语言切换成qlexpress
$ option el qlexpress

对于观察表达式,获取观测数据的方法:
OGNL:{params, target, returnObj}
QLEXPRESS:[params, target, returnObj]

常用用法举例
OGNL语法
$ watch demo.MathGame primeFactors '{params, returnObj}' '#cost>200' -x 2
QLEXPRESS4语法
$ watch demo.MathGame primeFactors '[params, returnOb]' 'cost>200' -x 2

更多qlexpress4.0语法链接
https://github.com/alibaba/QLExpress/tree/v4.0.0-beta.1

@CLAassistant
Copy link

CLAassistant commented Dec 22, 2024

CLA assistant check
All committers have signed the CLA.

@hengyunabc
Copy link
Collaborator

缺少测试,有冲突需要解决。

* 是否切换使用表达式ognl/qlexpress开关
*/
@Option(level = 1,
name = "express-type",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

是不是可以考虑短一点,比如就缩写成 el(expression language)

* qlexpress使用参数
*/
@Option(level = 1,
name = "qlexpress-config",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这么复杂的东西暂时没必要交给用户设置吧。我们直接给一个最推荐的就行了,没必要暴露给用户,毕竟这个场景比较单一,给个通用配置就够了

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

public QLExpress(ClassSupplier classResolver) {
initQLExpress(classResolver);
initConfig();
initContext();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

其实这三个函数就是分别初始化三个成员变量,下面这种写法可能更加清晰:

this.expressRunner = initQLExpress(classResolver);
this.qlOptions = initConfig();
this.qlGlobalContext = initContext();

尽量不要使用有副作用的函数

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix


@Override
public Express bind(String name, Object value) {
qlGlobalContext.put(name, value);
Copy link

@DQinYuan DQinYuan Feb 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

个人觉得在 put 的时候加上 "#" 前缀,要比 get 的时候 replace 要好。这样 context 的逻辑更加简单纯粹。replace 可能会导致改变不该改变的字符。

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok


public void clear() {
context.clear();
this.context.put("reflectLoader",reflectLoader);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里的用处是?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

每次bind之前调用清理现成参数

if ((this.reflectLoader != null) && (this.object != null) && !variableName.startsWith("#")) {
return this.reflectLoader.loadField(this.object, variableName, true, PureErrReporter.INSTANCE);
}
String newVariableName = variableName.replace("#","");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

个人觉得在 put 的时候加上前缀,比 get 的时候隐藏一个 # 替换逻辑要更好

@Override
public Value get(Map<String, Object> attachments, String variableName) {
if ((this.reflectLoader != null) && (this.object != null) && !variableName.startsWith("#")) {
return this.reflectLoader.loadField(this.object, variableName, true, PureErrReporter.INSTANCE);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个 api 有点底层,可以考虑继承 ObjectFieldExpressContext,然后额外加一个 map 功能,而不是直接调用 reflectLoader。或者我直接在 Express4Runner 上开个 api

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

待讨论点

+ " ognl -c 5d113a51 '@com.taobao.arthas.core.GlobalOptions@isDump' \n"
+ Constants.WIKI + Constants.WIKI_HOME + "ognl\n"
+ " https://commons.apache.org/proper/commons-ognl/language-guide.html")
+ " ognl '@[email protected](\"hello \\u4e2d\\u6587\")' \n"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

是不是也得有一个类似 ognl 这样的纯执行 qlexpress 表达式的命令

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

待讨论点

@taokan taokan changed the title Qlexpress 表达式支持使用qlexpress4.0 Mar 23, 2025
| verbose | false | This option enables print verbose information |
| strict | true | whether to enable strict mode |

| express-type | ognl | Option to use ognl/qlexpress in commands, default ognl, can change to qlexpress |

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

上次讨论的这个选项的名称是否考虑短一点,比如叫做 el,这样避免用户在使用 qle 时敲的命令太长

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

另外文档中是不是得有一些例子

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

然后 qlexpress 也不用输全名,就写 qle 即可

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

例子可以加一些有特色的,能解决具体问题的,比如复杂对象构造,list新建等等

@DQinYuan
Copy link

DQinYuan commented May 6, 2025

可以再举一个复杂对象构造的例子。
另外 arthas 代码仓库中的 md 文档也需要补充一些常见命令的例子,方便用户查阅

taokan added 3 commits August 4, 2025 21:54
# Conflicts:
#	core/src/main/java/com/taobao/arthas/core/GlobalOptions.java
# Conflicts:
#	core/src/main/java/com/taobao/arthas/core/GlobalOptions.java
@hengyunabc hengyunabc requested a review from Copilot August 10, 2025 03:18
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces support for QLExpress 4.0 as an alternative expression engine to OGNL in Arthas. The implementation adds a new qlexpress command and allows users to switch the default expression language through a global option.

Key changes:

  • Added QLExpress 4.0 dependency and integration classes
  • Implemented new qlexpress command with similar functionality to the existing ognl command
  • Added global option el to switch between OGNL and QLExpress engines
  • Updated documentation to reflect the new expression language options

Reviewed Changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
pom.xml Added QLExpress 4.0 dependency
core/pom.xml Added QLExpress 4.0 dependency with duplicate OGNL entry
GlobalOptions.java Added el option to switch expression languages
ExpressFactory.java Modified to support both OGNL and QLExpress based on global option
QLExpressCommand.java New command implementation for QLExpress functionality
QLExpress*.java Supporting classes for QLExpress integration
BuiltinCommandPack.java Registered new QLExpress command
site/docs/* Updated documentation for new expression language option
Comments suppressed due to low confidence (1)

site/docs/en/doc/options.md:10

  • [nitpick] The table formatting appears inconsistent. The original formatting with spaces should be maintained for better readability: | ---------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|------------------------| ------------- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

core/pom.xml Outdated
Comment on lines 270 to 273
<dependency>
<groupId>ognl</groupId>
<artifactId>ognl</artifactId>
</dependency>
Copy link

Copilot AI Aug 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There appears to be a duplicate OGNL dependency declaration. The OGNL dependency is already declared at line 211-213, making this additional declaration redundant.

Suggested change
<dependency>
<groupId>ognl</groupId>
<artifactId>ognl</artifactId>
</dependency>

Copilot uses AI. Check for mistakes.

import com.taobao.arthas.core.command.express.ExpressFactory;
import com.taobao.arthas.core.command.model.ClassLoaderVO;
import com.taobao.arthas.core.command.model.ObjectVO;
import com.taobao.arthas.core.command.model.OgnlModel;
Copy link

Copilot AI Aug 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import for OgnlModel is unused in this class. Since this class uses QLExpressModel instead, this import should be removed.

Suggested change
import com.taobao.arthas.core.command.model.OgnlModel;

Copilot uses AI. Check for mistakes.

import com.taobao.arthas.core.GlobalOptions;
import com.taobao.arthas.core.advisor.Advice;
import com.taobao.arthas.core.command.model.ExpressTypeEnum;
import ognl.OgnlException;
Copy link

Copilot AI Aug 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import for OgnlException appears incorrect for a QLExpress test class. The test methods declare throwing OgnlException but should likely throw ExpressException instead.

Suggested change
import ognl.OgnlException;

Copilot uses AI. Check for mistakes.

Copy link

@DQinYuan DQinYuan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@hengyunabc
Copy link
Collaborator

测试了一下 qlexpress 同样有 ognl 的 classloader 不能回收问题。

比如以 jdk 8 (jdk 8比较容易复现) 来启动 math-game.jar 。再来 attach 之后执行 :

options el qlexpress
watch demo.MathGame primeFactors '[params, target, returnObj, throwExp]' -x 2

再执行 stop 来彻底退出。

再次 attach, 再执行(注意 forceGc可能要执行多次):

vmtool --action forceGc
classloader -l

可以观察到 ArthasClassloader 的实例没有被回收。

$ classloader -l
 name                                                loadedCount  hash      parent
 BootstrapClassLoader                                2955         null      null
 com.taobao.arthas.agent.ArthasClassloader@2c8c7cef  2381         2c8c7cef  sun.misc.Launcher$ExtClassLoader@677327b6
 com.taobao.arthas.agent.ArthasClassloader@40e52b6c  3121         40e52b6c  sun.misc.Launcher$ExtClassLoader@677327b6

如果没有执行 watch demo.MathGame primeFactors '[params, target, returnObj, throwExp]' -x 2 ,执上面的方式,多次 attach 之后 ArthasClassloader是可以回收的。


heapdump 之后,使用 visualvm 分析:

先查找 com.taobao.arthas.agent.ArthasClassloader 的实例,再查找 gc 根节点:

image

可以发现是 threadlocal 里有引用,导致没有回收。

image

@DQinYuan
Copy link

测试了一下 qlexpress 同样有 ognl 的 classloader 不能回收问题。

比如以 jdk 8 (jdk 8比较容易复现) 来启动 math-game.jar 。再来 attach 之后执行 :

options el qlexpress
watch demo.MathGame primeFactors '[params, target, returnObj, throwExp]' -x 2

再执行 stop 来彻底退出。

再次 attach, 再执行(注意 forceGc可能要执行多次):

vmtool --action forceGc
classloader -l

可以观察到 ArthasClassloader 的实例没有被回收。

$ classloader -l
 name                                                loadedCount  hash      parent
 BootstrapClassLoader                                2955         null      null
 com.taobao.arthas.agent.ArthasClassloader@2c8c7cef  2381         2c8c7cef  sun.misc.Launcher$ExtClassLoader@677327b6
 com.taobao.arthas.agent.ArthasClassloader@40e52b6c  3121         40e52b6c  sun.misc.Launcher$ExtClassLoader@677327b6

如果没有执行 watch demo.MathGame primeFactors '[params, target, returnObj, throwExp]' -x 2 ,执上面的方式,多次 attach 之后 ArthasClassloader是可以回收的。

heapdump 之后,使用 visualvm 分析:

先查找 com.taobao.arthas.agent.ArthasClassloader 的实例,再查找 gc 根节点:

image 可以发现是 threadlocal 里有引用,导致没有回收。 image

我们研究下能不能解决这个问题

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants