Skip to content

Commit

Permalink
Introduce spring-boot-autoconfigure-processor
Browse files Browse the repository at this point in the history
Add an annotation processor that generates properties files for certain
auto-configuration class annotations. Currently attribute values from
@AutoConfigureOrder, @AutoConfigureBefore, @AutoConfigureAfter and
@ConditionalOnClass annotations are stored.

The properties file will allow optimizations to be added in the
`spring-boot-autoconfigure` project. Primarily by removing the need
to ASM parse as many `.class` files.

See spring-projectsgh-7573
  • Loading branch information
mbhave authored and philwebb committed Jan 24, 2017
1 parent ccf964e commit ca43551
Show file tree
Hide file tree
Showing 25 changed files with 799 additions and 15 deletions.
2 changes: 1 addition & 1 deletion eclipse/spring-boot-project.setup
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@
name="spring-boot-tools">
<predicate
xsi:type="predicates:NamePredicate"
pattern="spring-boot-(tools|antlib|configuration-.*|loader|.*-tools|.*-plugin)"/>
pattern="spring-boot-(tools|antlib|configuration-.*|loader|.*-tools|.*-plugin|autoconfigure-processor)"/>
</workingSet>
<workingSet
name="spring-boot-starters">
Expand Down
5 changes: 5 additions & 0 deletions spring-boot-actuator/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,11 @@
<optional>true</optional>
</dependency>
<!-- Annotation processing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
Expand Down
5 changes: 5 additions & 0 deletions spring-boot-autoconfigure/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,11 @@
<optional>true</optional>
</dependency>
<!-- Annotation processing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
Expand Down
5 changes: 5 additions & 0 deletions spring-boot-dependencies/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,11 @@
<artifactId>spring-boot-autoconfigure</artifactId>
<version>1.5.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
<version>1.5.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-metadata</artifactId>
Expand Down
5 changes: 5 additions & 0 deletions spring-boot-devtools/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@
<optional>true</optional>
</dependency>
<!-- Annotation processing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2015 the original author or authors.
* Copyright 2012-2017 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 All @@ -14,7 +14,7 @@
* limitations under the License.
*/

package org.springframework.boot.configurationprocessor;
package org.springframework.boot.junit.compiler;

