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:
parent
84633dae00
commit
064998697d
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
@SpringApplicationConfiguration(classes=ReactorApplication)
|
||||||
|
@IntegrationTest('server.port:0')
|
||||||
|
class RestTests {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
Reactor reactor
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void test() {
|
||||||
|
assertNotNull(reactor)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
@Configuration
|
||||||
|
@EnableReactor
|
||||||
|
class ReactorApplication {
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue