Support reading nested annotations via ASM
Background
Spring 3.1 introduced the @ComponentScan annotation, which can accept
an optional array of include and/or exclude @Filter annotations, e.g.
@ComponentScan(
basePackages = "com.acme.app",
includeFilters = { @Filter(MyStereotype.class), ... }
)
@Configuration
public class AppConfig { ... }
@ComponentScan and other annotations related to @Configuration class
processing such as @Import, @ImportResource and the @Enable*
annotations are parsed using reflection in certain code paths, e.g.
when registered directly against AnnotationConfigApplicationContext,
and via ASM in other code paths, e.g. when a @Configuration class is
discovered via an XML bean definition or when included via the
@Import annotation.
The ASM-based approach is designed to avoid premature classloading of
user types and is instrumental in providing tooling support (STS, etc).
Prior to this commit, the ASM-based routines for reading annotation
attributes were unable to recurse into nested annotations, such as in
the @Filter example above. Prior to Spring 3.1 this was not a problem,
because prior to @ComponentScan, there were no cases of nested
annotations in the framework.
This limitation manifested itself in cases where users encounter
the ASM-based annotation parsing code paths AND declare
@ComponentScan annotations with explicit nested @Filter annotations.
In these cases, the 'includeFilters' and 'excludeFilters' attributes
are simply empty where they should be populated, causing the framework
to ignore the filter directives and provide incorrect results from
component scanning.
The purpose of this change then, is to introduce the capability on the
ASM side to recurse into nested annotations and annotation arrays. The
challenge in doing so is that the nested annotations themselves cannot
be realized as annotation instances, so must be represented as a
nested Map (or, as described below, the new AnnotationAttributes type).
Furthermore, the reflection-based annotation parsing must also be
updated to treat nested annotations in a similar fashion; even though
the reflection-based approach has no problem accessing nested
annotations (it just works out of the box), for substitutability
against the AnnotationMetadata SPI, both ASM- and reflection-based
implementations should return the same results in any case. Therefore,
the reflection-based StandardAnnotationMetadata has also been updated
with an optional 'nestedAnnotationsAsMap' constructor argument that is
false by default to preserve compatibility in the rare case that
StandardAnnotationMetadata is being used outside the core framework.
Within the framework, all uses of StandardAnnotationMetadata have been
updated to set this new flag to true, meaning that nested annotation
results will be consistent regardless the parsing approach used.
Spr9031Tests corners this bug and demonstrates that nested @Filter
annotations can be parsed and read in both the ASM- and
reflection-based paths.
Major changes
- AnnotationAttributes has been introduced as a concrete
LinkedHashMap<String, Object> to be used anywhere annotation
attributes are accessed, providing error reporting on attribute
lookup and convenient type-safe access to common annotation types
such as String, String[], boolean, int, and nested annotation and
annotation arrays, with the latter two also returned as
AnnotationAttributes instances.
- AnnotationUtils#getAnnotationAttributes methods now return
AnnotationAttributes instances, even though for binary compatibility
the signatures of these methods have been preserved as returning
Map<String, Object>.
- AnnotationAttributes#forMap provides a convenient mechanism for
adapting any Map<String, Object> into an AnnotationAttributes
instance. In the case that the Map is already actually of
type AnnotationAttributes, it is simply casted and returned.
Otherwise, the map is supplied to the AnnotationAttributes(Map)
constructor and wrapped in common collections style.
- The protected MetadataUtils#attributesFor(Metadata, Class) provides
further convenience in the many locations throughout the
.context.annotation packagage that depend on annotation attribute
introspection.
- ASM-based core.type.classreading package reworked
Specifically, AnnotationAttributesReadingVisitor has been enhanced to
support recursive reading of annotations and annotation arrays, for
example in @ComponentScan's nested array of @Filter annotations,
ensuring that nested AnnotationAttributes objects are populated as
described above.
AnnotationAttributesReadingVisitor has also been refactored for
clarity, being broken up into several additional ASM
AnnotationVisitor implementations. Given that all types are
package-private here, these changes represent no risk to binary
compatibility.
- Reflection-based StandardAnnotationMetadata updated
As described above, the 'nestedAnnotationsAsMap' constructor argument
has been added, and all framework-internal uses of this class have
been updated to set this flag to true.
Issue: SPR-7979, SPR-8719, SPR-9031
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
* Copyright 2002-2012 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.
|
||||
@@ -19,20 +19,20 @@ package org.springframework.context.annotation;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.config.BeanDefinitionHolder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanNameGenerator;
|
||||
import org.springframework.context.annotation.ComponentScan.Filter;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.core.type.filter.AnnotationTypeFilter;
|
||||
import org.springframework.core.type.filter.AssignableTypeFilter;
|
||||
import org.springframework.core.type.filter.TypeFilter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -46,18 +46,23 @@ import org.springframework.util.StringUtils;
|
||||
class ComponentScanAnnotationParser {
|
||||
|
||||
private final ResourceLoader resourceLoader;
|
||||
|
||||
private final Environment environment;
|
||||
|
||||
private final BeanDefinitionRegistry registry;
|
||||
|
||||
public ComponentScanAnnotationParser(ResourceLoader resourceLoader, Environment environment, BeanDefinitionRegistry registry) {
|
||||
|
||||
public ComponentScanAnnotationParser(
|
||||
ResourceLoader resourceLoader, Environment environment, BeanDefinitionRegistry registry) {
|
||||
this.resourceLoader = resourceLoader;
|
||||
this.environment = environment;
|
||||
this.registry = registry;
|
||||
}
|
||||
|
||||
public Set<BeanDefinitionHolder> parse(Map<String, Object> componentScanAttributes) {
|
||||
|
||||
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan) {
|
||||
ClassPathBeanDefinitionScanner scanner =
|
||||
new ClassPathBeanDefinitionScanner(registry, (Boolean)componentScanAttributes.get("useDefaultFilters"));
|
||||
new ClassPathBeanDefinitionScanner(registry, componentScan.getBoolean("useDefaultFilters"));
|
||||
|
||||
Assert.notNull(this.environment, "Environment must not be null");
|
||||
scanner.setEnvironment(this.environment);
|
||||
@@ -66,45 +71,42 @@ class ComponentScanAnnotationParser {
|
||||
scanner.setResourceLoader(this.resourceLoader);
|
||||
|
||||
scanner.setBeanNameGenerator(BeanUtils.instantiateClass(
|
||||
(Class<?>)componentScanAttributes.get("nameGenerator"), BeanNameGenerator.class));
|
||||
componentScan.getClass("nameGenerator", BeanNameGenerator.class)));
|
||||
|
||||
ScopedProxyMode scopedProxyMode = (ScopedProxyMode) componentScanAttributes.get("scopedProxy");
|
||||
ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy", ScopedProxyMode.class);
|
||||
if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
|
||||
scanner.setScopedProxyMode(scopedProxyMode);
|
||||
} else {
|
||||
scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(
|
||||
(Class<?>)componentScanAttributes.get("scopeResolver"), ScopeMetadataResolver.class));
|
||||
componentScan.getClass("scopeResolver", ScopeMetadataResolver.class)));
|
||||
}
|
||||
|
||||
scanner.setResourcePattern((String)componentScanAttributes.get("resourcePattern"));
|
||||
scanner.setResourcePattern(componentScan.getString("resourcePattern"));
|
||||
|
||||
for (Filter filterAnno : (Filter[])componentScanAttributes.get("includeFilters")) {
|
||||
for (TypeFilter typeFilter : typeFiltersFor(filterAnno)) {
|
||||
for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
|
||||
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
|
||||
scanner.addIncludeFilter(typeFilter);
|
||||
}
|
||||
}
|
||||
for (Filter filterAnno : (Filter[])componentScanAttributes.get("excludeFilters")) {
|
||||
for (TypeFilter typeFilter : typeFiltersFor(filterAnno)) {
|
||||
for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
|
||||
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
|
||||
scanner.addExcludeFilter(typeFilter);
|
||||
}
|
||||
}
|
||||
|
||||
List<String> basePackages = new ArrayList<String>();
|
||||
for (String pkg : (String[])componentScanAttributes.get("value")) {
|
||||
for (String pkg : componentScan.getStringArray("value")) {
|
||||
if (StringUtils.hasText(pkg)) {
|
||||
basePackages.add(pkg);
|
||||
}
|
||||
}
|
||||
for (String pkg : (String[])componentScanAttributes.get("basePackages")) {
|
||||
for (String pkg : componentScan.getStringArray("basePackages")) {
|
||||
if (StringUtils.hasText(pkg)) {
|
||||
basePackages.add(pkg);
|
||||
}
|
||||
}
|
||||
for (Class<?> clazz : (Class<?>[])componentScanAttributes.get("basePackageClasses")) {
|
||||
// TODO: loading user types directly here. implications on load-time
|
||||
// weaving may mean we need to revert to stringified class names in
|
||||
// annotation metadata
|
||||
basePackages.add(clazz.getPackage().getName());
|
||||
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
|
||||
basePackages.add(ClassUtils.getPackageName(clazz));
|
||||
}
|
||||
|
||||
if (basePackages.isEmpty()) {
|
||||
@@ -114,10 +116,12 @@ class ComponentScanAnnotationParser {
|
||||
return scanner.doScan(basePackages.toArray(new String[]{}));
|
||||
}
|
||||
|
||||
private List<TypeFilter> typeFiltersFor(Filter filterAnno) {
|
||||
private List<TypeFilter> typeFiltersFor(AnnotationAttributes filterAttributes) {
|
||||
List<TypeFilter> typeFilters = new ArrayList<TypeFilter>();
|
||||
for (Class<?> filterClass : (Class<?>[])filterAnno.value()) {
|
||||
switch (filterAnno.type()) {
|
||||
FilterType filterType = filterAttributes.getEnum("type", FilterType.class);
|
||||
|
||||
for (Class<?> filterClass : filterAttributes.getClassArray("value")) {
|
||||
switch (filterType) {
|
||||
case ANNOTATION:
|
||||
Assert.isAssignable(Annotation.class, filterClass,
|
||||
"An error occured when processing a @ComponentScan " +
|
||||
@@ -136,7 +140,7 @@ class ComponentScanAnnotationParser {
|
||||
typeFilters.add(BeanUtils.instantiateClass(filterClass, TypeFilter.class));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("unknown filter type " + filterAnno.type());
|
||||
throw new IllegalArgumentException("unknown filter type " + filterType);
|
||||
}
|
||||
}
|
||||
return typeFilters;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
* Copyright 2002-2012 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.
|
||||
@@ -97,7 +97,7 @@ final class ConfigurationClass {
|
||||
*/
|
||||
public ConfigurationClass(Class<?> clazz, String beanName) {
|
||||
Assert.hasText(beanName, "bean name must not be null");
|
||||
this.metadata = new StandardAnnotationMetadata(clazz);
|
||||
this.metadata = new StandardAnnotationMetadata(clazz, true);
|
||||
this.resource = new DescriptiveResource(clazz.toString());
|
||||
this.beanName = beanName;
|
||||
this.imported = false;
|
||||
@@ -112,7 +112,7 @@ final class ConfigurationClass {
|
||||
* @since 3.1.1
|
||||
*/
|
||||
public ConfigurationClass(Class<?> clazz, boolean imported) {
|
||||
this.metadata = new StandardAnnotationMetadata(clazz);
|
||||
this.metadata = new StandardAnnotationMetadata(clazz, true);
|
||||
this.resource = new DescriptiveResource(clazz.toString());
|
||||
this.imported = imported;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
* Copyright 2002-2012 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.
|
||||
@@ -35,6 +35,7 @@ import org.springframework.beans.factory.parsing.Location;
|
||||
import org.springframework.beans.factory.parsing.Problem;
|
||||
import org.springframework.beans.factory.parsing.ProblemReporter;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
@@ -139,7 +140,7 @@ class ConfigurationClassParser {
|
||||
if (superClassName != null && !Object.class.getName().equals(superClassName)) {
|
||||
if (metadata instanceof StandardAnnotationMetadata) {
|
||||
Class<?> clazz = ((StandardAnnotationMetadata) metadata).getIntrospectedClass();
|
||||
metadata = new StandardAnnotationMetadata(clazz.getSuperclass());
|
||||
metadata = new StandardAnnotationMetadata(clazz.getSuperclass(), true);
|
||||
}
|
||||
else {
|
||||
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(superClassName);
|
||||
@@ -187,10 +188,10 @@ class ConfigurationClassParser {
|
||||
}
|
||||
|
||||
// process any @ComponentScan annotions
|
||||
Map<String, Object> componentScanAttributes = metadata.getAnnotationAttributes(ComponentScan.class.getName());
|
||||
if (componentScanAttributes != null) {
|
||||
AnnotationAttributes componentScan = MetadataUtils.attributesFor(metadata, ComponentScan.class);
|
||||
if (componentScan != null) {
|
||||
// the config class is annotated with @ComponentScan -> perform the scan immediately
|
||||
Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScanAttributes);
|
||||
Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan);
|
||||
|
||||
// check the set of scanned definitions for any further config classes and parse recursively if necessary
|
||||
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
* Copyright 2002-2012 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.
|
||||
@@ -60,7 +60,8 @@ abstract class ConfigurationClassUtils {
|
||||
// Check already loaded Class if present...
|
||||
// since we possibly can't even load the class file for this Class.
|
||||
if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
|
||||
metadata = new StandardAnnotationMetadata(((AbstractBeanDefinition) beanDef).getBeanClass());
|
||||
Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();
|
||||
metadata = new StandardAnnotationMetadata(beanClass, true);
|
||||
}
|
||||
else {
|
||||
String className = beanDef.getBeanClassName();
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2002-2012 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.annotation;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.core.type.MethodMetadata;
|
||||
|
||||
/**
|
||||
* Convenience methods adapting {@link AnnotationMetadata} and {@link MethodMetadata}
|
||||
* annotation attribute maps to the {@link AnnotationAttributes} API. As of Spring 3.1.1,
|
||||
* both the reflection- and ASM-based implementations of these SPIs return
|
||||
* {@link AnnotationAttributes} instances anyway, but for backward-compatibility, their
|
||||
* signatures still return Maps. Therefore, for the usual case, these methods perform
|
||||
* little more than a cast from Map to AnnotationAttributes.
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @since 3.1.1
|
||||
* @see AnnotationAttributes#fromMap(java.util.Map)
|
||||
*/
|
||||
class MetadataUtils {
|
||||
|
||||
public static AnnotationAttributes attributesFor(AnnotationMetadata metadata, Class<?> annoClass) {
|
||||
return attributesFor(metadata, annoClass.getName());
|
||||
}
|
||||
|
||||
public static AnnotationAttributes attributesFor(AnnotationMetadata metadata, String annoClassName) {
|
||||
return AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(annoClassName, false));
|
||||
}
|
||||
|
||||
public static AnnotationAttributes attributesFor(MethodMetadata metadata, Class<?> targetAnno) {
|
||||
return AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(targetAnno.getName()));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user