import java.io.File;
import java.io.IOException;
Expand All @@ -40,7 +40,10 @@
*/
public class TestCompiler {

public static final File ORIGINAL_SOURCE_FOLDER = new File("src/test/java");
/**
* The default source folder.
*/
public static final File SOURCE_FOLDER = new File("src/test/java");

private final JavaCompiler compiler;

Expand Down Expand Up @@ -100,7 +103,7 @@ public static String sourcePathFor(Class<?> type) {
}

protected File getSourceFolder() {
return ORIGINAL_SOURCE_FOLDER;
return SOURCE_FOLDER;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright 2012-2017 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* Utilities to work with the Java compiler at test time.
*/
package org.springframework.boot.junit.compiler;
1 change: 1 addition & 0 deletions spring-boot-tools/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<main.basedir>${basedir}/..</main.basedir>
</properties>
<modules>
<module>spring-boot-autoconfigure-processor</module>
<module>spring-boot-configuration-metadata</module>
<module>spring-boot-configuration-processor</module>
<module>spring-boot-loader</module>
Expand Down
40 changes: 40 additions & 0 deletions spring-boot-tools/spring-boot-autoconfigure-processor/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-tools</artifactId>
<version>1.5.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
<name>Spring Boot Auto-Configure Annotation Processor</name>
<description>Spring Auto-Configure Annotation Processor</description>
<url>http://projects.spring.io/spring-boot/</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test-support</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<!-- Ensure own annotation processor doesn't kick in -->
<proc>none</proc>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
/*
* Copyright 2012-2017 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.autoconfigureprocessor;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.tools.FileObject;
import javax.tools.StandardLocation;

/**
* Annotation processor to store certain annotations from auto-configuration classes in a
* property file.
*
* @author Madhura Bhave
* @author Phillip Webb
*/
@SupportedAnnotationTypes({ "org.springframework.context.annotation.Configuration",
"org.springframework.boot.autoconfigure.condition.ConditionalOnClass",
"org.springframework.boot.autoconfigure.AutoConfigureBefore",
"org.springframework.boot.autoconfigure.AutoConfigureAfter",
"org.springframework.boot.autoconfigure.AutoConfigureOrder" })
public class AutoConfigureAnnotationProcessor extends AbstractProcessor {

protected static final String PROPERTIES_PATH = "META-INF/"
+ "spring-autoconfigure-metadata.properties";

private Map<String, String> annotations;

private final Properties properties = new Properties();

public AutoConfigureAnnotationProcessor() {
Map<String, String> annotations = new LinkedHashMap<String, String>();
addAnnotations(annotations);
this.annotations = Collections.unmodifiableMap(annotations);
}

protected void addAnnotations(Map<String, String> annotations) {
annotations.put("Configuration",
"org.springframework.context.annotation.Configuration");
annotations.put("ConditionalOnClass",
"org.springframework.boot.autoconfigure.condition.ConditionalOnClass");
annotations.put("AutoConfigureBefore",
"org.springframework.boot.autoconfigure.AutoConfigureBefore");
annotations.put("AutoConfigureAfter",
"org.springframework.boot.autoconfigure.AutoConfigureAfter");
annotations.put("AutoConfigureOrder",
"org.springframework.boot.autoconfigure.AutoConfigureOrder");
}

@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
for (Map.Entry<String, String> entry : this.annotations.entrySet()) {
process(roundEnv, entry.getKey(), entry.getValue());
}
if (roundEnv.processingOver()) {
try {
writeProperties();
}
catch (Exception ex) {
throw new IllegalStateException("Failed to write metadata", ex);
}
}
return false;
}

private void process(RoundEnvironment roundEnv, String propertyKey,
String annotationName) {
TypeElement annotationType = this.processingEnv.getElementUtils()
.getTypeElement(annotationName);
if (annotationType != null) {
for (Element element : roundEnv.getElementsAnnotatedWith(annotationType)) {
Element enclosingElement = element.getEnclosingElement();
if (enclosingElement != null
&& enclosingElement.getKind() == ElementKind.PACKAGE) {
processElement(element, propertyKey, annotationName);
}
}
}
}

private void processElement(Element element, String propertyKey,
String annotationName) {
try {
String qualifiedName = getQualifiedName(element);
AnnotationMirror annotation = getAnnotation(element, annotationName);
if (qualifiedName != null && annotation != null) {
List<Object> values = getValues(annotation);
this.properties.put(qualifiedName + "." + propertyKey,
toCommaDelimitedString(values));
this.properties.put(qualifiedName, "");
}
}
catch (Exception ex) {
throw new IllegalStateException(
"Error processing configuration meta-data on " + element, ex);
}
}

private AnnotationMirror getAnnotation(Element element, String type) {
if (element != null) {
for (AnnotationMirror annotation : element.getAnnotationMirrors()) {
if (type.equals(annotation.getAnnotationType().toString())) {
return annotation;
}
}
}
return null;
}

private String toCommaDelimitedString(List<Object> list) {
StringBuilder result = new StringBuilder();
for (Object item : list) {
result.append(result.length() != 0 ? "," : "");
result.append(item);
}
return result.toString();
}

@SuppressWarnings("unchecked")
private List<Object> getValues(AnnotationMirror annotation) {
List<Object> result = new ArrayList<Object>();
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : annotation
.getElementValues().entrySet()) {
String attributeName = entry.getKey().getSimpleName().toString();
if ("name".equals(attributeName) || "value".equals(attributeName)) {
Object value = entry.getValue().getValue();
if (value instanceof List) {
for (AnnotationValue annotationValue : (List<AnnotationValue>) value) {
result.add(annotationValue.getValue());
}
}
else {
result.add(value);
}
}
}
return result;
}

private String getQualifiedName(Element element) {
if (element != null) {
TypeElement enclosingElement = getEnclosingTypeElement(element.asType());
if (enclosingElement != null) {
return getQualifiedName(enclosingElement) + "$"
+ ((DeclaredType) element.asType()).asElement().getSimpleName()
.toString();
}
if (element instanceof TypeElement) {
return ((TypeElement) element).getQualifiedName().toString();
}
}
return null;
}

private TypeElement getEnclosingTypeElement(TypeMirror type) {
if (type instanceof DeclaredType) {
DeclaredType declaredType = (DeclaredType) type;
Element enclosingElement = declaredType.asElement().getEnclosingElement();
if (enclosingElement != null && enclosingElement instanceof TypeElement) {
return (TypeElement) enclosingElement;
}
}
return null;
}

private void writeProperties() throws IOException {
if (!this.properties.isEmpty()) {
FileObject file = this.processingEnv.getFiler()
.createResource(StandardLocation.CLASS_OUTPUT, "", PROPERTIES_PATH);
OutputStream outputStream = file.openOutputStream();
try {
this.properties.store(outputStream, null);
}
finally {
outputStream.close();
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.springframework.boot.autoconfigureprocessor.AutoConfigureAnnotationProcessor
Loading

0 comments on commit ca43551

Please sign in to comment.