diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/Configuration.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/Configuration.java index 47de69733d9..3faf9c4920f 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/Configuration.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/Configuration.java @@ -18,7 +18,6 @@ package org.springframework.context.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -27,7 +26,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; - /** * Indicates that a class declares one or more {@link Bean} methods and may be processed * by the Spring container to generate bean definitions and service requests for those beans @@ -59,7 +57,6 @@ import org.springframework.stereotype.Component; */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) -@Inherited @Documented @Component public @interface Configuration { diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java index 1f7d838e262..4c98bc8d749 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java @@ -16,20 +16,19 @@ package org.springframework.context.annotation; -import java.lang.annotation.Annotation; -import java.lang.reflect.Modifier; -import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; -import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.factory.parsing.Location; import org.springframework.beans.factory.parsing.Problem; import org.springframework.beans.factory.parsing.ProblemReporter; -import org.springframework.core.io.ClassPathResource; -import org.springframework.util.Assert; +import org.springframework.core.io.DescriptiveResource; +import org.springframework.core.io.Resource; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.StandardAnnotationMetadata; +import org.springframework.core.type.classreading.MetadataReader; import org.springframework.util.ClassUtils; /** @@ -45,75 +44,42 @@ import org.springframework.util.ClassUtils; * @see ConfigurationClassMethod * @see ConfigurationClassParser */ -final class ConfigurationClass implements BeanMetadataElement { +final class ConfigurationClass { - private String name; + private final AnnotationMetadata metadata; - private ConfigurationClass declaringClass; - - private Object source; + private final Resource resource; private String beanName; - private int modifiers; - - private final Set annotations = new HashSet(); - private final Set methods = new LinkedHashSet(); private final Map overloadedMethodMap = new LinkedHashMap(); - /** - * Sets the fully-qualified name of this class. - */ - public void setName(String className) { - this.name = className; + public ConfigurationClass(MetadataReader metadataReader, String beanName) { + this.metadata = metadataReader.getAnnotationMetadata(); + this.resource = metadataReader.getResource(); + this.beanName = beanName; } - /** - * Returns the fully-qualified name of this class. - */ - public String getName() { - return name; + public ConfigurationClass(Class clazz, String beanName) { + this.metadata = new StandardAnnotationMetadata(clazz); + this.resource = new DescriptiveResource(clazz.toString()); + this.beanName = beanName; + } + + + public AnnotationMetadata getMetadata() { + return this.metadata; + } + + public Resource getResource() { + return this.resource; } - /** - * Returns the non-qualified name of this class. Given com.acme.Foo, returns 'Foo'. - */ public String getSimpleName() { - return name == null ? null : ClassUtils.getShortName(name); - } - - public void setDeclaringClass(ConfigurationClass configurationClass) { - this.declaringClass = configurationClass; - } - - public ConfigurationClass getDeclaringClass() { - return declaringClass; - } - - /** - * Set the source location for this class. Must be a resource-path formatted string. - * @param source resource path to the .java file that declares this class. - */ - public void setSource(Object source) { - this.source = source; - } - - /** - * Returns a resource path-formatted representation of the .java file that declares this - * class - */ - public Object getSource() { - return source; - } - - public Location getLocation() { - if (getName() == null) { - throw new IllegalStateException("'name' property is null. Call setName() before calling getLocation()"); - } - return new Location(new ClassPathResource(ClassUtils.convertClassNameToResourcePath(getName())), getSource()); + return ClassUtils.getShortName(getMetadata().getClassName()); } public void setBeanName(String beanName) { @@ -121,71 +87,29 @@ final class ConfigurationClass implements BeanMetadataElement { } public String getBeanName() { - return beanName; - } - - public void setModifiers(int modifiers) { - Assert.isTrue(modifiers >= 0, "modifiers must be non-negative"); - this.modifiers = modifiers; - } - - public int getModifiers() { - return modifiers; - } - - public void addAnnotation(Annotation annotation) { - this.annotations.add(annotation); - } - - /** - * @return the annotation on this class matching annoType or - * {@literal null} if not present. - * @see #getRequiredAnnotation(Class) - */ - @SuppressWarnings("unchecked") - public A getAnnotation(Class annoType) { - for (Annotation annotation : annotations) { - if (annotation.annotationType().equals(annoType)) { - return (A) annotation; - } - } - return null; - } - - /** - * @return the annotation on this class matching annoType - * @throws {@link IllegalStateException} if not present - * @see #getAnnotation(Class) - */ - public A getRequiredAnnotation(Class annoType) { - A anno = getAnnotation(annoType); - if (anno == null) { - throw new IllegalStateException( - String.format("Required annotation %s is not present on %s", annoType.getSimpleName(), this)); - } - return anno; + return this.beanName; } public ConfigurationClass addMethod(ConfigurationClassMethod method) { - method.setDeclaringClass(this); - methods.add(method); - Integer count = overloadedMethodMap.get(method.getName()); + this.methods.add(method); + String name = method.getMetadata().getMethodName(); + Integer count = this.overloadedMethodMap.get(name); if (count != null) { - overloadedMethodMap.put(method.getName(), count + 1); + this.overloadedMethodMap.put(name, count + 1); } else { - overloadedMethodMap.put(method.getName(), 1); + this.overloadedMethodMap.put(name, 1); } return this; } - public Set getBeanMethods() { - return methods; + public Set getConfigurationMethods() { + return this.methods; } public void validate(ProblemReporter problemReporter) { // No overloading of factory methods allowed - for (Map.Entry entry : overloadedMethodMap.entrySet()) { + for (Map.Entry entry : this.overloadedMethodMap.entrySet()) { String methodName = entry.getKey(); int count = entry.getValue(); if (count > 1) { @@ -194,11 +118,11 @@ final class ConfigurationClass implements BeanMetadataElement { } // A configuration class may not be final (CGLIB limitation) - if (getAnnotation(Configuration.class) != null) { - if (Modifier.isFinal(modifiers)) { + if (getMetadata().hasAnnotation(Configuration.class.getName())) { + if (getMetadata().isFinal()) { problemReporter.error(new FinalConfigurationProblem()); } - for (ConfigurationClassMethod method : methods) { + for (ConfigurationClassMethod method : this.methods) { method.validate(problemReporter); } } @@ -210,7 +134,7 @@ final class ConfigurationClass implements BeanMetadataElement { public FinalConfigurationProblem() { super(String.format("@Configuration class '%s' may not be final. Remove the final modifier to continue.", - getSimpleName()), ConfigurationClass.this.getLocation()); + getSimpleName()), new Location(getResource())); } } @@ -221,9 +145,8 @@ final class ConfigurationClass implements BeanMetadataElement { public OverloadedMethodProblem(String methodName, int count) { super(String.format("@Configuration class '%s' has %s overloaded factory methods of name '%s'. " + "Only one factory method of the same name allowed.", - getSimpleName(), count, methodName), ConfigurationClass.this.getLocation()); + getSimpleName(), count, methodName), new Location(getResource())); } } - } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassAnnotation.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassAnnotation.java deleted file mode 100644 index 710ea636407..00000000000 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassAnnotation.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2002-2009 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 java.lang.annotation.Annotation; - -/** - * Interface used when dynamically creating mutable instances of annotations associated - * with {@link Configuration} class processing. This functionality is necessary given - * that parsing of Configuration classes is done with ASM. Annotation metadata (including - * attributes) is parsed from the class files, and instances of those annotations are - * then created using this interface and its associated utilities. The annotation - * instances are attached to the configuration model objects at runtime, namely - * {@link ConfigurationClassMethod}. This approach is better than the alternative of - * creating fine-grained model representations of all annotations and attributes. - * It is better to simply attach annotation instances and read them as needed. - * - * @author Chris Beams - * @author Juergen Hoeller - * @since 3.0 - * @see ConfigurationClassAnnotationVisitor - * @see ConfigurationClassReaderUtils#createMutableAnnotation - */ -public interface ConfigurationClassAnnotation extends Annotation { - - void setAttributeValue(String attribName, Object attribValue); - - Class getAttributeType(String attributeName); - -} diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassAnnotationVisitor.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassAnnotationVisitor.java deleted file mode 100644 index a838750bba3..00000000000 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassAnnotationVisitor.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright 2002-2009 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 java.lang.annotation.Annotation; -import java.lang.reflect.Array; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; - -import org.springframework.asm.AnnotationVisitor; -import org.springframework.asm.Type; -import org.springframework.asm.commons.EmptyVisitor; - -/** - * ASM {@link AnnotationVisitor} that populates a given {@link ConfigurationClassAnnotation} instance - * with its attributes. - * - * @author Chris Beams - * @author Juergen Hoeller - * @since 3.0 - * @see ConfigurationClassAnnotation - * @see ConfigurationClassReaderUtils#createMutableAnnotation - */ -class ConfigurationClassAnnotationVisitor implements AnnotationVisitor { - - protected final ConfigurationClassAnnotation mutableAnno; - - private final ClassLoader classLoader; - - - /** - * Creates a new {@link ConfigurationClassAnnotationVisitor} instance that will populate the the - * attributes of the given mutableAnno. Accepts {@link Annotation} instead of - * {@link ConfigurationClassAnnotation} to avoid the need for callers to typecast. - * @param mutableAnno {@link ConfigurationClassAnnotation} instance to visit and populate - * @see ConfigurationClassReaderUtils#createMutableAnnotation - */ - public ConfigurationClassAnnotationVisitor(ConfigurationClassAnnotation mutableAnno, ClassLoader classLoader) { - this.mutableAnno = mutableAnno; - this.classLoader = classLoader; - } - - public void visit(String attribName, Object attribValue) { - Class attribReturnType = mutableAnno.getAttributeType(attribName); - if (attribReturnType.equals(Class.class)) { - // the attribute type is Class -> load it and set it. - String className = ((Type) attribValue).getClassName(); - try { - Class classVal = classLoader.loadClass(className); - if (classVal != null) { - mutableAnno.setAttributeValue(attribName, classVal); - } - } - catch (ClassNotFoundException ex) { - throw new IllegalStateException("Cannot resolve attribute type [" + className + "]", ex); - } - } - else { - // otherwise, assume the value can be set literally - mutableAnno.setAttributeValue(attribName, attribValue); - } - } - - @SuppressWarnings("unchecked") - public void visitEnum(String attribName, String enumTypeDescriptor, String strEnumValue) { - String enumTypeName = ConfigurationClassReaderUtils.convertAsmTypeDescriptorToClassName(enumTypeDescriptor); - try { - Class enumType = (Class) classLoader.loadClass(enumTypeName); - if (enumType == null) { - return; - } - Enum enumValue = Enum.valueOf(enumType, strEnumValue); - mutableAnno.setAttributeValue(attribName, enumValue); - } - catch (ClassNotFoundException ex) { - throw new IllegalStateException("Cannot resolve enum type [" + enumTypeName + "]", ex); - } - } - - public AnnotationVisitor visitAnnotation(String attribName, String annoTypeDesc) { - Class annoClass = ConfigurationClassReaderUtils.loadAnnotationType(annoTypeDesc, classLoader); - if (annoClass == null) { - return new EmptyVisitor(); - } - ConfigurationClassAnnotation anno = ConfigurationClassReaderUtils.createMutableAnnotation(annoClass, classLoader); - try { - Field attribute = mutableAnno.getClass().getField(attribName); - attribute.set(mutableAnno, anno); - } - catch (Exception ex) { - throw new IllegalStateException("Could not reflectively set annotation field", ex); - } - return new ConfigurationClassAnnotationVisitor(anno, classLoader); - } - - public AnnotationVisitor visitArray(final String attribName) { - return new MutableAnnotationArrayVisitor(mutableAnno, attribName, classLoader); - } - - public void visitEnd() { - } - - - /** - * ASM {@link AnnotationVisitor} that visits any annotation array values while populating - * a new {@link ConfigurationClassAnnotation} instance. - */ - private static class MutableAnnotationArrayVisitor implements AnnotationVisitor { - - private final List values = new ArrayList(); - - private final ConfigurationClassAnnotation mutableAnno; - - private final String attribName; - - private final ClassLoader classLoader; - - public MutableAnnotationArrayVisitor(ConfigurationClassAnnotation mutableAnno, String attribName, ClassLoader classLoader) { - this.mutableAnno = mutableAnno; - this.attribName = attribName; - this.classLoader = classLoader; - } - - public void visit(String na, Object value) { - values.add(value); - } - - public void visitEnum(String s, String s1, String s2) { - } - - public AnnotationVisitor visitAnnotation(String na, String annoTypeDesc) { - Class annoClass = ConfigurationClassReaderUtils.loadAnnotationType(annoTypeDesc, classLoader); - if (annoClass == null) { - return new EmptyVisitor(); - } - ConfigurationClassAnnotation anno = ConfigurationClassReaderUtils.createMutableAnnotation(annoClass, classLoader); - values.add(anno); - return new ConfigurationClassAnnotationVisitor(anno, classLoader); - } - - public AnnotationVisitor visitArray(String s) { - return new EmptyVisitor(); - } - - public void visitEnd() { - Class arrayType = mutableAnno.getAttributeType(attribName); - Object[] array = (Object[]) Array.newInstance(arrayType.getComponentType(), 0); - mutableAnno.setAttributeValue(attribName, values.toArray(array)); - } - } - -} diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java index 151155df9c2..5c32e9ad271 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java @@ -20,6 +20,7 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; @@ -35,6 +36,7 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.core.io.Resource; +import org.springframework.core.type.MethodMetadata; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -78,7 +80,7 @@ class ConfigurationClassBeanDefinitionReader { */ private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) { doLoadBeanDefinitionForConfigurationClass(configClass); - for (ConfigurationClassMethod method : configClass.getBeanMethods()) { + for (ConfigurationClassMethod method : configClass.getConfigurationMethods()) { loadBeanDefinitionsForModelMethod(method); } } @@ -89,7 +91,7 @@ class ConfigurationClassBeanDefinitionReader { private void doLoadBeanDefinitionForConfigurationClass(ConfigurationClass configClass) { if (configClass.getBeanName() == null) { GenericBeanDefinition configBeanDef = new GenericBeanDefinition(); - configBeanDef.setBeanClassName(configClass.getName()); + configBeanDef.setBeanClassName(configClass.getMetadata().getClassName()); String configBeanName = BeanDefinitionReaderUtils.registerWithGeneratedName(configBeanDef, registry); configClass.setBeanName(configBeanName); if (logger.isDebugEnabled()) { @@ -103,24 +105,26 @@ class ConfigurationClassBeanDefinitionReader { * {@link #registry} based on its contents. */ private void loadBeanDefinitionsForModelMethod(ConfigurationClassMethod method) { + MethodMetadata metadata = method.getMetadata(); + RootBeanDefinition beanDef = new ConfigurationClassBeanDefinition(); ConfigurationClass configClass = method.getDeclaringClass(); beanDef.setFactoryBeanName(configClass.getBeanName()); - beanDef.setUniqueFactoryMethodName(method.getName()); + beanDef.setUniqueFactoryMethodName(metadata.getMethodName()); beanDef.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR); // consider name and any aliases - Bean bean = method.getRequiredAnnotation(Bean.class); - List names = new ArrayList(Arrays.asList(bean.name())); - String beanName = (names.size() > 0) ? names.remove(0) : method.getName(); - for (String alias : bean.name()) { + Map beanAttributes = metadata.getAnnotationAttributes(Bean.class.getName()); + List names = new ArrayList(Arrays.asList((String[]) beanAttributes.get("name"))); + String beanName = (names.size() > 0 ? names.remove(0) : method.getMetadata().getMethodName()); + for (String alias : names) { registry.registerAlias(beanName, alias); } // has this already been overriden (i.e.: via XML)? if (registry.containsBeanDefinition(beanName)) { BeanDefinition existingBeanDef = registry.getBeanDefinition(beanName); - // is the existing bean definition one that was created by JavaConfig? + // is the existing bean definition one that was created from a configuration class? if (!(existingBeanDef instanceof ConfigurationClassBeanDefinition)) { // no -> then it's an external override, probably XML // overriding is legal, return immediately @@ -132,48 +136,46 @@ class ConfigurationClassBeanDefinitionReader { } } - if (method.getAnnotation(Primary.class) != null) { + if (metadata.hasAnnotation(Primary.class.getName())) { beanDef.setPrimary(true); } // is this bean to be instantiated lazily? - Lazy lazy = method.getAnnotation(Lazy.class); - if (lazy != null) { - beanDef.setLazyInit(lazy.value()); + if (metadata.hasAnnotation(Lazy.class.getName())) { + beanDef.setLazyInit((Boolean) metadata.getAnnotationAttributes(Lazy.class.getName()).get("value")); } - else { - Lazy defaultLazy = configClass.getAnnotation(Lazy.class); - if (defaultLazy != null) { - beanDef.setLazyInit(defaultLazy.value()); + else if (configClass.getMetadata().hasAnnotation(Lazy.class.getName())){ + beanDef.setLazyInit((Boolean) configClass.getMetadata().getAnnotationAttributes(Lazy.class.getName()).get("value")); + } + + if (metadata.hasAnnotation(DependsOn.class.getName())) { + String[] dependsOn = (String[]) metadata.getAnnotationAttributes(DependsOn.class.getName()).get("value"); + if (dependsOn.length > 0) { + beanDef.setDependsOn(dependsOn); } } - DependsOn dependsOn = method.getAnnotation(DependsOn.class); - if (dependsOn != null && dependsOn.value().length > 0) { - beanDef.setDependsOn(dependsOn.value()); - } - - Autowire autowire = bean.autowire(); + Autowire autowire = (Autowire) beanAttributes.get("autowire"); if (autowire.isAutowire()) { beanDef.setAutowireMode(autowire.value()); } - String initMethodName = bean.initMethod(); + String initMethodName = (String) beanAttributes.get("initMethod"); if (StringUtils.hasText(initMethodName)) { beanDef.setInitMethodName(initMethodName); } - String destroyMethodName = bean.destroyMethod(); + String destroyMethodName = (String) beanAttributes.get("destroyMethod"); if (StringUtils.hasText(destroyMethodName)) { beanDef.setDestroyMethodName(destroyMethodName); } // consider scoping - Scope scope = method.getAnnotation(Scope.class); ScopedProxyMode proxyMode = ScopedProxyMode.NO; - if (scope != null) { - beanDef.setScope(scope.value()); - proxyMode = scope.proxyMode(); + if (metadata.hasAnnotation(Scope.class.getName())) { + Map scopeAttributes = metadata.getAnnotationAttributes(Scope.class.getName()); + beanDef.setScope((String) scopeAttributes.get("value")); + proxyMode = (ScopedProxyMode) scopeAttributes.get("proxyMode"); if (proxyMode == ScopedProxyMode.DEFAULT) { proxyMode = ScopedProxyMode.NO; } @@ -188,7 +190,7 @@ class ConfigurationClassBeanDefinitionReader { } if (logger.isDebugEnabled()) { - logger.debug(String.format("Registering bean definition for @Bean method %s.%s()", configClass.getName(), beanName)); + logger.debug(String.format("Registering bean definition for @Bean method %s.%s()", configClass.getMetadata().getClassName(), beanName)); } registry.registerBeanDefinition(beanName, beanDefToRegister); @@ -196,11 +198,11 @@ class ConfigurationClassBeanDefinitionReader { /** - * {@link RootBeanDefinition} marker subclass used to signify that a bean definition created - * by JavaConfig as opposed to any other configuration source. Used in bean overriding cases - * where it's necessary to determine whether the bean definition was created externally. + * {@link RootBeanDefinition} marker subclass used to signify that a bean definition + * created from a configuration class as opposed to any other configuration source. + * Used in bean overriding cases where it's necessary to determine whether the bean + * definition was created externally. */ - @SuppressWarnings("serial") private class ConfigurationClassBeanDefinition extends RootBeanDefinition { public ConfigurationClassBeanDefinition() { diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java index 0cbae633883..aaa6c2fcf89 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java @@ -60,17 +60,14 @@ class ConfigurationClassEnhancer { */ public ConfigurationClassEnhancer(ConfigurableBeanFactory beanFactory) { Assert.notNull(beanFactory, "BeanFactory must not be null"); - - callbackInstances.add(new BeanMethodInterceptor(beanFactory)); - callbackInstances.add(NoOp.INSTANCE); - - for (Callback callback : callbackInstances) { - callbackTypes.add(callback.getClass()); + this.callbackInstances.add(new BeanMethodInterceptor(beanFactory)); + this.callbackInstances.add(NoOp.INSTANCE); + for (Callback callback : this.callbackInstances) { + this.callbackTypes.add(callback.getClass()); } - // Set up the callback filter to return the index of the BeanMethodInterceptor when // handling a @Bean-annotated method; otherwise, return index of the NoOp callback. - callbackFilter = new CallbackFilter() { + this.callbackFilter = new CallbackFilter() { public int accept(Method candidateMethod) { return (AnnotationUtils.findAnnotation(candidateMethod, Bean.class) != null) ? 0 : 1; } @@ -84,12 +81,12 @@ class ConfigurationClassEnhancer { * @return fully-qualified name of the enhanced subclass */ public Class enhance(Class configClass) { - if (logger.isInfoEnabled()) { - logger.info("Enhancing " + configClass.getName()); + if (logger.isDebugEnabled()) { + logger.debug("Enhancing " + configClass.getName()); } Class enhancedClass = createClass(newEnhancer(configClass)); - if (logger.isInfoEnabled()) { - logger.info(String.format("Successfully enhanced %s; enhanced class name is: %s", + if (logger.isDebugEnabled()) { + logger.debug(String.format("Successfully enhanced %s; enhanced class name is: %s", configClass.getName(), enhancedClass.getName())); } return enhancedClass; @@ -100,17 +97,14 @@ class ConfigurationClassEnhancer { */ private Enhancer newEnhancer(Class superclass) { Enhancer enhancer = new Enhancer(); - - // because callbackFilter and callbackTypes are dynamically populated + // Because callbackFilter and callbackTypes are dynamically populated // there's no opportunity for caching. This does not appear to be causing // any performance problem. enhancer.setUseCache(false); - enhancer.setSuperclass(superclass); enhancer.setUseFactory(false); - enhancer.setCallbackFilter(callbackFilter); - enhancer.setCallbackTypes(callbackTypes.toArray(new Class[callbackTypes.size()])); - + enhancer.setCallbackFilter(this.callbackFilter); + enhancer.setCallbackTypes(this.callbackTypes.toArray(new Class[this.callbackTypes.size()])); return enhancer; } @@ -120,7 +114,7 @@ class ConfigurationClassEnhancer { */ private Class createClass(Enhancer enhancer) { Class subclass = enhancer.createClass(); - Enhancer.registerCallbacks(subclass, callbackInstances.toArray(new Callback[callbackInstances.size()])); + Enhancer.registerCallbacks(subclass, this.callbackInstances.toArray(new Callback[this.callbackInstances.size()])); return subclass; } @@ -142,7 +136,6 @@ class ConfigurationClassEnhancer { this.beanFactory = beanFactory; } - /** * Enhance a {@link Bean @Bean} method to check the supplied BeanFactory for the * existence of this bean object. @@ -161,7 +154,7 @@ class ConfigurationClassEnhancer { Scope scope = AnnotationUtils.findAnnotation(method, Scope.class); if (scope != null && scope.proxyMode() != ScopedProxyMode.NO) { String scopedBeanName = ScopedProxyUtils.getTargetBeanName(beanName); - if (beanFactory.isCurrentlyInCreation(scopedBeanName)) { + if (this.beanFactory.isCurrentlyInCreation(scopedBeanName)) { beanName = scopedBeanName; } } @@ -170,7 +163,7 @@ class ConfigurationClassEnhancer { // container for already cached instances if (factoryContainsBean(beanName)) { // we have an already existing cached instance of this bean -> retrieve it - Object cachedBean = beanFactory.getBean(beanName); + Object cachedBean = this.beanFactory.getBean(beanName); if (logger.isDebugEnabled()) { logger.debug(String.format("Returning cached object [%s] for @Bean method %s.%s", cachedBean, method.getDeclaringClass().getSimpleName(), beanName)); @@ -185,18 +178,18 @@ class ConfigurationClassEnhancer { /** * Check the beanFactory to see whether the bean named beanName already * exists. Accounts for the fact that the requested bean may be "in creation", i.e.: - * we're in the middle of servicing the initial request for this bean. From JavaConfig's - * perspective, this means that the bean does not actually yet exist, and that it is now - * our job to create it for the first time by executing the logic in the corresponding - * Bean method. + * we're in the middle of servicing the initial request for this bean. From an enhanced + * factory method's perspective, this means that the bean does not actually yet exist, + * and that it is now our job to create it for the first time by executing the logic + * in the corresponding factory method. *

Said another way, this check repurposes * {@link ConfigurableBeanFactory#isCurrentlyInCreation(String)} to determine whether * the container is calling this method or the user is calling this method. * @param beanName name of bean to check for - * @return true if beanName already exists in the factory + * @return whether beanName already exists in the factory */ private boolean factoryContainsBean(String beanName) { - return beanFactory.containsBean(beanName) && !beanFactory.isCurrentlyInCreation(beanName); + return (this.beanFactory.containsBean(beanName) && !this.beanFactory.isCurrentlyInCreation(beanName)); } } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassMethod.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassMethod.java index a9e977b09bb..a93ecc0e63c 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassMethod.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassMethod.java @@ -16,17 +16,10 @@ package org.springframework.context.annotation; -import java.lang.annotation.Annotation; -import java.lang.reflect.Modifier; -import java.util.ArrayList; - -import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.factory.parsing.Location; import org.springframework.beans.factory.parsing.Problem; import org.springframework.beans.factory.parsing.ProblemReporter; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.core.io.ClassPathResource; +import org.springframework.core.type.MethodMetadata; /** * Represents a {@link Configuration} class method marked with the {@link Bean} annotation. @@ -38,213 +31,46 @@ import org.springframework.core.io.ClassPathResource; * @see ConfigurationClassParser * @see ConfigurationClassBeanDefinitionReader */ -final class ConfigurationClassMethod implements BeanMetadataElement { +final class ConfigurationClassMethod { - private final String name; + private final MethodMetadata metadata; - private final int modifiers; - - private final ReturnType returnType; - - private final ArrayList annotations = new ArrayList(); - - private transient ConfigurationClass declaringClass; - - private transient Object source; + private final ConfigurationClass declaringClass; - public ConfigurationClassMethod(String name, int modifiers, ReturnType returnType, Annotation... annotations) { - Assert.hasText(name); - this.name = name; - - Assert.notNull(annotations); - for (Annotation annotation : annotations) { - this.annotations.add(annotation); - } - - Assert.isTrue(modifiers >= 0, "modifiers must be non-negative: " + modifiers); - this.modifiers = modifiers; - - Assert.notNull(returnType); - this.returnType = returnType; - } - - public String getName() { - return name; - } - - public ReturnType getReturnType() { - return returnType; - } - - /** - * @see java.lang.reflect.Modifier - */ - public int getModifiers() { - return modifiers; - } - - /** - * @return the annotation on this method matching annoType or - * {@literal null} if not present. - * @see #getRequiredAnnotation(Class) - */ - @SuppressWarnings("unchecked") - public A getAnnotation(Class annoType) { - for (Annotation anno : annotations) - if (anno.annotationType().equals(annoType)) - return (A) anno; - - return null; - } - - /** - * @return the annotation on this method matching annoType - * @throws {@link IllegalStateException} if not present - * @see #getAnnotation(Class) - */ - public T getRequiredAnnotation(Class annoType) { - T anno = getAnnotation(annoType); - if (anno == null) { - throw new IllegalStateException( - String.format("required annotation %s is not present on %s", annoType.getSimpleName(), this)); - } - return anno; - } - - /** - * Set up a bi-directional relationship between this method and its declaring class. - * - * @see ConfigurationClass#addMethod(ConfigurationClassMethod) - */ - public void setDeclaringClass(ConfigurationClass declaringClass) { + public ConfigurationClassMethod(MethodMetadata metadata, ConfigurationClass declaringClass) { + this.metadata = metadata; this.declaringClass = declaringClass; } + + public MethodMetadata getMetadata() { + return this.metadata; + } + public ConfigurationClass getDeclaringClass() { - return declaringClass; + return this.declaringClass; } - public void setSource(Object source) { - this.source = source; - } - - public Object getSource() { - return source; - } - - public Location getLocation() { - if (declaringClass == null) { - throw new IllegalStateException( - "declaringClass property is null. Call setDeclaringClass() before calling getLocation()"); - } - return new Location(declaringClass.getLocation().getResource(), getSource()); + public Location getResourceLocation() { + return new Location(this.declaringClass.getResource(), this.metadata.getMethodName()); } public void validate(ProblemReporter problemReporter) { - if (Modifier.isPrivate(getModifiers())) { - problemReporter.error(new PrivateMethodError()); - } - if (Modifier.isFinal(getModifiers())) { - problemReporter.error(new FinalMethodError()); - } - } - - - static class ReturnType implements BeanMetadataElement { - - private String name; - - private boolean isInterface; - - private transient Object source; - - - public ReturnType(String name) { - this.name = name; - } - - /** - * Returns the fully-qualified name of this class. - */ - public String getName() { - return name; - } - - /** - * Sets the fully-qualified name of this class. - */ - public void setName(String className) { - this.name = className; - } - - /** - * Returns the non-qualified name of this class. Given com.acme.Foo, returns 'Foo'. - */ - public String getSimpleName() { - return name == null ? null : ClassUtils.getShortName(name); - } - - /** - * Returns whether the class represented by this ModelClass instance is an interface. - */ - public boolean isInterface() { - return isInterface; - } - - /** - * Signifies that this class is (true) or is not (false) an interface. - */ - public void setInterface(boolean isInterface) { - this.isInterface = isInterface; - } - - /** - * Returns a resource path-formatted representation of the .java file that declares this - * class - */ - public Object getSource() { - return source; - } - - /** - * Set the source location for this class. Must be a resource-path formatted string. - * - * @param source resource path to the .java file that declares this class. - */ - public void setSource(Object source) { - this.source = source; - } - - public Location getLocation() { - if (getName() == null) { - throw new IllegalStateException("'name' property is null. Call setName() before calling getLocation()"); - } - return new Location(new ClassPathResource(ClassUtils.convertClassNameToResourcePath(getName())), getSource()); + if (this.declaringClass.getMetadata().hasAnnotation(Configuration.class.getName()) && !getMetadata().isOverridable()) { + problemReporter.error(new NonOverridableMethodError()); } } /** - * {@link Bean} methods must be non-private in order to accommodate CGLIB. + * {@link Bean} methods must be overridable in order to accommodate CGLIB. */ - private class PrivateMethodError extends Problem { + private class NonOverridableMethodError extends Problem { - public PrivateMethodError() { - super(String.format("Method '%s' must not be private; increase the method's visibility to continue", - getName()), ConfigurationClassMethod.this.getLocation()); - } - } - - - /** - * {@link Bean} methods must be non-final in order to accommodate CGLIB. - */ - private class FinalMethodError extends Problem { - - public FinalMethodError() { - super(String.format("Method '%s' must not be final; remove the final modifier to continue", - getName()), ConfigurationClassMethod.this.getLocation()); + public NonOverridableMethodError() { + super(String.format("Method '%s' must not be private, final or static; change the method's modifiers to continue", + getMetadata().getMethodName()), getResourceLocation()); } } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index 6fab47d19c5..64482a637f0 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -17,13 +17,23 @@ package org.springframework.context.annotation; import java.io.IOException; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Set; +import java.util.Stack; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.parsing.Location; +import org.springframework.beans.factory.parsing.Problem; import org.springframework.beans.factory.parsing.ProblemReporter; -import org.springframework.core.type.classreading.SimpleMetadataReader; -import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; +import org.springframework.core.io.Resource; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.MethodMetadata; +import org.springframework.core.type.StandardAnnotationMetadata; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; /** * Parses a {@link Configuration} class definition, populating a configuration model. @@ -41,19 +51,20 @@ import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; */ class ConfigurationClassParser { - private final SimpleMetadataReaderFactory metadataReaderFactory; + private final MetadataReaderFactory metadataReaderFactory; private final ProblemReporter problemReporter; private final Set model; + private final Stack importStack = new ImportStack(); + /** - * Create a new {@link ConfigurationClassParser} instance that will be used to populate a - * configuration model. - * @param model model to be populated by each successive call to {@link #parse} + * Create a new {@link ConfigurationClassParser} instance that will be used + * to populate a configuration model. */ - public ConfigurationClassParser(SimpleMetadataReaderFactory metadataReaderFactory, ProblemReporter problemReporter) { + public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory, ProblemReporter problemReporter) { this.metadataReaderFactory = metadataReaderFactory; this.problemReporter = problemReporter; this.model = new LinkedHashSet(); @@ -67,17 +78,80 @@ class ConfigurationClassParser { * (assumes that this configuration class was configured via XML) */ public void parse(String className, String beanName) throws IOException { - SimpleMetadataReader reader = this.metadataReaderFactory.getMetadataReader(className); - ConfigurationClass configClass = new ConfigurationClass(); - configClass.setBeanName(beanName); - reader.getClassReader().accept(new ConfigurationClassVisitor(configClass, model, problemReporter, metadataReaderFactory), false); + MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className); + processConfigurationClass(new ConfigurationClass(reader, beanName)); + } + + /** + * Parse the specified {@link Configuration @Configuration} class. + * @param clazz the Clazz to parse + * @param beanName may be null, but if populated represents the bean id + * (assumes that this configuration class was configured via XML) + */ + public void parse(Class clazz, String beanName) throws IOException { + processConfigurationClass(new ConfigurationClass(clazz, beanName)); + } + + + protected void processConfigurationClass(ConfigurationClass configClass) throws IOException { + AnnotationMetadata metadata = configClass.getMetadata(); + while (metadata != null) { + doProcessConfigurationClass(configClass, metadata); + String superClassName = metadata.getSuperClassName(); + if (superClassName != null && !Object.class.getName().equals(superClassName)) { + if (metadata instanceof StandardAnnotationMetadata) { + Class clazz = ((StandardAnnotationMetadata) metadata).getIntrospectedClass(); + metadata = new StandardAnnotationMetadata(clazz.getSuperclass()); + } + else { + MetadataReader reader = this.metadataReaderFactory.getMetadataReader(superClassName); + metadata = reader.getAnnotationMetadata(); + } + } + else { + metadata = null; + } + } model.add(configClass); } + protected void doProcessConfigurationClass(ConfigurationClass configClass, AnnotationMetadata metadata) throws IOException { + if (metadata.hasAnnotation(Import.class.getName())) { + processImport(configClass, (String[]) metadata.getAnnotationAttributes(Import.class.getName()).get("value")); + } + Set methods = metadata.getAnnotatedMethods(Bean.class.getName()); + for (MethodMetadata methodMetadata : methods) { + configClass.addMethod(new ConfigurationClassMethod(methodMetadata, configClass)); + } + } + + public void processImport(ConfigurationClass configClass, String[] classesToImport) throws IOException { + if (this.importStack.contains(configClass)) { + this.problemReporter.error(new CircularImportProblem(configClass, this.importStack)); + } + else { + this.importStack.push(configClass); + for (String classToImport : classesToImport) { + processClassToImport(classToImport); + } + this.importStack.pop(); + } + } + + private void processClassToImport(String classToImport) throws IOException { + MetadataReader reader = this.metadataReaderFactory.getMetadataReader(classToImport); + AnnotationMetadata metadata = reader.getAnnotationMetadata(); + if (!metadata.hasAnnotation(Configuration.class.getName())) { + this.problemReporter.error( + new NonAnnotatedConfigurationProblem(metadata.getClassName(), reader.getResource())); + } + else { + processConfigurationClass(new ConfigurationClass(reader, null)); + } + } + /** * Recurse through the model validating each {@link ConfigurationClass}. - * @param problemReporter {@link ProblemReporter} against which any validation errors - * will be registered * @see ConfigurationClass#validate */ public void validate() { @@ -90,4 +164,78 @@ class ConfigurationClassParser { return this.model; } + + @SuppressWarnings("serial") + private static class ImportStack extends Stack { + + /** + * Simplified contains() implementation that tests to see if any {@link ConfigurationClass} + * exists within this stack that has the same name as elem. Elem must be of + * type ConfigurationClass. + */ + @Override + public boolean contains(Object elem) { + ConfigurationClass configClass = (ConfigurationClass) elem; + Comparator comparator = new Comparator() { + public int compare(ConfigurationClass first, ConfigurationClass second) { + return first.getMetadata().getClassName().equals(second.getMetadata().getClassName()) ? 0 : 1; + } + }; + return (Collections.binarySearch(this, configClass, comparator) != -1); + } + + /** + * Given a stack containing (in order) + *

    + *
  1. com.acme.Foo
  2. + *
  3. com.acme.Bar
  4. + *
  5. com.acme.Baz
  6. + *
+ * Returns "Foo->Bar->Baz". In the case of an empty stack, returns empty string. + */ + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + Iterator iterator = iterator(); + while (iterator.hasNext()) { + builder.append(iterator.next().getSimpleName()); + if (iterator.hasNext()) { + builder.append("->"); + } + } + return builder.toString(); + } + } + + + /** + * Configuration classes must be annotated with {@link Configuration @Configuration}. + */ + private static class NonAnnotatedConfigurationProblem extends Problem { + + public NonAnnotatedConfigurationProblem(String className, Resource resource) { + super(String.format("%s was imported as a @Configuration class but was not actually annotated " + + "with @Configuration. Annotate the class or do not attempt to process it.", className), + new Location(resource)); + } + + } + + + /** + * {@link Problem} registered upon detection of a circular {@link Import}. + */ + private static class CircularImportProblem extends Problem { + + public CircularImportProblem(ConfigurationClass attemptedImport, Stack importStack) { + super(String.format("A circular @Import has been detected: " + + "Illegal attempt by @Configuration class '%s' to import class '%s' as '%s' is " + + "already present in the current import stack [%s]", + importStack.peek().getSimpleName(), attemptedImport.getSimpleName(), + attemptedImport.getSimpleName(), importStack), + new Location(importStack.peek().getResource()) + ); + } + } + } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java index 40f715f20a6..1944b5533ce 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java @@ -35,23 +35,23 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.core.Conventions; import org.springframework.core.Ordered; -import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.StandardAnnotationMetadata; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReader; -import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; +import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.stereotype.Component; import org.springframework.util.ClassUtils; /** * {@link BeanFactoryPostProcessor} used for bootstrapping processing of * {@link Configuration @Configuration} classes. - *

- * Registered by default when using {@literal } or + * + *

Registered by default when using {@literal } or * {@literal }. Otherwise, may be declared manually as * with any other BeanFactoryPostProcessor. - *

- * This post processor is {@link Ordered#HIGHEST_PRECEDENCE} as it's important + * + *

This post processor is {@link Ordered#HIGHEST_PRECEDENCE} as it is important * that any {@link Bean} methods declared in Configuration classes have their * respective bean definitions registered before any other BeanFactoryPostProcessor * executes. @@ -62,32 +62,33 @@ import org.springframework.util.ClassUtils; */ public class ConfigurationClassPostProcessor implements BeanFactoryPostProcessor, BeanClassLoaderAware { - public static final String CONFIGURATION_CLASS_ATTRIBUTE = + private static final String CONFIGURATION_CLASS_ATTRIBUTE = Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "configurationClass"); + private static final String CONFIGURATION_CLASS_FULL = "full"; + + private static final String CONFIGURATION_CLASS_LITE = "lite"; + + /** Whether the CGLIB2 library is present on the classpath */ private static final boolean cglibAvailable = ClassUtils.isPresent( "net.sf.cglib.proxy.Enhancer", ConfigurationClassPostProcessor.class.getClassLoader()); - private static final Log logger = LogFactory.getLog(ConfigurationClassPostProcessor.class); + private final Log logger = LogFactory.getLog(getClass()); - /** - * Used to register any problems detected with {@link Configuration} or {@link Bean} - * declarations. For instance, a Bean method marked as {@literal final} is illegal - * and would be reported as a problem. Defaults to {@link FailFastProblemReporter}, - * but is overridable with {@link #setProblemReporter} - */ private ProblemReporter problemReporter = new FailFastProblemReporter(); private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); - private SimpleMetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory(); + private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(); /** - * Override the default {@link ProblemReporter}. - * @param problemReporter custom problem reporter + * Set the {@link ProblemReporter} to use. + *

Used to register any problems detected with {@link Configuration} or {@link Bean} + * declarations. For instance, a Bean method marked as {@literal final} is illegal + * and would be reported as a problem. Defaults to {@link FailFastProblemReporter}. */ public void setProblemReporter(ProblemReporter problemReporter) { this.problemReporter = problemReporter; @@ -116,34 +117,38 @@ public class ConfigurationClassPostProcessor implements BeanFactoryPostProcessor enhanceConfigurationClasses(beanFactory); } - /** * Build and validate a configuration model based on the registry of * {@link Configuration} classes. */ protected final void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { - Set configBeanDefs = new LinkedHashSet(); + Set configCandidates = new LinkedHashSet(); for (String beanName : registry.getBeanDefinitionNames()) { BeanDefinition beanDef = registry.getBeanDefinition(beanName); - if (checkConfigurationClassBeanDefinition(beanDef)) { - configBeanDefs.add(new BeanDefinitionHolder(beanDef, beanName)); + if (checkConfigurationClassCandidate(beanDef)) { + configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); } } // Return immediately if no @Configuration classes were found - if (configBeanDefs.isEmpty()) { + if (configCandidates.isEmpty()) { return; } // Populate a new configuration model by parsing each @Configuration classes ConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter); - for (BeanDefinitionHolder holder : configBeanDefs) { - String beanClassName = holder.getBeanDefinition().getBeanClassName(); + for (BeanDefinitionHolder holder : configCandidates) { + BeanDefinition bd = holder.getBeanDefinition(); try { - parser.parse(beanClassName, holder.getBeanName()); + if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) { + parser.parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName()); + } + else { + parser.parse(bd.getBeanClassName(), holder.getBeanName()); + } } catch (IOException ex) { - throw new BeanDefinitionStoreException("Failed to load bean class [" + beanClassName + "]", ex); + throw new BeanDefinitionStoreException("Failed to load bean class: " + bd.getBeanClassName(), ex); } } parser.validate(); @@ -152,6 +157,42 @@ public class ConfigurationClassPostProcessor implements BeanFactoryPostProcessor new ConfigurationClassBeanDefinitionReader(registry).loadBeanDefinitions(parser.getModel()); } + private boolean checkConfigurationClassCandidate(BeanDefinition beanDef) { + AnnotationMetadata metadata; + + // 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()) { + Class beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass(); + metadata = new StandardAnnotationMetadata(beanClass); + } + else { + String className = beanDef.getBeanClassName(); + try { + MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(className); + metadata = metadataReader.getAnnotationMetadata(); + } + catch (IOException ex) { + if (logger.isDebugEnabled()) { + logger.debug("Could not find class file for introspecting factory methods: " + className, ex); + } + return false; + } + } + + if (metadata.hasAnnotation(Configuration.class.getName())) { + beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL); + return true; + } + else if (metadata.hasAnnotation(Component.class.getName())) { + beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE); + return true; + } + else { + return false; + } + } + /** * Post-processes a BeanFactory in search of Configuration class BeanDefinitions; * any candidates are then enhanced by a {@link ConfigurationClassEnhancer}. @@ -162,7 +203,7 @@ public class ConfigurationClassPostProcessor implements BeanFactoryPostProcessor Set configBeanDefs = new LinkedHashSet(); for (String beanName : beanFactory.getBeanDefinitionNames()) { BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName); - if ("full".equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE))) { + if (CONFIGURATION_CLASS_FULL.equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE))) { configBeanDefs.add(new BeanDefinitionHolder(beanDef, beanName)); } } @@ -197,48 +238,4 @@ public class ConfigurationClassPostProcessor implements BeanFactoryPostProcessor } } - /** - * @return whether the BeanDefinition's beanClass (or its ancestry) is - * {@link Configuration}-annotated, false if no beanClass is specified. - */ - private boolean checkConfigurationClassBeanDefinition(BeanDefinition beanDef) { - // accommodating SPR-5655 - if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) { - if (AnnotationUtils.findAnnotation( - ((AbstractBeanDefinition) beanDef).getBeanClass(), Configuration.class) != null) { - beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, "full"); - return true; - } - else if (AnnotationUtils.findAnnotation( - ((AbstractBeanDefinition) beanDef).getBeanClass(), Component.class) != null) { - beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, "lite"); - return true; - } - else { - return false; - } - } - String className = beanDef.getBeanClassName(); - while (className != null && !(className.equals(Object.class.getName()))) { - try { - MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(className); - AnnotationMetadata metadata = metadataReader.getAnnotationMetadata(); - if (metadata.hasAnnotation(Configuration.class.getName())) { - beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, "full"); - return true; - } - if (metadata.hasAnnotation(Component.class.getName())) { - beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, "lite"); - return true; - } - className = metadata.getSuperClassName(); - } - catch (IOException ex) { - throw new BeanDefinitionStoreException("Failed to load class file [" + className + "]", ex); - } - } - - return false; - } - } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassReaderUtils.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassReaderUtils.java deleted file mode 100644 index ecdb0847414..00000000000 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassReaderUtils.java +++ /dev/null @@ -1,282 +0,0 @@ -/* - * Copyright 2002-2009 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 static java.lang.String.*; -import java.lang.annotation.Annotation; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * Various utility methods commonly used when interacting with ASM, classloading - * and creating {@link ConfigurationClassAnnotation} instances. - * - * @author Chris Beams - * @since 3.0 - */ -class ConfigurationClassReaderUtils { - - /** - * Convert a type descriptor to a classname suitable for classloading with - * Class.forName(). - * - * @param typeDescriptor see ASM guide section 2.1.3 - */ - public static String convertAsmTypeDescriptorToClassName(String typeDescriptor) { - final String internalName; // See ASM guide section 2.1.2 - - if ("V".equals(typeDescriptor)) - return Void.class.getName(); - if ("I".equals(typeDescriptor)) - return Integer.class.getName(); - if ("Z".equals(typeDescriptor)) - return Boolean.class.getName(); - - // strip the leading array/object/primitive identifier - if (typeDescriptor.startsWith("[[")) - internalName = typeDescriptor.substring(3); - else if (typeDescriptor.startsWith("[")) - internalName = typeDescriptor.substring(2); - else - internalName = typeDescriptor.substring(1); - - // convert slashes to dots - String className = internalName.replace('/', '.'); - - // and strip trailing semicolon (if present) - if (className.endsWith(";")) - className = className.substring(0, internalName.length() - 1); - - return className; - } - - /** - * @param methodDescriptor see ASM guide section 2.1.4 - */ - public static String getReturnTypeFromAsmMethodDescriptor(String methodDescriptor) { - String returnTypeDescriptor = methodDescriptor.substring(methodDescriptor.indexOf(')') + 1); - return convertAsmTypeDescriptorToClassName(returnTypeDescriptor); - } - - @SuppressWarnings("unchecked") - public static Class loadAnnotationType(String annoTypeDesc, ClassLoader classLoader) { - String annoTypeName = ConfigurationClassReaderUtils.convertAsmTypeDescriptorToClassName(annoTypeDesc); - try { - return (Class) classLoader.loadClass(annoTypeName); - } - catch (ClassNotFoundException ex) { - throw new IllegalStateException("Could not load annotation type [" + annoTypeName + "]", ex); - } - } - - /** - * Creates a {@link ConfigurationClassAnnotation} for {@code annoType}. JDK dynamic proxies are used, - * and the returned proxy implements both {@link ConfigurationClassAnnotation} and the annotation type. - * @param annoType annotation type that must be supplied and returned - * @param annoType type of annotation to create - */ - @SuppressWarnings("unchecked") - public static ConfigurationClassAnnotation createMutableAnnotation(Class annoType, ClassLoader classLoader) { - MutableAnnotationInvocationHandler handler = new MutableAnnotationInvocationHandler(annoType); - Class[] interfaces = new Class[] {annoType, ConfigurationClassAnnotation.class}; - return (ConfigurationClassAnnotation) Proxy.newProxyInstance(classLoader, interfaces, handler); - } - - - /** - * Handles calls to {@link ConfigurationClassAnnotation} attribute methods at runtime. Essentially - * emulates what JDK annotation dynamic proxies do. - */ - private static final class MutableAnnotationInvocationHandler implements InvocationHandler { - - private final Class annoType; - private final Map attributes = new HashMap(); - private final Map> attributeTypes = new HashMap>(); - - public MutableAnnotationInvocationHandler(Class annoType) { - // pre-populate the attributes hash will all the names - // and default values of the attributes defined in 'annoType' - Method[] attribs = annoType.getDeclaredMethods(); - for (Method attrib : attribs) { - this.attributes.put(attrib.getName(), AnnotationUtils.getDefaultValue(annoType, attrib.getName())); - this.attributeTypes.put(attrib.getName(), getAttributeType(annoType, attrib.getName())); - } - - this.annoType = annoType; - } - - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - Assert.isInstanceOf(Annotation.class, proxy); - - String methodName = method.getName(); - - // first -> check to see if this method is an attribute on our annotation - if (attributes.containsKey(methodName)) - return attributes.get(methodName); - - - // second -> is it a method from java.lang.annotation.Annotation? - if (methodName.equals("annotationType")) - return annoType; - - - // third -> is it a method from java.lang.Object? - if (methodName.equals("toString")) - return format("@%s(%s)", annoType.getName(), getAttribs()); - - if (methodName.equals("equals")) - return isEqualTo(proxy, args[0]); - - if (methodName.equals("hashCode")) - return calculateHashCode(proxy); - - - // finally -> is it a method specified by MutableAnno? - if (methodName.equals("setAttributeValue")) { - attributes.put((String) args[0], args[1]); - return null; // setAttributeValue has a 'void' return type - } - - if (methodName.equals("getAttributeType")) - return attributeTypes.get(args[0]); - - throw new UnsupportedOperationException("this proxy does not support method: " + methodName); - } - - /** - * Conforms to the hashCode() specification for Annotation. - * - * @see Annotation#hashCode() - */ - private Object calculateHashCode(Object proxy) { - int sum = 0; - - for (String attribName : attributes.keySet()) { - Object attribValue = attributes.get(attribName); - - final int attribNameHashCode = attribName.hashCode(); - final int attribValueHashCode; - - if (attribValue == null) - // memberValue may be null when a mutable annotation is being added to a - // collection - // and before it has actually been visited (and populated) by - // MutableAnnotationVisitor - attribValueHashCode = 0; - else if (attribValue.getClass().isArray()) - attribValueHashCode = Arrays.hashCode((Object[]) attribValue); - else - attribValueHashCode = attribValue.hashCode(); - - sum += (127 * attribNameHashCode) ^ attribValueHashCode; - } - - return sum; - } - - /** - * Compares proxy object and other object by comparing the return - * values of the methods specified by their common {@link Annotation} ancestry. - *

- * other must be the same type as or a subtype of proxy. Will - * return false otherwise. - *

- * Eagerly returns true if {@code proxy} == other - *

- *

- * Conforms strictly to the equals() specification for Annotation - *

- * - * @see Annotation#equals(Object) - */ - private Object isEqualTo(Object proxy, Object other) { - if (proxy == other) - return true; - - if (other == null) - return false; - - if (!annoType.isAssignableFrom(other.getClass())) - return false; - - for (String attribName : attributes.keySet()) { - Object thisVal; - Object thatVal; - - try { - thisVal = attributes.get(attribName); - thatVal = other.getClass().getDeclaredMethod(attribName).invoke(other); - } catch (Exception ex) { - throw new IllegalStateException(ex); - } - - if ((thisVal == null) && (thatVal != null)) - return false; - - if ((thatVal == null) && (thisVal != null)) - return false; - - if (thatVal.getClass().isArray()) { - if (!Arrays.equals((Object[]) thatVal, (Object[]) thisVal)) { - return false; - } - } else if (thisVal instanceof Double) { - if (!Double.valueOf((Double) thisVal).equals(Double.valueOf((Double) thatVal))) - return false; - } else if (thisVal instanceof Float) { - if (!Float.valueOf((Float) thisVal).equals(Float.valueOf((Float) thatVal))) - return false; - } else if (!thisVal.equals(thatVal)) { - return false; - } - } - - return true; - } - - private String getAttribs() { - List attribs = new ArrayList(); - for (String attribName : attributes.keySet()) { - attribs.add(format("%s=%s", attribName, attributes.get(attribName))); - } - return StringUtils.collectionToDelimitedString(attribs, ", "); - } - - /** - * Retrieve the type of the given annotation attribute. - */ - private static Class getAttributeType(Class annotationType, String attributeName) { - try { - return annotationType.getDeclaredMethod(attributeName).getReturnType(); - } - catch (Exception ex) { - throw new IllegalStateException("Could not introspect return type", ex); - } - } - } - -} diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassVisitor.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassVisitor.java deleted file mode 100644 index 4062d9a5c01..00000000000 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassVisitor.java +++ /dev/null @@ -1,445 +0,0 @@ -/* - * Copyright 2002-2009 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 java.io.IOException; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.Iterator; -import java.util.List; -import java.util.Set; -import java.util.Stack; - -import org.springframework.asm.AnnotationVisitor; -import org.springframework.asm.Attribute; -import org.springframework.asm.ClassAdapter; -import org.springframework.asm.ClassReader; -import org.springframework.asm.ClassVisitor; -import org.springframework.asm.FieldVisitor; -import org.springframework.asm.Label; -import org.springframework.asm.MethodAdapter; -import org.springframework.asm.MethodVisitor; -import org.springframework.asm.Opcodes; -import org.springframework.asm.Type; -import org.springframework.asm.commons.EmptyVisitor; -import org.springframework.beans.factory.BeanDefinitionStoreException; -import org.springframework.beans.factory.parsing.Location; -import org.springframework.beans.factory.parsing.Problem; -import org.springframework.beans.factory.parsing.ProblemReporter; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.type.classreading.SimpleMetadataReader; -import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; - -/** - * ASM {@link ClassVisitor} that visits a {@link Configuration} class, populating a - * {@link ConfigurationClass} instance with information gleaned along the way. - * - * @author Chris Beams - * @author Juergen Hoeller - * @since 3.0 - * @see ConfigurationClassParser - * @see ConfigurationClass - */ -class ConfigurationClassVisitor implements ClassVisitor { - - private static final String OBJECT_DESC = ClassUtils.convertClassNameToResourcePath(Object.class.getName()); - - private final ConfigurationClass configClass; - - private final Set model; - - private final ProblemReporter problemReporter; - - private final SimpleMetadataReaderFactory metadataReaderFactory; - - private final Stack importStack; - - - public ConfigurationClassVisitor(ConfigurationClass configClass, Set model, - ProblemReporter problemReporter, SimpleMetadataReaderFactory metadataReaderFactory) { - - this.configClass = configClass; - this.model = model; - this.problemReporter = problemReporter; - this.metadataReaderFactory = metadataReaderFactory; - this.importStack = new ImportStack(); - } - - private ConfigurationClassVisitor(ConfigurationClass configClass, Set model, - ProblemReporter problemReporter, SimpleMetadataReaderFactory metadataReaderFactory, - Stack importStack) { - this.configClass = configClass; - this.model = model; - this.problemReporter = problemReporter; - this.metadataReaderFactory = metadataReaderFactory; - this.importStack = importStack; - } - - - public void visit(int classVersion, int modifiers, String classTypeDesc, String arg3, String superTypeDesc, String[] arg5) { - visitSuperType(superTypeDesc); - - configClass.setName(ClassUtils.convertResourcePathToClassName(classTypeDesc)); - - // ASM always adds ACC_SUPER to the opcodes/modifiers for class definitions. - // Unknown as to why (JavaDoc is silent on the matter), but it should be - // eliminated in order to comply with java.lang.reflect.Modifier values. - configClass.setModifiers(modifiers - Opcodes.ACC_SUPER); - } - - private void visitSuperType(String superTypeDesc) { - // traverse up the type hierarchy unless the next ancestor is java.lang.Object - if (OBJECT_DESC.equals(superTypeDesc)) { - return; - } - ConfigurationClassVisitor visitor = new ConfigurationClassVisitor(configClass, model, problemReporter, metadataReaderFactory, importStack); - String superClassName = ClassUtils.convertResourcePathToClassName(superTypeDesc); - try { - SimpleMetadataReader reader = this.metadataReaderFactory.getMetadataReader(superClassName); - reader.getClassReader().accept(visitor, false); - } - catch (IOException ex) { - throw new BeanDefinitionStoreException("Failed to load bean super class [" + superClassName + "]", ex); - } - } - - public void visitSource(String sourceFile, String debug) { - String resourcePath = - ClassUtils.convertClassNameToResourcePath(configClass.getName()) - .substring(0, configClass.getName().lastIndexOf('.') + 1).concat(sourceFile); - - configClass.setSource(resourcePath); - } - - public void visitOuterClass(String s, String s1, String s2) { - } - - /** - * Visits a class level annotation on a {@link Configuration @Configuration} class. - *

Upon encountering such an annotation, updates the {@link #configClass} model - * object appropriately, and then returns an {@link AnnotationVisitor} implementation - * that can populate the annotation appropriately with its attribute data as parsed - * by ASM. - * @see ConfigurationClassAnnotation - * @see Configuration - * @see Lazy - * @see Import - */ - @SuppressWarnings("unchecked") - public AnnotationVisitor visitAnnotation(String annoTypeDesc, boolean visible) { - ClassLoader classLoader = metadataReaderFactory.getResourceLoader().getClassLoader(); - Class annoClass = ConfigurationClassReaderUtils.loadAnnotationType(annoTypeDesc, classLoader); - if (annoClass == null) { - // annotation was unable to be loaded -> probably Spring IDE unable to load a user-defined annotation - return new EmptyVisitor(); - } - if (Import.class.equals(annoClass)) { - if (!importStack.contains(configClass)) { - importStack.push(configClass); - return new ImportAnnotationVisitor(model, problemReporter, classLoader); - } - problemReporter.error(new CircularImportProblem(configClass, importStack)); - } - - ConfigurationClassAnnotation mutableAnnotation = ConfigurationClassReaderUtils.createMutableAnnotation(annoClass, classLoader); - configClass.addAnnotation(mutableAnnotation); - return new ConfigurationClassAnnotationVisitor(mutableAnnotation, classLoader); - } - - public void visitAttribute(Attribute attribute) { - } - - public void visitInnerClass(String s, String s1, String s2, int i) { - } - - public FieldVisitor visitField(int i, String s, String s1, String s2, Object o) { - return new EmptyVisitor(); - } - - /** - * Delegates all {@link Configuration @Configuration} class method parsing to - * {@link ConfigurationClassMethodVisitor}. - */ - public MethodVisitor visitMethod(int modifiers, String methodName, String methodDescriptor, String arg3, String[] arg4) { - ClassLoader classLoader = metadataReaderFactory.getResourceLoader().getClassLoader(); - return new ConfigurationClassMethodVisitor(configClass, methodName, methodDescriptor, modifiers, classLoader); - } - - public void visitEnd() { - } - - - /** - * ASM {@link MethodVisitor} that visits a single method declared in a given - * {@link Configuration} class. Determines whether the method is a {@link Bean} - * method and if so, adds it to the {@link ConfigurationClass}. - */ - private class ConfigurationClassMethodVisitor extends MethodAdapter { - - private final ConfigurationClass configClass; - private final String methodName; - private final int modifiers; - private final ConfigurationClassMethod.ReturnType returnType; - private final List annotations = new ArrayList(); - private final ClassLoader classLoader; - - private int lineNumber; - - /** - * Create a new {@link ConfigurationClassMethodVisitor} instance. - * @param configClass model object to which this method will be added - * @param methodName name of the method declared in the {@link Configuration} class - * @param methodDescriptor ASM representation of the method signature - * @param modifiers modifiers for this method - */ - public ConfigurationClassMethodVisitor(ConfigurationClass configClass, String methodName, - String methodDescriptor, int modifiers, ClassLoader classLoader) { - super(new EmptyVisitor()); - this.configClass = configClass; - this.methodName = methodName; - this.classLoader = classLoader; - this.modifiers = modifiers; - this.returnType = initReturnTypeFromMethodDescriptor(methodDescriptor); - } - - /** - * Visits a single annotation on this method. Will be called once for each annotation - * present (regardless of its RetentionPolicy). - */ - @Override - @SuppressWarnings("unchecked") - public AnnotationVisitor visitAnnotation(String annoTypeDesc, boolean visible) { - Class annoClass = ConfigurationClassReaderUtils.loadAnnotationType(annoTypeDesc, classLoader); - if (annoClass == null) { - return super.visitAnnotation(annoTypeDesc, visible); - } - ConfigurationClassAnnotation annotation = ConfigurationClassReaderUtils.createMutableAnnotation(annoClass, classLoader); - annotations.add(annotation); - return new ConfigurationClassAnnotationVisitor(annotation, classLoader); - } - - /** - * Provides the line number of this method within its declaring class. In reality, this - * number is always inaccurate - lineNo represents the line number of the - * first instruction in this method. Method declaration line numbers are not in any way - * tracked in the bytecode. Any tooling or output that reads this value will have to - * compensate and estimate where the actual method declaration is. - */ - @Override - public void visitLineNumber(int lineNo, Label start) { - this.lineNumber = lineNo; - } - - /** - * Parses through all {@link #annotations} on this method in order to determine whether - * it is a {@link Bean} method and if so adds it to the enclosing {@link #configClass}. - */ - @Override - public void visitEnd() { - for (Annotation anno : annotations) { - if (Bean.class.equals(anno.annotationType())) { - // this method is annotated with @Bean -> add it to the ConfigurationClass model - Annotation[] annoArray = annotations.toArray(new Annotation[annotations.size()]); - ConfigurationClassMethod method = new ConfigurationClassMethod(methodName, modifiers, returnType, annoArray); - method.setSource(lineNumber); - configClass.addMethod(method); - break; - } - } - } - - /** - * Determines return type from ASM methodDescriptor and determines whether - * that type is an interface. - */ - private ConfigurationClassMethod.ReturnType initReturnTypeFromMethodDescriptor(String methodDescriptor) { - final ConfigurationClassMethod.ReturnType returnType = new ConfigurationClassMethod.ReturnType(ConfigurationClassReaderUtils.getReturnTypeFromAsmMethodDescriptor(methodDescriptor)); - // detect whether the return type is an interface - try { - metadataReaderFactory.getMetadataReader(returnType.getName()).getClassReader().accept( - new ClassAdapter(new EmptyVisitor()) { - @Override - public void visit(int arg0, int arg1, String arg2, String arg3, String arg4, String[] arg5) { - returnType.setInterface((arg1 & Opcodes.ACC_INTERFACE) == Opcodes.ACC_INTERFACE); - } - }, false); - return returnType; - } - catch (IOException ex) { - throw new BeanDefinitionStoreException("Failed to load bean return type [" + returnType.getName() + "]", ex); - } - } - } - - - /** - * ASM {@link AnnotationVisitor} implementation that reads an {@link Import} annotation - * for all its specified classes and then one by one processes each class with a new - * {@link ConfigurationClassVisitor}. - */ - private class ImportAnnotationVisitor implements AnnotationVisitor{ - - private final Set model; - - private final ProblemReporter problemReporter; - - private final List classesToImport = new ArrayList(); - - public ImportAnnotationVisitor( - Set model, ProblemReporter problemReporter, ClassLoader classLoader) { - - this.model = model; - this.problemReporter = problemReporter; - } - - public void visit(String s, Object o) { - } - - public void visitEnum(String s, String s1, String s2) { - } - - public AnnotationVisitor visitAnnotation(String s, String s1) { - return null; - } - - public AnnotationVisitor visitArray(String attribName) { - Assert.isTrue("value".equals(attribName)); - return new AnnotationVisitor() { - public void visit(String na, Object type) { - Assert.isInstanceOf(Type.class, type); - classesToImport.add(((Type) type).getClassName()); - } - public void visitEnum(String s, String s1, String s2) { - } - public AnnotationVisitor visitAnnotation(String s, String s1) { - return new EmptyVisitor(); - } - public AnnotationVisitor visitArray(String s) { - return new EmptyVisitor(); - } - public void visitEnd() { - } - }; - } - - public void visitEnd() { - for (String classToImport : classesToImport) { - processClassToImport(classToImport); - } - importStack.pop(); - } - - private void processClassToImport(String classToImport) { - ConfigurationClass configClass = new ConfigurationClass(); - try { - ClassReader reader = metadataReaderFactory.getMetadataReader(classToImport).getClassReader(); - reader.accept(new ConfigurationClassVisitor(configClass, model, problemReporter, metadataReaderFactory, importStack), false); - if (configClass.getAnnotation(Configuration.class) == null) { - problemReporter.error(new NonAnnotatedConfigurationProblem(configClass.getName(), configClass.getLocation())); - } - else { - model.add(configClass); - } - } - catch (IOException ex) { - throw new BeanDefinitionStoreException("Failed to load imported configuration class [" + classToImport + "]", ex); - } - } - } - - - @SuppressWarnings("serial") - private static class ImportStack extends Stack { - - /** - * Simplified contains() implementation that tests to see if any {@link ConfigurationClass} - * exists within this stack that has the same name as elem. Elem must be of - * type ConfigurationClass. - */ - @Override - public boolean contains(Object elem) { - if (!(elem instanceof ConfigurationClass)) { - return false; - } - ConfigurationClass configClass = (ConfigurationClass) elem; - Comparator comparator = new Comparator() { - public int compare(ConfigurationClass first, ConfigurationClass second) { - return first.getName().equals(second.getName()) ? 0 : 1; - } - }; - return (Collections.binarySearch(this, configClass, comparator) != -1); - } - - /** - * Given a stack containing (in order) - *

    - *
  1. com.acme.Foo
  2. - *
  3. com.acme.Bar
  4. - *
  5. com.acme.Baz
  6. - *
- * Returns "Foo->Bar->Baz". In the case of an empty stack, returns empty string. - */ - @Override - public synchronized String toString() { - StringBuilder builder = new StringBuilder(); - Iterator iterator = iterator(); - while (iterator.hasNext()) { - builder.append(iterator.next().getSimpleName()); - if (iterator.hasNext()) { - builder.append("->"); - } - } - return builder.toString(); - } - } - - - /** Configuration classes must be annotated with {@link Configuration @Configuration}. */ - private static class NonAnnotatedConfigurationProblem extends Problem { - - public NonAnnotatedConfigurationProblem(String className, Location location) { - super(String.format("%s was imported as a @Configuration class but was not actually annotated " + - "with @Configuration. Annotate the class or do not attempt to process it.", className), location); - } - - } - - - /** - * {@link Problem} registered upon detection of a circular {@link Import}. - * @see Import - */ - private static class CircularImportProblem extends Problem { - - public CircularImportProblem(ConfigurationClass attemptedImport, Stack importStack) { - super(String.format("A circular @Import has been detected: " + - "Illegal attempt by @Configuration class '%s' to import class '%s' as '%s' is " + - "already present in the current import stack [%s]", - importStack.peek().getSimpleName(), attemptedImport.getSimpleName(), - attemptedImport.getSimpleName(), importStack), - new Location(new ClassPathResource( - ClassUtils.convertClassNameToResourcePath(importStack.peek().getName())), - importStack.peek().getSource()) - ); - } - } - -} diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/BeanMethodTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/BeanMethodTests.java deleted file mode 100644 index 9bb3092fe67..00000000000 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/BeanMethodTests.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2002-2009 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 java.lang.reflect.Modifier; - -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; -import org.junit.Test; - -import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; -import org.springframework.beans.factory.parsing.FailFastProblemReporter; -import org.springframework.beans.factory.parsing.Location; -import org.springframework.beans.factory.parsing.ProblemReporter; -import static org.springframework.context.annotation.ConfigurationClassReaderUtils.*; -import static org.springframework.context.annotation.ScopedProxyMode.*; -import static org.springframework.context.annotation.StandardScopes.*; -import org.springframework.core.io.ClassPathResource; -import org.springframework.util.ClassUtils; - -/** - * @author Chris Beams - */ -public class BeanMethodTests { - - private ProblemReporter problemReporter = new FailFastProblemReporter(); - private String beanName = "foo"; - private Bean beanAnno = (Bean) createMutableAnnotation(Bean.class, ClassUtils.getDefaultClassLoader()); - private ConfigurationClassMethod.ReturnType returnType = new ConfigurationClassMethod.ReturnType("FooType"); - private ConfigurationClass declaringClass = new ConfigurationClass(); - { declaringClass.setName("test.Config"); } - - @Test - public void testWellFormedMethod() { - ConfigurationClassMethod beanMethod = new ConfigurationClassMethod(beanName, 0, returnType, beanAnno); - - assertThat(beanMethod.getName(), sameInstance(beanName)); - assertThat(beanMethod.getModifiers(), equalTo(0)); - assertThat(beanMethod.getReturnType(), sameInstance(returnType)); - assertThat(beanMethod.getAnnotation(Bean.class), sameInstance(beanAnno)); - assertThat(beanMethod.getAnnotation(Override.class), nullValue()); - assertThat(beanMethod.getRequiredAnnotation(Bean.class), sameInstance(beanAnno)); - try { - beanMethod.getRequiredAnnotation(Override.class); - fail("expected IllegalStateException ex"); - } catch (IllegalStateException ex) { /* expected */ } - - // must call setDeclaringClass() before calling getLocation() - try { - beanMethod.getLocation(); - fail("expected IllegalStateException ex"); - } catch (IllegalStateException ex) { /* expected */ } - - - beanMethod.setDeclaringClass(declaringClass); - assertThat(beanMethod.getDeclaringClass(), sameInstance(declaringClass)); - - beanMethod.setSource(12); // indicating a line number - assertEquals(beanMethod.getSource(), 12); - - Location location = beanMethod.getLocation(); - assertEquals(location.getResource(), new ClassPathResource("test/Config")); - assertEquals(location.getSource(), 12); - - // should validate without throwing as this is a well-formed method - beanMethod.validate(problemReporter); - } - - @Test - public void finalMethodsAreIllegal() { - ConfigurationClassMethod beanMethod = new ConfigurationClassMethod(beanName, Modifier.FINAL, returnType, beanAnno); - beanMethod.setDeclaringClass(declaringClass); - try { - beanMethod.validate(problemReporter); - fail("should have failed due to final bean method"); - } catch (BeanDefinitionParsingException ex) { - assertTrue(ex.getMessage().contains("remove the final modifier")); - } - } - - @Test - public void privateMethodsAreIllegal() { - ConfigurationClassMethod beanMethod = new ConfigurationClassMethod(beanName, Modifier.PRIVATE, returnType, beanAnno); - beanMethod.setDeclaringClass(declaringClass); - try { - beanMethod.validate(problemReporter); - fail("should have failed due to private bean method"); - } catch (BeanDefinitionParsingException ex) { - assertTrue(ex.getMessage().contains("increase the method's visibility")); - } - } - - @Test - public void singletonsSansProxyAreLegal() { - Scope scope = SingletonNoProxy.class.getAnnotation(Scope.class); - ConfigurationClassMethod beanMethod = new ConfigurationClassMethod(beanName, 0, returnType, beanAnno, scope); - beanMethod.setDeclaringClass(declaringClass); - beanMethod.validate(problemReporter); // should validate without problems - it's legal - } - - @Test - public void sessionInterfaceScopedProxiesAreLegal() { - Scope scope = SessionInterfaceProxy.class.getAnnotation(Scope.class); - ConfigurationClassMethod beanMethod = new ConfigurationClassMethod(beanName, 0, returnType, beanAnno, scope); - beanMethod.setDeclaringClass(declaringClass); - beanMethod.validate(problemReporter); // should validate without problems - it's legal - } - - @Scope(value=SINGLETON, proxyMode=INTERFACES) - private class SingletonInterfaceProxy { } - - @Scope(value=SINGLETON, proxyMode=TARGET_CLASS) - private class SingletonTargetClassProxy { } - - @Scope(value=SINGLETON, proxyMode=NO) - private class SingletonNoProxy { } - - @Scope(value=PROTOTYPE, proxyMode=INTERFACES) - private class PrototypeInterfaceProxy { } - - @Scope(value=SESSION, proxyMode=INTERFACES) - private class SessionInterfaceProxy { } -} diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/AutowiredConfigurationTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/AutowiredConfigurationTests.java index 68755bc06f9..f76300180ec 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/AutowiredConfigurationTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/AutowiredConfigurationTests.java @@ -34,12 +34,10 @@ import org.springframework.core.io.ClassPathResource; import test.beans.Colour; import test.beans.TestBean; - /** * System tests covering use of {@link Autowired} and {@link Value} within * {@link Configuration} classes. * - * * @author Chris Beams */ public class AutowiredConfigurationTests { @@ -73,6 +71,7 @@ public class AutowiredConfigurationTests { } } + /** * {@link Autowired} constructors are not supported on {@link Configuration} classes * due to CGLIB constraints @@ -96,6 +95,7 @@ public class AutowiredConfigurationTests { } } + @Test public void testValueInjection() { System.setProperty("myProp", "foo"); @@ -119,6 +119,7 @@ public class AutowiredConfigurationTests { } } + @Test public void testCustomProperties() { ClassPathXmlApplicationContext factory = new ClassPathXmlApplicationContext( @@ -143,4 +144,5 @@ public class AutowiredConfigurationTests { return new TestBean(hostname); } } + } diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java index cd8f40f00ab..608c2e88104 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java @@ -74,15 +74,6 @@ public class ConfigurationClassProcessingTests { } catch (NoSuchBeanDefinitionException ex) { /* expected */ } } - @Configuration - static class ConfigWithBeanWithCustomName { - static TestBean testBean = new TestBean(); - @Bean(name="customName") - public TestBean methodName() { - return testBean; - } - } - @Test public void aliasesAreRespected() { BeanFactory factory = initBeanFactory(ConfigWithBeanWithAliases.class); @@ -98,27 +89,11 @@ public class ConfigurationClassProcessingTests { } catch (NoSuchBeanDefinitionException ex) { /* expected */ } } - @Configuration - static class ConfigWithBeanWithAliases { - static TestBean testBean = new TestBean(); - @Bean(name={"name1", "alias1", "alias2", "alias3"}) - public TestBean methodName() { - return testBean; - } - } - @Test(expected=BeanDefinitionParsingException.class) public void testFinalBeanMethod() { initBeanFactory(ConfigWithFinalBean.class); } - @Configuration - static class ConfigWithFinalBean { - public final @Bean TestBean testBean() { - return new TestBean(); - } - } - @Test public void simplestPossibleConfiguration() { BeanFactory factory = initBeanFactory(SimplestPossibleConfig.class); @@ -139,6 +114,24 @@ public class ConfigurationClassProcessingTests { } + @Configuration + static class ConfigWithBeanWithCustomName { + static TestBean testBean = new TestBean(); + @Bean(name="customName") + public TestBean methodName() { + return testBean; + } + } + + + @Configuration + static class ConfigWithFinalBean { + public final @Bean TestBean testBean() { + return new TestBean(); + } + } + + @Configuration static class SimplestPossibleConfig { public @Bean String stringBean() { @@ -147,6 +140,16 @@ public class ConfigurationClassProcessingTests { } + @Configuration + static class ConfigWithBeanWithAliases { + static TestBean testBean = new TestBean(); + @Bean(name={"name1", "alias1", "alias2", "alias3"}) + public TestBean methodName() { + return testBean; + } + } + + @Configuration static class ConfigWithPrototypeBean { diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/PolymorphicConfigurationTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/PolymorphicConfigurationTests.java index 2fefd1bb8eb..52532a28e58 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/PolymorphicConfigurationTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/PolymorphicConfigurationTests.java @@ -19,14 +19,14 @@ package org.springframework.context.annotation.configuration; import java.lang.annotation.Inherited; import org.junit.Test; +import test.beans.TestBean; + import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ConfigurationClassPostProcessor; -import test.beans.TestBean; - /** * Tests that polymorphic Configuration classes need not explicitly redeclare the * {@link Configuration} annotation. This respects the {@link Inherited} nature @@ -37,7 +37,7 @@ import test.beans.TestBean; public class PolymorphicConfigurationTests { @Test - public void subclassNeedNotDeclareConfigurationAnnotation() { + public void beanMethodsDetectedOnSuperClass() { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); beanFactory.registerBeanDefinition("config", new RootBeanDefinition(Config.class)); ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor(); @@ -56,6 +56,7 @@ public class PolymorphicConfigurationTests { } + @Configuration static class Config extends SuperConfig { } diff --git a/org.springframework.core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/org.springframework.core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java index eaec9f41447..faf76154c23 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java +++ b/org.springframework.core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java @@ -231,19 +231,46 @@ public abstract class AnnotationUtils { } /** - * Retrieve the given annotation's attributes as a Map. + * Retrieve the given annotation's attributes as a Map, + * preserving all attribute types as-is. * @param annotation the annotation to retrieve the attributes for * @return the Map of annotation attributes, with attribute names as keys * and corresponding attribute values as values */ public static Map getAnnotationAttributes(Annotation annotation) { + return getAnnotationAttributes(annotation, false); + } + + /** + * Retrieve the given annotation's attributes as a Map. + * @param annotation the annotation to retrieve the attributes for + * @param filterClasses whether to turn Class references into Strings + * (for 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 corresponding attribute values as values + */ + public static Map getAnnotationAttributes(Annotation annotation, boolean filterClasses) { Map attrs = new HashMap(); Method[] methods = annotation.annotationType().getDeclaredMethods(); - for (int j = 0; j < methods.length; j++) { - Method method = methods[j]; + for (Method method : methods) { if (method.getParameterTypes().length == 0 && method.getReturnType() != void.class) { try { - attrs.put(method.getName(), method.invoke(annotation)); + Object value = method.invoke(annotation); + if (filterClasses) { + if (value instanceof Class) { + value = ((Class) value).getName(); + } + else if (value instanceof Class[]) { + Class[] clazzArray = (Class[]) value; + String[] newValue = new String[clazzArray.length]; + for (int i = 0; i < clazzArray.length; i++) { + newValue[i] = clazzArray[i].getName(); + } + value = newValue; + } + } + attrs.put(method.getName(), value); } catch (Exception ex) { throw new IllegalStateException("Could not obtain annotation attribute values", ex); diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/AnnotationMetadata.java b/org.springframework.core/src/main/java/org/springframework/core/type/AnnotationMetadata.java index 4db70031c35..1d1f2b64bcc 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/type/AnnotationMetadata.java +++ b/org.springframework.core/src/main/java/org/springframework/core/type/AnnotationMetadata.java @@ -45,9 +45,20 @@ public interface AnnotationMetadata extends ClassMetadata { */ boolean hasAnnotation(String annotationType); + /** + * Retrieve the attributes of the annotation of the given type, + * if any (i.e. if defined on the underlying class). + * @param annotationType the annotation type to look for + * @return a Map of attributes, with the attribute name as key (e.g. "value") + * and the defined attribute value as Map value. This return value will be + * null if no matching annotation is defined. + */ + Map getAnnotationAttributes(String annotationType); + /** * Return the names of all meta-annotation types defined on the * given annotation type of the underlying class. + * @param annotationType the meta-annotation type to look for * @return the meta-annotation type names */ Set getMetaAnnotationTypes(String annotationType); @@ -61,26 +72,20 @@ public interface AnnotationMetadata extends ClassMetadata { boolean hasMetaAnnotation(String metaAnnotationType); /** - * Retrieve the attributes of the annotation of the given type, - * if any (i.e. if defined on the underlying class). - * @param annotationType the annotation type to look for - * @return a Map of attributes, with the attribute name as key - * (e.g. "value") and the defined attribute value as Map value. - * This return value will be null if no matching - * annotation is defined. + * Retrieve the method metadata for all methods that are annotated + * with at least one annotation type. + * @return a Set of {@link MethodMetadata} for methods that have annotations. + * The return value will be an empty set if no annotated methods are found. */ - Map getAnnotationAttributes(String annotationType); - - - // TODO return null would be more consistent with other methods if no match is found + Set getAnnotatedMethods(); /** - * Retrieve the method meta-data for all methods that have the + * Retrieve the method metadata for all methods that have the * given annotation type. * @param annotationType the annotation type to look for - * @return a Set of (@link MethodMetadata) for methods that - * have a matching annotation. The return value will be an - * empty set if no methods match the annotation type. + * @return a Set of {@link MethodMetadata} for methods that have a matching + * annotation. The return value will be an empty set if no methods match + * the annotation type. */ Set getAnnotatedMethods(String annotationType); diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/ClassMetadata.java b/org.springframework.core/src/main/java/org/springframework/core/type/ClassMetadata.java index c3fb972548f..f2c9ecb1651 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/type/ClassMetadata.java +++ b/org.springframework.core/src/main/java/org/springframework/core/type/ClassMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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. @@ -49,6 +49,11 @@ public interface ClassMetadata { */ boolean isConcrete(); + /** + * Return whether the underlying class is marked as 'final'. + */ + boolean isFinal(); + /** * Determine whether the underlying class is independent, * i.e. whether it is a top-level class or a nested class diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/MethodMetadata.java b/org.springframework.core/src/main/java/org/springframework/core/type/MethodMetadata.java index 58c89913086..62d94efbda3 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/type/MethodMetadata.java +++ b/org.springframework.core/src/main/java/org/springframework/core/type/MethodMetadata.java @@ -20,41 +20,60 @@ import java.util.Map; import java.util.Set; /** + * Interface that defines abstract access to the annotations of a specific + * class, in a form that does not require that class to be loaded yet. + * * @author Mark Pollack + * @author Juergen Hoeller * @since 3.0 + * @see StandardMethodMetadata + * @see AnnotationMetadata#getAnnotatedMethods */ public interface MethodMetadata { - int getModifiers(); - - boolean isStatic(); - + /** + * Return the name of the method. + */ String getMethodName(); - - //TODO does the method return type have a generic wildcard or generic type parameters? - - // annotation metadata - - Set getAnnotationTypes(); - - boolean hasAnnotation(String annotationType); - - Map getAnnotationAttributes(String annotationType); - - /** - * Determine whether the underlying class has an annotation that - * is itself annotated with the meta-annotation of the given type. - * @param metaAnnotationType the meta-annotation type to look for - * @return whether a matching meta-annotation is defined - */ - boolean hasMetaAnnotation(String metaAnnotationType); - - /** - * Return the names of all meta-annotation types defined on the - * given annotation type of the underlying class. - * @return the meta-annotation type names - */ - Set getMetaAnnotationTypes(String annotationType); - Set getAnnotationTypesWithMetaAnnotation(String qualifierClassName); + /** + * Return whether the underlying method is declared as 'static'. + */ + boolean isStatic(); + + /** + * Return whether the underlying method is marked as 'final'. + */ + boolean isFinal(); + + /** + * Return whether the underlying method is overridable, + * i.e. not marked as static, final or private. + */ + boolean isOverridable(); + + /** + * Return the names of all annotation types defined on the underlying method. + * @return the annotation type names, or an empty Set if none found + */ + Set getAnnotationTypes(); + + /** + * Determine whether the underlying method has an annotation of the given + * type defined. + * @param annotationType the annotation type to look for + * @return whether a matching annotation is defined + */ + boolean hasAnnotation(String annotationType); + + /** + * Retrieve the attributes of the annotation of the given type, + * if any (i.e. if defined on the underlying method). + * @param annotationType the annotation type to look for + * @return a Map of attributes, with the attribute name as key (e.g. "value") + * and the defined attribute value as Map value. This return value will be + * null if no matching annotation is defined. + */ + Map getAnnotationAttributes(String annotationType); + } diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java b/org.springframework.core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java index 043653b8b4c..ac672ad3313 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java +++ b/org.springframework.core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java @@ -35,6 +35,10 @@ import org.springframework.core.annotation.AnnotationUtils; */ public class StandardAnnotationMetadata extends StandardClassMetadata implements AnnotationMetadata { + /** + * Create a new StandardAnnotationMetadata wrapper for the given Class. + * @param introspectedClass the Class to introspect + */ public StandardAnnotationMetadata(Class introspectedClass) { super(introspectedClass); } @@ -59,6 +63,16 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements return false; } + public Map getAnnotationAttributes(String annotationType) { + Annotation[] anns = getIntrospectedClass().getAnnotations(); + for (Annotation ann : anns) { + if (ann.annotationType().getName().equals(annotationType)) { + return AnnotationUtils.getAnnotationAttributes(ann, true); + } + } + return null; + } + public Set getMetaAnnotationTypes(String annotationType) { Annotation[] anns = getIntrospectedClass().getAnnotations(); for (Annotation ann : anns) { @@ -87,25 +101,25 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements return false; } - public Map getAnnotationAttributes(String annotationType) { - Annotation[] anns = getIntrospectedClass().getAnnotations(); - for (Annotation ann : anns) { - if (ann.annotationType().getName().equals(annotationType)) { - return AnnotationUtils.getAnnotationAttributes(ann); + public Set getAnnotatedMethods() { + Method[] methods = getIntrospectedClass().getDeclaredMethods(); + Set annotatedMethods = new LinkedHashSet(); + for (Method method : methods) { + if (method.getAnnotations().length > 0) { + annotatedMethods.add(new StandardMethodMetadata(method)); } } - return null; + return annotatedMethods; } public Set getAnnotatedMethods(String annotationType) { - Method[] methods = getIntrospectedClass().getMethods(); + Method[] methods = getIntrospectedClass().getDeclaredMethods(); Set annotatedMethods = new LinkedHashSet(); for (Method method : methods) { Annotation[] methodAnnotations = method.getAnnotations(); for (Annotation ann : methodAnnotations) { if (ann.annotationType().getName().equals(annotationType)) { - MethodMetadata mm = new StandardMethodMetadata(method); - annotatedMethods.add(mm); + annotatedMethods.add(new StandardMethodMetadata(method)); } } } diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/StandardClassMetadata.java b/org.springframework.core/src/main/java/org/springframework/core/type/StandardClassMetadata.java index 162cae63b4c..b7ae58be73a 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/type/StandardClassMetadata.java +++ b/org.springframework.core/src/main/java/org/springframework/core/type/StandardClassMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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. @@ -18,6 +18,8 @@ package org.springframework.core.type; import java.lang.reflect.Modifier; +import org.springframework.util.Assert; + /** * {@link ClassMetadata} implementation that uses standard reflection * to introspect a given Class. @@ -30,57 +32,69 @@ public class StandardClassMetadata implements ClassMetadata { private final Class introspectedClass; + /** + * Create a new StandardClassMetadata wrapper for the given Class. + * @param introspectedClass the Class to introspect + */ public StandardClassMetadata(Class introspectedClass) { + Assert.notNull(introspectedClass, "Class must not be null"); this.introspectedClass = introspectedClass; } + /** + * Return the underlying Class. + */ public final Class getIntrospectedClass() { return this.introspectedClass; } public String getClassName() { - return getIntrospectedClass().getName(); + return this.introspectedClass.getName(); } public boolean isInterface() { - return getIntrospectedClass().isInterface(); + return this.introspectedClass.isInterface(); } public boolean isAbstract() { - return Modifier.isAbstract(getIntrospectedClass().getModifiers()); + return Modifier.isAbstract(this.introspectedClass.getModifiers()); } public boolean isConcrete() { return !(isInterface() || isAbstract()); } + public boolean isFinal() { + return Modifier.isFinal(this.introspectedClass.getModifiers()); + } + public boolean isIndependent() { return (!hasEnclosingClass() || - (getIntrospectedClass().getDeclaringClass() != null && - Modifier.isStatic(getIntrospectedClass().getModifiers()))); + (this.introspectedClass.getDeclaringClass() != null && + Modifier.isStatic(this.introspectedClass.getModifiers()))); } public boolean hasEnclosingClass() { - return (getIntrospectedClass().getEnclosingClass() != null); + return (this.introspectedClass.getEnclosingClass() != null); } public String getEnclosingClassName() { - Class enclosingClass = getIntrospectedClass().getEnclosingClass(); + Class enclosingClass = this.introspectedClass.getEnclosingClass(); return (enclosingClass != null ? enclosingClass.getName() : null); } public boolean hasSuperClass() { - return (getIntrospectedClass().getSuperclass() != null); + return (this.introspectedClass.getSuperclass() != null); } public String getSuperClassName() { - Class superClass = getIntrospectedClass().getSuperclass(); + Class superClass = this.introspectedClass.getSuperclass(); return (superClass != null ? superClass.getName() : null); } public String[] getInterfaceNames() { - Class[] ifcs = getIntrospectedClass().getInterfaces(); + Class[] ifcs = this.introspectedClass.getInterfaces(); String[] ifcNames = new String[ifcs.length]; for (int i = 0; i < ifcs.length; i++) { ifcNames[i] = ifcs[i].getName(); diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java b/org.springframework.core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java index 4a005effd46..144973c3d47 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java +++ b/org.springframework.core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java @@ -17,7 +17,6 @@ package org.springframework.core.type; import java.lang.annotation.Annotation; -import java.lang.reflect.AccessibleObject; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.HashSet; @@ -28,52 +27,62 @@ import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.Assert; /** + * {@link MethodMetadata} implementation that uses standard reflection + * to introspect a given Method. + * * @author Mark Pollack + * @author Juergen Hoeller * @since 3.0 */ public class StandardMethodMetadata implements MethodMetadata { private final Method introspectedMethod; - - public StandardMethodMetadata(Method method) { - Assert.notNull(method, "Method must not be null"); - introspectedMethod = method; + + + /** + * Create a new StandardMethodMetadata wrapper for the given Method. + * @param introspectedMethod the Method to introspect + */ + public StandardMethodMetadata(Method introspectedMethod) { + Assert.notNull(introspectedMethod, "Method must not be null"); + this.introspectedMethod = introspectedMethod; } - + + /** + * Return the underlying Method. + */ public final Method getIntrospectedMethod() { return this.introspectedMethod; } - - public Map getAnnotationAttributes(String annotationType) { - Annotation[] anns = getIntrospectedMethod().getAnnotations(); - for (Annotation ann : anns) { - if (ann.annotationType().getName().equals(annotationType)) { - return AnnotationUtils.getAnnotationAttributes(ann); - } - } - return null; + + public String getMethodName() { + return this.introspectedMethod.getName(); + } + + public boolean isStatic() { + return Modifier.isStatic(this.introspectedMethod.getModifiers()); + } + + public boolean isFinal() { + return Modifier.isFinal(this.introspectedMethod.getModifiers()); + } + + public boolean isOverridable() { + return (!isStatic() && !isFinal() && !Modifier.isPrivate(this.introspectedMethod.getModifiers())); } public Set getAnnotationTypes() { Set types = new HashSet(); - Annotation[] anns = getIntrospectedMethod().getAnnotations(); + Annotation[] anns = this.introspectedMethod.getAnnotations(); for (Annotation ann : anns) { types.add(ann.annotationType().getName()); } return types; } - public String getMethodName() { - return introspectedMethod.getName(); - } - - public int getModifiers() { - return introspectedMethod.getModifiers(); - } - public boolean hasAnnotation(String annotationType) { - Annotation[] anns = getIntrospectedMethod().getAnnotations(); + Annotation[] anns = this.introspectedMethod.getAnnotations(); for (Annotation ann : anns) { if (ann.annotationType().getName().equals(annotationType)) { return true; @@ -82,44 +91,14 @@ public class StandardMethodMetadata implements MethodMetadata { return false; } - public boolean isStatic() { - return Modifier.isStatic(getIntrospectedMethod().getModifiers()); - } - - public Set getMetaAnnotationTypes(String annotationType) { - Annotation[] anns = getIntrospectedMethod().getAnnotations(); + public Map getAnnotationAttributes(String annotationType) { + Annotation[] anns = this.introspectedMethod.getAnnotations(); for (Annotation ann : anns) { if (ann.annotationType().getName().equals(annotationType)) { - Set types = new HashSet(); - Annotation[] metaAnns = ann.annotationType().getAnnotations(); - for (Annotation meta : metaAnns) { - types.add(meta.annotationType().getName()); - } - return types; + return AnnotationUtils.getAnnotationAttributes(ann, true); } } return null; } - public boolean hasMetaAnnotation(String metaAnnotationType) { - //TODO can refactor into shared (utility) method with StandardAnnotationMetadata - Annotation[] anns = getIntrospectedMethod().getAnnotations(); - for (Annotation ann : anns) { - Annotation[] metaAnns = ann.annotationType().getAnnotations(); - for (Annotation meta : metaAnns) { - if (meta.annotationType().getName().equals(metaAnnotationType)) { - return true; - } - } - } - return false; - } - - public Set getAnnotationTypesWithMetaAnnotation(String qualifierClassName) { - // TODO Auto-generated method stub - return null; - } - - - } diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java new file mode 100644 index 00000000000..2105b2d4dbc --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java @@ -0,0 +1,150 @@ +/* + * Copyright 2002-2009 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.type.classreading; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import org.springframework.asm.AnnotationVisitor; +import org.springframework.asm.Type; +import org.springframework.asm.commons.EmptyVisitor; +import org.springframework.util.ObjectUtils; +import org.springframework.util.ReflectionUtils; + +/** + * ASM visitor which looks for the annotations defined on a class or method. + * + * @author Juergen Hoeller + * @since 3.0 + */ +final class AnnotationAttributesReadingVisitor implements AnnotationVisitor { + + private final String annotationType; + + private final Map> annotationMap; + + private final Map> metaAnnotationMap; + + private final ClassLoader classLoader; + + private final Map attributes = new LinkedHashMap(); + + + public AnnotationAttributesReadingVisitor( + String annotationType, Map> attributesMap, + Map> metaAnnotationMap, ClassLoader classLoader) { + + this.annotationType = annotationType; + this.annotationMap = attributesMap; + this.metaAnnotationMap = metaAnnotationMap; + this.classLoader = classLoader; + } + + + public void visit(String name, Object value) { + Object valueToUse = value; + if (valueToUse instanceof Type) { + valueToUse = ((Type) value).getClassName(); + } + this.attributes.put(name, valueToUse); + } + + public void visitEnum(String name, String desc, String value) { + Object valueToUse = value; + try { + Class enumType = this.classLoader.loadClass(Type.getType(desc).getClassName()); + Field enumConstant = ReflectionUtils.findField(enumType, value); + if (enumConstant != null) { + valueToUse = enumConstant.get(null); + } + } + catch (Exception ex) { + // Class not found - can't resolve class reference in annotation attribute. + } + this.attributes.put(name, valueToUse); + } + + public AnnotationVisitor visitAnnotation(String name, String desc) { + return new EmptyVisitor(); + } + + public AnnotationVisitor visitArray(final String attrName) { + return new AnnotationVisitor() { + public void visit(String name, Object value) { + Object newValue = value; + if (newValue instanceof Type) { + newValue = ((Type) value).getClassName(); + } + Object existingValue = attributes.get(attrName); + if (existingValue != null) { + newValue = ObjectUtils.addObjectToArray((Object[]) existingValue, newValue); + } + else { + Object[] newArray = (Object[]) Array.newInstance(newValue.getClass(), 1); + newArray[0] = newValue; + newValue = newArray; + } + attributes.put(attrName, newValue); + } + public void visitEnum(String name, String desc, String value) { + } + public AnnotationVisitor visitAnnotation(String name, String desc) { + return new EmptyVisitor(); + } + public AnnotationVisitor visitArray(String name) { + return new EmptyVisitor(); + } + public void visitEnd() { + } + }; + } + + public void visitEnd() { + try { + Class annotationClass = this.classLoader.loadClass(this.annotationType); + // 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)) { + this.attributes.put(attributeName, defaultValue); + } + } + // Register annotations that the annotation type is annotated with. + if (this.metaAnnotationMap != null) { + Annotation[] metaAnnotations = annotationClass.getAnnotations(); + Set metaAnnotationTypeNames = new HashSet(); + for (Annotation metaAnnotation : metaAnnotations) { + metaAnnotationTypeNames.add(metaAnnotation.annotationType().getName()); + } + this.metaAnnotationMap.put(this.annotationType, metaAnnotationTypeNames); + } + } + catch (ClassNotFoundException ex) { + // Class not found - can't determine meta-annotations. + } + this.annotationMap.put(this.annotationType, this.attributes); + } + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java index 30addb57175..1e9967e8e5b 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java @@ -16,12 +16,7 @@ package org.springframework.core.type.classreading; -import java.lang.annotation.Annotation; -import java.lang.reflect.Array; -import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.util.Collection; -import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; @@ -30,11 +25,8 @@ import java.util.Set; import org.springframework.asm.AnnotationVisitor; import org.springframework.asm.MethodVisitor; import org.springframework.asm.Type; -import org.springframework.asm.commons.EmptyVisitor; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.MethodMetadata; -import org.springframework.util.ObjectUtils; -import org.springframework.util.ReflectionUtils; /** * ASM class visitor which looks for the class name and implemented types as @@ -45,9 +37,9 @@ import org.springframework.util.ReflectionUtils; * @author Mark Fisher * @since 2.5 */ -class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor implements AnnotationMetadata { +final class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor implements AnnotationMetadata { - private final Map> attributesMap = new LinkedHashMap>(); + private final Map> annotationMap = new LinkedHashMap>(); private final Map> metaAnnotationMap = new LinkedHashMap>(); @@ -63,109 +55,28 @@ class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor imple @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - MethodMetadataReadingVisitor md = new MethodMetadataReadingVisitor(classLoader, name, access); - methodMetadataSet.add(md); - return md; + MethodMetadataReadingVisitor mm = new MethodMetadataReadingVisitor(name, access, this.classLoader); + this.methodMetadataSet.add(mm); + return mm; } @Override public AnnotationVisitor visitAnnotation(final String desc, boolean visible) { - final String className = Type.getType(desc).getClassName(); - final Map attributes = new LinkedHashMap(); - return new AnnotationVisitor() { - public void visit(String name, Object value) { - // Explicitly defined annotation attribute value. - Object valueToUse = value; - if (value instanceof Type) { - try { - valueToUse = classLoader.loadClass(((Type) value).getClassName()); - } - catch (ClassNotFoundException ex) { - // Class not found - can't resolve class reference in annotation attribute. - } - } - attributes.put(name, valueToUse); - } - public void visitEnum(String name, String desc, String value) { - Object valueToUse = value; - try { - Class enumType = classLoader.loadClass(Type.getType(desc).getClassName()); - Field enumConstant = ReflectionUtils.findField(enumType, value); - if (enumConstant != null) { - valueToUse = enumConstant.get(null); - } - } - catch (Exception ex) { - // Class not found - can't resolve class reference in annotation attribute. - } - attributes.put(name, valueToUse); - } - public AnnotationVisitor visitAnnotation(String name, String desc) { - return new EmptyVisitor(); - } - public AnnotationVisitor visitArray(final String attrName) { - return new AnnotationVisitor() { - public void visit(String name, Object value) { - Object newValue = value; - Object existingValue = attributes.get(attrName); - if (existingValue != null) { - newValue = ObjectUtils.addObjectToArray((Object[]) existingValue, newValue); - } - else { - Object[] newArray = (Object[]) Array.newInstance(newValue.getClass(), 1); - newArray[0] = newValue; - newValue = newArray; - } - attributes.put(attrName, newValue); - } - public void visitEnum(String name, String desc, String value) { - } - public AnnotationVisitor visitAnnotation(String name, String desc) { - return new EmptyVisitor(); - } - public AnnotationVisitor visitArray(String name) { - return new EmptyVisitor(); - } - public void visitEnd() { - } - }; - } - public void visitEnd() { - try { - Class annotationClass = classLoader.loadClass(className); - // 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 && !attributes.containsKey(attributeName)) { - attributes.put(attributeName, defaultValue); - } - } - // Register annotations that the annotation type is annotated with. - Annotation[] metaAnnotations = annotationClass.getAnnotations(); - Set metaAnnotationTypeNames = new HashSet(); - for (Annotation metaAnnotation : metaAnnotations) { - metaAnnotationTypeNames.add(metaAnnotation.annotationType().getName()); - } - metaAnnotationMap.put(className, metaAnnotationTypeNames); - } - catch (ClassNotFoundException ex) { - // Class not found - can't determine meta-annotations. - } - attributesMap.put(className, attributes); - - } - }; + String className = Type.getType(desc).getClassName(); + return new AnnotationAttributesReadingVisitor(className, this.annotationMap, this.metaAnnotationMap, this.classLoader); } public Set getAnnotationTypes() { - return this.attributesMap.keySet(); + return this.annotationMap.keySet(); } public boolean hasAnnotation(String annotationType) { - return this.attributesMap.containsKey(annotationType); + return this.annotationMap.containsKey(annotationType); + } + + public Map getAnnotationAttributes(String annotationType) { + return this.annotationMap.get(annotationType); } public Set getMetaAnnotationTypes(String annotationType) { @@ -182,17 +93,20 @@ class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor imple return false; } - - public Map getAnnotationAttributes(String annotationType) { - return this.attributesMap.get(annotationType); + public Set getAnnotatedMethods() { + Set annotatedMethods = new LinkedHashSet(); + for (MethodMetadata method : this.methodMetadataSet) { + if (!method.getAnnotationTypes().isEmpty()) { + annotatedMethods.add(method); + } + } + return annotatedMethods; } - public Set getAnnotatedMethods(String annotationType) { Set annotatedMethods = new LinkedHashSet(); - for (MethodMetadata method : methodMetadataSet) { - if (method.hasAnnotation(annotationType)) - { + for (MethodMetadata method : this.methodMetadataSet) { + if (method.hasAnnotation(annotationType)) { annotatedMethods.add(method); } } diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/CachingMetadataReaderFactory.java b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/CachingMetadataReaderFactory.java index 92e06e86752..3112365aaaa 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/CachingMetadataReaderFactory.java +++ b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/CachingMetadataReaderFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * Copyright 2002-2009 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. @@ -33,7 +33,7 @@ import org.springframework.core.io.ResourceLoader; */ public class CachingMetadataReaderFactory extends SimpleMetadataReaderFactory { - private final Map classReaderCache = new HashMap(); + private final Map classReaderCache = new HashMap(); /** @@ -62,9 +62,9 @@ public class CachingMetadataReaderFactory extends SimpleMetadataReaderFactory { @Override - public SimpleMetadataReader getMetadataReader(Resource resource) throws IOException { + public MetadataReader getMetadataReader(Resource resource) throws IOException { synchronized (this.classReaderCache) { - SimpleMetadataReader metadataReader = this.classReaderCache.get(resource); + MetadataReader metadataReader = this.classReaderCache.get(resource); if (metadataReader == null) { metadataReader = super.getMetadataReader(resource); this.classReaderCache.put(resource, metadataReader); diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitor.java b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitor.java index 3766215a1a3..07581904273 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitor.java @@ -45,6 +45,8 @@ class ClassMetadataReadingVisitor implements ClassVisitor, ClassMetadata { private boolean isAbstract; + private boolean isFinal; + private String enclosingClassName; private boolean independentInnerClass; @@ -58,6 +60,7 @@ class ClassMetadataReadingVisitor implements ClassVisitor, ClassMetadata { this.className = ClassUtils.convertResourcePathToClassName(name); this.isInterface = ((access & Opcodes.ACC_INTERFACE) != 0); this.isAbstract = ((access & Opcodes.ACC_ABSTRACT) != 0); + this.isFinal = ((access & Opcodes.ACC_FINAL) != 0); if (supername != null) { this.superClassName = ClassUtils.convertResourcePathToClassName(supername); } @@ -122,6 +125,10 @@ class ClassMetadataReadingVisitor implements ClassVisitor, ClassMetadata { return !(this.isInterface || this.isAbstract); } + public boolean isFinal() { + return this.isFinal; + } + public boolean isIndependent() { return (this.enclosingClassName == null || this.independentInnerClass); } diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/MetadataReader.java b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/MetadataReader.java index 9c602489a72..99b67d4d30a 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/MetadataReader.java +++ b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/MetadataReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * Copyright 2002-2009 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. @@ -16,6 +16,7 @@ package org.springframework.core.type.classreading; +import org.springframework.core.io.Resource; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.ClassMetadata; @@ -28,13 +29,19 @@ import org.springframework.core.type.ClassMetadata; */ public interface MetadataReader { + /** + * Return the resource reference for the class file. + */ + Resource getResource(); + /** * Read basic class metadata for the underlying class. */ ClassMetadata getClassMetadata(); /** - * Read full annotation metadata for the underlying class. + * Read full annotation metadata for the underlying class, + * including metadata for annotated methods. */ AnnotationMetadata getAnnotationMetadata(); diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java index a3f38e9c6b7..4a9718f7fc8 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java @@ -16,13 +16,7 @@ package org.springframework.core.type.classreading; -import java.lang.annotation.Annotation; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.Collection; -import java.util.HashSet; import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; @@ -32,139 +26,68 @@ import org.springframework.asm.Opcodes; import org.springframework.asm.Type; import org.springframework.asm.commons.EmptyVisitor; import org.springframework.core.type.MethodMetadata; -import org.springframework.util.ReflectionUtils; /** + * ASM method visitor which looks for the annotations defined on the method, + * exposing them through the {@link org.springframework.core.type.MethodMetadata} + * interface. + * * @author Mark Pollack + * @author Juergen Hoeller * @since 3.0 */ -class MethodMetadataReadingVisitor extends MethodAdapter implements MethodMetadata { +final class MethodMetadataReadingVisitor extends MethodAdapter implements MethodMetadata { - private ClassLoader classLoader; + private final String name; - private String name; + private final int access; - private int access; + private final ClassLoader classLoader; - private boolean isStatic; - - private final Map> attributesMap = new LinkedHashMap>(); - - private final Map> metaAnnotationMap = new LinkedHashMap>(); + private final Map> annotationMap = new LinkedHashMap>(); - public MethodMetadataReadingVisitor(ClassLoader classLoader, String name, int access) { + public MethodMetadataReadingVisitor(String name, int access, ClassLoader classLoader) { super(new EmptyVisitor()); - this.classLoader = classLoader; this.name = name; this.access = access; - this.isStatic = ((access & Opcodes.ACC_STATIC) != 0); + this.classLoader = classLoader; } - public Map getAnnotationAttributes(String annotationType) { - return this.attributesMap.get(annotationType); + public String getMethodName() { + return this.name; + } + + public boolean isStatic() { + return ((this.access & Opcodes.ACC_STATIC) != 0); + } + + public boolean isFinal() { + return ((this.access & Opcodes.ACC_FINAL) != 0); + } + + public boolean isOverridable() { + return (!isStatic() && !isFinal() && ((this.access & Opcodes.ACC_PRIVATE) == 0)); } public Set getAnnotationTypes() { - return this.attributesMap.keySet(); - } - - public String getMethodName() { - return name; - } - - public int getModifiers() { - return access; + return this.annotationMap.keySet(); } public boolean hasAnnotation(String annotationType) { - return this.attributesMap.containsKey(annotationType); + return this.annotationMap.containsKey(annotationType); } - public Set getMetaAnnotationTypes(String annotationType) { - return this.metaAnnotationMap.get(annotationType); - } - - public boolean hasMetaAnnotation(String metaAnnotationType) { - Collection> allMetaTypes = this.metaAnnotationMap.values(); - for (Set metaTypes : allMetaTypes) { - if (metaTypes.contains(metaAnnotationType)) { - return true; - } - } - return false; - } - - public boolean isStatic() { - return this.isStatic; - } - - public Set getAnnotationTypesWithMetaAnnotation(String metaAnnotationType) { - Set annotationTypes = new LinkedHashSet(); - for (Map.Entry> entry : metaAnnotationMap.entrySet()) { - String attributeType = entry.getKey(); - Set metaAttributes = entry.getValue(); - if (metaAttributes.contains(metaAnnotationType)) { - annotationTypes.add(attributeType); - } - } - return annotationTypes; + public Map getAnnotationAttributes(String annotationType) { + return this.annotationMap.get(annotationType); } @Override public AnnotationVisitor visitAnnotation(final String desc, boolean visible) { - final String className = Type.getType(desc).getClassName(); - final Map attributes = new LinkedHashMap(); - return new EmptyVisitor() { - @Override - public void visit(String name, Object value) { - // Explicitly defined annotation attribute value. - attributes.put(name, value); - } - @Override - public void visitEnum(String name, String desc, String value) { - Object valueToUse = value; - try { - Class enumType = classLoader.loadClass(Type.getType(desc).getClassName()); - Field enumConstant = ReflectionUtils.findField(enumType, value); - if (enumConstant != null) { - valueToUse = enumConstant.get(null); - } - } - catch (Exception ex) { - // Class not found - can't resolve class reference in annotation attribute. - } - attributes.put(name, valueToUse); - } - @Override - public void visitEnd() { - try { - Class annotationClass = classLoader.loadClass(className); - // 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 && !attributes.containsKey(attributeName)) { - attributes.put(attributeName, defaultValue); - } - } - // Register annotations that the annotation type is annotated with. - Annotation[] metaAnnotations = annotationClass.getAnnotations(); - Set metaAnnotationTypeNames = new HashSet(); - for (Annotation metaAnnotation : metaAnnotations) { - metaAnnotationTypeNames.add(metaAnnotation.annotationType().getName()); - } - metaAnnotationMap.put(className, metaAnnotationTypeNames); - } - catch (ClassNotFoundException ex) { - // Class not found - } - attributesMap.put(className, attributes); - } - }; + String className = Type.getType(desc).getClassName(); + return new AnnotationAttributesReadingVisitor(className, this.annotationMap, null, this.classLoader); } } diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java index 3ef52111538..d146d32e6b5 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java +++ b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java @@ -16,7 +16,11 @@ package org.springframework.core.type.classreading; +import java.io.IOException; +import java.io.InputStream; + import org.springframework.asm.ClassReader; +import org.springframework.core.io.Resource; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.ClassMetadata; @@ -30,7 +34,9 @@ import org.springframework.core.type.ClassMetadata; * @author Juergen Hoeller * @since 2.5 */ -public class SimpleMetadataReader implements MetadataReader { +final class SimpleMetadataReader implements MetadataReader { + + private final Resource resource; private final ClassReader classReader; @@ -41,27 +47,23 @@ public class SimpleMetadataReader implements MetadataReader { private AnnotationMetadata annotationMetadata; - SimpleMetadataReader(ClassReader classReader, ClassLoader classLoader) { - this.classReader = classReader; + SimpleMetadataReader(Resource resource, ClassLoader classLoader) throws IOException { + this.resource = resource; + InputStream is = this.resource.getInputStream(); + try { + this.classReader = new ClassReader(is); + } + finally { + is.close(); + } this.classLoader = classLoader; } - /** - * Return the underlying ASM ClassReader. - */ - public final ClassReader getClassReader() { - return this.classReader; + public Resource getResource() { + return this.resource; } - /** - * Return the underlying ClassLoader. - */ - public final ClassLoader getClassLoader() { - return this.classLoader; - } - - public ClassMetadata getClassMetadata() { if (this.classMetadata == null) { ClassMetadataReadingVisitor visitor = new ClassMetadataReadingVisitor(); diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReaderFactory.java b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReaderFactory.java index b7b1d190478..2d8b40b5244 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReaderFactory.java +++ b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReaderFactory.java @@ -17,9 +17,7 @@ package org.springframework.core.type.classreading; import java.io.IOException; -import java.io.InputStream; -import org.springframework.asm.ClassReader; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; @@ -72,20 +70,14 @@ public class SimpleMetadataReaderFactory implements MetadataReaderFactory { } - public SimpleMetadataReader getMetadataReader(String className) throws IOException { + public MetadataReader getMetadataReader(String className) throws IOException { String resourcePath = ResourceLoader.CLASSPATH_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(className) + ClassUtils.CLASS_FILE_SUFFIX; return getMetadataReader(this.resourceLoader.getResource(resourcePath)); } - public SimpleMetadataReader getMetadataReader(Resource resource) throws IOException { - InputStream is = resource.getInputStream(); - try { - return new SimpleMetadataReader(new ClassReader(is), this.resourceLoader.getClassLoader()); - } - finally { - is.close(); - } + public MetadataReader getMetadataReader(Resource resource) throws IOException { + return new SimpleMetadataReader(resource, this.resourceLoader.getClassLoader()); } }