Skip to content

Commit

Permalink
Improve names of classes generated by the SpEL compiler
Browse files Browse the repository at this point in the history
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
  • Loading branch information
sbrannen committed Mar 20, 2024
1 parent 2f070e5 commit 7f40b49
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -122,21 +122,22 @@ public CompiledExpression compile(SpelNodeImpl expression) {
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);
Expand Down Expand Up @@ -184,12 +185,31 @@ private Class<? extends CompiledExpression> createExpressionClass(SpelNodeImpl e
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6724,20 +6724,4 @@ public void setValue2(Integer value) {
}
}

// 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);
// }
// }

}

0 comments on commit 7f40b49

Please sign in to comment.