Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Qlexpress #2978

Open
wants to merge 29 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
29 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
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
Next Next commit
[support qlexpress]
  • Loading branch information
taokan committed Oct 7, 2024
commit b126872f3c28803fd9d43815789edd5940151648
8 changes: 6 additions & 2 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
<source>8</source>
<target>8</target>
<encoding>UTF-8</encoding>
<showDeprecation>true</showDeprecation>
</configuration>
Expand Down Expand Up @@ -222,6 +222,10 @@
<groupId>ognl</groupId>
<artifactId>ognl</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>QLExpress4</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand Down
24 changes: 24 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 @@ -11,6 +11,8 @@ public class GlobalOptions {
+ "not allowed to set object properties. "
+ "Want to set object properties, execute `options strict false`";

public static final String QLEXPRESS_CONFIG = "qlexpress-config";

/**
* 是否支持系统类<br/>
* 这个开关打开之后将能代理到来自JVM的部分类,由于有非常强的安全风险可能会引起系统崩溃<br/>
Expand Down Expand Up @@ -136,4 +138,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"
)
public static volatile String ExpressType = "ognl";


/**
* qlexpress使用参数
*/
@Option(level = 1,
name = QLEXPRESS_CONFIG,
summary = "config init when use qlExpress, example: {\"\": \"\"} ",
description = ""
)
public static volatile String QLExpressConfig = "";
}
Original file line number Diff line number Diff line change
@@ -1,32 +1,57 @@
package com.taobao.arthas.core.command.express;

import com.alibaba.fastjson.JSON;
import com.taobao.arthas.core.GlobalOptions;
import com.taobao.arthas.core.command.model.ExpressTypeEnum;
import com.taobao.arthas.core.command.model.QLExpressConfigModel;

/**
* 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 == 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 == ExpressTypeEnum.QLEXPRESS.getExpressType()) {
return new QLExpress(classloader);
}
return new OgnlExpress(new ClassLoaderClassResolver(classloader));
}

public static Express unpooledExpressByOGNL(ClassLoader classloader) {
if (classloader == null) {
classloader = ClassLoader.getSystemClassLoader();
}
return new OgnlExpress(new ClassLoaderClassResolver(classloader));
}

public static boolean checkQLExpressConfig(String configValue) {
try {
if ("".equals(configValue)) {
return true;
}
JSON.parseObject(configValue, QLExpressConfigModel.class);
return true;
}catch (Throwable t){
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
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.fastjson.JSON;
import com.alibaba.qlexpress4.Express4Runner;
import com.alibaba.qlexpress4.InitOptions;
import com.alibaba.qlexpress4.QLOptions;
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 Object runResult;

private QLExpressConfigModel qlExpressConfigModel;

private QLOptions qlOptions;

private InitOptions initOptions;

public QLExpress() {
initQLExpress();
initConfig(null);
initContext();
}

public QLExpress(ClassLoader classloader) {
initQLExpress();
initConfig(classloader);
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(ClassLoader classloader) {
try {
QLExpressConfigModel qlExpressConfigModel = JSON.parseObject(GlobalOptions.QLExpressConfig, QLExpressConfigModel.class);
this.qlExpressConfigModel = qlExpressConfigModel;
QLOptions.Builder qlOptionsBuilder = QLOptions.builder();
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();

InitOptions.Builder initOptionsBuilder = InitOptions.builder();
initOptionsBuilder.allowPrivateAccess(qlExpressConfigModel.isAllowPrivateAccess());
initOptionsBuilder.debug(qlExpressConfigModel.isDebug());
initOptionsBuilder.useCacheClear(qlExpressConfigModel.isUseCacheClear());
initOptions = initOptionsBuilder.build();
//4.0设置InitOptions
}catch (Throwable t){
//异常不设置options
logger.error("Error Init Options For QLExpress:", t);
}
}

private void initQLExpress() {
expressRunner = QLExpressRunner.getInstance();
}

private void initContext() {
qlGlobalContext = new QLGlobalContext();
}

@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);

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,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(){
expressRunner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
}

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


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

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 {

Choose a reason for hiding this comment

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

考虑继承 ObjectFieldExpressContext 添加额外功能

Choose a reason for hiding this comment

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

new ObjectFieldExpressContext(runner);

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

public QLGlobalContext(Map<String, Object> context, Object bindObject, ReflectLoader reflectLoader) {
this.context = context;
this.object = bindObject;
this.reflectLoader = reflectLoader;
}

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

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

public void clear() {
context.clear();
}

public void bindObj(Object object) {
this.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.

待讨论点

}
return new MapItemValue(this.context, variableName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@
@Name("ognl")
@Summary("Execute ognl expression.")
@Description(Constants.EXAMPLE
+ " ognl '@[email protected](\"hello \\u4e2d\\u6587\")' \n"
+ " ognl -x 2 '@Singleton@getInstance()' \n"
+ " ognl '@Demo@staticFiled' \n"
+ " ognl '#value1=@System@getProperty(\"java.home\"), #value2=@System@getProperty(\"java.runtime.name\"), {#value1, #value2}'\n"
+ " 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.

待讨论点

+ " ognl -x 2 '@Singleton@getInstance()' \n"
+ " ognl '@Demo@staticFiled' \n"
+ " ognl '#value1=@System@getProperty(\"java.home\"), #value2=@System@getProperty(\"java.runtime.name\"), {#value1, #value2}'\n"
+ " 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")
public class OgnlCommand extends AnnotatedCommand {
private static final Logger logger = LoggerFactory.getLogger(OgnlCommand.class);

Expand Down Expand Up @@ -100,7 +100,7 @@ public void process(CommandProcess process) {
classLoader = ClassLoader.getSystemClassLoader();
}

Express unpooledExpress = ExpressFactory.unpooledExpress(classLoader);
Express unpooledExpress = ExpressFactory.unpooledExpressByOGNL(classLoader);
try {
Object value = unpooledExpress.get(express);
OgnlModel ognlModel = new OgnlModel()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.taobao.arthas.core.command.model;

/**
* @Author TaoKan
* @Date 2024/9/22 7:32 AM
*/
public enum ExpressTypeEnum
{
OGNL("ognl"),
QLEXPRESS("QLExpress");

private String expressType;

ExpressTypeEnum(String expressType) {
this.expressType = expressType;
}

public String getExpressType() {
return expressType;
}
}
Loading