From fc9c3009fe7fd570ba3f7446553b1181be1ff3bd Mon Sep 17 00:00:00 2001 From: Mark Pollack Date: Sat, 7 Mar 2009 07:42:25 +0000 Subject: [PATCH] Initial cut of feature to create factory beans using the @FactoryBean annotation within a @Component --- .../AutowiredAnnotationBeanPostProcessor.java | 31 ++-- .../factory/annotation/FactoryMethod.java | 36 +++++ .../beans/factory/annotation/Qualifier.java | 2 +- .../beans/factory/annotation/ScopedProxy.java | 45 ++++++ .../ClassPathBeanDefinitionScanner.java | 26 +++- ...athScanningCandidateComponentProvider.java | 98 ++++++++++++ .../core/type/AnnotationMetadata.java | 13 ++ .../core/type/MethodMetadata.java | 40 +++++ .../core/type/StandardAnnotationMetadata.java | 19 +++ .../core/type/StandardMethodMetadata.java | 110 ++++++++++++++ .../AnnotationMetadataReadingVisitor.java | 38 ++++- .../ClassMetadataReadingVisitor.java | 11 +- .../MethodMetadataReadingVisitor.java | 139 ++++++++++++++++++ .../context/annotation/Scope.java | 2 +- .../core/type/AnnotationMetadataTests.java | 29 +++- 15 files changed, 617 insertions(+), 22 deletions(-) create mode 100644 org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/FactoryMethod.java create mode 100644 org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/ScopedProxy.java create mode 100644 org.springframework.core/src/main/java/org/springframework/core/type/MethodMetadata.java create mode 100644 org.springframework.core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java create mode 100644 org.springframework.core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java index 41bbfce232a..c3c7fd9ee89 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java @@ -54,6 +54,7 @@ import org.springframework.core.GenericTypeResolver; import org.springframework.core.MethodParameter; import org.springframework.core.Ordered; import org.springframework.core.PriorityOrdered; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; @@ -316,19 +317,29 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean }); ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() { public void doWith(Method method) { - Annotation annotation = findAutowiredAnnotation(method); - if (annotation != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) { - if (Modifier.isStatic(method.getModifiers())) { - throw new IllegalStateException("Autowired annotation is not supported on static methods"); + if (!isFactoryMethod(method)) { + Annotation annotation = findAutowiredAnnotation(method); + if (annotation != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) { + if (Modifier.isStatic(method.getModifiers())) { + throw new IllegalStateException("Autowired annotation is not supported on static methods"); + } + if (method.getParameterTypes().length == 0) { + throw new IllegalStateException("Autowired annotation requires at least one argument: " + method); + } + boolean required = determineRequiredStatus(annotation); + PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method); + newMetadata.addInjectedMethod(new AutowiredMethodElement(method, required, pd)); } - if (method.getParameterTypes().length == 0) { - throw new IllegalStateException("Autowired annotation requires at least one argument: " + method); - } - boolean required = determineRequiredStatus(annotation); - PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method); - newMetadata.addInjectedMethod(new AutowiredMethodElement(method, required, pd)); } } + + private boolean isFactoryMethod(Method method) { + if (AnnotationUtils.findAnnotation(method, FactoryMethod.class)!= null) { + return true; + } else { + return false; + } + } }); metadata = newMetadata; this.injectionMetadataCache.put(clazz, metadata); diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/FactoryMethod.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/FactoryMethod.java new file mode 100644 index 00000000000..bf37f999a51 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/FactoryMethod.java @@ -0,0 +1,36 @@ +/* + * 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.beans.factory.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a method as being a factory-method of the class. Use during component scanning + * to create a bean definition that has factory-bean and factory-method metadata + * + * @author Mark Pollack + * @since 3.0 + * @see RequiredAnnotationBeanPostProcessor + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface FactoryMethod { + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/Qualifier.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/Qualifier.java index cc1584c8438..17caeacabf3 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/Qualifier.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/Qualifier.java @@ -33,7 +33,7 @@ import java.lang.annotation.Target; * @since 2.5 */ @Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE}) +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE}) @Inherited @Documented public @interface Qualifier { diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/ScopedProxy.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/ScopedProxy.java new file mode 100644 index 00000000000..1aeb505389a --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/ScopedProxy.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2008 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.beans.factory.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * Marker annotation identical in functionality with <aop:scoped-proxy/> tag. Provides a smart + * proxy backed by a scoped bean, which can be injected into object instances (usually singletons) + * allowing the same reference to be held while delegating method invocations to the backing, scoped + * beans. + * + * @author Costin Leau + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface ScopedProxy { + + /** + * Use CGLib-based class proxies (true) or JDK interface-based (false). + * + * Default is CGLib (true). + * @return + */ + boolean proxyTargetClass() default true; +} diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java index fdadb55ab9d..24b3021016c 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.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. @@ -211,11 +211,33 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo beanDefinitions.add(definitionHolder); registerBeanDefinition(definitionHolder, this.registry); } - } + } } + + + postProcessComponentBeanDefinitions(beanDefinitions); return beanDefinitions; } + protected void postProcessComponentBeanDefinitions(Set beanDefinitions) { + //TODO refactor increment index count as part of naming strategy. + int count = 0; + Set factoryBeanDefinitions = new LinkedHashSet(); + for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitions) { + Set candidates = findCandidateFactoryMethods(beanDefinitionHolder); + for (BeanDefinition candidate : candidates ) { + //TODO refactor to introduce naming strategy and some sanity checks. + String beanName = beanDefinitionHolder.getBeanName() + "$" + candidate.getFactoryMethodName() + "#" + count++; + BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); + factoryBeanDefinitions.add(definitionHolder); + registerBeanDefinition(definitionHolder, this.registry); + } + } + beanDefinitions.addAll(factoryBeanDefinitions); + } + + + /** * Apply further settings to the given bean definition, * beyond the contents retrieved from scanning the component class. diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java index e65ae74e295..0dd7a419351 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java @@ -17,9 +17,11 @@ package org.springframework.context.annotation; import java.io.IOException; + import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; @@ -27,13 +29,18 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.AutowireCandidateQualifier; import org.springframework.context.ResourceLoaderAware; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternUtils; +import org.springframework.core.type.MethodMetadata; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; @@ -67,6 +74,9 @@ public class ClassPathScanningCandidateComponentProvider implements ResourceLoad protected static final String DEFAULT_RESOURCE_PATTERN = "**/*.class"; + protected static final String QUALIFIER_CLASS_NAME = "org.springframework.beans.factory.annotation.Qualifier"; + + protected static final String SCOPE_CLASS_NAME = "org.springframework.context.annotation.Scope"; protected final Log logger = LogFactory.getLog(getClass()); @@ -223,6 +233,94 @@ public class ClassPathScanningCandidateComponentProvider implements ResourceLoad } return candidates; } + + public Set findCandidateFactoryMethods(final BeanDefinitionHolder beanDefinitionHolder) { + Set candidates = new LinkedHashSet(); + AbstractBeanDefinition containingBeanDef = (AbstractBeanDefinition)beanDefinitionHolder.getBeanDefinition(); + Resource resource = containingBeanDef.getResource(); + boolean debugEnabled = logger.isDebugEnabled(); + boolean traceEnabled = logger.isTraceEnabled(); + + try { + if (resource.isReadable()) { + MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource); + Set factoryMethodMetadataSet = metadataReader.getAnnotationMetadata().getAnnotatedMethods("org.springframework.beans.factory.annotation.FactoryMethod"); + for (MethodMetadata methodMetadata : factoryMethodMetadataSet) { + if (isCandidateFactoryMethod(methodMetadata)) { + ScannedGenericBeanDefinition factoryBeanDef = new ScannedGenericBeanDefinition(metadataReader); + + if (!methodMetadata.isStatic()) { + factoryBeanDef.setFactoryBeanName(beanDefinitionHolder.getBeanName()); + } + factoryBeanDef.setFactoryMethodName(methodMetadata.getMethodName()); + + addQualifierToFactoryMethodBeanDefinition(methodMetadata, factoryBeanDef); + addScopeToFactoryMethodBeanDefinition(containingBeanDef, methodMetadata, factoryBeanDef); + + factoryBeanDef.setResource(containingBeanDef.getResource()); + factoryBeanDef.setSource(containingBeanDef.getSource()); + if (debugEnabled) { + logger.debug("Identified candidate factory method in class: " + resource); + } + candidates.add(factoryBeanDef); + } + else { + if (traceEnabled) { + logger.trace("Ignored because not matching any filter: " + resource); + } + } + } + + } + } catch (IOException ex) { + throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex); + } + + return candidates; + } + + + private void addScopeToFactoryMethodBeanDefinition( + AbstractBeanDefinition containingBeanDefinition, + MethodMetadata factoryMethodMetadata, + ScannedGenericBeanDefinition factoryBeanDefinition) { + if (factoryMethodMetadata.hasAnnotation(SCOPE_CLASS_NAME)) { + Map attributes = factoryMethodMetadata.getAnnotationAttributes(SCOPE_CLASS_NAME); + factoryBeanDefinition.setScope(attributes.get("value").toString()); + } else { + factoryBeanDefinition.setScope(containingBeanDefinition.getScope()); + } + } + + + protected void addQualifierToFactoryMethodBeanDefinition(MethodMetadata methodMetadata, + ScannedGenericBeanDefinition beanDef) { + //Add qualifiers to bean definition + if (methodMetadata.hasAnnotation(QUALIFIER_CLASS_NAME)) + { + Map attributes = methodMetadata.getAnnotationAttributes(QUALIFIER_CLASS_NAME); + beanDef.addQualifier(new AutowireCandidateQualifier(Qualifier.class, attributes.get("value"))); + } + + if (methodMetadata.hasMetaAnnotation(QUALIFIER_CLASS_NAME)) + { + //Need the attribute that has a qualifier meta-annotation. + Set annotationTypes = methodMetadata.getAnnotationTypesWithMetaAnnotation(QUALIFIER_CLASS_NAME); + if (annotationTypes.size() == 1) + { + String annotationType = annotationTypes.iterator().next(); + Map attributes = methodMetadata.getAnnotationAttributes(annotationType); + beanDef.addQualifier(new AutowireCandidateQualifier(annotationType, attributes.get("value"))); + } + } + } + + protected boolean isCandidateFactoryMethod(MethodMetadata methodMetadata) { + + //TODO decide if we can support generic wildcard return types, parameter-less method and put in appropriate checks + return true; + } + /** * Resolve the specified base package into a pattern specification for 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 2252cc5ae25..d02062f28a9 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 @@ -70,5 +70,18 @@ public interface AnnotationMetadata extends ClassMetadata { * annotation is defined. */ Map getAnnotationAttributes(String annotationType); + + + // TODO return null would be more consistent with other methods if no match is found + + /** + * Retrieve the method meta-data 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. + */ + Set getAnnotatedMethods(String annotationType); } 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 new file mode 100644 index 00000000000..3a1e85e9909 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/type/MethodMetadata.java @@ -0,0 +1,40 @@ +package org.springframework.core.type; + +import java.util.Map; +import java.util.Set; + +public interface MethodMetadata { + + int getModifiers(); + + boolean isStatic(); + + 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); +} 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 471cd55cb0c..0485a191586 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 @@ -17,7 +17,9 @@ package org.springframework.core.type; import java.lang.annotation.Annotation; +import java.lang.reflect.Method; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; @@ -96,4 +98,21 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements return null; } + public Set getAnnotatedMethods(String annotationType) { + Method[] methods = getIntrospectedClass().getMethods(); + Set annotatedMethods = new LinkedHashSet(); + for (int i = 0; i < methods.length; i++) { + Method method = methods[i]; + Annotation[] methodAnnotations = method.getAnnotations(); + for (int j = 0; j < methodAnnotations.length; j++) { + Annotation ann = methodAnnotations[j]; + if (ann.annotationType().getName().equals(annotationType)) { + MethodMetadata mm = new StandardMethodMetadata(method); + annotatedMethods.add(mm); + } + } + } + return annotatedMethods; + } + } 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 new file mode 100644 index 00000000000..50af7421004 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java @@ -0,0 +1,110 @@ +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; +import java.util.Map; +import java.util.Set; + +import org.springframework.core.annotation.AnnotationUtils; + +public class StandardMethodMetadata implements MethodMetadata { + + private final Method introspectedMethod; + + public StandardMethodMetadata(Method method) { + introspectedMethod = method; + } + + public final Method getIntrospectedMethod() { + return this.introspectedMethod; + } + + + public Map getAnnotationAttributes(String annotationType) { + Annotation[] anns = getIntrospectedMethod().getAnnotations(); + for (int i = 0; i < anns.length; i++) { + Annotation ann = anns[i]; + if (ann.annotationType().getName().equals(annotationType)) { + return AnnotationUtils.getAnnotationAttributes(ann); + } + } + return null; + } + + public Set getAnnotationTypes() { + Set types = new HashSet(); + Annotation[] anns = getIntrospectedMethod().getAnnotations(); + for (int i = 0; i < anns.length; i++) { + types.add(anns[i].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(); + for (int i = 0; i < anns.length; i++) { + if (anns[i].annotationType().getName().equals(annotationType)) { + return true; + } + } + return false; + } + + public boolean isStatic() { + return Modifier.isStatic(getIntrospectedMethod().getModifiers()); + } + + public Set getMetaAnnotationTypes(String annotationType) { + Annotation[] anns = getIntrospectedMethod().getAnnotations(); + for (int i = 0; i < anns.length; i++) { + if (anns[i].annotationType().getName().equals(annotationType)) { + Set types = new HashSet(); + Annotation[] metaAnns = anns[i].annotationType().getAnnotations(); + for (Annotation meta : metaAnns) { + types.add(meta.annotationType().getName()); + } + return types; + } + } + return null; + } + + + public boolean hasMetaAnnotation(String metaAnnotationType) { + + //TODO can refactor into shared (utility) method with StandardAnnotationMetadata + Annotation[] anns = getIntrospectedMethod().getAnnotations(); + for (int i = 0; i < anns.length; i++) { + Annotation[] metaAnns = anns[i].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/AnnotationMetadataReadingVisitor.java b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java index 689bd0da26d..58ddea47772 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 @@ -22,15 +22,18 @@ 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; import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; import org.objectweb.asm.commons.EmptyVisitor; import org.springframework.core.type.AnnotationMetadata; import org.springframework.util.ReflectionUtils; +import org.springframework.core.type.MethodMetadata; /** * ASM class visitor which looks for the class name and implemented types as @@ -46,8 +49,9 @@ class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor imple private final Map> attributesMap = new LinkedHashMap>(); private final Map> metaAnnotationMap = new LinkedHashMap>(); - - + + private final Set methodMetadataSet = new LinkedHashSet(); + private final ClassLoader classLoader; @@ -55,11 +59,23 @@ class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor imple this.classLoader = classLoader; } + + + @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; + } + + @Override public AnnotationVisitor visitAnnotation(final String desc, boolean visible) { final String className = Type.getType(desc).getClassName(); final Map attributes = new LinkedHashMap(); + final Map metaAttributes = new LinkedHashMap(); return new EmptyVisitor() { @Override public void visit(String name, Object value) { @@ -107,7 +123,7 @@ class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor imple Annotation[] metaAnnotations = annotationClass.getAnnotations(); Set metaAnnotationTypeNames = new HashSet(); for (Annotation metaAnnotation : metaAnnotations) { - metaAnnotationTypeNames.add(metaAnnotation.annotationType().getName()); + metaAnnotationTypeNames.add(metaAnnotation.annotationType().getName()); } metaAnnotationMap.put(className, metaAnnotationTypeNames); } @@ -115,6 +131,7 @@ class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor imple // Class not found - can't determine meta-annotations. } attributesMap.put(className, attributes); + } }; } @@ -131,7 +148,7 @@ class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor imple public Set getMetaAnnotationTypes(String annotationType) { return this.metaAnnotationMap.get(annotationType); } - + public boolean hasMetaAnnotation(String metaAnnotationType) { Collection> allMetaTypes = this.metaAnnotationMap.values(); for (Set metaTypes : allMetaTypes) { @@ -142,8 +159,21 @@ class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor imple return false; } + public Map getAnnotationAttributes(String annotationType) { return this.attributesMap.get(annotationType); } + + public Set getAnnotatedMethods(String annotationType) { + Set annotatedMethods = new LinkedHashSet(); + for (MethodMetadata method : methodMetadataSet) { + if (method.hasAnnotation(annotationType)) + { + annotatedMethods.add(method); + } + } + return annotatedMethods; + } + } 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 3dcfbb9a673..68b3fe3d9a5 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 @@ -16,6 +16,7 @@ package org.springframework.core.type.classreading; +import org.objectweb.asm.ClassAdapter; import org.objectweb.asm.Opcodes; import org.objectweb.asm.commons.EmptyVisitor; @@ -33,7 +34,7 @@ import org.springframework.util.ClassUtils; * @author Ramnivas Laddad * @since 2.5 */ -class ClassMetadataReadingVisitor extends EmptyVisitor implements ClassMetadata { +class ClassMetadataReadingVisitor extends ClassAdapter implements ClassMetadata { private String className; @@ -48,8 +49,12 @@ class ClassMetadataReadingVisitor extends EmptyVisitor implements ClassMetadata private String superClassName; private String[] interfaces; - - + + public ClassMetadataReadingVisitor() + { + super(new EmptyVisitor()); + } + @Override public void visit(int version, int access, String name, String signature, String supername, String[] interfaces) { this.className = ClassUtils.convertResourcePathToClassName(name); 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 new file mode 100644 index 00000000000..532b54cf63a --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java @@ -0,0 +1,139 @@ +package org.springframework.core.type.classreading; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.MethodAdapter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.EmptyVisitor; +import org.springframework.core.type.MethodMetadata; + +public class MethodMetadataReadingVisitor extends MethodAdapter implements MethodMetadata { + + private final Map> attributesMap = new LinkedHashMap>(); + + private final Map> metaAnnotationMap = new LinkedHashMap>(); + + private ClassLoader classLoader; + private String name; + private int access; + private boolean isStatic; + + public MethodMetadataReadingVisitor(ClassLoader classLoader, String name, int access) { + super(new EmptyVisitor()); + this.classLoader = classLoader; + this.name = name; + this.access = access; + this.isStatic = ((access & Opcodes.ACC_STATIC) != 0); + } + + public Map getAnnotationAttributes(String annotationType) { + return this.attributesMap.get(annotationType); + } + + public Set getAnnotationTypes() { + return this.attributesMap.keySet(); + } + + public String getMethodName() { + return name; + } + + public int getModifiers() { + return access; + } + + public boolean hasAnnotation(String annotationType) { + return this.attributesMap.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 isStatic; + } + + + public Set getAnnotationTypesWithMetaAnnotation(String metaAnnotationType) { + + ///metaAnnotationMap.put(className, metaAnnotationTypeNames); + Set annotationTypes = new LinkedHashSet(); + Set< Map.Entry> > metaValues = metaAnnotationMap.entrySet(); + Iterator> > metaIterator = metaValues.iterator(); + while (metaIterator.hasNext()) + { + Map.Entry> entry = metaIterator.next(); + String attributeType = entry.getKey(); + Set metaAttributes = entry.getValue(); + if (metaAttributes.contains(metaAnnotationType)) + { + annotationTypes.add(attributeType); + } + } + return annotationTypes; + } + + @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 visitEnd() { + try { + Class annotationClass = classLoader.loadClass(className); + // Check declared default values of attributes in the annotation type. + Method[] annotationAttributes = annotationClass.getMethods(); + for (int i = 0; i < annotationAttributes.length; i++) { + Method annotationAttribute = annotationAttributes[i]; + 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); + } + }; + } + + + +} diff --git a/org.springframework.core/src/test/java/org/springframework/context/annotation/Scope.java b/org.springframework.core/src/test/java/org/springframework/context/annotation/Scope.java index aad3593988e..fad32d5fa67 100644 --- a/org.springframework.core/src/test/java/org/springframework/context/annotation/Scope.java +++ b/org.springframework.core/src/test/java/org/springframework/context/annotation/Scope.java @@ -23,7 +23,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -@Target(ElementType.TYPE) +@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Scope { diff --git a/org.springframework.core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java b/org.springframework.core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java index 0f983bdb8d2..872a38b22a6 100644 --- a/org.springframework.core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java +++ b/org.springframework.core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java @@ -23,9 +23,12 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.Map; +import java.util.Set; import junit.framework.TestCase; - +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Scope; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; @@ -37,15 +40,18 @@ import org.springframework.stereotype.Component; */ public class AnnotationMetadataTests extends TestCase { + public void testStandardAnnotationMetadata() throws IOException { StandardAnnotationMetadata annInfo = new StandardAnnotationMetadata(AnnotatedComponent.class); doTestAnnotationInfo(annInfo); + doTestMethodAnnotationInfo(annInfo); } public void testAsmAnnotationMetadata() throws IOException { MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory(); MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(AnnotatedComponent.class.getName()); doTestAnnotationInfo(metadataReader.getAnnotationMetadata()); + doTestMethodAnnotationInfo(metadataReader.getAnnotationMetadata()); } private void doTestAnnotationInfo(AnnotationMetadata metadata) { @@ -77,6 +83,16 @@ public class AnnotationMetadataTests extends TestCase { assertEquals(String.class, specialAttrs.get("clazz")); assertEquals(Thread.State.NEW, specialAttrs.get("state")); } + + private void doTestMethodAnnotationInfo(AnnotationMetadata classMetadata) { + Set methods = classMetadata.getAnnotatedMethods("org.springframework.beans.factory.annotation.Autowired"); + assertEquals(1, methods.size()); + for (MethodMetadata methodMetadata : methods) { + Set annotationTypes = methodMetadata.getAnnotationTypes(); + assertEquals(1, annotationTypes.size()); + } + + } @Target(ElementType.TYPE) @@ -93,6 +109,17 @@ public class AnnotationMetadataTests extends TestCase { @Scope("myScope") @SpecialAttr(clazz = String.class, state = Thread.State.NEW) private static class AnnotatedComponent implements Serializable { + + @Autowired + public void doWork(@Qualifier("myColor") java.awt.Color color) { + + } + @Test + public void doSleep() + { + + } } + }