Skip to content

Commit fd3ec69

Browse files
taokanDQinYuan
authored andcommitted
feta: support qlexpress. #2978
Co-authored-by: taokankan.tk <taokan1991@aliyun.com> Co-authored-by: DQYuan <932087612@qq.com>
1 parent 896fc7e commit fd3ec69

23 files changed

+766
-27
lines changed

core/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,10 @@
216216
<groupId>ognl</groupId>
217217
<artifactId>ognl</artifactId>
218218
</dependency>
219+
<dependency>
220+
<groupId>com.alibaba</groupId>
221+
<artifactId>qlexpress4</artifactId>
222+
</dependency>
219223
<dependency>
220224
<groupId>org.junit.vintage</groupId>
221225
<artifactId>junit-vintage-engine</artifactId>

core/src/main/java/com/taobao/arthas/core/GlobalOptions.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,15 @@ public class GlobalOptions {
142142
description = STRICT_MESSAGE
143143
)
144144
public static volatile boolean strict = true;
145+
/**
146+
* 是否切换使用表达式ognl/qlexpress开关
147+
*/
148+
@Option(level = 1,
149+
name = "el",
150+
summary = "expression language",
151+
description = "Option to use ognl/qlexpress in commands, default ognl, can change to qlexpress"
152+
)
153+
public static volatile String ExpressType = "ognl";
145154

