Improve names of classes generated by the SpEL compiler
Prior to this commit, the SpEL compiler generated classes in a package named "spel" with names following the pattern "Ex#", where # was an index starting with 2. This resulted in class names such as: - spel.Ex2 - spel.Ex3 This commit improves the names of classes created by the SpEL compiler by generating classes in a package named "org.springframework.expression.spel.generated" with names following the pattern "CompiledExpression#####", where ##### is a 0-padded counter starting with 00001. This results in class names such as: - org.springframework.expression.spel.generated.CompiledExpression00001 - org.springframework.expression.spel.generated.CompiledExpression00002 This commit also moves the saveGeneratedClassFile() method from SpelCompilationCoverageTests to SpelCompiler and enhances it to: - Save classes in a "build/generated-classes" directory. - Convert package names to directories. - Create missing parent directories. - Use logging instead of System.out.println(). Running a test with saveGeneratedClassFile() enabled now logs something similar to the following. DEBUG o.s.e.s.s.SpelCompiler - Saving compiled SpEL expression [(#root.empty ? 0 : #root.size)] to [/Users/<username>/spring-framework/spring-expression/build/generated-classes/org/springframework/expression/spel/generated/CompiledExpression00001.class] Closes gh-32497
This commit is contained in:
parent
2f070e59e6
commit
7f40b49f4d
|
|
@ -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.
|
||||
* <p>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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue