/******************************************************************************* * Copyright (c) 2009 Spring IDE Developers * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Spring IDE Developers - initial API and implementation *******************************************************************************/ package org.springframework.ide.eclipse.beans.core.autowire.internal.provider; import java.beans.PropertyDescriptor; import java.lang.annotation.Annotation; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.core.GenericTypeResolver; import org.springframework.core.MethodParameter; import org.springframework.ide.eclipse.beans.core.autowire.IAutowireDependencyResolver; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; /** * {@link IInjectionMetadataProvider} implementation that autowires annotated fields, setter methods and arbitrary * config methods. Such members to be injected are detected through a Java 5 annotation: by default, Spring's * {@link Autowired} annotation. * <p> * Only one constructor (at max) of any given bean class may carry this annotation with the 'required' parameter set to * <code>true</code>, indicating <i>the</i> constructor to autowire when used as a Spring bean. If multiple * <i>non-required</i> constructors carry the annotation, they will be considered as candidates for autowiring. The * constructor with the greatest number of dependencies that can be satisfied by matching beans in the Spring container * will be chosen. If none of the candidates can be satisfied, then a default constructor (if present) will be used. An * annotated constructor does not have to be public. * <p> * Fields are injected right after construction of a bean, before any config methods are invoked. Such a config field * does not have to be public. * <p> * Config methods may have an arbitrary name and any number of arguments; each of those arguments will be autowired with * a matching bean in the Spring container. Bean property setter methods are effectively just a special case of such a * general config method. Such config methods do not have to be public. * <p> * Also supports JSR-330's {@link javax.inject.Inject} annotation, if available. * <p> * Note: A default AutowiredAnnotationBeanPostProcessor will be registered by the "context:annotation-config" and * "context:component-scan" XML tags. Remove or turn off the default annotation configuration there if you intend to * specify a custom AutowiredAnnotationBeanPostProcessor bean definition. * @author Christian Dupuis * @author Juergen Hoeller * @author Mark Fisher * @since 2.5 * @see #setAutowiredAnnotationType * @see Autowired * @see org.springframework.context.annotation.CommonAnnotationBeanPostProcessor */ public class AutowiredAnnotationInjectionMetadataProvider implements IInjectionMetadataProvider { private final Set<Class<? extends Annotation>> autowiredAnnotationTypes = new LinkedHashSet<Class<? extends Annotation>>(); private String requiredParameterName = "required"; private boolean requiredParameterValue = true; private final Map<Class<?>, InjectionMetadata> injectionMetadataCache = new ConcurrentHashMap<Class<?>, InjectionMetadata>(); private IInjectionMetadataProviderProblemReporter problemReporter = new PassThroughProblemReporter(); /** * Create a new AutowiredAnnotationBeanPostProcessor for Spring's standard {@link Autowired} annotation. * <p> * Also supports JSR-330's {@link javax.inject.Inject} annotation, if available. */ public AutowiredAnnotationInjectionMetadataProvider(ClassLoader cl) { try { this.autowiredAnnotationTypes.add((Class<? extends Annotation>) cl.loadClass(Autowired.class.getName())); this.autowiredAnnotationTypes.add((Class<? extends Annotation>) cl.loadClass(Value.class.getName())); this.autowiredAnnotationTypes.add((Class<? extends Annotation>) cl.loadClass("javax.inject.Inject")); } catch (ClassNotFoundException ex) { // JSR-330 API not available - simply skip. } } /** * Set the internally used {@link IInjectionMetadataProviderProblemReporter}. */ public void setProblemReporter(IInjectionMetadataProviderProblemReporter problemReporter) { this.problemReporter = problemReporter; } /** * Set the 'autowired' annotation type, to be used on constructors, fields, setter methods and arbitrary config * methods. * <p> * The default autowired annotation type is the Spring-provided {@link Autowired} annotation. * <p> * This setter property exists so that developers can provide their own (non-Spring-specific) annotation type to * indicate that a member is supposed to be autowired. */ public void setAutowiredAnnotationType(Class<? extends Annotation> autowiredAnnotationType) { this.autowiredAnnotationTypes.clear(); this.autowiredAnnotationTypes.add(autowiredAnnotationType); } /** * Set the 'autowired' annotation types, to be used on constructors, fields, setter methods and arbitrary config * methods. * <p> * The default autowired annotation type is the Spring-provided {@link Autowired} annotation, as well as * {@link Value} and raw use of the {@link Qualifier} annotation. * <p> * This setter property exists so that developers can provide their own (non-Spring-specific) annotation types to * indicate that a member is supposed to be autowired. */ public void setAutowiredAnnotationTypes(Set<Class<? extends Annotation>> autowiredAnnotationTypes) { this.autowiredAnnotationTypes.clear(); this.autowiredAnnotationTypes.addAll(autowiredAnnotationTypes); } /** * Set the name of a parameter of the annotation that specifies whether it is required. * @see #setRequiredParameterValue(boolean) */ public void setRequiredParameterName(String requiredParameterName) { this.requiredParameterName = requiredParameterName; } /** * Set the boolean value that marks a dependency as required * <p> * For example if using 'required=true' (the default), this value should be <code>true</code>; but if using * 'optional=false', this value should be <code>false</code>. * @see #setRequiredParameterName(String) */ public void setRequiredParameterValue(boolean requiredParameterValue) { this.requiredParameterValue = requiredParameterValue; } /** * {@inheritDoc} */ public InjectionMetadata findAutowiringMetadata(final Class<?> clazz) { // Quick check on the concurrent map first, with minimal locking. InjectionMetadata metadata = this.injectionMetadataCache.get(clazz); if (metadata == null) { synchronized (this.injectionMetadataCache) { metadata = this.injectionMetadataCache.get(clazz); if (metadata == null) { final InjectionMetadata newMetadata = new InjectionMetadata(); ReflectionUtils.doWithFields(clazz, new ReflectionUtils.FieldCallback() { public void doWith(Field field) { Annotation annotation = findAutowiredAnnotation(field); if (annotation != null) { if (Modifier.isStatic(field.getModifiers())) { problemReporter.error("@Autowired annotation is not supported on static fields", field); return; } boolean required = determineRequiredStatus(annotation); newMetadata.addInjectedField(new AutowiredFieldElement(field, required)); } } }); ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() { public void doWith(Method method) { Annotation annotation = findAutowiredAnnotation(method); if (annotation != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) { boolean error = false; if (Modifier.isStatic(method.getModifiers())) { problemReporter.error("@Autowired annotation is not supported on static methods", method); error = true; } if (method.getParameterTypes().length == 0) { problemReporter.error("@Autowired annotation requires at least one argument", method); error = true; } if (!error) { boolean required = determineRequiredStatus(annotation); PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method); newMetadata.addInjectedMethod(new AutowiredMethodElement(clazz, method, required, pd)); } } } }); // add constructor Constructor<?>[] rawCandidates = clazz.getDeclaredConstructors(); List<Constructor<?>> candidates = new ArrayList<Constructor<?>>(rawCandidates.length); Constructor<?> requiredConstructor = null; for (Constructor<?> candidate : rawCandidates) { Annotation annotation = findAutowiredAnnotation(candidate); if (annotation != null) { if (requiredConstructor != null) { problemReporter.error("Invalid @Autowire-marked constructor", candidate); problemReporter.error( "Found another constructor with 'required' @Autowired annotation", requiredConstructor); break; } if (candidate.getParameterTypes().length == 0) { problemReporter .error("@Autowired annotation requires at least one argument", candidate); break; } boolean required = determineRequiredStatus(annotation); if (required) { if (!candidates.isEmpty()) { for (Constructor<?> ctor : candidates) { problemReporter.error("Invalid @Autowire-marked constructor", ctor); } problemReporter.error( "Found another constructor with 'required' @Autowired annotation", requiredConstructor); break; } requiredConstructor = candidate; } newMetadata.addInjectedConstructor(new AutowiredConstructorElement(clazz, candidate, required)); candidates.add(candidate); } } metadata = newMetadata; this.injectionMetadataCache.put(clazz, metadata); } } } return metadata; } private Annotation findAutowiredAnnotation(AccessibleObject ao) { for (Class<? extends Annotation> type : this.autowiredAnnotationTypes) { Annotation annotation = ao.getAnnotation(type); if (annotation != null) { return annotation; } } return null; } /** * Determine if the annotated field or method requires its dependency. * <p> * A 'required' dependency means that autowiring should fail when no beans are found. Otherwise, the autowiring * process will simply bypass the field or method when no beans are found. * @param annotation the Autowired annotation * @return whether the annotation indicates that a dependency is required */ protected boolean determineRequiredStatus(Annotation annotation) { try { Method method = ReflectionUtils.findMethod(annotation.annotationType(), this.requiredParameterName); return (this.requiredParameterValue == (Boolean) ReflectionUtils.invokeMethod(method, annotation)); } catch (Exception ex) { // required by default return true; } } /** * Class representing injection information about an annotated field. */ private class AutowiredFieldElement extends InjectionMetadata.InjectedElement { private final boolean required; public AutowiredFieldElement(Field field, boolean required) { super(field, null); this.required = required; } protected DependencyDescriptor[] getDependencyDescriptor(IAutowireDependencyResolver resolver) { return new DependencyDescriptor[] { new DependencyDescriptor((Field) getMember(), this.required) }; } } /** * Class representing injection information about an annotated method. */ private class AutowiredMethodElement extends InjectionMetadata.InjectedElement { private final boolean required; private final Class<?> beanClass; public AutowiredMethodElement(Class<?> beanClass, Method method, boolean required, PropertyDescriptor pd) { super(method, pd); this.required = required; this.beanClass = beanClass; } @Override public DependencyDescriptor[] getDependencyDescriptor(IAutowireDependencyResolver resolver) { Method method = (Method) this.member; Class<?>[] paramTypes = method.getParameterTypes(); DependencyDescriptor[] descriptors = new DependencyDescriptor[paramTypes.length]; for (int i = 0; i < paramTypes.length; i++) { MethodParameter methodParam = new MethodParameter(method, i); GenericTypeResolver.resolveParameterType(methodParam, beanClass); descriptors[i] = new DependencyDescriptor(methodParam, this.required); } return descriptors; } @Override public boolean shouldSkip(BeanDefinition bd) { return checkPropertySkipping(bd.getPropertyValues()); } } private class AutowiredConstructorElement extends InjectionMetadata.InjectedElement { private final boolean required; private final Class<?> beanClass; public AutowiredConstructorElement(Class<?> beanClass, Constructor<?> ctor, boolean required) { super(ctor, null); this.required = required; this.beanClass = beanClass; } @Override public DependencyDescriptor[] getDependencyDescriptor(IAutowireDependencyResolver resolver) { Constructor<?> method = (Constructor<?>) this.member; Class<?>[] paramTypes = method.getParameterTypes(); DependencyDescriptor[] descriptors = new DependencyDescriptor[paramTypes.length]; for (int i = 0; i < paramTypes.length; i++) { MethodParameter methodParam = new MethodParameter(method, i); GenericTypeResolver.resolveParameterType(methodParam, beanClass); descriptors[i] = new DependencyDescriptor(methodParam, this.required); } return descriptors; } } }