Ensure CLI adds @EnableAutoConfiguration in an appropriate place

Up to now we have been treating the *first* class to be compiled
as the "main" application and adding @EnableAutoConfiguration. This
isn't always appropriate (e.g. if it's a test case), so now we
look for an appropriate annotation before falling back to the old
behaviour.

In addition ensures classes with a field of type Reactor trigger
the reactor auto imports.

See gh-969
This commit is contained in:
Dave Syer 2014-05-28 12:11:48 +01:00
parent 84633dae00
commit 064998697d
10 changed files with 93 additions and 23 deletions

View File

@ -34,6 +34,7 @@ import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.stmt.BlockStatement; import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.ExpressionStatement; import org.codehaus.groovy.ast.stmt.ExpressionStatement;
import org.codehaus.groovy.ast.stmt.Statement; import org.codehaus.groovy.ast.stmt.Statement;
import org.springframework.util.PatternMatchUtils;
/** /**
* General purpose AST utilities. * General purpose AST utilities.
@ -69,7 +70,7 @@ public abstract class AstUtils {
String... annotations) { String... annotations) {
for (AnnotationNode annotationNode : node.getAnnotations()) { for (AnnotationNode annotationNode : node.getAnnotations()) {
for (String annotation : annotations) { for (String annotation : annotations) {
if (annotation.equals(annotationNode.getClassNode().getName())) { if (PatternMatchUtils.simpleMatch(annotation, annotationNode.getClassNode().getName())) {
return true; return true;
} }
} }

View File

@ -191,8 +191,8 @@ public class GroovyCompiler {
for (Object loadedClass : collector.getLoadedClasses()) { for (Object loadedClass : collector.getLoadedClasses()) {
classes.add((Class<?>) loadedClass); classes.add((Class<?>) loadedClass);
} }
ClassNode mainClassNode = (ClassNode) compilationUnit.getAST().getClasses() ClassNode mainClassNode = getMainClass(compilationUnit);
.get(0);
Class<?> mainClass = null; Class<?> mainClass = null;
for (Class<?> loadedClass : classes) { for (Class<?> loadedClass : classes) {
if (mainClassNode.getName().equals(loadedClass.getName())) { if (mainClassNode.getName().equals(loadedClass.getName())) {
@ -265,6 +265,7 @@ public class GroovyCompiler {
throws CompilationFailedException { throws CompilationFailedException {
ImportCustomizer importCustomizer = new ImportCustomizer(); ImportCustomizer importCustomizer = new ImportCustomizer();
ClassNode mainClassNode = getMainClass(source.getAST().getClasses());
// Additional auto configuration // Additional auto configuration
for (CompilerAutoConfiguration autoConfiguration : GroovyCompiler.this.compilerAutoConfigurations) { for (CompilerAutoConfiguration autoConfiguration : GroovyCompiler.this.compilerAutoConfigurations) {
@ -273,8 +274,7 @@ public class GroovyCompiler {
autoConfiguration.applyImports(importCustomizer); autoConfiguration.applyImports(importCustomizer);
importCustomizer.call(source, context, classNode); importCustomizer.call(source, context, classNode);
} }
if (source.getAST().getClasses().size() > 0 if (classNode.equals(mainClassNode)) {
&& classNode.equals(source.getAST().getClasses().get(0))) {
autoConfiguration.applyToMainClass(GroovyCompiler.this.loader, autoConfiguration.applyToMainClass(GroovyCompiler.this.loader,
GroovyCompiler.this.configuration, context, source, GroovyCompiler.this.configuration, context, source,
classNode); classNode);
@ -290,4 +290,23 @@ public class GroovyCompiler {
} }
@SuppressWarnings("unchecked")
private static ClassNode getMainClass(CompilationUnit source) {
return getMainClass((List<ClassNode>) source.getAST().getClasses());
}
private static ClassNode getMainClass(List<ClassNode> classes) {
for (ClassNode node : classes) {
if (AstUtils.hasAtLeastOneAnnotation(node, "Enable*AutoConfiguration")) {
return null; // No need to enhance this
}
if (AstUtils.hasAtLeastOneAnnotation(node, "*Controller", "Configuration",
"Component", "*Service", "Repository", "Enable*")) {
return node;
}
}
return classes.isEmpty() ? null : classes.get(0);
}
} }

View File

@ -31,7 +31,7 @@ public class ReactorCompilerAutoConfiguration extends CompilerAutoConfiguration
@Override @Override
public boolean matches(ClassNode classNode) { public boolean matches(ClassNode classNode) {
return AstUtils.hasAtLeastOneAnnotation(classNode, "EnableReactor"); return AstUtils.hasAtLeastOneAnnotation(classNode, "EnableReactor") || AstUtils.hasAtLeastOneFieldOrMethod(classNode, "Reactor");
} }
@Override @Override

View File

@ -19,6 +19,7 @@ package org.springframework.boot.cli.compiler.autoconfigure;
import groovy.lang.GroovyClassLoader; import groovy.lang.GroovyClassLoader;
import org.codehaus.groovy.ast.AnnotationNode; import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.classgen.GeneratorContext; import org.codehaus.groovy.classgen.GeneratorContext;
import org.codehaus.groovy.control.CompilationFailedException; import org.codehaus.groovy.control.CompilationFailedException;
@ -79,17 +80,10 @@ public class SpringBootCompilerAutoConfiguration extends CompilerAutoConfigurati
private void addEnableAutoConfigurationAnnotation(SourceUnit source, private void addEnableAutoConfigurationAnnotation(SourceUnit source,
ClassNode classNode) { ClassNode classNode) {
if (!hasEnableAutoConfigureAnnotation(classNode)) { if (!hasEnableAutoConfigureAnnotation(classNode)) {
try { AnnotationNode annotationNode = new AnnotationNode(
Class<?> annotationClass = source.getClassLoader().loadClass( ClassHelper.make("EnableAutoConfiguration"));
"org.springframework.boot.autoconfigure.EnableAutoConfiguration");
AnnotationNode annotationNode = new AnnotationNode(new ClassNode(
annotationClass));
classNode.addAnnotation(annotationNode); classNode.addAnnotation(annotationNode);
} }
catch (ClassNotFoundException ex) {
throw new IllegalStateException(ex);
}
}
} }
private boolean hasEnableAutoConfigureAnnotation(ClassNode classNode) { private boolean hasEnableAutoConfigureAnnotation(ClassNode classNode) {

View File

@ -35,11 +35,13 @@ import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
* *
* @author Dave Syer * @author Dave Syer
*/ */
public class SpringTestCompilerAutoConfiguration extends CompilerAutoConfiguration { public class SpringTestCompilerAutoConfiguration extends
CompilerAutoConfiguration {
@Override @Override
public boolean matches(ClassNode classNode) { public boolean matches(ClassNode classNode) {
return AstUtils.hasAtLeastOneAnnotation(classNode, "SpringApplicationConfiguration"); return AstUtils.hasAtLeastOneAnnotation(classNode,
"SpringApplicationConfiguration");
} }
@Override @Override
@ -48,8 +50,12 @@ public class SpringTestCompilerAutoConfiguration extends CompilerAutoConfigurati
GeneratorContext generatorContext, SourceUnit source, GeneratorContext generatorContext, SourceUnit source,
ClassNode classNode) throws CompilationFailedException { ClassNode classNode) throws CompilationFailedException {
if (!AstUtils.hasAtLeastOneAnnotation(classNode, "RunWith")) { if (!AstUtils.hasAtLeastOneAnnotation(classNode, "RunWith")) {
AnnotationNode runwith = new AnnotationNode(ClassHelper.make("RunWith")); AnnotationNode runwith = new AnnotationNode(
runwith.addMember("value", new ClassExpression(ClassHelper.make("SpringJUnit4ClassRunner"))); ClassHelper.make("RunWith"));
runwith.addMember(
"value",
new ClassExpression(ClassHelper
.make("SpringJUnit4ClassRunner")));
classNode.addAnnotation(runwith); classNode.addAnnotation(runwith);
} }
} }
@ -59,6 +65,8 @@ public class SpringTestCompilerAutoConfiguration extends CompilerAutoConfigurati
throws CompilationFailedException { throws CompilationFailedException {
imports.addStarImports("org.junit.runner") imports.addStarImports("org.junit.runner")
.addStarImports("org.springframework.boot.test") .addStarImports("org.springframework.boot.test")
.addStarImports("org.springframework.test.context.junit4"); .addStarImports("org.springframework.test.context.junit4")
.addImports(
"org.springframework.test.context.web.WebAppConfiguration");
} }
} }

