Skip to content
Open
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
b126872
[support qlexpress]
taokan Oct 7, 2024
2b88739
Merge branch 'master' of https://github.com/taokan/arthas
taokan Oct 7, 2024
30fe689
【支持qlexpress表达式】
taokan Oct 8, 2024
efebecd
【支持qlexpress表达式】
taokan Oct 8, 2024
b1a0379
【支持qlexpress表达式】
taokan Oct 8, 2024
a878dc6
【支持qlexpress表达式】
taokan Oct 27, 2024
c780218
【支持qlexpress表达式】
taokan Nov 3, 2024
a54ac65
【支持qlexpress表达式】
taokan Nov 3, 2024
cbdf590
【支持qlexpress表达式】
taokan Nov 3, 2024
ec0f951
【支持qlexpress表达式】
taokan Nov 10, 2024
2dab795
【支持qlexpress表达式】
taokan Nov 10, 2024
44fde5c
【支持qlexpress表达式】
taokan Nov 10, 2024
aebe302
【支持qlexpress表达式】
taokan Nov 10, 2024
7d73a17
【支持qlexpress表达式】
taokan Nov 10, 2024
49d9397
【支持qlexpress表达式】
taokan Dec 1, 2024
87b71d0
【支持qlexpress表达式】
taokan Dec 22, 2024
35899a8
【支持qlexpress表达式】
taokan Dec 22, 2024
5a77e93
【支持qlexpress表达式】yarn revert
taokan Dec 22, 2024
adb0257
【支持qlexpress表达式】pom版本升级
taokan Jan 4, 2025
4f3a518
【支持qlexpress表达式】冲突
taokan Jan 4, 2025
564c11a
【支持qlexpress表达式】冲突
taokan Jan 4, 2025
23c3df1
【支持qlexpress表达式】冲突
taokan Jan 4, 2025
74a84f8
【支持qlexpress表达式】冲突
taokan Jan 4, 2025
92cff37
【支持qlexpress表达式】冲突
taokan Jan 12, 2025
63c648c
【支持qlexpress表达式】冲突
taokan Jan 12, 2025
dac03f5
【支持qlexpress表达式】冲突
taokan Jan 12, 2025
99f4850
【支持qlexpress表达式】冲突
taokan Jan 12, 2025
0de1a3f
Merge branch 'qlexpress' into qlexpress20240112
taokan Jan 12, 2025
ded1ca5
【支持qlexpress表达式】文档
taokan Feb 12, 2025
6dfe5fa
【支持qlexpress表达式】文档
taokan Mar 9, 2025
4bb7b1e
【支持qlexpress表达式】刪除未引用代码
taokan Mar 18, 2025
cd4ce45
【支持qlexpress表达式】刪除未引用代码
taokan Mar 18, 2025
df33bba
【支持qlexpress表达式】刪除未引用代码
taokan Mar 23, 2025
cf8a6ee
【增加gl命令】command命令
taokan Apr 27, 2025
4bc09fb
【增加qlexpress命令】command命令
taokan May 5, 2025
28d99d4
Merge branch 'master' of https://github.com/taokan/arthas
taokan Aug 4, 2025
5a8a935
Merge branch 'master' into qlexpress
taokan Aug 4, 2025
b9f8f56
【qlexpress版本】4.0.0
taokan Aug 9, 2025
8001cf1
【qlexpress版本】4.0.0
taokan Aug 18, 2025
15e0ffd
【qlexpress版本】4.0.0
taokan Aug 18, 2025
993e4e9
qlexpress version 4.0.3
DQinYuan Sep 22, 2025
7c390e6
Update site/docs/doc/options.md
DQinYuan Sep 22, 2025
17ceb67
【qlexpress版本】4.0.0
taokan Sep 26, 2025
e11628f
【qlexpress版本】4.0.0
taokan Sep 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,10 @@
<groupId>ognl</groupId>
<artifactId>ognl</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>QLExpress4</artifactId>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
Expand Down Expand Up @@ -263,6 +267,10 @@
<scope>provided</scope>
<optional>true</optional>
</dependency>
<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.

</dependencies>

