spring-projects / spring-framework

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

Improve documentation for limitations of `SpelCompilerMode.IMMEDIATE` #33223

Open quaff opened 1 month ago

quaff commented 1 month ago
class org.springframework.expression.spel.standard.SpelCompilerTests$Bean3 cannot be cast to class org.springframework.expression.spel.standard.SpelCompilerTests$Bean2 (org.springframework.expression.spel.standard.SpelCompilerTests$Bean3 and org.springframework.expression.spel.standard.SpelCompilerTests$Bean2 are in unnamed module of loader 'app')
java.lang.ClassCastException: class org.springframework.expression.spel.standard.SpelCompilerTests$Bean3 cannot be cast to class org.springframework.expression.spel.standard.SpelCompilerTests$Bean2 (org.springframework.expression.spel.standard.SpelCompilerTests$Bean3 and org.springframework.expression.spel.standard.SpelCompilerTests$Bean2 are in unnamed module of loader 'app')
    at org.springframework.expression.spel.generated.CompiledExpression00001.getValue(Unknown Source)
    at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:257)
    at org.springframework.expression.spel.standard.SpelCompilerTests.lambda$changingRegisteredVariableTypeDoesNotResultInFailure$1(SpelCompilerTests.java:95)
    at java.base/java.util.stream.ForEachOps$ForEachOp$OfInt.accept(ForEachOps.java:204)
    at java.base/java.util.stream.Streams$RangeIntSpliterator.forEachRemaining(Streams.java:104)
    at java.base/java.util.Spliterator$OfInt.forEachRemaining(Spliterator.java:711)
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
    at java.base/java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:290)
    at java.base/java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:754)
    at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
    at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
    at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
    at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
    at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)

Here is test case

I think we should recompile if possible whilst registered variable type changing detected, Or document this limitation on javadoc of IMMEDIATE.

Related to https://github.com/spring-projects/spring-framework/issues/28043

sbrannen commented 1 month ago

Hi @quaff,

This is the expected behavior for the IMMEDIATE compiler mode.

I think we should recompile if possible whilst registered variable type changing detected

We don't actually detect any changes in the types. Rather, the expression is compiled against a single set of types (determined during the initial reflective evaluation of the expression), and any subsequent invocation of the compiled expression fails if the there is a type mismatch in the compiled byte code.

Or document this limitation on javadoc of IMMEDIATE.

The Javadoc currently states:

If a compiled expression fails it will throw an exception to the caller.

And the Javadoc for MIXED goes into more detail.

Though, I agree that similar information should be added to the Javadoc for IMMEDIATE.

In light of that, I changed the title of this issue and assigned it the documentation label.

Generally speaking, one should avoid compilation of SpEL expressions for which types are expected to change behind the scenes, since that defeats the goal of static compilation. See also: https://github.com/spring-projects/spring-framework/issues/28043#issuecomment-1044640271