spring-projects / spring-framework

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

Explore how to pre-compile SpEL expressions during AOT processing #29548

Open sdeleuze opened 1 year ago

sdeleuze commented 1 year ago

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:

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.

sbrannen commented 7 months ago

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:

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.