diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelCompiler.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelCompiler.java index 1ca414375b..e8802052bd 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelCompiler.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelCompiler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -81,7 +81,7 @@ public final class SpelCompiler implements Opcodes { private volatile ChildClassLoader childClassLoader; // Counter suffix for generated classes within this SpelCompiler instance - private final AtomicInteger suffixId = new AtomicInteger(1); + private final AtomicInteger suffixId = new AtomicInteger(0); private SpelCompiler(@Nullable ClassLoader classloader) { @@ -122,21 +122,22 @@ public final class SpelCompiler implements Opcodes { return null; } - private int getNextSuffix() { - return this.suffixId.incrementAndGet(); + private String getNextSuffix() { + return "%05d".formatted(this.suffixId.incrementAndGet()); } /** * Generate the class that encapsulates the compiled expression and define it. - * The generated class will be a subtype of CompiledExpression. + *
The generated class will be a subtype of {@link CompiledExpression}. * @param expressionToCompile the expression to be compiled * @return the expression call, or {@code null} if the decision was to opt out of * compilation during code generation */ @Nullable private Class extends CompiledExpression> createExpressionClass(SpelNodeImpl expressionToCompile) { - // Create class outline 'spel/ExNNN extends org.springframework.expression.spel.CompiledExpression' - String className = "spel/Ex" + getNextSuffix(); + // Create class outline: + // org.springframework.expression.spel.generated.CompiledExpression##### extends org.springframework.expression.spel.CompiledExpression + String className = "org/springframework/expression/spel/generated/CompiledExpression" + getNextSuffix(); String evaluationContextClass = "org/springframework/expression/EvaluationContext"; ClassWriter cw = new ExpressionClassWriter(); cw.visit(V1_8, ACC_PUBLIC, className, null, "org/springframework/expression/spel/CompiledExpression", null); @@ -184,12 +185,31 @@ public final class SpelCompiler implements Opcodes { cf.finish(); byte[] data = cw.toByteArray(); - // TODO Save generated class files conditionally based on a debug flag. - // Source code for the following method resides in SpelCompilationCoverageTests. + // TODO Save generated class files conditionally based on a flag. // saveGeneratedClassFile(expressionToCompile.toStringAST(), className, data); return loadClass(StringUtils.replace(className, "/", "."), data); } + // NOTE: saveGeneratedClassFile() can be uncommented in order to review generated byte code for + // debugging purposes. See also: https://github.com/spring-projects/spring-framework/issues/29548 + // + // private static void saveGeneratedClassFile(String stringAST, String className, byte[] data) { + // try { + // // TODO Make target directory configurable. + // String targetDir = "build/generated-classes"; + // Path path = Path.of(targetDir, className + ".class"); + // Files.deleteIfExists(path); + // Files.createDirectories(path.getParent()); + // if (logger.isDebugEnabled()) { + // logger.debug("Saving compiled SpEL expression [%s] to [%s]".formatted(stringAST, path.toAbsolutePath())); + // } + // Files.copy(new ByteArrayInputStream(data), path); + // } + // catch (IOException ex) { + // throw new UncheckedIOException(ex); + // } + // } + /** * Load a compiled expression class. Makes sure the classloaders aren't used too much * because they anchor compiled classes in memory and prevent GC. If you have expressions diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java index d19f50f00e..a2c1f3926e 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java @@ -6724,20 +6724,4 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { } } - // NOTE: saveGeneratedClassFile() can be copied to SpelCompiler and uncommented - // at the end of createExpressionClass(SpelNodeImpl) in order to review generated - // byte code for debugging purposes. - // - // private static void saveGeneratedClassFile(String stringAST, String className, byte[] data) { - // try { - // Path path = Path.of("build", StringUtils.replace(className, "/", ".") + ".class"); - // Files.deleteIfExists(path); - // System.out.println("Writing compiled SpEL expression [%s] to [%s]".formatted(stringAST, path.toAbsolutePath())); - // Files.copy(new ByteArrayInputStream(data), path); - // } - // catch (IOException ex) { - // throw new UncheckedIOException(ex); - // } - // } - }