spring-projects / spring-framework

Spring Framework
https://spring.io/projects/spring-framework
Apache License 2.0
56.66k stars 38.14k forks source link

SpEL `T()` operator not able to locate user types with default `StandardTypeLocator` configuration #26253

Closed jjpianta closed 1 year ago

jjpianta commented 3 years ago

Hi, we are experiencing such a strange behavior using SPEL T() operator to get references to custom enums or types in our thymeleaf templates: everything goes straight till we get random failures in resolving our custom types. "random" means that same expression sometimes get parsed properly and sometimes don't. It looks like failures occur only in asynch context (StreamingResponseBody's callbacks and @‌Asynch annotated methods)

Same issue submitted to the Thymeleaf team

org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "myVar == T(my.custom.TypeEnum).VALUE)" (template: "myTemplate" - line xx, col yy)
        at org.thymeleaf.spring4.expression.SPELVariableExpressionEvaluator.evaluate(SPELVariableExpressionEvaluator.java:290) ~[thymeleaf-spring4-3.0.11.RELEASE.jar:3.0.11.RELEASE]
        at org.thymeleaf.standard.expression.VariableExpression.executeVariableExpression(VariableExpression.java:166) ~[thymeleaf-3.0.11.RELEASE.jar:3.0.11.RELEASE]
        at org.thymeleaf.standard.expression.SimpleExpression.executeSimple(SimpleExpression.java:66) ~[thymeleaf-3.0.11.RELEASE.jar:3.0.11.RELEASE]
 ...
        at org.thymeleaf.engine.TemplateManager.parseAndProcess(TemplateManager.java:592) ~[thymeleaf-3.0.11.RELEASE.jar:3.0.11.RELEASE]
        at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1098) [thymeleaf-3.0.11.RELEASE.jar:3.0.11.RELEASE]
        at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1059) [thymeleaf-3.0.11.RELEASE.jar:3.0.11.RELEASE]
        at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1048) [thymeleaf-3.0.11.RELEASE.jar:3.0.11.RELEASE]
...
        at org.springframework.aop.interceptor.AsyncExecutionInterceptor$1.call(AsyncExecutionInterceptor.java:115) [spring-aop-4.3.29.RELEASE.jar:4.3.29.RELEASE]
        at java.util.concurrent.FutureTask.run(FutureTask.java:266) [?:1.8.0_201]
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1005E: Type cannot be found 'my.custom.TypeEnum'
        at org.springframework.expression.spel.support.StandardTypeLocator.findType(StandardTypeLocator.java:115) ~[spring-expression-4.3.29.RELEASE.jar:4.3.29.RELEASE]
        at org.springframework.expression.spel.ExpressionState.findType(ExpressionState.java:154) ~[spring-expression-4.3.29.RELEASE.jar:4.3.29.RELEASE]
        at org.springframework.expression.spel.ast.TypeReference.getValueInternal(TypeReference.java:64) ~[spring-expression-4.3.29.RELEASE.jar:4.3.29.RELEASE]
        at org.springframework.expression.spel.ast.CompoundExpression.getValueRef(CompoundExpression.java:52) ~[spring-expression-4.3.29.RELEASE.jar:4.3.29.RELEASE]
        at org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:88) ~[spring-expression-4.3.29.RELEASE.jar:4.3.29.RELEASE]
        at org.springframework.expression.spel.ast.OpEQ.getValueInternal(OpEQ.java:43) ~[spring-expression-4.3.29.RELEASE.jar:4.3.29.RELEASE]
        at org.springframework.expression.spel.ast.OpEQ.getValueInternal(OpEQ.java:32) ~[spring-expression-4.3.29.RELEASE.jar:4.3.29.RELEASE]
        at org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:169) ~[spring-expression-4.3.29.RELEASE.jar:4.3.29.RELEASE]
        at org.springframework.expression.spel.ast.OpAnd.getBooleanValue(OpAnd.java:56) ~[spring-expression-4.3.29.RELEASE.jar:4.3.29.RELEASE]
        at org.springframework.expression.spel.ast.OpAnd.getValueInternal(OpAnd.java:51) ~[spring-expression-4.3.29.RELEASE.jar:4.3.29.RELEASE]
        at org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:169) ~[spring-expression-4.3.29.RELEASE.jar:4.3.29.RELEASE]
        at org.springframework.expression.spel.ast.Ternary.getValueInternal(Ternary.java:51) ~[spring-expression-4.3.29.RELEASE.jar:4.3.29.RELEASE]
        at org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:119) ~[spring-expression-4.3.29.RELEASE.jar:4.3.29.RELEASE]
        at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:327) ~[spring-expression-4.3.29.RELEASE.jar:4.3.29.RELEASE]
        at org.thymeleaf.spring4.expression.SPELVariableExpressionEvaluator.evaluate(SPELVariableExpressionEvaluator.java:263) ~[thymeleaf-spring4-3.0.11.RELEASE.jar:3.0.11.RELEASE]
        ... 53 more

see full stack trace

mqueb commented 3 years ago

got the same bug.

my Cacheable:

