The purpose of this issue is to explore if it is possible to remove SpEL implementation from the reachable code path by performing an AOT processing of SpEL expressions to generate related bytecode at build-time, and introduce a dedicated BeanExpressionResolver
used in AOT mode without a dependency on StandardBeanExpressionResolver
with 3 goals:
- Reduce the number of hints required at runtime on native due to SpEL reflection usage
- Avoid loading most of the SpEL infrastructure by default for optimization purpose
- Perform SpEL to bytecode generation at build-time to skip that processing at runtime
Pre-processing of SpEL expressions in annotations like Spring Framework @Value
or Spring Security @PreAuthorize
/ @PostAuthorize
/ @PreFilter
/ @PostFilter
(cc @rwinch) would be the main use cases, but other potential use cases of BeanExpressionResolver
should be explored to ensure the feasibility of this optimization.
Usage of SpEL in Thymeleaf would be probably out of the scope, or would require build-time compilation of Thymeleaf templates which is out of the scope of this issue, even if that's an interesting idea.
Comment From: sbrannen
Perform SpEL to bytecode generation at build-time to skip that processing at runtime
I've put some thought into this, and I'll share my findings here.
The first challenge is saving the compiled bytecode to .class
files.
Luckily, we already have a prototype for that.
At the end of the SpelCompiler.createExpressionClass(SpelNodeImpl)
method, there's a commented-out invocation of saveGeneratedClassFile(expressionToCompile.toStringAST(), className, data)
.
A simple implementation of that exists in SpelCompilationCoverageTests
.
Using that and running a SpEL compilation test currently generates files such as build/spel.Ex2.class
. We'd probably want to rename the package from spel
to something like org.springframework.expression.spel.compiled
. And we'd need to come up with unique class names that can be mapped to the expression.
So, compiling the expressions and saving the generated byte code to disk should be achievable tasks.
The main challenges that I foresee are:
- Mapping each expression to a unique ID (unique generated class name), depending on the context in which the expression is used, potentially dependent on the ClassLoader used to compile the expression, etc.
- Eager evaluation of each expression, and eager evaluation of all branches of the expression if applicable.
The latter is necessary to successfully compile a SpEL expression. For example, a simple ternary expression such as #list.isEmpty() ? -1 : #list.size()
cannot be compiled unless both branches are evaluated. This means that such an expression must be evaluated twice: once with isEmpty()
returning true
and one with isEmpty()
returning false
.
Comment From: JeroenAdam
This is my use case: I'm using SPEL in the @Document annotation (for my Note entity) to dynamically resolve the (Elasticsearch) indexName from application.properties GraalVM compilation fails becasue of this.
Comment From: sbrannen
Hi @JeroenAdam,
Does it fail during compilation or at runtime?
Without seeing your application I cannot really judge it, but it might be that you need to register the necessary GraalVM reachability metadata (runtime hints) for the types used in your SpEL expression.
In any case, I suggest you ask this question on Stack Overflow, providing a concrete, minimal example of your application and the failure you encounter. Feel free to add a comment to this issue with a link to your Stack Overflow question, and we will take a look if we find the time.
Cheers,
Sam
Comment From: JeroenAdam
@sbrannen Thanks! In my simple note-taking application, it fails during runtime after compilation with GraalVM 22.0.2+9-jvmci-b01. Source code here: https://github.com/JeroenAdam/ta3lim-backend/blob/main/src/main/java/com/ta3lim/backend/domain/Note.java
I just wanted to report this. Since I implemented SPEL in the @document annotation, the binary fails at runtime, the application fails to start. When I remove it, all good again. I've not looked into runtime hints yet as a solution.
Comment From: sbrannen
Since I implemented SPEL in the @document annotation, the binary fails at runtime, the application fails to start. When I remove it, all good again. I've not looked into runtime hints yet as a solution.
Then it sounds like adding runtime hints might be enough.
@Document(indexName = "#{@environment.getProperty('spring.application.name')}")
For that you might just need to register runtime hints to allow the getProperty(String)
method in the Environment
to be invoked via reflection.
Comment From: JeroenAdam
Thanks, that worked fine, based on the official documentation and knowing the right class AbstractEnvironment.
Comment From: sbrannen
Thanks, that worked fine, based on the official documentation and knowing the right class AbstractEnvironment.
Glad to hear that worked for you!
In light of that, I'm hiding this series of comments since they are "off topic".