forked from spring-projects/spring-framework
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support for candidate components index
This commit adds a "spring-context-indexer" module that can be added to any project in order to generate an index of candidate components defined in the project. `CandidateComponentsIndexer` is a standard annotation processor that looks for source files with target annotations (typically `@Component`) and references them in a `META-INF/spring.components` generated file. Each entry in the index is the fully qualified name of a candidate component and the comma-separated list of stereotypes that apply to that candidate. A typical example of a stereotype is `@Component`. If a project has a `com.example.FooService` annotated with `@Component` the following `META-INF/spring.components` file is generated at compile time: ``` com.example.FooService=org.springframework.stereotype.Component ``` A new `@Indexed` annotation can be added on any annotation to instructs the scanner to include a source file that contains that annotation. For instance, `@Component` is meta-annotated with `@Indexed` now and adding `@Indexed` to more annotation types will transparently improve the index with additional information. The indexer also adds any class or interface that has a type-level annotation from the `javax` package. This includes obviously JPA (`@Entity` and related) but also CDI (`@Named`, `@ManagedBean`) and servlet annotations (i.e. `@WebFilter`). These are meant to handle cases where a component needs to identify candidates and use classpath scanning currently. If a `package-info.java` file exists, the package is registered using a "package-info" stereotype. Such files can later be reused by the `ApplicationContext` to avoid using component scan. A global `CandidateComponentsIndex` can be easily loaded from the current classpath using `CandidateComponentsIndexLoader`. The core framework uses such infrastructure in two areas: to retrieve the candidate `@Component`s and to build a default `PersistenceUnitInfo`. Rather than scanning the classpath and using ASM to identify candidates, the index is used if present. If custom include filters are specified, the index is not used. In case the index is incomplete or cannot be used, The `spring.index.ignore` system property can be set to `false` or, alternatively, in a "spring.properties" at the root of the classpath. Issue: SPR-11890
- Loading branch information
Showing
51 changed files
with
2,617 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
111 changes: 111 additions & 0 deletions
111
...t-indexer/src/main/java/org/springframework/context/index/CandidateComponentsIndexer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
/* | ||
* Copyright 2002-2016 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.context.index; | ||
|
||
import java.io.IOException; | ||
import java.util.ArrayList; | ||
import java.util.LinkedHashSet; | ||
import java.util.List; | ||
import java.util.Set; | ||
import javax.annotation.processing.AbstractProcessor; | ||
import javax.annotation.processing.ProcessingEnvironment; | ||
import javax.annotation.processing.Processor; | ||
import javax.annotation.processing.RoundEnvironment; | ||
import javax.annotation.processing.SupportedAnnotationTypes; | ||
import javax.annotation.processing.SupportedSourceVersion; | ||
import javax.lang.model.SourceVersion; | ||
import javax.lang.model.element.Element; | ||
import javax.lang.model.element.TypeElement; | ||
|
||
import org.springframework.context.index.metadata.CandidateComponentsMetadata; | ||
import org.springframework.context.index.metadata.ItemMetadata; | ||
|
||
/** | ||
* Annotation {@link Processor} that writes {@link CandidateComponentsMetadata} | ||
* file for spring components. | ||
* | ||
* @author Stephane Nicoll | ||
* @since 5.0 | ||
*/ | ||
@SupportedAnnotationTypes({"*"}) | ||
@SupportedSourceVersion(SourceVersion.RELEASE_8) | ||
public class CandidateComponentsIndexer extends AbstractProcessor { | ||
|
||
private MetadataStore metadataStore; | ||
|
||
private MetadataCollector metadataCollector; | ||
|
||
private TypeUtils typeUtils; | ||
|
||
private List<StereotypesProvider> stereotypesProviders; | ||
|
||
@Override | ||
public synchronized void init(ProcessingEnvironment env) { | ||
this.stereotypesProviders = getStereotypesProviders(env); | ||
this.typeUtils = new TypeUtils(env); | ||
this.metadataStore = new MetadataStore(env); | ||
this.metadataCollector = new MetadataCollector(env, | ||
this.metadataStore.readMetadata()); | ||
} | ||
|
||
@Override | ||
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { | ||
this.metadataCollector.processing(roundEnv); | ||
roundEnv.getRootElements().forEach(this::processElement); | ||
|
||
if (roundEnv.processingOver()) { | ||
writeMetaData(); | ||
} | ||
return false; | ||
} | ||
|
||
protected List<StereotypesProvider> getStereotypesProviders(ProcessingEnvironment env) { | ||
List<StereotypesProvider> result = new ArrayList<>(); | ||
TypeUtils typeUtils = new TypeUtils(env); | ||
result.add(new IndexedStereotypesProvider(typeUtils)); | ||
result.add(new StandardStereotypesProvider(typeUtils)); | ||
result.add(new PackageInfoStereotypesProvider()); | ||
return result; | ||
} | ||
|
||
private void processElement(Element element) { | ||
Set<String> stereotypes = new LinkedHashSet<>(); | ||
this.stereotypesProviders.forEach(p -> { | ||
stereotypes.addAll(p.getStereotypes(element)); | ||
|
||
}); | ||
if (!stereotypes.isEmpty()) { | ||
this.metadataCollector.add(new ItemMetadata( | ||
this.typeUtils.getType(element), stereotypes)); | ||
} | ||
} | ||
|
||
protected CandidateComponentsMetadata writeMetaData() { | ||
CandidateComponentsMetadata metadata = this.metadataCollector.getMetadata(); | ||
if (!metadata.getItems().isEmpty()) { | ||
try { | ||
this.metadataStore.writeMetadata(metadata); | ||
} | ||
catch (IOException ex) { | ||
throw new IllegalStateException("Failed to write metadata", ex); | ||
} | ||
return metadata; | ||
} | ||
return null; | ||
} | ||
|
||
} |
88 changes: 88 additions & 0 deletions
88
...t-indexer/src/main/java/org/springframework/context/index/IndexedStereotypesProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
/* | ||
* Copyright 2002-2016 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.context.index; | ||
|
||
import java.util.HashSet; | ||
import java.util.LinkedHashSet; | ||
import java.util.Set; | ||
import javax.lang.model.element.AnnotationMirror; | ||
import javax.lang.model.element.Element; | ||
import javax.lang.model.element.ElementKind; | ||
|
||
/** | ||
* A {@link StereotypesProvider} implementation that extracts the stereotypes | ||
* flagged by the {@value INDEXED_ANNOTATION} annotation. This implementation | ||
* honors stereotypes defined this way on meta-annotations. | ||
* | ||
* @author Stephane Nicoll | ||
*/ | ||
class IndexedStereotypesProvider implements StereotypesProvider { | ||
|
||
private static final String INDEXED_ANNOTATION = "org.springframework.stereotype.Indexed"; | ||
|
||
private final TypeUtils typeUtils; | ||
|
||
public IndexedStereotypesProvider(TypeUtils typeUtils) { | ||
this.typeUtils = typeUtils; | ||
} | ||
|
||
@Override | ||
public Set<String> getStereotypes(Element element) { | ||
Set<String> stereotypes = new LinkedHashSet<>(); | ||
ElementKind kind = element.getKind(); | ||
if (kind != ElementKind.CLASS && kind != ElementKind.INTERFACE) { | ||
return stereotypes; | ||
} | ||
Set<Element> seen = new HashSet<>(); | ||
collectStereotypes(seen, stereotypes, element); | ||
return stereotypes; | ||
} | ||
|
||
private void collectStereotypes(Set<Element> seen, Set<String> stereotypes, | ||
Element element) { | ||
for (AnnotationMirror annotation : this.typeUtils.getAllAnnotationMirrors(element)) { | ||
Element next = collectStereotypes(seen, stereotypes, element, annotation); | ||
if (next != null) { | ||
collectStereotypes(seen, stereotypes, next); | ||
} | ||
} | ||
} | ||
|
||
private Element collectStereotypes(Set<Element> seen, Set<String> stereotypes, | ||
Element element, AnnotationMirror annotation) { | ||
if (isIndexedAnnotation(annotation)) { | ||
stereotypes.add(this.typeUtils.getType(element)); | ||
} | ||
return getCandidateAnnotationElement(seen, annotation); | ||
} | ||
|
||
private Element getCandidateAnnotationElement(Set<Element> seen, AnnotationMirror annotation) { | ||
Element element = annotation.getAnnotationType().asElement(); | ||
if (seen.contains(element)) { | ||
return null; | ||
} | ||
// We need to visit all indexed annotations. | ||
if (!isIndexedAnnotation(annotation)) { | ||
seen.add(element); | ||
} | ||
return (!element.toString().startsWith("java.lang") ? element : null); | ||
} | ||
|
||
private boolean isIndexedAnnotation(AnnotationMirror annotation) { | ||
return INDEXED_ANNOTATION.equals(annotation.getAnnotationType().toString()); | ||
} | ||
} |
107 changes: 107 additions & 0 deletions
107
...ng-context-indexer/src/main/java/org/springframework/context/index/MetadataCollector.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
/* | ||
* Copyright 2002-2016 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.context.index; | ||
|
||
import java.util.ArrayList; | ||
import java.util.HashSet; | ||
import java.util.List; | ||
import java.util.Set; | ||
import javax.annotation.processing.ProcessingEnvironment; | ||
import javax.annotation.processing.RoundEnvironment; | ||
import javax.lang.model.element.Element; | ||
import javax.lang.model.element.TypeElement; | ||
|
||
import org.springframework.context.index.metadata.ItemMetadata; | ||
import org.springframework.context.index.metadata.CandidateComponentsMetadata; | ||
|
||
/** | ||
* Used by {@link CandidateComponentsIndexer} to collect {@link CandidateComponentsMetadata}. | ||
* | ||
* @author Stephane Nicoll | ||
*/ | ||
class MetadataCollector { | ||
|
||
private final List<ItemMetadata> metadataItems = new ArrayList<ItemMetadata>(); | ||
|
||
private final ProcessingEnvironment processingEnvironment; | ||
|
||
private final CandidateComponentsMetadata previousMetadata; | ||
|
||
private final TypeUtils typeUtils; | ||
|
||
private final Set<String> processedSourceTypes = new HashSet<String>(); | ||
|
||
/** | ||
* Creates a new {@code MetadataProcessor} instance. | ||
* @param processingEnvironment The processing environment of the build | ||
* @param previousMetadata Any previous metadata or {@code null} | ||
*/ | ||
public MetadataCollector(ProcessingEnvironment processingEnvironment, | ||
CandidateComponentsMetadata previousMetadata) { | ||
this.processingEnvironment = processingEnvironment; | ||
this.previousMetadata = previousMetadata; | ||
this.typeUtils = new TypeUtils(processingEnvironment); | ||
} | ||
|
||
public void processing(RoundEnvironment roundEnv) { | ||
for (Element element : roundEnv.getRootElements()) { | ||
markAsProcessed(element); | ||
} | ||
} | ||
|
||
private void markAsProcessed(Element element) { | ||
if (element instanceof TypeElement) { | ||
this.processedSourceTypes.add(this.typeUtils.getType(element)); | ||
} | ||
} | ||
|
||
public void add(ItemMetadata metadata) { | ||
this.metadataItems.add(metadata); | ||
} | ||
|
||
public CandidateComponentsMetadata getMetadata() { | ||
CandidateComponentsMetadata metadata = new CandidateComponentsMetadata(); | ||
for (ItemMetadata item : this.metadataItems) { | ||
metadata.add(item); | ||
} | ||
if (this.previousMetadata != null) { | ||
List<ItemMetadata> items = this.previousMetadata.getItems(); | ||
for (ItemMetadata item : items) { | ||
if (shouldBeMerged(item)) { | ||
metadata.add(item); | ||
} | ||
} | ||
} | ||
return metadata; | ||
} | ||
|
||
private boolean shouldBeMerged(ItemMetadata itemMetadata) { | ||
String sourceType = itemMetadata.getType(); | ||
return (sourceType != null && !deletedInCurrentBuild(sourceType) | ||
&& !processedInCurrentBuild(sourceType)); | ||
} | ||
|
||
private boolean deletedInCurrentBuild(String sourceType) { | ||
return this.processingEnvironment.getElementUtils() | ||
.getTypeElement(sourceType) == null; | ||
} | ||
|
||
private boolean processedInCurrentBuild(String sourceType) { | ||
return this.processedSourceTypes.contains(sourceType); | ||
} | ||
|
||
} |
Oops, something went wrong.