@Cacheable(value = "myValue", key = "#myKey", cacheManager = "myManager",
            unless = "#result.erreur != null && #result.erreur.myEnumValue == T(com.xxx.MyEnum).YYY")

work fine calling it without async.....

but if done with

CompletableFuture.supplyAsync(() -> my function())

Error :

2021-03-31T09:22:34.248-04:00 [APP/PROC/WEB/0] [OUT] {"@timestamp":"2021-03-31T13:22:34.247+00:00","@version":1,"message":"Une erreur technique est survenue lors de l'appel \u00E0 InformationTelephoneGateway.","logger_name":"com.xxx.MyServiceimpl","thread_name":"ForkJoinPool.commonPool-worker-1","level":"ERROR","level_value":40000,"stack_trace":"org.springframework.expression.spel.SpelEvaluationException: EL1005E: Type cannot be found 'com.xxx.MyEnum'\n\tat org.springframework.expression.spel.support.StandardTypeLocator.findType(StandardTypeLocator.java:117)\n\tat org.springframework.expression.spel.ExpressionState.findType(ExpressionState.java:155)\n\tat org.springframework.expression.spel.ast.TypeReference.getValueInternal(TypeReference.java:69)\n\tat org.springframework.expression.spel.ast.CompoundExpression.getValueRef(CompoundExpression.java:55)\n\tat org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:91)\n\tat org.springframework.expression.spel.ast.OpEQ.getValueInternal(OpEQ.java:43)\n\tat org.springframework.expression.spel.ast.OpEQ.getValueInternal(OpEQ.java:32)\n\tat org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:188)\n\tat org.springframework.expression.spel.ast.OpAnd.getBooleanValue(OpAnd.java:57)\n\tat org.springframework.expression.spel.ast.OpAnd.getValueInternal(OpAnd.java:52)\n\tat org.springframework.expression.spel.ast.SpelNodeImpl.getTypedValue(SpelNodeImpl.java:117)\n\tat org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:308)\n\tat org.springframework.cache.interceptor.CacheOperationExpressionEvaluator.unless(CacheOperationExpressionEvaluator.java:113)\n\tat org.springframework.cache.interceptor.CacheAspectSupport$CacheOperationContext.canPutToCache(CacheAspectSupport.java:783)\n\tat org.springframework.cache.interceptor.CacheAspectSupport$CachePutRequest.apply(CacheAspectSupport.java:835)\n\tat org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:430)\n\tat org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:346)\n\tat org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:61)\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)\n\tat org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)\n\tat org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95)\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)\n\tat org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)\n\tat org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:691)\n\tat

stupid workarround:

@Cacheable(value = "infoTelephones", key = "#numeroTelephone", cacheManager = "infoTelephonesCacheManager",
            unless = "#result.notCacheable")

and move the test in the method added on the object...

jjpianta commented 3 years ago

It turned out it was an issue with StandardTypeLocator used by Thymeleaf Context: dunno why, but sometimes it gets created with System classloader, so it can't resolve our app types. We made a dirty fix supplying a TypeLocator initialized with our web app's classloader

        Context ctx = new Context(locale);
        StandardEvaluationContext delegate = new StandardEvaluationContext();
        StandardTypeLocator tl = new StandardTypeLocator(MyClass.class.getClassLoader());
        delegate.setTypeLocator(tl);
        EvaluationContext evaluationContext = new ThymeleafEvaluationContextWrapper(delegate);

        ctx.setVariable(ThymeleafEvaluationContext.THYMELEAF_EVALUATION_CONTEXT_CONTEXT_VARIABLE_NAME,
                evaluationContext);
vvvinamer commented 2 years ago

Hey @jjpianta, have we got any updates regarding this? Can this be solved without some dirty work around?

jjpianta commented 2 years ago

Hi @vvvinamer. No, here we have no update, we're still going with the dirty fix.

Cheers J.J.

On Mon, 16 May 2022 at 20:54, vvvinamer @.***> wrote:

Hey @jjpianta https://github.com/jjpianta, have we got any updates regarding this? Can this be solved without some dirty work around?

— Reply to this email directly, view it on GitHub https://github.com/spring-projects/spring-framework/issues/26253#issuecomment-1128021916, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABVFRMXUWW4GYN5XVHYEMXLVKKKWVANCNFSM4UUY53YQ . You are receiving this because you were mentioned.Message ID: @.***>

VuKrampHub commented 1 year ago

I still run into this as of 2023 with java 18 + spring boot 2.7.7 (spring framework 5.3.24)

sbrannen commented 1 year ago

We made a dirty fix supplying a TypeLocator initialized with our web app's classloader

I don't necessarily consider that a "dirty fix" but rather a robust solution.

The SpEL expression parser needs to be able to reliably locate user types, and providing a suitable ClassLoader serves that purpose.

sbrannen commented 1 year ago

This issue has been repurposed to improve the documentation regarding proper configuration of the StandardTypeLocator.

See https://github.com/spring-projects/spring-framework/commit/10de295a7216fe51566bb977714953ffcdd17574 for details.