From 94533976d0db9604fb9265d0eed20cedd4a42650 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 20 Oct 2009 18:18:25 +0000 Subject: [PATCH] full compliance with the JSR-330 TCK --- .../AutowiredAnnotationBeanPostProcessor.java | 65 +++---- .../factory/annotation/InjectionMetadata.java | 57 ++---- .../AbstractAutowireCapableBeanFactory.java | 14 +- .../support/BeanDefinitionReaderUtils.java | 8 +- .../AnnotatedBeanDefinitionReader.java | 164 ++++++++++++++++++ .../ClassPathBeanDefinitionScanner.java | 1 - .../CommonAnnotationBeanPostProcessor.java | 46 +++-- .../ConfigurationClassApplicationContext.java | 26 +-- .../annotation/jsr330/SpringAtInjectTck.java | 129 ++++++++++++++ .../org/springframework/util/ClassUtils.java | 17 +- .../springframework/util/ReflectionUtils.java | 33 ++-- ...ersistenceAnnotationBeanPostProcessor.java | 38 ++-- 12 files changed, 434 insertions(+), 164 deletions(-) create mode 100644 org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java create mode 100644 org.springframework.context/src/test/java/org/springframework/context/annotation/jsr330/SpringAtInjectTck.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 2eba7cc85dc..2ebd6ed1c8f 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 @@ -27,6 +27,7 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -270,28 +271,16 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean return (candidateConstructors.length > 0 ? candidateConstructors : null); } - @Override - public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { - InjectionMetadata metadata = findAutowiringMetadata(bean.getClass()); - try { - metadata.injectFields(bean, beanName); - } - catch (Throwable ex) { - throw new BeanCreationException(beanName, "Autowiring of fields failed", ex); - } - return true; - } - @Override public PropertyValues postProcessPropertyValues( PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException { InjectionMetadata metadata = findAutowiringMetadata(bean.getClass()); try { - metadata.injectMethods(bean, beanName, pvs); + metadata.inject(bean, beanName, pvs); } catch (Throwable ex) { - throw new BeanCreationException(beanName, "Autowiring of methods failed", ex); + throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex); } return pvs; } @@ -300,15 +289,16 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean * 'Native' processing method for direct calls with an arbitrary target instance, * resolving all of its fields and methods which are annotated with @Autowired. * @param bean the target instance to process + * @throws BeansException if autowiring failed */ public void processInjection(Object bean) throws BeansException { - InjectionMetadata metadata = findAutowiringMetadata(bean.getClass()); + Class clazz = bean.getClass(); + InjectionMetadata metadata = findAutowiringMetadata(clazz); try { - metadata.injectFields(bean, null); - metadata.injectMethods(bean, null, null); + metadata.inject(bean, null, null); } catch (Throwable ex) { - throw new BeanCreationException("Autowiring of fields/methods failed", ex); + throw new BeanCreationException("Injection of autowired dependencies failed for class [" + clazz + "]", ex); } } @@ -320,36 +310,49 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean synchronized (this.injectionMetadataCache) { metadata = this.injectionMetadataCache.get(clazz); if (metadata == null) { - final InjectionMetadata newMetadata = new InjectionMetadata(clazz); - ReflectionUtils.doWithFields(clazz, new ReflectionUtils.FieldCallback() { - public void doWith(Field field) { + LinkedList elements = new LinkedList(); + Class targetClass = clazz; + + do { + LinkedList currElements = new LinkedList(); + for (Field field : targetClass.getDeclaredFields()) { Annotation annotation = findAutowiredAnnotation(field); if (annotation != null) { if (Modifier.isStatic(field.getModifiers())) { - throw new IllegalStateException("Autowired annotation is not supported on static fields"); + if (logger.isWarnEnabled()) { + logger.warn("Autowired annotation is not supported on static fields: " + field); + } + continue; } boolean required = determineRequiredStatus(annotation); - newMetadata.addInjectedField(new AutowiredFieldElement(field, required)); + currElements.add(new AutowiredFieldElement(field, required)); } } - }); - ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() { - public void doWith(Method method) { + for (Method method : targetClass.getDeclaredMethods()) { 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 (logger.isWarnEnabled()) { + logger.warn("Autowired annotation is not supported on static methods: " + method); + } + continue; } if (method.getParameterTypes().length == 0) { - throw new IllegalStateException("Autowired annotation requires at least one argument: " + method); + if (logger.isWarnEnabled()) { + logger.warn("Autowired annotation should be used on methods with actual parameters: " + method); + } } boolean required = determineRequiredStatus(annotation); PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method); - newMetadata.addInjectedMethod(new AutowiredMethodElement(method, required, pd)); + currElements.add(new AutowiredMethodElement(method, required, pd)); } } - }); - metadata = newMetadata; + elements.addAll(0, currElements); + targetClass = targetClass.getSuperclass(); + } + while (targetClass != null && targetClass != Object.class); + + metadata = new InjectionMetadata(clazz, elements); this.injectionMetadataCache.put(clazz, metadata); } } diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/InjectionMetadata.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/InjectionMetadata.java index 60c2732aa9a..737e3b75859 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/InjectionMetadata.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/InjectionMetadata.java @@ -49,42 +49,21 @@ public class InjectionMetadata { private final Log logger = LogFactory.getLog(InjectionMetadata.class); - private String targetClassName; - - private final Set injectedFields = new LinkedHashSet(); - - private final Set injectedMethods = new LinkedHashSet(); + private final Set injectedElements; - public InjectionMetadata() { - } - - public InjectionMetadata(Class targetClass) { - this.targetClassName = targetClass.getName(); - } - - - public void addInjectedField(InjectedElement element) { - if (logger.isDebugEnabled()) { - logger.debug("Found injected field on class [" + this.targetClassName + "]: " + element); + public InjectionMetadata(Class targetClass, Collection elements) { + this.injectedElements = new LinkedHashSet(); + for (InjectedElement element : elements) { + if (logger.isDebugEnabled()) { + logger.debug("Found injected element on class [" + targetClass.getName() + "]: " + element); + } + this.injectedElements.add(element); } - this.injectedFields.add(element); - } - - public void addInjectedMethod(InjectedElement element) { - if (logger.isDebugEnabled()) { - logger.debug("Found injected method on class [" + this.targetClassName + "]: " + element); - } - this.injectedMethods.add(element); } public void checkConfigMembers(RootBeanDefinition beanDefinition) { - doRegisterConfigMembers(beanDefinition, this.injectedFields); - doRegisterConfigMembers(beanDefinition, this.injectedMethods); - } - - private void doRegisterConfigMembers(RootBeanDefinition beanDefinition, Collection members) { - for (Iterator it = members.iterator(); it.hasNext();) { + for (Iterator it = this.injectedElements.iterator(); it.hasNext();) { Member member = it.next().getMember(); if (!beanDefinition.isExternallyManagedConfigMember(member)) { beanDefinition.registerExternallyManagedConfigMember(member); @@ -95,22 +74,10 @@ public class InjectionMetadata { } } - public void injectFields(Object target, String beanName) throws Throwable { - if (!this.injectedFields.isEmpty()) { + public void inject(Object target, String beanName, PropertyValues pvs) throws Throwable { + if (!this.injectedElements.isEmpty()) { boolean debug = logger.isDebugEnabled(); - for (InjectedElement element : this.injectedFields) { - if (debug) { - logger.debug("Processing injected field of bean '" + beanName + "': " + element); - } - element.inject(target, beanName, null); - } - } - } - - public void injectMethods(Object target, String beanName, PropertyValues pvs) throws Throwable { - if (!this.injectedMethods.isEmpty()) { - boolean debug = logger.isDebugEnabled(); - for (InjectedElement element : this.injectedMethods) { + for (InjectedElement element : this.injectedElements) { if (debug) { logger.debug("Processing injected method of bean '" + beanName + "': " + element); } diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index 6d232c5642a..7728c939f4a 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -780,12 +780,18 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac protected void applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class beanType, String beanName) throws BeansException { - for (BeanPostProcessor bp : getBeanPostProcessors()) { - if (bp instanceof MergedBeanDefinitionPostProcessor) { - MergedBeanDefinitionPostProcessor bdp = (MergedBeanDefinitionPostProcessor) bp; - bdp.postProcessMergedBeanDefinition(mbd, beanType, beanName); + try { + for (BeanPostProcessor bp : getBeanPostProcessors()) { + if (bp instanceof MergedBeanDefinitionPostProcessor) { + MergedBeanDefinitionPostProcessor bdp = (MergedBeanDefinitionPostProcessor) bp; + bdp.postProcessMergedBeanDefinition(mbd, beanType, beanName); + } } } + catch (Exception ex) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "Post-processing failed of bean type [" + beanType + "] failed", ex); + } } /** diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReaderUtils.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReaderUtils.java index da266c1526b..1d80a290cfc 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReaderUtils.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReaderUtils.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. @@ -16,12 +16,10 @@ package org.springframework.beans.factory.support; -import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; -import org.springframework.beans.factory.config.ConstructorArgumentValues; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -152,8 +150,8 @@ public class BeanDefinitionReaderUtils { // Register aliases for bean name, if any. String[] aliases = definitionHolder.getAliases(); if (aliases != null) { - for (int i = 0; i < aliases.length; i++) { - registry.registerAlias(beanName, aliases[i]); + for (String aliase : aliases) { + registry.registerAlias(beanName, aliase); } } } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java new file mode 100644 index 00000000000..6cbc488bdc4 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java @@ -0,0 +1,164 @@ +/* + * 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 org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.support.AutowireCandidateQualifier; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanNameGenerator; + +/** + * Convenient adapter for programmatic registration of annotated bean classes. + * + * + * @author Juergen Hoeller + * @since 3.0 + */ +public class AnnotatedBeanDefinitionReader { + + private final BeanDefinitionRegistry registry; + + private BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator(); + + private ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver(); + + private boolean includeAnnotationConfig = true; + + + public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) { + this.registry = registry; + } + + + /** + * Return the BeanDefinitionRegistry that this scanner operates on. + */ + public final BeanDefinitionRegistry getRegistry() { + return this.registry; + } + + /** + * Set the BeanNameGenerator to use for detected bean classes. + *

Default is a {@link AnnotationBeanNameGenerator}. + */ + public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) { + this.beanNameGenerator = (beanNameGenerator != null ? beanNameGenerator : new AnnotationBeanNameGenerator()); + } + + /** + * Set the ScopeMetadataResolver to use for detected bean classes. + * Note that this will override any custom "scopedProxyMode" setting. + *

The default is an {@link AnnotationScopeMetadataResolver}. + * @see #setScopedProxyMode + */ + public void setScopeMetadataResolver(ScopeMetadataResolver scopeMetadataResolver) { + this.scopeMetadataResolver = scopeMetadataResolver; + } + + /** + * Specify the proxy behavior for non-singleton scoped beans. + * Note that this will override any custom "scopeMetadataResolver" setting. + *

The default is {@link ScopedProxyMode#NO}. + * @see #setScopeMetadataResolver + */ + public void setScopedProxyMode(ScopedProxyMode scopedProxyMode) { + this.scopeMetadataResolver = new AnnotationScopeMetadataResolver(scopedProxyMode); + } + + /** + * Specify whether to register annotation config post-processors. + *

The default is to register the post-processors. Turn this off + * to be able to ignore the annotations or to process them differently. + */ + public void setIncludeAnnotationConfig(boolean includeAnnotationConfig) { + this.includeAnnotationConfig = includeAnnotationConfig; + } + + + public void registerBeans(Class... annotatedClasses) { + for (Class annotatedClass : annotatedClasses) { + registerBean(annotatedClass); + } + } + + public void registerBean(Class annotatedClass) { + registerBean(annotatedClass, null, (Class[]) null); + } + + public void registerBean(Class annotatedClass, Class... qualifiers) { + registerBean(annotatedClass, null, qualifiers); + } + + public void registerBean(Class annotatedClass, String name, Class... qualifiers) { + AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass); + ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd); + abd.setScope(scopeMetadata.getScopeName()); + String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry)); + if (abd.getMetadata().isAnnotated(Primary.class.getName())) { + abd.setPrimary(true); + } + if (abd.getMetadata().isAnnotated(Lazy.class.getName())) { + Boolean value = (Boolean) abd.getMetadata().getAnnotationAttributes(Lazy.class.getName()).get("value"); + abd.setLazyInit(value); + } + if (abd.getMetadata().isAnnotated(DependsOn.class.getName())) { + String[] value = (String[]) abd.getMetadata().getAnnotationAttributes(DependsOn.class.getName()).get("value"); + abd.setDependsOn(value); + } + if (qualifiers != null) { + for (Class qualifier : qualifiers) { + if (Primary.class.equals(qualifier)) { + abd.setPrimary(true); + } + else if (Lazy.class.equals(qualifier)) { + abd.setLazyInit(true); + } + else { + abd.addQualifier(new AutowireCandidateQualifier(qualifier)); + } + } + } + BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName); + definitionHolder = applyScopedProxyMode(definitionHolder, scopeMetadata); + BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry); + + // Register annotation config processors, if necessary. + if (this.includeAnnotationConfig) { + AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); + } + } + + /** + * Apply the specified scope to the given bean definition. + * @param definition the bean definition to configure + * @param metadata the corresponding scope metadata + * @return the final bean definition to use (potentially a proxy) + */ + private BeanDefinitionHolder applyScopedProxyMode(BeanDefinitionHolder definition, ScopeMetadata metadata) { + ScopedProxyMode scopedProxyMode = metadata.getScopedProxyMode(); + if (scopedProxyMode.equals(ScopedProxyMode.NO)) { + return definition; + } + boolean proxyTargetClass = scopedProxyMode.equals(ScopedProxyMode.TARGET_CLASS); + return ScopedProxyCreator.createScopedProxy(definition, this.registry, proxyTargetClass); + } + +} 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 2428e935890..28b38c633ba 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 @@ -315,5 +315,4 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo return ScopedProxyCreator.createScopedProxy(definition, this.registry, proxyTargetClass); } - } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java index 5f2bde0ab26..1e4495d1aff 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java @@ -31,6 +31,7 @@ import java.net.URL; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; +import java.util.LinkedList; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -62,7 +63,6 @@ import org.springframework.core.Ordered; import org.springframework.jndi.support.SimpleJndiBeanFactory; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; -import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; /** @@ -283,13 +283,6 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean } public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { - InjectionMetadata metadata = findResourceMetadata(bean.getClass()); - try { - metadata.injectFields(bean, beanName); - } - catch (Throwable ex) { - throw new BeanCreationException(beanName, "Injection of resource fields failed", ex); - } return true; } @@ -298,10 +291,10 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean InjectionMetadata metadata = findResourceMetadata(bean.getClass()); try { - metadata.injectMethods(bean, beanName, pvs); + metadata.inject(bean, beanName, pvs); } catch (Throwable ex) { - throw new BeanCreationException(beanName, "Injection of resource methods failed", ex); + throw new BeanCreationException(beanName, "Injection of resource dependencies failed", ex); } return pvs; } @@ -314,33 +307,34 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean synchronized (this.injectionMetadataCache) { metadata = this.injectionMetadataCache.get(clazz); if (metadata == null) { - final InjectionMetadata newMetadata = new InjectionMetadata(clazz); - ReflectionUtils.doWithFields(clazz, new ReflectionUtils.FieldCallback() { - public void doWith(Field field) { + LinkedList elements = new LinkedList(); + Class targetClass = clazz; + + do { + LinkedList currElements = new LinkedList(); + for (Field field : targetClass.getDeclaredFields()) { if (webServiceRefClass != null && field.isAnnotationPresent(webServiceRefClass)) { if (Modifier.isStatic(field.getModifiers())) { throw new IllegalStateException("@WebServiceRef annotation is not supported on static fields"); } - newMetadata.addInjectedField(new WebServiceRefElement(field, null)); + currElements.add(new WebServiceRefElement(field, null)); } else if (ejbRefClass != null && field.isAnnotationPresent(ejbRefClass)) { if (Modifier.isStatic(field.getModifiers())) { throw new IllegalStateException("@EJB annotation is not supported on static fields"); } - newMetadata.addInjectedField(new EjbRefElement(field, null)); + currElements.add(new EjbRefElement(field, null)); } else if (field.isAnnotationPresent(Resource.class)) { if (Modifier.isStatic(field.getModifiers())) { throw new IllegalStateException("@Resource annotation is not supported on static fields"); } if (!ignoredResourceTypes.contains(field.getType().getName())) { - newMetadata.addInjectedField(new ResourceElement(field, null)); + currElements.add(new ResourceElement(field, null)); } } } - }); - ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() { - public void doWith(Method method) { + for (Method method : targetClass.getDeclaredMethods()) { if (webServiceRefClass != null && method.isAnnotationPresent(webServiceRefClass) && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) { if (Modifier.isStatic(method.getModifiers())) { @@ -350,7 +344,7 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean throw new IllegalStateException("@WebServiceRef annotation requires a single-arg method: " + method); } PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method); - newMetadata.addInjectedMethod(new WebServiceRefElement(method, pd)); + currElements.add(new WebServiceRefElement(method, pd)); } else if (ejbRefClass != null && method.isAnnotationPresent(ejbRefClass) && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) { @@ -361,7 +355,7 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean throw new IllegalStateException("@EJB annotation requires a single-arg method: " + method); } PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method); - newMetadata.addInjectedMethod(new EjbRefElement(method, pd)); + currElements.add(new EjbRefElement(method, pd)); } else if (method.isAnnotationPresent(Resource.class) && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) { @@ -374,12 +368,16 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean } if (!ignoredResourceTypes.contains(paramTypes[0].getName())) { PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method); - newMetadata.addInjectedMethod(new ResourceElement(method, pd)); + currElements.add(new ResourceElement(method, pd)); } } } - }); - metadata = newMetadata; + elements.addAll(0, currElements); + targetClass = targetClass.getSuperclass(); + } + while (targetClass != null && targetClass != Object.class); + + metadata = new InjectionMetadata(clazz, elements); this.injectionMetadataCache.put(clazz, metadata); } } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassApplicationContext.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassApplicationContext.java index ab1cd69334c..7e6b7e2714a 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassApplicationContext.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassApplicationContext.java @@ -16,19 +16,16 @@ package org.springframework.context.annotation; -import java.io.IOException; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; -import org.springframework.beans.BeansException; +import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.DefaultBeanNameGenerator; import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.context.support.AbstractApplicationContext; +import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.support.AbstractRefreshableApplicationContext; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.Assert; @@ -108,8 +105,7 @@ public class ConfigurationClassApplicationContext extends AbstractRefreshableApp * @see Configuration#value() */ @Override - protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) - throws IOException, BeansException { + protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) { this.delegate.loadBeanDefinitions(beanFactory); } @@ -162,34 +158,27 @@ public class ConfigurationClassApplicationContext extends AbstractRefreshableApp AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory); for (Class configClass : this.configClasses) { - AbstractBeanDefinition def = BeanDefinitionBuilder.rootBeanDefinition(configClass).getBeanDefinition(); - + RootBeanDefinition def = new RootBeanDefinition(configClass); String name = AnnotationUtils.findAnnotation(configClass, Configuration.class).value(); if (!StringUtils.hasLength(name)) { name = new DefaultBeanNameGenerator().generateBeanName(def, beanFactory); } - beanFactory.registerBeanDefinition(name, def); } - - new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory); } /** * @see ConfigurationClassApplicationContext#getBean(Class) */ - @SuppressWarnings("unchecked") - public T getBean(Class requiredType, AbstractApplicationContext context) { + public T getBean(Class requiredType, ListableBeanFactory context) { Assert.notNull(requiredType, "requiredType may not be null"); Assert.notNull(context, "context may not be null"); - - Map beansOfType = context.getBeansOfType(requiredType); - + Map beansOfType = context.getBeansOfType(requiredType); switch (beansOfType.size()) { case 0: throw new NoSuchBeanDefinitionException(requiredType); case 1: - return (T) beansOfType.values().iterator().next(); + return beansOfType.values().iterator().next(); default: throw new NoSuchBeanDefinitionException(requiredType, beansOfType.size() + " matching bean definitions found " + @@ -199,4 +188,5 @@ public class ConfigurationClassApplicationContext extends AbstractRefreshableApp } } } + } diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/jsr330/SpringAtInjectTck.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/jsr330/SpringAtInjectTck.java new file mode 100644 index 00000000000..bfad584d1c6 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/jsr330/SpringAtInjectTck.java @@ -0,0 +1,129 @@ +/* + * 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.jsr330; + +import junit.framework.Test; +import org.atinject.tck.Tck; +import org.atinject.tck.auto.Car; +import org.atinject.tck.auto.Convertible; +import org.atinject.tck.auto.Drivers; +import org.atinject.tck.auto.DriversSeat; +import org.atinject.tck.auto.FuelTank; +import org.atinject.tck.auto.Seat; +import org.atinject.tck.auto.Tire; +import org.atinject.tck.auto.V8Engine; +import org.atinject.tck.auto.accessories.Cupholder; +import org.atinject.tck.auto.accessories.SpareTire; + +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.AutowireCandidateQualifier; +import org.springframework.beans.factory.support.GenericBeanDefinition; +import org.springframework.context.annotation.AnnotatedBeanDefinitionReader; +import org.springframework.context.annotation.AnnotationConfigUtils; +import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.ScopeMetadata; +import org.springframework.context.annotation.ScopeMetadataResolver; +import org.springframework.context.support.GenericApplicationContext; + +/** + * @author Juergen Hoeller + * @since 3.0 + */ +public class SpringAtInjectTck { + + public static Test suite() { + GenericApplicationContext ac = new GenericApplicationContext(); + AnnotatedBeanDefinitionReader bdr = new AnnotatedBeanDefinitionReader(ac); + bdr.setScopeMetadataResolver(new ScopeMetadataResolver() { + public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) { + ScopeMetadata metadata = new ScopeMetadata(); + if (definition instanceof AnnotatedBeanDefinition) { + AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition; + metadata.setScopeName(annDef.getMetadata().hasAnnotation(javax.inject.Singleton.class.getName()) ? + BeanDefinition.SCOPE_SINGLETON : BeanDefinition.SCOPE_PROTOTYPE); + } + return metadata; + } + }); + + bdr.registerBean(Convertible.class); + bdr.registerBean(DriversSeat.class, Drivers.class); + bdr.registerBean(Seat.class, Primary.class); + bdr.registerBean(V8Engine.class); + bdr.registerBean(SpareTire.class, "spare"); + bdr.registerBean(Cupholder.class); + bdr.registerBean(Tire.class, Primary.class); + bdr.registerBean(FuelTank.class); + + ac.refresh(); + Car car = ac.getBean("convertible", Car.class); + + return Tck.testsFor(car, false, true); + } + + public static Test suiteX() { + GenericApplicationContext ac = new GenericApplicationContext(); + + GenericBeanDefinition carDef = new GenericBeanDefinition(); + carDef.setScope(GenericBeanDefinition.SCOPE_PROTOTYPE); + carDef.setBeanClass(Convertible.class); + ac.registerBeanDefinition("car", carDef); + + GenericBeanDefinition driversSeatDef = new GenericBeanDefinition(); + driversSeatDef.setScope(GenericBeanDefinition.SCOPE_PROTOTYPE); + driversSeatDef.setBeanClass(DriversSeat.class); + driversSeatDef.addQualifier(new AutowireCandidateQualifier(Drivers.class)); + ac.registerBeanDefinition("driversSeat", driversSeatDef); + + GenericBeanDefinition seatDef = new GenericBeanDefinition(); + seatDef.setBeanClass(Seat.class); + seatDef.setPrimary(true); + ac.registerBeanDefinition("seat", seatDef); + + GenericBeanDefinition engineDef = new GenericBeanDefinition(); + engineDef.setScope(GenericBeanDefinition.SCOPE_PROTOTYPE); + engineDef.setBeanClass(V8Engine.class); + ac.registerBeanDefinition("engine", engineDef); + + GenericBeanDefinition spareDef = new GenericBeanDefinition(); + spareDef.setScope(GenericBeanDefinition.SCOPE_PROTOTYPE); + spareDef.setBeanClass(SpareTire.class); + spareDef.addQualifier(new AutowireCandidateQualifier(Drivers.class)); + ac.registerBeanDefinition("spare", spareDef); + + GenericBeanDefinition cupholderDef = new GenericBeanDefinition(); + cupholderDef.setBeanClass(Cupholder.class); + ac.registerBeanDefinition("cupholder", cupholderDef); + + GenericBeanDefinition tireDef = new GenericBeanDefinition(); + tireDef.setScope(GenericBeanDefinition.SCOPE_PROTOTYPE); + tireDef.setBeanClass(Tire.class); + tireDef.setPrimary(true); + ac.registerBeanDefinition("tire", tireDef); + + GenericBeanDefinition fuelTankDef = new GenericBeanDefinition(); + fuelTankDef.setBeanClass(FuelTank.class); + ac.registerBeanDefinition("fuelTank", fuelTankDef); + + AnnotationConfigUtils.registerAnnotationConfigProcessors(ac); + ac.refresh(); + Car car = ac.getBean("car", Car.class); + return Tck.testsFor(car, false, true); + } + +} diff --git a/org.springframework.core/src/main/java/org/springframework/util/ClassUtils.java b/org.springframework.core/src/main/java/org/springframework/util/ClassUtils.java index a5af7ae8d19..b0525af65ff 100644 --- a/org.springframework.core/src/main/java/org/springframework/util/ClassUtils.java +++ b/org.springframework.core/src/main/java/org/springframework/util/ClassUtils.java @@ -694,13 +694,28 @@ public abstract class ClassUtils { */ public static Method getMostSpecificMethod(Method method, Class targetClass) { Method specificMethod = null; - if (method != null && !Modifier.isPrivate(method.getModifiers()) && + if (method != null && isOverridable(method, targetClass) && targetClass != null && !targetClass.equals(method.getDeclaringClass())) { specificMethod = ReflectionUtils.findMethod(targetClass, method.getName(), method.getParameterTypes()); } return (specificMethod != null ? specificMethod : method); } + /** + * Determine whether the given method is overridable in the given target class. + * @param method the method to check + * @param targetClass the target class to check against + */ + private static boolean isOverridable(Method method, Class targetClass) { + if (Modifier.isPrivate(method.getModifiers())) { + return false; + } + if (Modifier.isPublic(method.getModifiers()) || Modifier.isProtected(method.getModifiers())) { + return true; + } + return getPackageName(method.getDeclaringClass()).equals(getPackageName(targetClass)); + } + /** * Return a static method of a class. * @param methodName the static method name diff --git a/org.springframework.core/src/main/java/org/springframework/util/ReflectionUtils.java b/org.springframework.core/src/main/java/org/springframework/util/ReflectionUtils.java index 6173671bcff..170c948f940 100644 --- a/org.springframework.core/src/main/java/org/springframework/util/ReflectionUtils.java +++ b/org.springframework.core/src/main/java/org/springframework/util/ReflectionUtils.java @@ -433,12 +433,12 @@ public abstract class ReflectionUtils { * class and superclasses. *

The same named method occurring on subclass and superclass will appear * twice, unless excluded by a {@link MethodFilter}. - * @param targetClass class to start looking at + * @param clazz class to start looking at * @param mc the callback to invoke for each method * @see #doWithMethods(Class, MethodCallback, MethodFilter) */ - public static void doWithMethods(Class targetClass, MethodCallback mc) throws IllegalArgumentException { - doWithMethods(targetClass, mc, null); + public static void doWithMethods(Class clazz, MethodCallback mc) throws IllegalArgumentException { + doWithMethods(clazz, mc, null); } /** @@ -446,14 +446,15 @@ public abstract class ReflectionUtils { * class and superclasses. *

The same named method occurring on subclass and superclass will appear * twice, unless excluded by the specified {@link MethodFilter}. - * @param targetClass class to start looking at + * @param clazz class to start looking at * @param mc the callback to invoke for each method * @param mf the filter that determines the methods to apply the callback to */ - public static void doWithMethods(Class targetClass, MethodCallback mc, MethodFilter mf) + public static void doWithMethods(Class clazz, MethodCallback mc, MethodFilter mf) throws IllegalArgumentException { // Keep backing up the inheritance hierarchy. + Class targetClass = clazz; do { Method[] methods = targetClass.getDeclaredMethods(); for (Method method : methods) { @@ -469,7 +470,8 @@ public abstract class ReflectionUtils { } } targetClass = targetClass.getSuperclass(); - } while (targetClass != null); + } + while (targetClass != null); } /** @@ -489,26 +491,26 @@ public abstract class ReflectionUtils { /** * Invoke the given callback on all fields in the target class, going up the * class hierarchy to get all declared fields. - * @param targetClass the target class to analyze + * @param clazz the target class to analyze * @param fc the callback to invoke for each field */ - public static void doWithFields(Class targetClass, FieldCallback fc) throws IllegalArgumentException { - doWithFields(targetClass, fc, null); + public static void doWithFields(Class clazz, FieldCallback fc) throws IllegalArgumentException { + doWithFields(clazz, fc, null); } /** * Invoke the given callback on all fields in the target class, going up the * class hierarchy to get all declared fields. - * @param targetClass the target class to analyze + * @param clazz the target class to analyze * @param fc the callback to invoke for each field * @param ff the filter that determines the fields to apply the callback to */ - public static void doWithFields(Class targetClass, FieldCallback fc, FieldFilter ff) + public static void doWithFields(Class clazz, FieldCallback fc, FieldFilter ff) throws IllegalArgumentException { // Keep backing up the inheritance hierarchy. + Class targetClass = clazz; do { - // Copy each field declared on this class unless it's static or file. Field[] fields = targetClass.getDeclaredFields(); for (Field field : fields) { // Skip static and final fields. @@ -519,12 +521,13 @@ public abstract class ReflectionUtils { fc.doWith(field); } catch (IllegalAccessException ex) { - throw new IllegalStateException("Shouldn't be illegal to access field '" + field.getName() + "': " - + ex); + throw new IllegalStateException( + "Shouldn't be illegal to access field '" + field.getName() + "': " + ex); } } targetClass = targetClass.getSuperclass(); - } while (targetClass != null && targetClass != Object.class); + } + while (targetClass != null && targetClass != Object.class); } /** diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java index 862c5e1b5ab..6a062fc5a88 100644 --- a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java +++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java @@ -23,6 +23,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.LinkedList; import java.util.Map; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; @@ -59,7 +60,6 @@ import org.springframework.orm.jpa.ExtendedEntityManagerCreator; import org.springframework.orm.jpa.SharedEntityManagerCreator; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; -import org.springframework.util.ReflectionUtils; /** * BeanPostProcessor that processes {@link javax.persistence.PersistenceUnit} @@ -302,13 +302,6 @@ public class PersistenceAnnotationBeanPostProcessor extends JndiLocatorSupport } public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { - InjectionMetadata metadata = findPersistenceMetadata(bean.getClass()); - try { - metadata.injectFields(bean, beanName); - } - catch (Throwable ex) { - throw new BeanCreationException(beanName, "Injection of persistence fields failed", ex); - } return true; } @@ -317,10 +310,10 @@ public class PersistenceAnnotationBeanPostProcessor extends JndiLocatorSupport InjectionMetadata metadata = findPersistenceMetadata(bean.getClass()); try { - metadata.injectMethods(bean, beanName, pvs); + metadata.inject(bean, beanName, pvs); } catch (Throwable ex) { - throw new BeanCreationException(beanName, "Injection of persistence methods failed", ex); + throw new BeanCreationException(beanName, "Injection of persistence dependencies failed", ex); } return pvs; } @@ -346,21 +339,22 @@ public class PersistenceAnnotationBeanPostProcessor extends JndiLocatorSupport synchronized (this.injectionMetadataCache) { metadata = this.injectionMetadataCache.get(clazz); if (metadata == null) { - final InjectionMetadata newMetadata = new InjectionMetadata(clazz); - ReflectionUtils.doWithFields(clazz, new ReflectionUtils.FieldCallback() { - public void doWith(Field field) { + LinkedList elements = new LinkedList(); + Class targetClass = clazz; + + do { + LinkedList currElements = new LinkedList(); + for (Field field : targetClass.getDeclaredFields()) { PersistenceContext pc = field.getAnnotation(PersistenceContext.class); PersistenceUnit pu = field.getAnnotation(PersistenceUnit.class); if (pc != null || pu != null) { if (Modifier.isStatic(field.getModifiers())) { throw new IllegalStateException("Persistence annotations are not supported on static fields"); } - newMetadata.addInjectedField(new PersistenceElement(field, null)); + currElements.add(new PersistenceElement(field, null)); } } - }); - ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() { - public void doWith(Method method) { + for (Method method : targetClass.getDeclaredMethods()) { PersistenceContext pc = method.getAnnotation(PersistenceContext.class); PersistenceUnit pu = method.getAnnotation(PersistenceUnit.class); if (pc != null || pu != null && @@ -372,11 +366,15 @@ public class PersistenceAnnotationBeanPostProcessor extends JndiLocatorSupport throw new IllegalStateException("Persistence annotation requires a single-arg method: " + method); } PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method); - newMetadata.addInjectedMethod(new PersistenceElement(method, pd)); + currElements.add(new PersistenceElement(method, pd)); } } - }); - metadata = newMetadata; + elements.addAll(0, currElements); + targetClass = targetClass.getSuperclass(); + } + while (targetClass != null && targetClass != Object.class); + + metadata = new InjectionMetadata(clazz, elements); this.injectionMetadataCache.put(clazz, metadata); } }