View File

@ -88,6 +88,18 @@ public class TestCommandIntegrationTests {
assertThat(output, containsString("OK (1 test)")); assertThat(output, containsString("OK (1 test)"));
} }
@Test
public void integrationAutoConfigEmbeddedTest() throws Exception {
String output = this.cli.test("integration_auto.groovy");
assertThat(output, containsString("OK (1 test)"));
}
@Test
public void integrationAutoConfigTest() throws Exception {
String output = this.cli.test("integration_auto_test.groovy", "reactor.groovy");
assertThat(output, containsString("OK (1 test)"));
}
@Test @Test
public void spockTester() throws Exception { public void spockTester() throws Exception {
String output = this.cli.test("spock.groovy"); String output = this.cli.test("spock.groovy");

View File

@ -53,7 +53,7 @@ public class ExtendedGroovyClassLoaderTests {
} }
@Test @Test
public void filteresNonGroovy() throws Exception { public void filtersNonGroovy() throws Exception {
this.contextClassLoader.loadClass("org.springframework.util.StringUtils"); this.contextClassLoader.loadClass("org.springframework.util.StringUtils");
this.thrown.expect(ClassNotFoundException.class); this.thrown.expect(ClassNotFoundException.class);
this.defaultScopeGroovyClassLoader this.defaultScopeGroovyClassLoader

View File

@ -0,0 +1,19 @@
@SpringApplicationConfiguration(classes=Application)
@IntegrationTest('server.port:0')
@WebAppConfiguration
class RestTests {
@Value('${local.server.port}')
int port
@Test
void testHome() {
assertEquals('Hello', new TestRestTemplate().getForObject('http://localhost:' + port, String))
}
@RestController
static class Application {
@RequestMapping('/')
String hello() { 'Hello' }
}
}

View File

@ -0,0 +1,13 @@
@SpringApplicationConfiguration(classes=ReactorApplication)
@IntegrationTest('server.port:0')
class RestTests {
@Autowired
Reactor reactor
@Test
void test() {
assertNotNull(reactor)
}
}

View File

@ -0,0 +1,4 @@
@Configuration
@EnableReactor
class ReactorApplication {
}