</project>
20 changes: 20 additions & 0 deletions core/src/main/java/com/taobao/arthas/core/GlobalOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,26 @@ public class GlobalOptions {
description = STRICT_MESSAGE
)
public static volatile boolean strict = true;
/**
* 是否切换使用表达式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)

summary = "Option to use ognl/qlexpress",
description = "Option to use ognl/qlexpress in commands, default ognl, can change to qlexpress"
)
public static volatile String ExpressType = "ognl";


/**
* 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

summary = "config init when use qlexpress, with json-simple, for example: {\"precise\": true }",
description = ""
)
public static volatile String QLExpressConfig = "";

public static void updateOnglStrict(boolean strict) {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,42 @@
package com.taobao.arthas.core.command.express;

import com.taobao.arthas.core.GlobalOptions;
import com.taobao.arthas.core.command.model.ExpressTypeEnum;


/**
* ExpressFactory
* @author ralf0131 2017-01-04 14:40.
* @author hengyunabc 2018-10-08
*/
public class ExpressFactory {

private static final ThreadLocal<Express> expressRef = new ThreadLocal<Express>() {
@Override
protected Express initialValue() {
return new OgnlExpress();
}
};
private static final ThreadLocal<Express> expressRef = ThreadLocal.withInitial(() -> new OgnlExpress());
private static final ThreadLocal<Express> expressRefQLExpress = ThreadLocal.withInitial(() -> new QLExpress());

/**
* get ThreadLocal Express Object
* @param object
* @return
*/
public static Express threadLocalExpress(Object object) {
if (GlobalOptions.ExpressType.equals(ExpressTypeEnum.QLEXPRESS.getExpressType())) {
return expressRefQLExpress.get().reset().bind(object);
}
return expressRef.get().reset().bind(object);
}

public static Express unpooledExpress(ClassLoader classloader) {
if (classloader == null) {
classloader = ClassLoader.getSystemClassLoader();
}
if (GlobalOptions.ExpressType.equals(ExpressTypeEnum.QLEXPRESS.getExpressType())) {
return new QLExpress(new QLExpressClassLoaderClassResolver(classloader));
}
return new OgnlExpress(new ClassLoaderClassResolver(classloader));
}

public static Express unpooledExpressByOGNL(ClassLoader classloader) {
if (classloader == null) {
classloader = ClassLoader.getSystemClassLoader();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package com.taobao.arthas.core.command.express;

import com.alibaba.arthas.deps.org.slf4j.Logger;
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
import com.alibaba.fastjson2.JSON;
import com.alibaba.qlexpress4.ClassSupplier;
import com.alibaba.qlexpress4.Express4Runner;
import com.alibaba.qlexpress4.InitOptions;
import com.alibaba.qlexpress4.QLOptions;
import com.alibaba.qlexpress4.runtime.ReflectLoader;
import com.alibaba.qlexpress4.security.QLSecurityStrategy;
import com.taobao.arthas.core.GlobalOptions;
import com.taobao.arthas.core.command.model.QLExpressConfigModel;


/**
* @Author TaoKan
* @Date 2024/9/17 6:01 PM
*/
public class QLExpress implements Express {
private static final Logger logger = LoggerFactory.getLogger(QLExpress.class);
private Express4Runner expressRunner;
private QLGlobalContext qlGlobalContext;

private QLOptions qlOptions;

private InitOptions initOptions;

public QLExpress() {
this(QLExpressCustomClassResolver.customClassResolver);
}

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

}

private void initConfig() {
try {
if (GlobalOptions.QLExpressConfig.length() > 0) {
QLOptions.Builder qlOptionsBuilder = QLOptions.builder();
QLExpressConfigModel qlExpressConfigModel = JSON.parseObject(GlobalOptions.QLExpressConfig, QLExpressConfigModel.class);
qlOptionsBuilder.cache(qlExpressConfigModel.isCache());
qlOptionsBuilder.avoidNullPointer(qlExpressConfigModel.isAvoidNullPointer());
qlOptionsBuilder.maxArrLength(qlExpressConfigModel.getMaxArrLength());
qlOptionsBuilder.polluteUserContext(qlExpressConfigModel.isPolluteUserContext());
qlOptionsBuilder.precise(qlExpressConfigModel.isPrecise());
qlOptionsBuilder.timeoutMillis(qlExpressConfigModel.getTimeoutMillis());
qlOptions = qlOptionsBuilder.build();
}else {
qlOptions = QLOptions.DEFAULT_OPTIONS;
}
//4.0设置InitOptions
}catch (Throwable t){
//异常不设置options
logger.error("Error Init Options For QLExpress:", t);
}
}

private void initQLExpress(ClassSupplier classResolver) {
InitOptions.Builder initOptionsBuilder = InitOptions.builder();
initOptionsBuilder.securityStrategy(QLSecurityStrategy.open());
initOptionsBuilder.allowPrivateAccess(true);
initOptionsBuilder.classSupplier(classResolver);
initOptions = initOptionsBuilder.build();
expressRunner = QLExpressRunner.getInstance(initOptions);
}

private void initContext() {
ReflectLoader reflectLoader = new ReflectLoader(initOptions.getSecurityStrategy(), initOptions.getExtensionFunctions(), initOptions.isAllowPrivateAccess());
qlGlobalContext = new QLGlobalContext(reflectLoader);
}

@Override
public Object get(String express) throws ExpressException {
try {
Object result = expressRunner.execute(express, qlGlobalContext, qlOptions);
return result;
} catch (Exception e) {
logger.error("Error during evaluating the expression with QLExpress:", e);
throw new ExpressException(express, e);
}
}

@Override
public boolean is(String express) throws ExpressException {
final Object ret = get(express);
return ret instanceof Boolean && (Boolean) ret;
}

@Override
public Express bind(Object object) {
qlGlobalContext.bindObj(object);
return this;
}

@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

return this;
}

@Override
public Express reset() {
qlGlobalContext.clear();
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.taobao.arthas.core.command.express;

import com.alibaba.qlexpress4.ClassSupplier;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

/**
* @Author TaoKan
* @Date 2024/12/1 7:07 PM
*/
public class QLExpressClassLoaderClassResolver implements ClassSupplier {

private ClassLoader classLoader;

private final Map<String, Optional<Class<?>>> cache = new ConcurrentHashMap<>();

public QLExpressClassLoaderClassResolver(ClassLoader classLoader) {
this.classLoader = classLoader;
}

private Optional<Class<?>> loadClsInner(String clsQualifiedName) {
try {
Class<?> aClass = null;
if (classLoader != null) {
aClass = classLoader.loadClass(clsQualifiedName);
}else {
aClass = Class.forName(clsQualifiedName);
}
return Optional.of(aClass);
} catch (ClassNotFoundException | NoClassDefFoundError e) {
return Optional.empty();
}
}
@Override
public Class<?> loadCls(String className) {
Optional<Class<?>> clsOp = cache.computeIfAbsent(className, this::loadClsInner);
return clsOp.orElse(null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.taobao.arthas.core.command.express;

import com.alibaba.arthas.deps.org.slf4j.Logger;
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
import com.alibaba.qlexpress4.ClassSupplier;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

/**
* @Author TaoKan
* @Date 2024/12/1 7:06 PM
*/
public class QLExpressCustomClassResolver implements ClassSupplier {

public static final QLExpressCustomClassResolver customClassResolver = new QLExpressCustomClassResolver();

private final Map<String, Optional<Class<?>>> cache = new ConcurrentHashMap<>();

private QLExpressCustomClassResolver() {

}

private Optional<Class<?>> loadClsInner(String clsQualifiedName) {
try {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Class<?> aClass = null;
if (classLoader != null) {
aClass = classLoader.loadClass(clsQualifiedName);
} else {
aClass = Class.forName(clsQualifiedName);
}
return Optional.of(aClass);
} catch (ClassNotFoundException | NoClassDefFoundError e) {
return Optional.empty();
}
}
@Override
public Class<?> loadCls(String clsQualifiedName) {
Optional<Class<?>> clsOp = cache.computeIfAbsent(clsQualifiedName, this::loadClsInner);
return clsOp.orElse(null);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.taobao.arthas.core.command.express;


import com.alibaba.qlexpress4.Express4Runner;
import com.alibaba.qlexpress4.InitOptions;

/**
* @Author TaoKan
* @Date 2024/9/22 12:20 PM
*/
public class QLExpressRunner {
private volatile static QLExpressRunner instance = null;
private Express4Runner expressRunner;

private QLExpressRunner(InitOptions initOptions){
expressRunner = new Express4Runner(initOptions);
}

//对外提供静态方法获取对象
public static Express4Runner getInstance(InitOptions initOptions){
//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
if(instance == null){
synchronized (QLExpressRunner.class){
//抢到锁之后再次进行判断是否为null
if(instance == null){
instance = new QLExpressRunner(initOptions);
}
}
}
return instance.expressRunner;
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.taobao.arthas.core.command.express;

import com.alibaba.arthas.deps.org.slf4j.Logger;
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
import com.alibaba.fastjson2.JSON;
import com.alibaba.qlexpress4.exception.PureErrReporter;
import com.alibaba.qlexpress4.runtime.ReflectLoader;
import com.alibaba.qlexpress4.runtime.Value;
import com.alibaba.qlexpress4.runtime.context.ExpressContext;
import com.alibaba.qlexpress4.runtime.data.MapItemValue;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* @Author TaoKan
* @Date 2024/9/22 12:39 PM
*/
public class QLGlobalContext implements ExpressContext {
private static final Logger logger = LoggerFactory.getLogger(QLGlobalContext.class);

private Map<String, Object> context;
private Object object;
private ReflectLoader reflectLoader;

public QLGlobalContext(ReflectLoader reflectLoader) {
this.context = new ConcurrentHashMap<>();
this.reflectLoader = reflectLoader;
}

public void put(String name, Object value){
context.put(name, value);
}

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之前调用清理现成参数

}

public void bindObj(Object object) {
this.object = object;
context.put("object",object);
}
@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.

待讨论点

}
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 的时候隐藏一个 # 替换逻辑要更好

return new MapItemValue(this.context, newVariableName);
}


public Map<String, Object> getContext() {
return context;
}

public void setContext(Map<String, Object> context) {
this.context = context;
}

public Object getObject() {
return object;
}

public void setObject(Object object) {
this.object = object;
}

}
Loading
Loading