146155
public static void updateOnglStrict(boolean strict) {
147156
try {

core/src/main/java/com/taobao/arthas/core/command/BuiltinCommandPack.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ private void initCommands(List<String> disabledCommands) {
7777
commandClassList.add(PerfCounterCommand.class);
7878
// commandClassList.add(GroovyScriptCommand.class);
7979
commandClassList.add(OgnlCommand.class);
80+
commandClassList.add(QLExpressCommand.class);
8081
commandClassList.add(MemoryCompilerCommand.class);
8182
commandClassList.add(RedefineCommand.class);
8283
commandClassList.add(RetransformCommand.class);
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package com.taobao.arthas.core.command.basic1000;
2+
3+
import com.alibaba.arthas.deps.org.slf4j.Logger;
4+
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
5+
import com.taobao.arthas.core.command.Constants;
6+
import com.taobao.arthas.core.command.express.Express;
7+
import com.taobao.arthas.core.command.express.ExpressException;
8+
import com.taobao.arthas.core.command.express.ExpressFactory;
9+
import com.taobao.arthas.core.command.model.ClassLoaderVO;
10+
import com.taobao.arthas.core.command.model.ObjectVO;
11+
import com.taobao.arthas.core.command.model.QLExpressModel;
12+
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
13+
import com.taobao.arthas.core.shell.command.CommandProcess;
14+
import com.taobao.arthas.core.util.ClassLoaderUtils;
15+
import com.taobao.arthas.core.util.ClassUtils;
16+
import com.taobao.middleware.cli.annotations.*;
17+
18+
import java.lang.instrument.Instrumentation;
19+
import java.util.Collection;
20+
import java.util.List;
21+
22+
/**
23+
* @Author TaoKan
24+
* @Date 2025/3/23 8:59 PM
25+
*/
26+
@Name("qlexpress")
27+
@Summary("Execute qlexpress expression.")
28+
@Description(Constants.EXAMPLE
29+
+ " qlexpress 'java.lang.System.out.println(\"hello~\")' \n"
30+
+ " qlexpress -x 2 'java.lang.Math.abs(1)' \n"
31+
+ " qlexpress -c 5d113a51 'com.taobao.arthas.core.GlobalOptions.isDump' \n"
32+
+ Constants.WIKI + Constants.WIKI_HOME + "qlexpress\n"
33+
+ " https://github.com/alibaba/QLExpress/tree/v4.0.0-beta.1")
34+
public class QLExpressCommand extends AnnotatedCommand {
35+
private static final Logger logger = LoggerFactory.getLogger(QLExpressCommand.class);
36+
37+
private String express;
38+
private String hashCode;
39+
private String classLoaderClass;
40+
private int expand = 1;
41+
42+
@Argument(argName = "express", index = 0, required = true)
43+
@Description("The qlexpress expression.")
44+
public void setExpress(String express) {
45+
this.express = express;
46+
}
47+
48+
@Option(shortName = "c", longName = "classLoader")
49+
@Description("The hash code of the special class's classLoader, default classLoader is SystemClassLoader.")
50+
public void setHashCode(String hashCode) {
51+
this.hashCode = hashCode;
52+
}
53+
54+
55+
@Option(longName = "classLoaderClass")
56+
@Description("The class name of the special class's classLoader.")
57+
public void setClassLoaderClass(String classLoaderClass) {
58+
this.classLoaderClass = classLoaderClass;
59+
}
60+
61+
62+
@Option(shortName = "x", longName = "expand")
63+
@Description("Expand level of object (1 by default).")
64+
public void setExpand(Integer expand) {
65+
this.expand = expand;
66+
}
67+
@Override
68+
public void process(CommandProcess process) {
69+
Instrumentation inst = process.session().getInstrumentation();
70+
ClassLoader classLoader = null;
71+
if (hashCode != null) {
72+
classLoader = ClassLoaderUtils.getClassLoader(inst, hashCode);
73+
if (classLoader == null) {
74+
process.end(-1, "Can not find classloader with hashCode: " + hashCode + ".");
75+
return;
76+
}
77+
} else if (classLoaderClass != null) {
78+
List<ClassLoader> matchedClassLoaders = ClassLoaderUtils.getClassLoaderByClassName(inst, classLoaderClass);
79+
if (matchedClassLoaders.size() == 1) {
80+
classLoader = matchedClassLoaders.get(0);
81+
} else if (matchedClassLoaders.size() > 1) {
82+
Collection<ClassLoaderVO> classLoaderVOList = ClassUtils.createClassLoaderVOList(matchedClassLoaders);
83+
QLExpressModel qlModel = new QLExpressModel()
84+
.setClassLoaderClass(classLoaderClass)
85+
.setMatchedClassLoaders(classLoaderVOList);
86+
process.appendResult(qlModel);
87+
process.end(-1, "Found more than one classloader by class name, please specify classloader with '-c <classloader hash>'");
88+
return;
89+
} else {
90+
process.end(-1, "Can not find classloader by class name: " + classLoaderClass + ".");
91+
return;
92+
}
93+
} else {
94+
classLoader = ClassLoader.getSystemClassLoader();
95+
}
96+
97+
Express unpooledExpress = ExpressFactory.unpooledExpressByQL(classLoader);
98+
try {
99+
Object value = unpooledExpress.bind(new Object()).get(express);
100+
QLExpressModel qlModel = new QLExpressModel()
101+
.setValue(new ObjectVO(value, expand));
102+
process.appendResult(qlModel);
103+
process.end();
104+
} catch (ExpressException e) {
105+
logger.warn("qlexpress: failed execute express: " + express, e);
106+
process.end(-1, "Failed to execute qlexpress, exception message: " + e.getMessage()
107+
+ ", please check $HOME/logs/arthas/arthas.log for more details. ");
108+
}
109+
}
110+
}

core/src/main/java/com/taobao/arthas/core/command/express/ExpressFactory.java

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
import java.lang.ref.WeakReference;
44

5+
import com.taobao.arthas.core.GlobalOptions;
6+
import com.taobao.arthas.core.command.model.ExpressTypeEnum;
7+
58
/**
69
* ExpressFactory
710
* @author ralf0131 2017-01-04 14:40.
@@ -17,23 +20,54 @@ public class ExpressFactory {
1720
*/
1821
private static final ThreadLocal<WeakReference<Express>> expressRef = ThreadLocal
1922
.withInitial(() -> new WeakReference<Express>(new OgnlExpress()));
23+
private static final ThreadLocal<WeakReference<Express>> expressRefQLExpress = ThreadLocal
24+
.withInitial(() -> new WeakReference<Express>(new QLExpress()));
2025

2126
/**
2227
* get ThreadLocal Express Object
2328
* @param object
2429
* @return
2530
*/
2631
public static Express threadLocalExpress(Object object) {
27-
WeakReference<Express> reference = expressRef.get();
32+
if (GlobalOptions.ExpressType.equals(ExpressTypeEnum.QLEXPRESS.getExpressType())) {
33+
return getOrCreateExpress(expressRefQLExpress, QLExpress::new).reset().bind(object);
34+
}
35+
return getOrCreateExpress(expressRef, OgnlExpress::new).reset().bind(object);
36+
}
37+
38+
/**
39+
* 从 WeakReference 中获取 Express,如果已被 GC 回收则重新创建
40+
*/
41+
private static Express getOrCreateExpress(ThreadLocal<WeakReference<Express>> threadLocal,
42+
java.util.function.Supplier<Express> supplier) {
43+
WeakReference<Express> reference = threadLocal.get();
2844
Express express = reference == null ? null : reference.get();
2945
if (express == null) {
30-
express = new OgnlExpress();
31-
expressRef.set(new WeakReference<Express>(express));
46+
express = supplier.get();
47+
threadLocal.set(new WeakReference<Express>(express));
3248
}
33-
return express.reset().bind(object);
49+
return express;
3450
}
3551

3652
public static Express unpooledExpress(ClassLoader classloader) {
53+
if (classloader == null) {
54+
classloader = ClassLoader.getSystemClassLoader();
55+
}
56+
if (GlobalOptions.ExpressType.equals(ExpressTypeEnum.QLEXPRESS.getExpressType())) {
57+
return new QLExpress(new QLExpressClassLoaderClassResolver(classloader));
58+
}
59+
return new OgnlExpress(new ClassLoaderClassResolver(classloader));
60+
}
61+
62+
63+
public static Express unpooledExpressByQL(ClassLoader classloader) {
64+
if (classloader == null) {
65+
classloader = ClassLoader.getSystemClassLoader();
66+
}
67+
return new QLExpress(new QLExpressClassLoaderClassResolver(classloader));
68+
}
69+
70+
public static Express unpooledExpressByOGNL(ClassLoader classloader) {
3771
if (classloader == null) {
3872
classloader = ClassLoader.getSystemClassLoader();
3973
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package com.taobao.arthas.core.command.express;
2+
3+
import com.alibaba.arthas.deps.org.slf4j.Logger;
4+
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
5+
import com.alibaba.qlexpress4.*;
6+
import com.alibaba.qlexpress4.security.QLSecurityStrategy;
7+
8+
9+
/**
10+
* @Author TaoKan
11+
* @Date 2024/9/17 6:01 PM
12+
*/
13+
public class QLExpress implements Express {
14+
private static final Logger logger = LoggerFactory.getLogger(QLExpress.class);
15+
private Express4Runner expressRunner;
16+
private QLGlobalContext qlGlobalContext;
17+
18+
private QLOptions qlOptions;
19+
20+
private InitOptions initOptions;
21+
22+
public QLExpress() {
23+
this(QLExpressCustomClassResolver.customClassResolver);
24+
}
25+
26+
public QLExpress(ClassSupplier classResolver) {
27+
this.initOptions = initQLExpress(classResolver);
28+
this.expressRunner = QLExpressRunner.getInstance(initOptions);
29+
this.qlOptions = initConfig();
30+
this.qlGlobalContext = new QLGlobalContext(expressRunner);
31+
}
32+
33+
private QLOptions initConfig() {
34+
return QLOptions.DEFAULT_OPTIONS;
35+
}
36+
37+
private InitOptions initQLExpress(ClassSupplier classResolver) {
38+
InitOptions.Builder initOptionsBuilder = InitOptions.builder();
39+
initOptionsBuilder.securityStrategy(QLSecurityStrategy.open());
40+
initOptionsBuilder.allowPrivateAccess(true);
41+
initOptionsBuilder.classSupplier(classResolver);
42+
return initOptionsBuilder.build();
43+
}
44+
45+
@Override
46+
public Object get(String express) throws ExpressException {
47+
try {
48+
return expressRunner.execute(express, qlGlobalContext, qlOptions).getResult();
49+
} catch (Exception e) {
50+
logger.error("Error during evaluating the expression with QLExpress:", e);
51+
throw new ExpressException(express, e);
52+
}
53+
}
54+
55+
@Override
56+
public boolean is(String express) throws ExpressException {
57+
final Object ret = get(express);
58+
return ret instanceof Boolean && (Boolean) ret;
59+
}
60+
61+
@Override
62+
public Express bind(Object object) {
63+
qlGlobalContext.bindObj(object);
64+
return this;
65+
}
66+
67+
@Override
68+
public Express bind(String name, Object value) {
69+
qlGlobalContext.put(name, value);
70+
return this;
71+
}
72+
73+
@Override
74+
public Express reset() {
75+
qlGlobalContext.clear();
76+
return this;
77+
}
78+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.taobao.arthas.core.command.express;
2+
3+
import com.alibaba.qlexpress4.ClassSupplier;
4+
import java.util.Map;
5+
import java.util.Optional;
6+
import java.util.concurrent.ConcurrentHashMap;
7+
8+
/**
9+
* @Author TaoKan
10+
* @Date 2024/12/1 7:07 PM
11+
*/
12+
public class QLExpressClassLoaderClassResolver implements ClassSupplier {
13+
14+
private ClassLoader classLoader;
15+
16+
private final Map<String, Optional<Class<?>>> cache = new ConcurrentHashMap<>();
17+
18+
public QLExpressClassLoaderClassResolver(ClassLoader classLoader) {
19+
this.classLoader = classLoader;
20+
}
21+
22+
private Optional<Class<?>> loadClsInner(String clsQualifiedName) {
23+
try {
24+
Class<?> aClass = null;
25+
if (classLoader != null) {
26+
aClass = classLoader.loadClass(clsQualifiedName);
27+
}else {
28+
aClass = Class.forName(clsQualifiedName);
29+
}
30+
return Optional.of(aClass);
31+
} catch (ClassNotFoundException | NoClassDefFoundError e) {
32+
return Optional.empty();
33+
}
34+
}
35+
@Override
36+
public Class<?> loadCls(String className) {
37+
Optional<Class<?>> clsOp = cache.computeIfAbsent(className, this::loadClsInner);
38+
return clsOp.orElse(null);
39+
}
40+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.taobao.arthas.core.command.express;
2+
3+
import com.alibaba.arthas.deps.org.slf4j.Logger;
4+
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
5+
import com.alibaba.qlexpress4.ClassSupplier;
6+
import java.util.Map;
7+
import java.util.Optional;
8+
import java.util.concurrent.ConcurrentHashMap;
9+
10+
/**
11+
* @Author TaoKan
12+
* @Date 2024/12/1 7:06 PM
13+
*/
14+
public class QLExpressCustomClassResolver implements ClassSupplier {
15+
16+
public static final QLExpressCustomClassResolver customClassResolver = new QLExpressCustomClassResolver();
17+
18+
private final Map<String, Optional<Class<?>>> cache = new ConcurrentHashMap<>();
19+
20+
private QLExpressCustomClassResolver() {
21+
22+
}
23+
24+
private Optional<Class<?>> loadClsInner(String clsQualifiedName) {
25+
try {
26+
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
27+
Class<?> aClass = null;
28+
if (classLoader != null) {
29+
aClass = classLoader.loadClass(clsQualifiedName);
30+
} else {
31+
aClass = Class.forName(clsQualifiedName);
32+
}
33+
return Optional.of(aClass);
34+
} catch (ClassNotFoundException | NoClassDefFoundError e) {
35+
return Optional.empty();
36+
}
37+
}
38+
@Override
39+
public Class<?> loadCls(String clsQualifiedName) {
40+
Optional<Class<?>> clsOp = cache.computeIfAbsent(clsQualifiedName, this::loadClsInner);
41+
return clsOp.orElse(null);
42+
}
43+
44+
}

0 commit comments

Comments
 (0)