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:
Chris Beams 2011-04-06 15:41:08 +08:00
parent 905d17d444
commit d9f7fdd120
17 changed files with 918 additions and 196 deletions

View File

@ -47,7 +47,7 @@ public class AnnotatedGenericBeanDefinition extends GenericBeanDefinition implem
*/ */
public AnnotatedGenericBeanDefinition(Class beanClass) { public AnnotatedGenericBeanDefinition(Class beanClass) {
setBeanClass(beanClass); setBeanClass(beanClass);
this.annotationMetadata = new StandardAnnotationMetadata(beanClass); this.annotationMetadata = new StandardAnnotationMetadata(beanClass, true);
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.lang.annotation.Annotation;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator; 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.env.Environment;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.AssignableTypeFilter; import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.core.type.filter.TypeFilter; import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
@ -46,18 +46,23 @@ import org.springframework.util.StringUtils;
class ComponentScanAnnotationParser { class ComponentScanAnnotationParser {
private final ResourceLoader resourceLoader; private final ResourceLoader resourceLoader;
private final Environment environment; private final Environment environment;
private final BeanDefinitionRegistry registry; private final BeanDefinitionRegistry registry;
public ComponentScanAnnotationParser(ResourceLoader resourceLoader, Environment environment, BeanDefinitionRegistry registry) {
public ComponentScanAnnotationParser(
ResourceLoader resourceLoader, Environment environment, BeanDefinitionRegistry registry) {
this.resourceLoader = resourceLoader; this.resourceLoader = resourceLoader;
this.environment = environment; this.environment = environment;
this.registry = registry; this.registry = registry;
} }
public Set<BeanDefinitionHolder> parse(Map<String, Object> componentScanAttributes) {
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan) {
ClassPathBeanDefinitionScanner scanner = ClassPathBeanDefinitionScanner scanner =
new ClassPathBeanDefinitionScanner(registry, (Boolean)componentScanAttributes.get("useDefaultFilters")); new ClassPathBeanDefinitionScanner(registry, componentScan.getBoolean("useDefaultFilters"));
Assert.notNull(this.environment, "Environment must not be null"); Assert.notNull(this.environment, "Environment must not be null");
scanner.setEnvironment(this.environment); scanner.setEnvironment(this.environment);
@ -66,45 +71,42 @@ class ComponentScanAnnotationParser {
scanner.setResourceLoader(this.resourceLoader); scanner.setResourceLoader(this.resourceLoader);
scanner.setBeanNameGenerator(BeanUtils.instantiateClass( 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) { if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
scanner.setScopedProxyMode(scopedProxyMode); scanner.setScopedProxyMode(scopedProxyMode);
} else { } else {
scanner.setScopeMetadataResolver(BeanUtils.instantiateClass( 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 (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filterAnno)) { for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addIncludeFilter(typeFilter); scanner.addIncludeFilter(typeFilter);
} }
} }
for (Filter filterAnno : (Filter[])componentScanAttributes.get("excludeFilters")) { for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filterAnno)) { for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addExcludeFilter(typeFilter); scanner.addExcludeFilter(typeFilter);
} }
} }
List<String> basePackages = new ArrayList<String>(); List<String> basePackages = new ArrayList<String>();
for (String pkg : (String[])componentScanAttributes.get("value")) { for (String pkg : componentScan.getStringArray("value")) {
if (StringUtils.hasText(pkg)) { if (StringUtils.hasText(pkg)) {
basePackages.add(pkg); basePackages.add(pkg);
} }
} }
for (String pkg : (String[])componentScanAttributes.get("basePackages")) { for (String pkg : componentScan.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) { if (StringUtils.hasText(pkg)) {
basePackages.add(pkg); basePackages.add(pkg);
} }
} }
for (Class<?> clazz : (Class<?>[])componentScanAttributes.get("basePackageClasses")) { for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
// TODO: loading user types directly here. implications on load-time basePackages.add(ClassUtils.getPackageName(clazz));
// weaving may mean we need to revert to stringified class names in
// annotation metadata
basePackages.add(clazz.getPackage().getName());
} }
if (basePackages.isEmpty()) { if (basePackages.isEmpty()) {
@ -114,10 +116,12 @@ class ComponentScanAnnotationParser {
return scanner.doScan(basePackages.toArray(new String[]{})); 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>(); List<TypeFilter> typeFilters = new ArrayList<TypeFilter>();
for (Class<?> filterClass : (Class<?>[])filterAnno.value()) { FilterType filterType = filterAttributes.getEnum("type", FilterType.class);
switch (filterAnno.type()) {
for (Class<?> filterClass : filterAttributes.getClassArray("value")) {
switch (filterType) {
case ANNOTATION: case ANNOTATION:
Assert.isAssignable(Annotation.class, filterClass, Assert.isAssignable(Annotation.class, filterClass,
"An error occured when processing a @ComponentScan " + "An error occured when processing a @ComponentScan " +
@ -136,7 +140,7 @@ class ComponentScanAnnotationParser {
typeFilters.add(BeanUtils.instantiateClass(filterClass, TypeFilter.class)); typeFilters.add(BeanUtils.instantiateClass(filterClass, TypeFilter.class));
break; break;
default: default:
throw new IllegalArgumentException("unknown filter type " + filterAnno.type()); throw new IllegalArgumentException("unknown filter type " + filterType);
} }
} }
return typeFilters; return typeFilters;

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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) { public ConfigurationClass(Class<?> clazz, String beanName) {
Assert.hasText(beanName, "bean name must not be null"); 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.resource = new DescriptiveResource(clazz.toString());
this.beanName = beanName; this.beanName = beanName;
this.imported = false; this.imported = false;
@ -112,7 +112,7 @@ final class ConfigurationClass {
* @since 3.1.1 * @since 3.1.1
*/ */
public ConfigurationClass(Class<?> clazz, boolean imported) { public ConfigurationClass(Class<?> clazz, boolean imported) {
this.metadata = new StandardAnnotationMetadata(clazz); this.metadata = new StandardAnnotationMetadata(clazz, true);
this.resource = new DescriptiveResource(clazz.toString()); this.resource = new DescriptiveResource(clazz.toString());
this.imported = imported; this.imported = imported;
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.Problem;
import org.springframework.beans.factory.parsing.ProblemReporter; import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
@ -139,7 +140,7 @@ class ConfigurationClassParser {
if (superClassName != null && !Object.class.getName().equals(superClassName)) { if (superClassName != null && !Object.class.getName().equals(superClassName)) {
if (metadata instanceof StandardAnnotationMetadata) { if (metadata instanceof StandardAnnotationMetadata) {
Class<?> clazz = ((StandardAnnotationMetadata) metadata).getIntrospectedClass(); Class<?> clazz = ((StandardAnnotationMetadata) metadata).getIntrospectedClass();
metadata = new StandardAnnotationMetadata(clazz.getSuperclass()); metadata = new StandardAnnotationMetadata(clazz.getSuperclass(), true);
} }
else { else {
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(superClassName); MetadataReader reader = this.metadataReaderFactory.getMetadataReader(superClassName);
@ -187,10 +188,10 @@ class ConfigurationClassParser {
} }
// process any @ComponentScan annotions // process any @ComponentScan annotions
Map<String, Object> componentScanAttributes = metadata.getAnnotationAttributes(ComponentScan.class.getName()); AnnotationAttributes componentScan = MetadataUtils.attributesFor(metadata, ComponentScan.class);
if (componentScanAttributes != null) { if (componentScan != null) {
// the config class is annotated with @ComponentScan -> perform the scan immediately // 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 // check the set of scanned definitions for any further config classes and parse recursively if necessary
for (BeanDefinitionHolder holder : scannedBeanDefinitions) { for (BeanDefinitionHolder holder : scannedBeanDefinitions) {

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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... // Check already loaded Class if present...
// since we possibly can't even load the class file for this Class. // since we possibly can't even load the class file for this Class.
if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) { if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
metadata = new StandardAnnotationMetadata(((AbstractBeanDefinition) beanDef).getBeanClass()); Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();
metadata = new StandardAnnotationMetadata(beanClass, true);
} }
else { else {
String className = beanDef.getBeanClassName(); String className = beanDef.getBeanClassName();

View File

@ -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()));
}
}

View File

@ -0,0 +1,79 @@
/*
* 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.configuration.spr9031;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.configuration.spr9031.scanpackage.Spr9031Component;
/**
* Unit tests cornering bug SPR-9031.
*
* @author Chris Beams
* @since 3.1.1
*/
public class Spr9031Tests {
/**
* Use of @Import to register LowLevelConfig results in ASM-based annotation
* processing.
*/
@Test
public void withAsmAnnotationProcessing() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(HighLevelConfig.class);
ctx.refresh();
assertThat(ctx.getBean(LowLevelConfig.class).scanned, not(nullValue()));
}
/**
* Direct registration of LowLevelConfig results in reflection-based annotation
* processing.
*/
@Test
public void withoutAsmAnnotationProcessing() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(LowLevelConfig.class);
ctx.refresh();
assertThat(ctx.getBean(LowLevelConfig.class).scanned, not(nullValue()));
}
@Configuration
@Import(LowLevelConfig.class)
static class HighLevelConfig {}
@Configuration
@ComponentScan(
basePackages = "org.springframework.context.annotation.configuration.spr9031.scanpackage",
includeFilters = { @Filter(MarkerAnnotation.class) })
static class LowLevelConfig {
// fails to wire when LowLevelConfig is processed with ASM because nested @Filter
// annotation is not parsed
@Autowired Spr9031Component scanned;
}
public @interface MarkerAnnotation {}
}

View File

@ -0,0 +1,24 @@
/*
* 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.configuration.spr9031.scanpackage;
import org.springframework.context.annotation.configuration.spr9031.Spr9031Tests.MarkerAnnotation;
@MarkerAnnotation
public class Spr9031Component {
}

View File

@ -0,0 +1,130 @@
/*
* 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.core.annotation;
import static java.lang.String.format;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.util.Assert;
/**
* {@link LinkedHashMap} subclass representing annotation attribute key/value pairs
* as read by Spring's reflection- or ASM-based {@link
* org.springframework.core.type.AnnotationMetadata AnnotationMetadata} implementations.
* Provides 'pseudo-reification' to avoid noisy Map generics in the calling code as well
* as convenience methods for looking up annotation attributes in a type-safe fashion.
*
* @author Chris Beams
* @since 3.1.1
*/
@SuppressWarnings("serial")
public class AnnotationAttributes extends LinkedHashMap<String, Object> {
/**
* Create a new, empty {@link AnnotationAttributes} instance.
*/
public AnnotationAttributes() {
}
/**
* Create a new, empty {@link AnnotationAttributes} instance with the given initial
* capacity to optimize performance.
* @param initialCapacity initial size of the underlying map
*/
public AnnotationAttributes(int initialCapacity) {
super(initialCapacity);
}
/**
* Create a new {@link AnnotationAttributes} instance, wrapping the provided map
* and all its key/value pairs.
* @param map original source of annotation attribute key/value pairs to wrap
* @see #fromMap(Map)
*/
public AnnotationAttributes(Map<String, Object> map) {
super(map);
}
/**
* Return an {@link AnnotationAttributes} instance based on the given map; if the map
* is already an {@code AnnotationAttributes} instance, it is casted and returned
* immediately without creating any new instance; otherwise create a new instance by
* wrapping the map with the {@link #AnnotationAttributes(Map)} constructor.
* @param map original source of annotation attribute key/value pairs
*/
public static AnnotationAttributes fromMap(Map<String, Object> map) {
if (map == null) {
return null;
}
if (map instanceof AnnotationAttributes) {
return (AnnotationAttributes) map;
}
return new AnnotationAttributes(map);
}
public String getString(String attributeName) {
return doGet(attributeName, String.class);
}
public String[] getStringArray(String attributeName) {
return doGet(attributeName, String[].class);
}
public boolean getBoolean(String attributeName) {
return doGet(attributeName, Boolean.class);
}
public int getInt(String attributeName) {
return doGet(attributeName, Integer.class);
}
public <E extends Enum<?>> E getEnum(String attributeName, Class<E> enumType) {
return doGet(attributeName, enumType);
}
@SuppressWarnings("unchecked")
public <T> Class<? extends T> getClass(String attributeName, Class<T> expectedType) {
return (Class<T>)doGet(attributeName, Class.class);
}
public Class<?>[] getClassArray(String attributeName) {
return doGet(attributeName, Class[].class);
}
public AnnotationAttributes getAnnotation(String attributeName) {
return doGet(attributeName, AnnotationAttributes.class);
}
public AnnotationAttributes[] getAnnotationArray(String attributeName) {
return doGet(attributeName, AnnotationAttributes[].class);
}
@SuppressWarnings("unchecked")
private <T> T doGet(String attributeName, Class<T> expectedType) {
Assert.hasText(attributeName, "attributeName must not be null or empty");
Object value = this.get(attributeName);
Assert.notNull(value, format("Attribute '%s' not found", attributeName));
Assert.isAssignable(expectedType, value.getClass(),
format("Attribute '%s' is of type [%s], but [%s] was expected. Cause: ",
attributeName, value.getClass().getSimpleName(), expectedType.getSimpleName()));
return (T) value;
}
}

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -20,7 +20,6 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement; import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.WeakHashMap; import java.util.WeakHashMap;
@ -300,25 +299,58 @@ public abstract class AnnotationUtils {
} }
/** /**
* Retrieve the given annotation's attributes as a Map, preserving all attribute types as-is. * Retrieve the given annotation's attributes as a Map, preserving all attribute types
* as-is.
* <p>Note: As of Spring 3.1.1, the returned map is actually an
* {@link AnnotationAttributes} instance, however the Map signature of this method has
* been preserved for binary compatibility.
* @param annotation the annotation to retrieve the attributes for * @param annotation the annotation to retrieve the attributes for
* @return the Map of annotation attributes, with attribute names as keys and * @return the Map of annotation attributes, with attribute names as keys and
* corresponding attribute values as values * corresponding attribute values as values
*/ */
public static Map<String, Object> getAnnotationAttributes(Annotation annotation) { public static Map<String, Object> getAnnotationAttributes(Annotation annotation) {
return getAnnotationAttributes(annotation, false); return getAnnotationAttributes(annotation, false, false);
} }
/** /**
* Retrieve the given annotation's attributes as a Map. * Retrieve the given annotation's attributes as a Map. Equivalent to calling
* {@link #getAnnotationAttributes(Annotation, boolean, boolean)} with
* the {@code nestedAnnotationsAsMap} parameter set to {@code false}.
* <p>Note: As of Spring 3.1.1, the returned map is actually an
* {@link AnnotationAttributes} instance, however the Map signature of this method has
* been preserved for binary compatibility.
* @param annotation the annotation to retrieve the attributes for * @param annotation the annotation to retrieve the attributes for
* @param classValuesAsString whether to turn Class references into Strings (for compatibility with * @param classValuesAsString whether to turn Class references into Strings (for
* {@link org.springframework.core.type.AnnotationMetadata} or to preserve them as Class references * compatibility with {@link org.springframework.core.type.AnnotationMetadata} or to
* preserve them as Class references
* @return the Map of annotation attributes, with attribute names as keys and * @return the Map of annotation attributes, with attribute names as keys and
* corresponding attribute values as values * corresponding attribute values as values
*/ */
public static Map<String, Object> getAnnotationAttributes(Annotation annotation, boolean classValuesAsString) { public static Map<String, Object> getAnnotationAttributes(Annotation annotation, boolean classValuesAsString) {
Map<String, Object> attrs = new HashMap<String, Object>(); return getAnnotationAttributes(annotation, classValuesAsString, false);
}
/**
* Retrieve the given annotation's attributes as an {@link AnnotationAttributes}
* map structure. Implemented in Spring 3.1.1 to provide fully recursive annotation
* reading capabilities on par with that of the reflection-based
* {@link org.springframework.core.type.StandardAnnotationMetadata}.
* @param annotation the annotation to retrieve the attributes for
* @param classValuesAsString whether to turn Class references into Strings (for
* compatibility with {@link org.springframework.core.type.AnnotationMetadata} or to
* preserve them as Class references
* @param nestedAnnotationsAsMap whether to turn nested Annotation instances into
* {@link AnnotationAttributes} maps (for compatibility with
* {@link org.springframework.core.type.AnnotationMetadata} or to preserve them as
* Annotation instances
* @return the annotation attributes (a specialized Map) with attribute names as keys
* and corresponding attribute values as values
* @since 3.1.1
*/
public static AnnotationAttributes getAnnotationAttributes(
Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
AnnotationAttributes attrs = new AnnotationAttributes();
Method[] methods = annotation.annotationType().getDeclaredMethods(); Method[] methods = annotation.annotationType().getDeclaredMethods();
for (Method method : methods) { for (Method method : methods) {
if (method.getParameterTypes().length == 0 && method.getReturnType() != void.class) { if (method.getParameterTypes().length == 0 && method.getReturnType() != void.class) {
@ -337,7 +369,22 @@ public abstract class AnnotationUtils {
value = newValue; value = newValue;
} }
} }
attrs.put(method.getName(), value); if (nestedAnnotationsAsMap && value instanceof Annotation) {
attrs.put(method.getName(), getAnnotationAttributes(
(Annotation)value, classValuesAsString, nestedAnnotationsAsMap));
}
else if (nestedAnnotationsAsMap && value instanceof Annotation[]) {
Annotation[] realAnnotations = (Annotation[])value;
AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[realAnnotations.length];
for (int i = 0; i < realAnnotations.length; i++) {
mappedAnnotations[i] = getAnnotationAttributes(
realAnnotations[i], classValuesAsString, nestedAnnotationsAsMap);
}
attrs.put(method.getName(), mappedAnnotations);
}
else {
attrs.put(method.getName(), value);
}
} }
catch (Exception ex) { catch (Exception ex) {
throw new IllegalStateException("Could not obtain annotation attribute values", ex); throw new IllegalStateException("Could not obtain annotation attribute values", ex);

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2009 the original author or authors. * Copyright 2002-2012 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -22,24 +22,45 @@ import java.util.LinkedHashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
/** /**
* {@link AnnotationMetadata} implementation that uses standard reflection * {@link AnnotationMetadata} implementation that uses standard reflection
* to introspect a given <code>Class</code>. * to introspect a given {@link Class}.
* *
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Mark Fisher * @author Mark Fisher
* @author Chris Beams
* @since 2.5 * @since 2.5
*/ */
public class StandardAnnotationMetadata extends StandardClassMetadata implements AnnotationMetadata { public class StandardAnnotationMetadata extends StandardClassMetadata implements AnnotationMetadata {
private final boolean nestedAnnotationsAsMap;
/** /**
* Create a new StandardAnnotationMetadata wrapper for the given Class. * Create a new {@code StandardAnnotationMetadata} wrapper for the given Class.
* @param introspectedClass the Class to introspect * @param introspectedClass the Class to introspect
* @see #StandardAnnotationMetadata(Class, boolean)
*/ */
public StandardAnnotationMetadata(Class introspectedClass) { public StandardAnnotationMetadata(Class<?> introspectedClass) {
this(introspectedClass, false);
}
/**
* Create a new {@link StandardAnnotationMetadata} wrapper for the given Class,
* providing the option to return any nested annotations or annotation arrays in the
* form of {@link AnnotationAttributes} instead of actual {@link Annotation} instances.
* @param introspectedClass the Class to instrospect
* @param nestedAnnotationsAsMap return nested annotations and annotation arrays as
* {@link AnnotationAttributes} for compatibility with ASM-based
* {@link AnnotationMetadata} implementations
* @since 3.1.1
*/
public StandardAnnotationMetadata(Class<?> introspectedClass, boolean nestedAnnotationsAsMap) {
super(introspectedClass); super(introspectedClass);
this.nestedAnnotationsAsMap = nestedAnnotationsAsMap;
} }
@ -114,18 +135,20 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements
} }
public Map<String, Object> getAnnotationAttributes(String annotationType) { public Map<String, Object> getAnnotationAttributes(String annotationType) {
return getAnnotationAttributes(annotationType, false); return this.getAnnotationAttributes(annotationType, false);
} }
public Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString) { public Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString) {
Annotation[] anns = getIntrospectedClass().getAnnotations(); Annotation[] anns = getIntrospectedClass().getAnnotations();
for (Annotation ann : anns) { for (Annotation ann : anns) {
if (ann.annotationType().getName().equals(annotationType)) { if (ann.annotationType().getName().equals(annotationType)) {
return AnnotationUtils.getAnnotationAttributes(ann, classValuesAsString); return AnnotationUtils.getAnnotationAttributes(
ann, classValuesAsString, this.nestedAnnotationsAsMap);
} }
for (Annotation metaAnn : ann.annotationType().getAnnotations()) { for (Annotation metaAnn : ann.annotationType().getAnnotations()) {
if (metaAnn.annotationType().getName().equals(annotationType)) { if (metaAnn.annotationType().getName().equals(annotationType)) {
return AnnotationUtils.getAnnotationAttributes(metaAnn, classValuesAsString); return AnnotationUtils.getAnnotationAttributes(
metaAnn, classValuesAsString, this.nestedAnnotationsAsMap);
} }
} }
} }
@ -157,13 +180,13 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements
for (Method method : methods) { for (Method method : methods) {
for (Annotation ann : method.getAnnotations()) { for (Annotation ann : method.getAnnotations()) {
if (ann.annotationType().getName().equals(annotationType)) { if (ann.annotationType().getName().equals(annotationType)) {
annotatedMethods.add(new StandardMethodMetadata(method)); annotatedMethods.add(new StandardMethodMetadata(method, this.nestedAnnotationsAsMap));
break; break;
} }
else { else {
for (Annotation metaAnn : ann.annotationType().getAnnotations()) { for (Annotation metaAnn : ann.annotationType().getAnnotations()) {
if (metaAnn.annotationType().getName().equals(annotationType)) { if (metaAnn.annotationType().getName().equals(annotationType)) {
annotatedMethods.add(new StandardMethodMetadata(method)); annotatedMethods.add(new StandardMethodMetadata(method, this.nestedAnnotationsAsMap));
break; break;
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2009 the original author or authors. * Copyright 2002-2012 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -37,14 +37,27 @@ public class StandardMethodMetadata implements MethodMetadata {
private final Method introspectedMethod; private final Method introspectedMethod;
private final boolean nestedAnnotationsAsMap;
/** /**
* Create a new StandardMethodMetadata wrapper for the given Method. * Create a new StandardMethodMetadata wrapper for the given Method.
* @param introspectedMethod the Method to introspect * @param introspectedMethod the Method to introspect
*/ */
public StandardMethodMetadata(Method introspectedMethod) { public StandardMethodMetadata(Method introspectedMethod) {
this(introspectedMethod, false);
}
/**
* Create a new StandardMethodMetadata wrapper for the given Method.
* @param introspectedMethod the Method to introspect
* @param nestedAnnotationsAsMap
* @since 3.1.1
*/
public StandardMethodMetadata(Method introspectedMethod, boolean nestedAnnotationsAsMap) {
Assert.notNull(introspectedMethod, "Method must not be null"); Assert.notNull(introspectedMethod, "Method must not be null");
this.introspectedMethod = introspectedMethod; this.introspectedMethod = introspectedMethod;
this.nestedAnnotationsAsMap = nestedAnnotationsAsMap;
} }
/** /**
@ -94,11 +107,13 @@ public class StandardMethodMetadata implements MethodMetadata {
Annotation[] anns = this.introspectedMethod.getAnnotations(); Annotation[] anns = this.introspectedMethod.getAnnotations();
for (Annotation ann : anns) { for (Annotation ann : anns) {
if (ann.annotationType().getName().equals(annotationType)) { if (ann.annotationType().getName().equals(annotationType)) {
return AnnotationUtils.getAnnotationAttributes(ann, true); return AnnotationUtils.getAnnotationAttributes(
ann, true, nestedAnnotationsAsMap);
} }
for (Annotation metaAnn : ann.annotationType().getAnnotations()) { for (Annotation metaAnn : ann.annotationType().getAnnotations()) {
if (metaAnn.annotationType().getName().equals(annotationType)) { if (metaAnn.annotationType().getName().equals(annotationType)) {
return AnnotationUtils.getAnnotationAttributes(metaAnn, true); return AnnotationUtils.getAnnotationAttributes(
metaAnn, true, this.nestedAnnotationsAsMap);
} }
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2009 the original author or authors. * Copyright 2002-2012 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -20,131 +20,242 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.LinkedHashMap; import java.util.ArrayList;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.asm.AnnotationVisitor; import org.springframework.asm.AnnotationVisitor;
import org.springframework.asm.Type; import org.springframework.asm.Type;
import org.springframework.asm.commons.EmptyVisitor; import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
/** /**
* ASM visitor which looks for the annotations defined on a class or method. * @author Chris Beams
*
* @author Juergen Hoeller * @author Juergen Hoeller
* @since 3.0 * @since 3.1.1
*/ */
final class AnnotationAttributesReadingVisitor implements AnnotationVisitor { abstract class AbstractRecursiveAnnotationVisitor implements AnnotationVisitor {
private final String annotationType; protected final Log logger = LogFactory.getLog(this.getClass());
private final Map<String, Map<String, Object>> attributesMap; protected final AnnotationAttributes attributes;
private final Map<String, Set<String>> metaAnnotationMap; protected final ClassLoader classLoader;
private final ClassLoader classLoader;
private final Map<String, Object> localAttributes = new LinkedHashMap<String, Object>();
public AnnotationAttributesReadingVisitor( public AbstractRecursiveAnnotationVisitor(ClassLoader classLoader, AnnotationAttributes attributes) {
String annotationType, Map<String, Map<String, Object>> attributesMap,
Map<String, Set<String>> metaAnnotationMap, ClassLoader classLoader) {
this.annotationType = annotationType;
this.attributesMap = attributesMap;
this.metaAnnotationMap = metaAnnotationMap;
this.classLoader = classLoader; this.classLoader = classLoader;
this.attributes = attributes;
} }
public void visit(String name, Object value) { public void visit(String attributeName, Object attributeValue) {
this.localAttributes.put(name, value); this.attributes.put(attributeName, attributeValue);
} }
public void visitEnum(String name, String desc, String value) { public AnnotationVisitor visitAnnotation(String attributeName, String asmTypeDescriptor) {
Object valueToUse = value; String annotationType = Type.getType(asmTypeDescriptor).getClassName();
AnnotationAttributes nestedAttributes = new AnnotationAttributes();
this.attributes.put(attributeName, nestedAttributes);
return new RecursiveAnnotationAttributesVisitor(annotationType, nestedAttributes, this.classLoader);
}
public AnnotationVisitor visitArray(String attributeName) {
return new RecursiveAnnotationArrayVisitor(attributeName, this.attributes, this.classLoader);
}
public void visitEnum(String attributeName, String asmTypeDescriptor, String attributeValue) {
Object valueToUse = attributeValue;
try { try {
Class<?> enumType = this.classLoader.loadClass(Type.getType(desc).getClassName()); Class<?> enumType = this.classLoader.loadClass(Type.getType(asmTypeDescriptor).getClassName());
Field enumConstant = ReflectionUtils.findField(enumType, value); Field enumConstant = ReflectionUtils.findField(enumType, attributeValue);
if (enumConstant != null) { if (enumConstant != null) {
valueToUse = enumConstant.get(null); valueToUse = enumConstant.get(null);
} }
} }
catch (Exception ex) { catch (Exception ex) {
// Class not found - can't resolve class reference in annotation attribute. logNonFatalException(ex);
} }
this.localAttributes.put(name, valueToUse); this.attributes.put(attributeName, valueToUse);
} }
public AnnotationVisitor visitAnnotation(String name, String desc) {
return new EmptyVisitor(); protected void logNonFatalException(Exception ex) {
this.logger.warn("Failed to classload type while reading annotation metadata. " +
"This is a non-fatal error, but certain annotation metadata may be " +
"unavailable.", ex);
}
}
/**
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.1.1
*/
final class RecursiveAnnotationArrayVisitor extends AbstractRecursiveAnnotationVisitor {
private final String attributeName;
private final List<AnnotationAttributes> allNestedAttributes = new ArrayList<AnnotationAttributes>();
public RecursiveAnnotationArrayVisitor(
String attributeName, AnnotationAttributes attributes, ClassLoader classLoader) {
super(classLoader, attributes);
this.attributeName = attributeName;
} }
public AnnotationVisitor visitArray(final String attrName) { @Override
return new AnnotationVisitor() { public void visit(String attributeName, Object attributeValue) {
public void visit(String name, Object value) { Object newValue = attributeValue;
Object newValue = value; Object existingValue = this.attributes.get(this.attributeName);
Object existingValue = localAttributes.get(attrName); if (existingValue != null) {
if (existingValue != null) { newValue = ObjectUtils.addObjectToArray((Object[]) existingValue, newValue);
newValue = ObjectUtils.addObjectToArray((Object[]) existingValue, newValue); }
} else {
else { Object[] newArray = (Object[]) Array.newInstance(newValue.getClass(), 1);
Object[] newArray = (Object[]) Array.newInstance(newValue.getClass(), 1); newArray[0] = newValue;
newArray[0] = newValue; newValue = newArray;
newValue = newArray; }
} this.attributes.put(this.attributeName, newValue);
localAttributes.put(attrName, newValue); }
}
public void visitEnum(String name, String desc, String value) { @Override
} public AnnotationVisitor visitAnnotation(String attributeName, String asmTypeDescriptor) {
public AnnotationVisitor visitAnnotation(String name, String desc) { String annotationType = Type.getType(asmTypeDescriptor).getClassName();
return new EmptyVisitor(); AnnotationAttributes nestedAttributes = new AnnotationAttributes();
} this.allNestedAttributes.add(nestedAttributes);
public AnnotationVisitor visitArray(String name) { return new RecursiveAnnotationAttributesVisitor(annotationType, nestedAttributes, this.classLoader);
return new EmptyVisitor();
}
public void visitEnd() {
}
};
} }
public void visitEnd() { public void visitEnd() {
this.attributesMap.put(this.annotationType, this.localAttributes); if (!this.allNestedAttributes.isEmpty()) {
this.attributes.put(this.attributeName, this.allNestedAttributes.toArray(
new AnnotationAttributes[this.allNestedAttributes.size()]));
}
}
}
/**
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.1.1
*/
class RecursiveAnnotationAttributesVisitor extends AbstractRecursiveAnnotationVisitor {
private final String annotationType;
public RecursiveAnnotationAttributesVisitor(
String annotationType, AnnotationAttributes attributes, ClassLoader classLoader) {
super(classLoader, attributes);
this.annotationType = annotationType;
}
public final void visitEnd() {
try { try {
Class<?> annotationClass = this.classLoader.loadClass(this.annotationType); Class<?> annotationClass = this.classLoader.loadClass(this.annotationType);
// Check declared default values of attributes in the annotation type. this.doVisitEnd(annotationClass);
Method[] annotationAttributes = annotationClass.getMethods();
for (Method annotationAttribute : annotationAttributes) {
String attributeName = annotationAttribute.getName();
Object defaultValue = annotationAttribute.getDefaultValue();
if (defaultValue != null && !this.localAttributes.containsKey(attributeName)) {
this.localAttributes.put(attributeName, defaultValue);
}
}
// Register annotations that the annotation type is annotated with.
Set<String> metaAnnotationTypeNames = new LinkedHashSet<String>();
for (Annotation metaAnnotation : annotationClass.getAnnotations()) {
metaAnnotationTypeNames.add(metaAnnotation.annotationType().getName());
if (!this.attributesMap.containsKey(metaAnnotation.annotationType().getName())) {
this.attributesMap.put(metaAnnotation.annotationType().getName(),
AnnotationUtils.getAnnotationAttributes(metaAnnotation, true));
}
for (Annotation metaMetaAnnotation : metaAnnotation.annotationType().getAnnotations()) {
metaAnnotationTypeNames.add(metaMetaAnnotation.annotationType().getName());
}
}
if (this.metaAnnotationMap != null) {
this.metaAnnotationMap.put(this.annotationType, metaAnnotationTypeNames);
}
} }
catch (ClassNotFoundException ex) { catch (ClassNotFoundException ex) {
// Class not found - can't determine meta-annotations. logNonFatalException(ex);
} }
} }
protected void doVisitEnd(Class<?> annotationClass) {
registerDefaultValues(annotationClass);
}
private void registerDefaultValues(Class<?> annotationClass) {
// Check declared default values of attributes in the annotation type.
Method[] annotationAttributes = annotationClass.getMethods();
for (Method annotationAttribute : annotationAttributes) {
String attributeName = annotationAttribute.getName();
Object defaultValue = annotationAttribute.getDefaultValue();
if (defaultValue != null && !this.attributes.containsKey(attributeName)) {
if (defaultValue instanceof Annotation) {
defaultValue = AnnotationAttributes.fromMap(
AnnotationUtils.getAnnotationAttributes((Annotation)defaultValue, false, true));
}
else if (defaultValue instanceof Annotation[]) {
Annotation[] realAnnotations = (Annotation[]) defaultValue;
AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[realAnnotations.length];
for (int i = 0; i < realAnnotations.length; i++) {
mappedAnnotations[i] = AnnotationAttributes.fromMap(
AnnotationUtils.getAnnotationAttributes(realAnnotations[i], false, true));
}
defaultValue = mappedAnnotations;
}
this.attributes.put(attributeName, defaultValue);
}
}
}
} }
/**
* ASM visitor which looks for the annotations defined on a class or method, including
* tracking meta-annotations.
*
* <p>As of Spring 3.1.1, this visitor is fully recursive, taking into account any nested
* annotations or nested annotation arrays. These annotations are in turn read into
* {@link AnnotationAttributes} map structures.
*
* @author Juergen Hoeller
* @author Chris Beams
* @since 3.0
*/
final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttributesVisitor {
private final String annotationType;
private final Map<String, AnnotationAttributes> attributesMap;
private final Map<String, Set<String>> metaAnnotationMap;
public AnnotationAttributesReadingVisitor(
String annotationType, Map<String, AnnotationAttributes> attributesMap,
Map<String, Set<String>> metaAnnotationMap, ClassLoader classLoader) {
super(annotationType, new AnnotationAttributes(), classLoader);
this.annotationType = annotationType;
this.attributesMap = attributesMap;
this.metaAnnotationMap = metaAnnotationMap;
}
@Override
public void doVisitEnd(Class<?> annotationClass) {
super.doVisitEnd(annotationClass);
this.attributesMap.put(this.annotationType, this.attributes);
registerMetaAnnotations(annotationClass);
}
private void registerMetaAnnotations(Class<?> annotationClass) {
// Register annotations that the annotation type is annotated with.
Set<String> metaAnnotationTypeNames = new LinkedHashSet<String>();
for (Annotation metaAnnotation : annotationClass.getAnnotations()) {
metaAnnotationTypeNames.add(metaAnnotation.annotationType().getName());
if (!this.attributesMap.containsKey(metaAnnotation.annotationType().getName())) {
this.attributesMap.put(metaAnnotation.annotationType().getName(),
AnnotationUtils.getAnnotationAttributes(metaAnnotation, true, true));
}
for (Annotation metaMetaAnnotation : metaAnnotation.annotationType().getAnnotations()) {
metaAnnotationTypeNames.add(metaMetaAnnotation.annotationType().getName());
}
}
if (this.metaAnnotationMap != null) {
this.metaAnnotationMap.put(annotationClass.getName(), metaAnnotationTypeNames);
}
}
}

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -26,6 +26,7 @@ import java.util.Set;
import org.springframework.asm.AnnotationVisitor; import org.springframework.asm.AnnotationVisitor;
import org.springframework.asm.MethodVisitor; import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Type; import org.springframework.asm.Type;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata; import org.springframework.core.type.MethodMetadata;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
@ -50,7 +51,7 @@ final class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor
private final Map<String, Set<String>> metaAnnotationMap = new LinkedHashMap<String, Set<String>>(4); private final Map<String, Set<String>> metaAnnotationMap = new LinkedHashMap<String, Set<String>>(4);
private final Map<String, Map<String, Object>> attributeMap = new LinkedHashMap<String, Map<String, Object>>(4); private final Map<String, AnnotationAttributes> attributeMap = new LinkedHashMap<String, AnnotationAttributes>(4);
private final MultiValueMap<String, MethodMetadata> methodMetadataMap = new LinkedMultiValueMap<String, MethodMetadata>(); private final MultiValueMap<String, MethodMetadata> methodMetadataMap = new LinkedMultiValueMap<String, MethodMetadata>();
@ -99,20 +100,41 @@ final class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor
return this.attributeMap.containsKey(annotationType); return this.attributeMap.containsKey(annotationType);
} }
public Map<String, Object> getAnnotationAttributes(String annotationType) { public AnnotationAttributes getAnnotationAttributes(String annotationType) {
return getAnnotationAttributes(annotationType, false); return getAnnotationAttributes(annotationType, false);
} }
public Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString) { public AnnotationAttributes getAnnotationAttributes(String annotationType, boolean classValuesAsString) {
Map<String, Object> raw = this.attributeMap.get(annotationType); return getAnnotationAttributes(annotationType, classValuesAsString, false);
if (raw == null) { }
public AnnotationAttributes getAnnotationAttributes(
String annotationType, boolean classValuesAsString, boolean nestedAttributesAsMap) {
AnnotationAttributes raw = this.attributeMap.get(annotationType);
return convertClassValues(raw, classValuesAsString, nestedAttributesAsMap);
}
private AnnotationAttributes convertClassValues(
AnnotationAttributes original, boolean classValuesAsString, boolean nestedAttributesAsMap) {
if (original == null) {
return null; return null;
} }
Map<String, Object> result = new LinkedHashMap<String, Object>(raw.size()); AnnotationAttributes result = new AnnotationAttributes(original.size());
for (Map.Entry<String, Object> entry : raw.entrySet()) { for (Map.Entry<String, Object> entry : original.entrySet()) {
try { try {
Object value = entry.getValue(); Object value = entry.getValue();
if (value instanceof Type) { if (value instanceof AnnotationAttributes) {
value = convertClassValues((AnnotationAttributes)value, classValuesAsString, nestedAttributesAsMap);
}
else if (value instanceof AnnotationAttributes[]) {
AnnotationAttributes[] values = (AnnotationAttributes[])value;
for (int i = 0; i < values.length; i++) {
values[i] = convertClassValues(values[i], classValuesAsString, nestedAttributesAsMap);
}
}
else if (value instanceof Type) {
value = (classValuesAsString ? ((Type) value).getClassName() : value = (classValuesAsString ? ((Type) value).getClassName() :
this.classLoader.loadClass(((Type) value).getClassName())); this.classLoader.loadClass(((Type) value).getClassName()));
} }
@ -127,10 +149,10 @@ final class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor
} }
else if (classValuesAsString) { else if (classValuesAsString) {
if (value instanceof Class) { if (value instanceof Class) {
value = ((Class) value).getName(); value = ((Class<?>) value).getName();
} }
else if (value instanceof Class[]) { else if (value instanceof Class[]) {
Class[] clazzArray = (Class[]) value; Class<?>[] clazzArray = (Class[]) value;
String[] newValue = new String[clazzArray.length]; String[] newValue = new String[clazzArray.length];
for (int i = 0; i < clazzArray.length; i++) { for (int i = 0; i < clazzArray.length; i++) {
newValue[i] = clazzArray[i].getName(); newValue[i] = clazzArray[i].getName();

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2009 the original author or authors. * Copyright 2002-2012 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -24,6 +24,7 @@ import org.springframework.asm.MethodAdapter;
import org.springframework.asm.Opcodes; import org.springframework.asm.Opcodes;
import org.springframework.asm.Type; import org.springframework.asm.Type;
import org.springframework.asm.commons.EmptyVisitor; import org.springframework.asm.commons.EmptyVisitor;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.MethodMetadata; import org.springframework.core.type.MethodMetadata;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
@ -35,6 +36,7 @@ import org.springframework.util.MultiValueMap;
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Mark Pollack * @author Mark Pollack
* @author Costin Leau * @author Costin Leau
* @author Chris Beams
* @since 3.0 * @since 3.0
*/ */
final class MethodMetadataReadingVisitor extends MethodAdapter implements MethodMetadata { final class MethodMetadataReadingVisitor extends MethodAdapter implements MethodMetadata {
@ -49,7 +51,7 @@ final class MethodMetadataReadingVisitor extends MethodAdapter implements Method
private final MultiValueMap<String, MethodMetadata> methodMetadataMap; private final MultiValueMap<String, MethodMetadata> methodMetadataMap;
private final Map<String, Map<String, Object>> attributeMap = new LinkedHashMap<String, Map<String, Object>>(2); private final Map<String, AnnotationAttributes> attributeMap = new LinkedHashMap<String, AnnotationAttributes>(2);
public MethodMetadataReadingVisitor(String name, int access, String declaringClassName, ClassLoader classLoader, public MethodMetadataReadingVisitor(String name, int access, String declaringClassName, ClassLoader classLoader,
MultiValueMap<String, MethodMetadata> methodMetadataMap) { MultiValueMap<String, MethodMetadata> methodMetadataMap) {
@ -88,7 +90,7 @@ final class MethodMetadataReadingVisitor extends MethodAdapter implements Method
return this.attributeMap.containsKey(annotationType); return this.attributeMap.containsKey(annotationType);
} }
public Map<String, Object> getAnnotationAttributes(String annotationType) { public AnnotationAttributes getAnnotationAttributes(String annotationType) {
return this.attributeMap.get(annotationType); return this.attributeMap.get(annotationType);
} }

View File

@ -0,0 +1,110 @@
/*
* 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.core.annotation;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.Test;
/**
* Unit tests for {@link AnnotationAttributes}.
*
* @author Chris Beams
* @since 3.1.1
*/
public class AnnotationAttributesTests {
enum Color { RED, WHITE, BLUE }
@Test
public void testTypeSafeAttributeAccess() {
AnnotationAttributes a = new AnnotationAttributes();
a.put("name", "dave");
a.put("names", new String[] { "dave", "frank", "hal" });
a.put("bool1", true);
a.put("bool2", false);
a.put("color", Color.RED);
a.put("clazz", Integer.class);
a.put("classes", new Class<?>[] { Number.class, Short.class, Integer.class });
a.put("number", 42);
a.put("numbers", new int[] { 42, 43 });
AnnotationAttributes anno = new AnnotationAttributes();
anno.put("value", 10);
anno.put("name", "algernon");
a.put("anno", anno);
a.put("annoArray", new AnnotationAttributes[] { anno });
assertThat(a.getString("name"), equalTo("dave"));
assertThat(a.getStringArray("names"), equalTo(new String[] { "dave", "frank", "hal" }));
assertThat(a.getBoolean("bool1"), equalTo(true));
assertThat(a.getBoolean("bool2"), equalTo(false));
assertThat(a.getEnum("color", Color.class), equalTo(Color.RED));
assertTrue(a.getClass("clazz", Number.class).equals(Integer.class));
assertThat(a.getClassArray("classes"), equalTo(new Class[] { Number.class, Short.class, Integer.class }));
assertThat(a.getInt("number"), equalTo(42));
assertThat(a.getAnnotation("anno").getInt("value"), equalTo(10));
assertThat(a.getAnnotationArray("annoArray")[0].getString("name"), equalTo("algernon"));
}
@Test
public void getEnum_emptyAttributeName() {
AnnotationAttributes a = new AnnotationAttributes();
a.put("color", "RED");
try {
a.getEnum("", Color.class);
fail();
} catch (IllegalArgumentException ex) {
assertThat(ex.getMessage(), equalTo("attributeName must not be null or empty"));
}
try {
a.getEnum(null, Color.class);
fail();
} catch (IllegalArgumentException ex) {
assertThat(ex.getMessage(), equalTo("attributeName must not be null or empty"));
}
}
@Test
public void getEnum_notFound() {
AnnotationAttributes a = new AnnotationAttributes();
a.put("color", "RED");
try {
a.getEnum("colour", Color.class);
fail();
} catch (IllegalArgumentException ex) {
assertThat(ex.getMessage(), equalTo("Attribute 'colour' not found"));
}
}
@Test
public void getEnum_typeMismatch() {
AnnotationAttributes a = new AnnotationAttributes();
a.put("color", "RED");
try {
a.getEnum("color", Color.class);
fail();
} catch (IllegalArgumentException ex) {
String expected =
"Attribute 'color' is of type [String], but [Color] was expected";
assertThat(ex.getMessage().substring(0, expected.length()), equalTo(expected));
}
}
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2009 the original author or authors. * Copyright 2002-2012 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,99 +16,203 @@
package org.springframework.core.type; package org.springframework.core.type;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.io.IOException; import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import java.util.Map;
import java.util.Set; import java.util.Set;
import junit.framework.TestCase;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
* Unit tests demonstrating that the reflection-based {@link StandardAnnotationMetadata}
* and ASM-based {@code AnnotationMetadataReadingVisitor} produce identical output.
*
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Chris Beams
*/ */
public class AnnotationMetadataTests extends TestCase { public class AnnotationMetadataTests {
@Test
public void testStandardAnnotationMetadata() throws IOException { public void testStandardAnnotationMetadata() throws IOException {
StandardAnnotationMetadata annInfo = new StandardAnnotationMetadata(AnnotatedComponent.class); AnnotationMetadata metadata = new StandardAnnotationMetadata(AnnotatedComponent.class, true);
doTestAnnotationInfo(annInfo); doTestAnnotationInfo(metadata);
doTestMethodAnnotationInfo(annInfo); doTestMethodAnnotationInfo(metadata);
} }
@Test
public void testAsmAnnotationMetadata() throws IOException { public void testAsmAnnotationMetadata() throws IOException {
MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory(); MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(AnnotatedComponent.class.getName()); MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(AnnotatedComponent.class.getName());
doTestAnnotationInfo(metadataReader.getAnnotationMetadata()); AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
doTestMethodAnnotationInfo(metadataReader.getAnnotationMetadata()); doTestAnnotationInfo(metadata);
doTestMethodAnnotationInfo(metadata);
}
/**
* In order to preserve backward-compatibility, {@link StandardAnnotationMetadata}
* defaults to return nested annotations and annotation arrays as actual
* Annotation instances. It is recommended for compatibility with ASM-based
* AnnotationMetadata implementations to set the 'nestedAnnotationsAsMap' flag to
* 'true' as is done in the main test above.
*/
@Test
public void testStandardAnnotationMetadata_nestedAnnotationsAsMap_false() throws IOException {
AnnotationMetadata metadata = new StandardAnnotationMetadata(AnnotatedComponent.class);
AnnotationAttributes specialAttrs = (AnnotationAttributes) metadata.getAnnotationAttributes(SpecialAttr.class.getName());
Annotation[] nestedAnnoArray = (Annotation[])specialAttrs.get("nestedAnnoArray");
assertThat(nestedAnnoArray[0], instanceOf(NestedAnno.class));
} }
private void doTestAnnotationInfo(AnnotationMetadata metadata) { private void doTestAnnotationInfo(AnnotationMetadata metadata) {
assertEquals(AnnotatedComponent.class.getName(), metadata.getClassName()); assertThat(metadata.getClassName(), is(AnnotatedComponent.class.getName()));
assertFalse(metadata.isInterface()); assertThat(metadata.isInterface(), is(false));
assertFalse(metadata.isAbstract()); assertThat(metadata.isAbstract(), is(false));
assertTrue(metadata.isConcrete()); assertThat(metadata.isConcrete(), is(true));
assertTrue(metadata.hasSuperClass()); assertThat(metadata.hasSuperClass(), is(true));
assertEquals(Object.class.getName(), metadata.getSuperClassName()); assertThat(metadata.getSuperClassName(), is(Object.class.getName()));
assertEquals(1, metadata.getInterfaceNames().length); assertThat(metadata.getInterfaceNames().length, is(1));
assertEquals(Serializable.class.getName(), metadata.getInterfaceNames()[0]); assertThat(metadata.getInterfaceNames()[0], is(Serializable.class.getName()));
assertTrue(metadata.hasAnnotation(Component.class.getName())); assertThat(metadata.hasAnnotation(Component.class.getName()), is(true));
assertTrue(metadata.hasAnnotation(Scope.class.getName())); assertThat(metadata.hasAnnotation(Scope.class.getName()), is(true));
assertTrue(metadata.hasAnnotation(SpecialAttr.class.getName())); assertThat(metadata.hasAnnotation(SpecialAttr.class.getName()), is(true));
assertEquals(3, metadata.getAnnotationTypes().size()); assertThat(metadata.getAnnotationTypes().size(), is(3));
assertTrue(metadata.getAnnotationTypes().contains(Component.class.getName())); assertThat(metadata.getAnnotationTypes().contains(Component.class.getName()), is(true));
assertTrue(metadata.getAnnotationTypes().contains(Scope.class.getName())); assertThat(metadata.getAnnotationTypes().contains(Scope.class.getName()), is(true));
assertTrue(metadata.getAnnotationTypes().contains(SpecialAttr.class.getName())); assertThat(metadata.getAnnotationTypes().contains(SpecialAttr.class.getName()), is(true));
Map<String, Object> compAttrs = metadata.getAnnotationAttributes(Component.class.getName()); AnnotationAttributes compAttrs = (AnnotationAttributes) metadata.getAnnotationAttributes(Component.class.getName());
assertEquals(1, compAttrs.size()); assertThat(compAttrs.size(), is(1));
assertEquals("myName", compAttrs.get("value")); assertThat(compAttrs.getString("value"), is("myName"));
Map<String, Object> scopeAttrs = metadata.getAnnotationAttributes(Scope.class.getName()); AnnotationAttributes scopeAttrs = (AnnotationAttributes) metadata.getAnnotationAttributes(Scope.class.getName());
assertEquals(1, scopeAttrs.size()); assertThat(scopeAttrs.size(), is(1));
assertEquals("myScope", scopeAttrs.get("value")); assertThat(scopeAttrs.getString("value"), is("myScope"));
Map<String, Object> specialAttrs = metadata.getAnnotationAttributes(SpecialAttr.class.getName()); { // perform tests with classValuesAsString = false (the default)
assertEquals(2, specialAttrs.size()); AnnotationAttributes specialAttrs = (AnnotationAttributes) metadata.getAnnotationAttributes(SpecialAttr.class.getName());
assertEquals(String.class, specialAttrs.get("clazz")); assertThat(specialAttrs.size(), is(6));
assertEquals(Thread.State.NEW, specialAttrs.get("state")); assertTrue(String.class.isAssignableFrom(specialAttrs.getClass("clazz", Object.class)));
Map<String, Object> specialAttrsString = metadata.getAnnotationAttributes(SpecialAttr.class.getName(), true); assertThat(specialAttrs.getEnum("state", Thread.State.class), is(Thread.State.NEW));
assertEquals(String.class.getName(), specialAttrsString .get("clazz"));
assertEquals(Thread.State.NEW, specialAttrsString.get("state")); AnnotationAttributes nestedAnno = specialAttrs.getAnnotation("nestedAnno");
assertThat("na", is(nestedAnno.getString("value")));
assertThat(nestedAnno.getEnum("anEnum", SomeEnum.class), is(SomeEnum.LABEL1));
assertArrayEquals(new Class[]{String.class}, (Class[])nestedAnno.get("classArray"));
AnnotationAttributes[] nestedAnnoArray = specialAttrs.getAnnotationArray("nestedAnnoArray");
assertThat(nestedAnnoArray.length, is(2));
assertThat(nestedAnnoArray[0].getString("value"), is("default"));
assertThat(nestedAnnoArray[0].getEnum("anEnum", SomeEnum.class), is(SomeEnum.DEFAULT));
assertArrayEquals(new Class[]{Void.class}, (Class[])nestedAnnoArray[0].get("classArray"));
assertThat(nestedAnnoArray[1].getString("value"), is("na1"));
assertThat(nestedAnnoArray[1].getEnum("anEnum", SomeEnum.class), is(SomeEnum.LABEL2));
assertArrayEquals(new Class[]{Number.class}, (Class[])nestedAnnoArray[1].get("classArray"));
assertArrayEquals(new Class[]{Number.class}, nestedAnnoArray[1].getClassArray("classArray"));
AnnotationAttributes optional = specialAttrs.getAnnotation("optional");
assertThat(optional.getString("value"), is("optional"));
assertThat(optional.getEnum("anEnum", SomeEnum.class), is(SomeEnum.DEFAULT));
assertArrayEquals(new Class[]{Void.class}, (Class[])optional.get("classArray"));
assertArrayEquals(new Class[]{Void.class}, optional.getClassArray("classArray"));
AnnotationAttributes[] optionalArray = specialAttrs.getAnnotationArray("optionalArray");
assertThat(optionalArray.length, is(1));
assertThat(optionalArray[0].getString("value"), is("optional"));
assertThat(optionalArray[0].getEnum("anEnum", SomeEnum.class), is(SomeEnum.DEFAULT));
assertArrayEquals(new Class[]{Void.class}, (Class[])optionalArray[0].get("classArray"));
assertArrayEquals(new Class[]{Void.class}, optionalArray[0].getClassArray("classArray"));
}
{ // perform tests with classValuesAsString = true
AnnotationAttributes specialAttrs = (AnnotationAttributes) metadata.getAnnotationAttributes(SpecialAttr.class.getName(), true);
assertThat(specialAttrs.size(), is(6));
assertThat(specialAttrs.get("clazz"), is((Object)String.class.getName()));
assertThat(specialAttrs.getString("clazz"), is(String.class.getName()));
AnnotationAttributes nestedAnno = specialAttrs.getAnnotation("nestedAnno");
assertArrayEquals(new String[]{String.class.getName()}, (String[])nestedAnno.getStringArray("classArray"));
assertArrayEquals(new String[]{String.class.getName()}, nestedAnno.getStringArray("classArray"));
AnnotationAttributes[] nestedAnnoArray = specialAttrs.getAnnotationArray("nestedAnnoArray");
assertArrayEquals(new String[]{Void.class.getName()}, (String[])nestedAnnoArray[0].get("classArray"));
assertArrayEquals(new String[]{Void.class.getName()}, nestedAnnoArray[0].getStringArray("classArray"));
assertArrayEquals(new String[]{Number.class.getName()}, (String[])nestedAnnoArray[1].get("classArray"));
assertArrayEquals(new String[]{Number.class.getName()}, nestedAnnoArray[1].getStringArray("classArray"));
AnnotationAttributes optional = specialAttrs.getAnnotation("optional");
assertArrayEquals(new String[]{Void.class.getName()}, (String[])optional.get("classArray"));
assertArrayEquals(new String[]{Void.class.getName()}, optional.getStringArray("classArray"));
AnnotationAttributes[] optionalArray = specialAttrs.getAnnotationArray("optionalArray");
assertArrayEquals(new String[]{Void.class.getName()}, (String[])optionalArray[0].get("classArray"));
assertArrayEquals(new String[]{Void.class.getName()}, optionalArray[0].getStringArray("classArray"));
}
} }
private void doTestMethodAnnotationInfo(AnnotationMetadata classMetadata) { private void doTestMethodAnnotationInfo(AnnotationMetadata classMetadata) {
Set<MethodMetadata> methods = classMetadata.getAnnotatedMethods(Autowired.class.getName()); Set<MethodMetadata> methods = classMetadata.getAnnotatedMethods(Autowired.class.getName());
assertEquals(1, methods.size()); assertThat(methods.size(), is(1));
for (MethodMetadata methodMetadata : methods) { for (MethodMetadata methodMetadata : methods) {
assertTrue(methodMetadata.isAnnotated(Autowired.class.getName())); assertThat(methodMetadata.isAnnotated(Autowired.class.getName()), is(true));
} }
} }
public static enum SomeEnum {
LABEL1, LABEL2, DEFAULT;
}
@Target({})
@Retention(RetentionPolicy.RUNTIME)
public @interface NestedAnno {
String value() default "default";
SomeEnum anEnum() default SomeEnum.DEFAULT;
Class<?>[] classArray() default Void.class;
}
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface SpecialAttr { public @interface SpecialAttr {
Class clazz(); Class<?> clazz();
Thread.State state(); Thread.State state();
NestedAnno nestedAnno();
NestedAnno[] nestedAnnoArray();
NestedAnno optional() default @NestedAnno(value="optional", anEnum=SomeEnum.DEFAULT, classArray=Void.class);
NestedAnno[] optionalArray() default {@NestedAnno(value="optional", anEnum=SomeEnum.DEFAULT, classArray=Void.class)};
} }
@Component("myName") @Component("myName")
@Scope("myScope") @Scope("myScope")
@SpecialAttr(clazz = String.class, state = Thread.State.NEW) @SpecialAttr(clazz = String.class, state = Thread.State.NEW,
nestedAnno = @NestedAnno(value = "na", anEnum = SomeEnum.LABEL1, classArray = {String.class}),
nestedAnnoArray = {
@NestedAnno,
@NestedAnno(value = "na1", anEnum = SomeEnum.LABEL2, classArray = {Number.class})
})
@SuppressWarnings({"serial", "unused"})
private static class AnnotatedComponent implements Serializable { private static class AnnotatedComponent implements Serializable {
@Autowired @Autowired