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

修改默认EL表达式解析 #485

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open

Conversation

cyb233
Copy link

@cyb233 cyb233 commented Mar 14, 2023

What kind of change does this PR introduce? (check at least one)

  • Bugfix
  • Feature
  • Code style
  • Refactor
  • Doc
  • Other, please describe:

The description of the PR:

  1. 修改ParserContext默认实现,解决诸如@DS("#{T(pkg.A).B()}")报错org.springframework.expression.spel.SpelParseException: Expression [#{T(pkg.A).B()}] @1: EL1043E: Unexpected token. Expected 'identifier' but was 'lcurly({)',否则用EL表达式还得初始化BEAN也太没必要了
  2. 数据库名肯定是字符串,所以可以大胆点指定Class

Other information:
4ddfc4d

@cyb233 cyb233 changed the title 1. 修改ParserContext默认实现,解决@DS("#{T(pkg.A).B()}")报错`org.springframewo… 默认EL表达式解析bug Mar 14, 2023
@cyb233 cyb233 mentioned this pull request Mar 14, 2023
6 tasks
@cyb233 cyb233 changed the title 默认EL表达式解析bug 修改默认EL表达式解析 Mar 14, 2023
@cyb233
Copy link
Author

cyb233 commented Mar 15, 2023

https://github.com/baomidou/dynamic-datasource-spring-boot-starter/blob/d0300b5bef1a056a94c9c4cbd1a552a18927f464/core/src/main/java/com/baomidou/dynamic/datasource/aop/DynamicDataSourceAnnotationInterceptor.java#L58
这要求key以#开头才会进一步走DsProcessor
https://github.com/baomidou/dynamic-datasource-spring-boot-starter/blob/89d81b9b4b34f43140b4d8759b731e43f51d54a8/core/src/main/java/com/baomidou/dynamic/datasource/processor/DsSpelExpressionProcessor.java#L52-L68 https://github.com/baomidou/dynamic-datasource-spring-boot-starter/blob/89d81b9b4b34f43140b4d8759b731e43f51d54a8/core/src/main/java/com/baomidou/dynamic/datasource/processor/DsSpelExpressionProcessor.java#L83
这又导致#开头的key不能直接正确解析

这样一来想正常使用EL表达式就必须写一个Bean处理,我认为挺多余的,又不是存在不符合默认约定的特殊处理逻辑一定要写Bean

    @Bean
    public DsProcessor dsProcessor() {
        DsHeaderProcessor dsHeaderProcessor = new DsHeaderProcessor();
        DsSessionProcessor dsSessionProcessor = new DsSessionProcessor();
        DsSpelExpressionProcessor dsSpelExpressionProcessor = new DsSpelExpressionProcessor();
        dsSpelExpressionProcessor.setParserContext(ParserContext.TEMPLATE_EXPRESSION);
        dsHeaderProcessor.setNextProcessor(dsSessionProcessor);
        dsSessionProcessor.setNextProcessor(dsSpelExpressionProcessor);
        return new DsProcessor() {
            @Override
            public boolean matches(String s) {
                return false;
            }

            @Override
            public String determineDatasource(MethodInvocation invocation, String key) {
                return dsHeaderProcessor.determineDatasource(invocation, key);
            }

            @Override
            public String doDetermineDatasource(MethodInvocation methodInvocation, String s) {
                return null;
            }
        };
    }

@huayanYu
Copy link
Member

#199 有点不太懂到底该怎么样

@cyb233
Copy link
Author

cyb233 commented Jul 21, 2023

测试:

    public static void main(String[] args) {
        String spel1 = "T(Integer).MAX_VALUE";
        String spel2 = "new java.util.Date()";
        ExpressionParser parser = new SpelExpressionParser();
        // 这是你 DsSpelExpressionProcessor 里定义的
        ParserContext parserContext = new ParserContext() {
            @Override
            public boolean isTemplate() {
                return false;
            }
            @Override
            public String getExpressionPrefix() {
                return null;
            }
            @Override
            public String getExpressionSuffix() {
                return null;
            }
        };
        // 但实际上根据 DynamicDataSourceAutoConfiguration 来看,匹配流程是
        // 常量key --> #开头的key,而#开头的key匹配顺序是
        // headerProcessor --> sessionProcessor --> spelExpressionProcessor
        // 所以这时候的spel表达式必然是以#开头的key
        // 所以有三种思路:
        // 1, key写为 #SPEL,ParserContext 直接以 # 为 getExpressionPrefix 的返回值,
        //    但是会抛异常 SpelEvaluationException: EL1006E: Function 'T' could not be found,所以这个思路pass掉
        // 2, key写为 #{SPEL},即直接设计以 #{} 额外包裹SPEL表达式,这样直接使用 ParserContext.TEMPLATE_EXPRESSION 即可
        // 3, key.substring(1),即手动裁剪掉#,然后用全 null 的 ParserContext
        // 我这个pr倾向于思路2

        // 思路3的流程
        int result3 = parser.parseExpression(("#" + spel1).substring(1), parserContext).getValue(int.class);
        System.out.println(result3 == Integer.MAX_VALUE);

        Date result4 = parser.parseExpression(("#" + spel2).substring(1), parserContext).getValue(Date.class);
        System.out.println(result4);

        // 思路2的流程
        parserContext = ParserContext.TEMPLATE_EXPRESSION;
        int result1 = parser.parseExpression("#{" + spel1 + "}", parserContext).getValue(int.class);
        System.out.println(result1 == Integer.MAX_VALUE);

        Date result2 = parser.parseExpression("#{" + spel2 + "}", parserContext).getValue(Date.class);
        System.out.println(result2);

        // 思路1会报错
        parserContext = new ParserContext() {
            @Override
            public boolean isTemplate() {
                return false;
            }
            @Override
            public String getExpressionPrefix() {
                return "#";
            }
            @Override
            public String getExpressionSuffix() {
                return null;
            }
        };
        try {
            int result5 = parser.parseExpression(("#" + spel1), parserContext).getValue(int.class);
            System.out.println(result5 == Integer.MAX_VALUE);
        } catch (SpelEvaluationException e) {
            e.printStackTrace();
        }
        try {
            Date result6 = parser.parseExpression(("#" + spel2), parserContext).getValue(Date.class);
            System.out.println(result6);
        } catch (SpelEvaluationException e) {
            e.printStackTrace();
        }
    }

输出:

true
Fri Jul 21 11:29:25 CST 2023
true
Fri Jul 21 11:29:25 CST 2023
org.springframework.expression.spel.SpelEvaluationException: EL1006E: Function 'T' could not be found
	at org.springframework.expression.spel.ast.FunctionReference.getValueInternal(FunctionReference.java:71)
	at org.springframework.expression.spel.ast.CompoundExpression.getValueRef(CompoundExpression.java:55)
	at org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:91)
	at org.springframework.expression.spel.ast.SpelNodeImpl.getTypedValue(SpelNodeImpl.java:117)
	at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:178)
	at cn.onki.data.DataApplicationTests.main(DataApplicationTests.java:131)
Exception in thread "main" org.springframework.expression.spel.SpelParseException: EL1041E: After parsing a valid expression, there is still more data in the expression: 'java'
	at org.springframework.expression.spel.standard.InternalSpelExpressionParser.doParseExpression(InternalSpelExpressionParser.java:135)
	at org.springframework.expression.spel.standard.SpelExpressionParser.doParseExpression(SpelExpressionParser.java:61)
	at org.springframework.expression.spel.standard.SpelExpressionParser.doParseExpression(SpelExpressionParser.java:33)
	at org.springframework.expression.common.TemplateAwareExpressionParser.parseExpression(TemplateAwareExpressionParser.java:52)
	at cn.onki.data.DataApplicationTests.main(DataApplicationTests.java:137)

@cyb233
Copy link
Author

cyb233 commented Jul 21, 2023

#199 有点不太懂到底该怎么样

#199 (comment)
这不也是说用ParserContext.TEMPLATE_EXPRESSION

@huayanYu
Copy link
Member

反正合并你这个不会影响绝大部分用户吧. 我其实不懂SPEL这么深0 0, 我一般就是#obg.name最多.
没问题你就解决下冲突

@cyb233
Copy link
Author

cyb233 commented Jul 21, 2023

反正合并你这个不会影响绝大部分用户吧. 我其实不懂SPEL这么深0 0, 我一般就是#obg.name最多. 没问题你就解决下冲突

我能看看你文档里对应spel部分怎么写的不?我没买,对照确认下

但凡要用到SPEL表达式的,除了像你这样光调变量#obj.name#obj.method()的,其他的用法都要注入Bean覆盖你的配置
所以会存在一定影响,但我不能确定影响范围,总体上需要用SPEL表达式的用户应该不多吧?要不然应该早就有人该跟你提到这个问题了

覆盖后#obj.name#obj.method()用起来也是和上面示例一样,变成#{#obj.name}#{#obj.method()},因为你实际上需要的是明确定义SPEL表达式的标识符 ParserContext,即告诉SpelExpressionParser你的SPEL表达式将由getExpressionPrefix()开始,由getExpressionSuffix()结束

你之前#obj.name能解析是因为刚好是#开头,但不是所有SPEL表达式都是由#开头,例如调用静态方法T(pkg.A).B()时,以及通过Spring的@Value注解读取配置时常见的${spring.scheduled},甚至直接是语句new java.util.Date()

@cyb233 cyb233 closed this Jul 22, 2023
@cyb233 cyb233 force-pushed the master branch 2 times, most recently from 57e3834 to cd81af0 Compare July 22, 2023 09:55
…expression.spel.SpelParseException: Expression [#{T(pkg.A).B()}] @1: EL1043E: Unexpected token. Expected 'identifier' but was 'lcurly({)'`
@cyb233 cyb233 reopened this Jul 22, 2023
@cyb233
Copy link
Author

cyb233 commented Jul 22, 2023

同时用#{}还避免了遇到 DsHeaderProcessor#headerDsSessionProcessor#session 两个PREFIX时遇到同名变量的可能

@cyb233
Copy link
Author

cyb233 commented Jul 24, 2023

反正合并你这个不会影响绝大部分用户吧. 我其实不懂SPEL这么深0 0, 我一般就是#obg.name最多. 没问题你就解决下冲突

周末已经解决好冲突了

修改为TEMPLATE_EXPRESSION后,正确的用法包括不限于以下
#{'str'} //字符串
#{#obj.name} //变量属性,你原来的写法属于是弄巧成拙了
#{T(pkg.A).B()} //调用方法
#{new java.util.Date()} //构建对象
#{${spring.datasource.dynamic.datasource.dbname.url}} //配置文件参数
#{true ? 'dbA' : 'dbB'} //三元表达式

可以参考一些中文文档例如
https://www.w3schools.cn/spring_expression_language/index.html

@huayanYu
Copy link
Member

嗯,我得考虑下

@c834292137
Copy link

这个pr还考虑合并吗

@volcanoliu
Copy link

如果用 DsSpelExpressionProcessor 使用 ParserContext.TEMPLATE_EXPRESSION 的话,在 org.springframework.expression.common.TemplateAwareExpressionParser.parseExpressions() 里会检查是否有前后缀,如果没有就把整体当做 spel 的结果输出了,后果就是 #param.field 这种用法就失效了。
如果考虑兼容性的话,还是在 DsSpelExpressionProcessor.doDetermineDatasource() 里,parse 之前对 key substring(1)。

@cyb233
Copy link
Author

cyb233 commented Sep 11, 2024

后果就是 #param.field 这种用法就失效了。

其实还就是历史包袱拖累的问题,这个问题最早因为没有采用标准的SPEL表达式而产生,在数年前出现相关issue后没有及时查清SPEL的恰当用法,最终让一次改正的包袱变得沉重。。。

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