Skip to content

Commit 7f40b49

Browse files
committed
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
1 parent 2f070e5 commit 7f40b49

File tree

2 files changed

+29
-25
lines changed

2 files changed

+29
-25
lines changed

spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelCompiler.java

+29-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -81,7 +81,7 @@ public final class SpelCompiler implements Opcodes {
8181
private volatile ChildClassLoader childClassLoader;
8282

8383
// Counter suffix for generated classes within this SpelCompiler instance
84-
private final AtomicInteger suffixId = new AtomicInteger(1);
84+
private final AtomicInteger suffixId = new AtomicInteger(0);
8585

8686

8787
private SpelCompiler(@Nullable ClassLoader classloader) {
@@ -122,21 +122,22 @@ public CompiledExpression compile(SpelNodeImpl expression) {
122122
return null;
123123
}
124124

125-
private int getNextSuffix() {
126-
return this.suffixId.incrementAndGet();
125+
private String getNextSuffix() {
126+
return "%05d".formatted(this.suffixId.incrementAndGet());
127127
}
128128

129129
/**
130130
* Generate the class that encapsulates the compiled expression and define it.
131-
* The generated class will be a subtype of CompiledExpression.
131+
* <p>The generated class will be a subtype of {@link CompiledExpression}.
132132
* @param expressionToCompile the expression to be compiled
133133
* @return the expression call, or {@code null} if the decision was to opt out of
134134
* compilation during code generation
135135
*/
136136
@Nullable
137137
private Class<? extends CompiledExpression> createExpressionClass(SpelNodeImpl expressionToCompile) {
138-
// Create class outline 'spel/ExNNN extends org.springframework.expression.spel.CompiledExpression'
139-
String className = "spel/Ex" + getNextSuffix();
138+
// Create class outline:
139+
// org.springframework.expression.spel.generated.CompiledExpression##### extends org.springframework.expression.spel.CompiledExpression
140+
String className = "org/springframework/expression/spel/generated/CompiledExpression" + getNextSuffix();
140141
String evaluationContextClass = "org/springframework/expression/EvaluationContext";
141142
ClassWriter cw = new ExpressionClassWriter();
142143
cw.visit(V1_8, ACC_PUBLIC, className, null, "org/springframework/expression/spel/CompiledExpression", null);
@@ -184,12 +185,31 @@ private Class<? extends CompiledExpression> createExpressionClass(SpelNodeImpl e
184185
cf.finish();
185186

186187
byte[] data = cw.toByteArray();
187-
// TODO Save generated class files conditionally based on a debug flag.
188-
// Source code for the following method resides in SpelCompilationCoverageTests.
188+
// TODO Save generated class files conditionally based on a flag.
189189
// saveGeneratedClassFile(expressionToCompile.toStringAST(), className, data);
190190
return loadClass(StringUtils.replace(className, "/", "."), data);
191191
}
192192

193+
// NOTE: saveGeneratedClassFile() can be uncommented in order to review generated byte code for
194+
// debugging purposes. See also: https://github.com/spring-projects/spring-framework/issues/29548
195+
//
196+
// private static void saveGeneratedClassFile(String stringAST, String className, byte[] data) {
197+
// try {
198+
// // TODO Make target directory configurable.
199+
// String targetDir = "build/generated-classes";
200+
// Path path = Path.of(targetDir, className + ".class");
201+
// Files.deleteIfExists(path);
202+
// Files.createDirectories(path.getParent());
203+
// if (logger.isDebugEnabled()) {
204+
// logger.debug("Saving compiled SpEL expression [%s] to [%s]".formatted(stringAST, path.toAbsolutePath()));
205+
// }
206+
// Files.copy(new ByteArrayInputStream(data), path);
207+
// }
208+
// catch (IOException ex) {
209+
// throw new UncheckedIOException(ex);
210+
// }
211+
// }
212+
193213
/**
194214
* Load a compiled expression class. Makes sure the classloaders aren't used too much
195215
* because they anchor compiled classes in memory and prevent GC. If you have expressions

spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java

-16
Original file line numberDiff line numberDiff line change
@@ -6724,20 +6724,4 @@ public void setValue2(Integer value) {
67246724
}
67256725
}
67266726

6727-
// NOTE: saveGeneratedClassFile() can be copied to SpelCompiler and uncommented
6728-
// at the end of createExpressionClass(SpelNodeImpl) in order to review generated
6729-
// byte code for debugging purposes.
6730-
//
6731-
// private static void saveGeneratedClassFile(String stringAST, String className, byte[] data) {
6732-
// try {
6733-
// Path path = Path.of("build", StringUtils.replace(className, "/", ".") + ".class");
6734-
// Files.deleteIfExists(path);
6735-
// System.out.println("Writing compiled SpEL expression [%s] to [%s]".formatted(stringAST, path.toAbsolutePath()));
6736-
// Files.copy(new ByteArrayInputStream(data), path);
6737-
// }
6738-
// catch (IOException ex) {
6739-
// throw new UncheckedIOException(ex);
6740-
// }
6741-
// }
6742-
67436727
}

0 commit comments

Comments
 (0)