diff --git a/org.springframework.beans/.classpath b/org.springframework.beans/.classpath index 3ffedda5269..32041b1cea4 100644 --- a/org.springframework.beans/.classpath +++ b/org.springframework.beans/.classpath @@ -8,6 +8,7 @@ + diff --git a/org.springframework.beans/beans.iml b/org.springframework.beans/beans.iml index 3c72a061f01..4a343bb3961 100644 --- a/org.springframework.beans/beans.iml +++ b/org.springframework.beans/beans.iml @@ -1,28 +1,29 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.beans/ivy.xml b/org.springframework.beans/ivy.xml index bbd5a1baa2a..bf9e51672fa 100644 --- a/org.springframework.beans/ivy.xml +++ b/org.springframework.beans/ivy.xml @@ -21,6 +21,7 @@ + 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 d60a75cec78..fdfe35e7c9f 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 @@ -83,6 +83,8 @@ import org.springframework.util.ReflectionUtils; * a special case of such a general config method. Such config methods * do not have to be public. * + *

Also supports JSR-330's {@link javax.inject.Inject} annotation, if available. + * *

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 @@ -100,8 +102,8 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean protected final Log logger = LogFactory.getLog(AutowiredAnnotationBeanPostProcessor.class); - @SuppressWarnings("unchecked") - private Class[] autowiredAnnotationTypes = new Class[] {Autowired.class, Value.class}; + private final Set> autowiredAnnotationTypes = + new LinkedHashSet>(); private String requiredParameterName = "required"; @@ -119,20 +121,24 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean /** - * Set the 'autowired' annotation types, to be used on constructors, fields, - * setter methods and arbitrary config methods. - *

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. - *

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. + * Create a new AutowiredAnnotationBeanPostProcessor + * for Spring's standard {@link Autowired} annotation. + *

Also supports JSR-330's {@link javax.inject.Inject} annotation, if available. */ - public void setAutowiredAnnotationTypes(Class[] autowiredAnnotationTypes) { - Assert.notEmpty(autowiredAnnotationTypes, "'autowiredAnnotationTypes' must not be empty"); - this.autowiredAnnotationTypes = autowiredAnnotationTypes; + @SuppressWarnings("unchecked") + public AutowiredAnnotationBeanPostProcessor() { + this.autowiredAnnotationTypes.add(Autowired.class); + this.autowiredAnnotationTypes.add(Value.class); + ClassLoader cl = AutowiredAnnotationBeanPostProcessor.class.getClassLoader(); + try { + this.autowiredAnnotationTypes.add((Class) cl.loadClass("javax.inject.Inject")); + } + catch (ClassNotFoundException ex) { + // JSR-330 API not available - simply skip. + } } + /** * Set the 'autowired' annotation type, to be used on constructors, fields, * setter methods and arbitrary config methods. @@ -142,10 +148,26 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean * (non-Spring-specific) annotation type to indicate that a member is * supposed to be autowired. */ - @SuppressWarnings("unchecked") public void setAutowiredAnnotationType(Class autowiredAnnotationType) { Assert.notNull(autowiredAnnotationType, "'autowiredAnnotationType' must not be null"); - this.autowiredAnnotationTypes = new Class[] {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. + *

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. + *

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> autowiredAnnotationTypes) { + Assert.notEmpty(autowiredAnnotationTypes, "'autowiredAnnotationTypes' must not be empty"); + this.autowiredAnnotationTypes.clear(); + this.autowiredAnnotationTypes.addAll(autowiredAnnotationTypes); } /** diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java index 72068cdb156..4654f4a46e1 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java @@ -18,7 +18,7 @@ package org.springframework.beans.factory.annotation; 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; @@ -38,9 +38,11 @@ import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; /** - * {@link AutowireCandidateResolver} implementation that matches bean definition - * qualifiers against qualifier annotations on the field or parameter to be autowired. - * Also supports suggested expression values through a value annotation. + * {@link AutowireCandidateResolver} implementation that matches bean definition qualifiers + * against {@link Qualifier qualifier annotations} on the field or parameter to be autowired. + * Also supports suggested expression values through a {@link Value value} annotation. + * + *

Also supports JSR-330's {@link javax.inject.Qualifier} annotation, if available. * * @author Mark Fisher * @author Juergen Hoeller @@ -51,7 +53,7 @@ import org.springframework.util.ObjectUtils; */ public class QualifierAnnotationAutowireCandidateResolver implements AutowireCandidateResolver, BeanFactoryAware { - private final Set> qualifierTypes; + private final Set> qualifierTypes = new LinkedHashSet>(); private Class valueAnnotationType = Value.class; @@ -61,10 +63,18 @@ public class QualifierAnnotationAutowireCandidateResolver implements AutowireCan /** * Create a new QualifierAnnotationAutowireCandidateResolver * for Spring's standard {@link Qualifier} annotation. + *

Also supports JSR-330's {@link javax.inject.Qualifier} annotation, if available. */ + @SuppressWarnings("unchecked") public QualifierAnnotationAutowireCandidateResolver() { - this.qualifierTypes = new HashSet>(1); this.qualifierTypes.add(Qualifier.class); + ClassLoader cl = QualifierAnnotationAutowireCandidateResolver.class.getClassLoader(); + try { + this.qualifierTypes.add((Class) cl.loadClass("javax.inject.Qualifier")); + } + catch (ClassNotFoundException ex) { + // JSR-330 API not available - simply skip. + } } /** @@ -74,7 +84,6 @@ public class QualifierAnnotationAutowireCandidateResolver implements AutowireCan */ public QualifierAnnotationAutowireCandidateResolver(Class qualifierType) { Assert.notNull(qualifierType, "'qualifierType' must not be null"); - this.qualifierTypes = new HashSet>(1); this.qualifierTypes.add(qualifierType); } @@ -85,7 +94,7 @@ public class QualifierAnnotationAutowireCandidateResolver implements AutowireCan */ public QualifierAnnotationAutowireCandidateResolver(Set> qualifierTypes) { Assert.notNull(qualifierTypes, "'qualifierTypes' must not be null"); - this.qualifierTypes = new HashSet>(qualifierTypes); + this.qualifierTypes.addAll(qualifierTypes); } diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateResolver.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateResolver.java index 84aaf252259..0f30540e676 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateResolver.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateResolver.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. @@ -43,6 +43,7 @@ public interface AutowireCandidateResolver { * @param descriptor the descriptor for the target method parameter or field * @return the value suggested (typically an expression String), * or null if none found + * @since 3.0 */ Object getSuggestedValue(DependencyDescriptor descriptor); diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index 52211c2fd20..2ae21e45431 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -33,6 +33,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import javax.inject.Provider; import org.springframework.beans.BeansException; import org.springframework.beans.FatalBeanException; @@ -642,9 +643,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto Set autowiredBeanNames, TypeConverter typeConverter) throws BeansException { descriptor.initParameterNameDiscovery(getParameterNameDiscoverer()); - if (ObjectFactory.class.equals(descriptor.getDependencyType())) { + if (descriptor.getDependencyType().equals(ObjectFactory.class)) { return new DependencyObjectFactory(descriptor, beanName); } + else if (descriptor.getDependencyType().getName().equals("javax.inject.Provider")) { + return new DependencyProviderFactory().createDependencyProvider(descriptor, beanName); + } else { return doResolveDependency(descriptor, descriptor.getDependencyType(), beanName, autowiredBeanNames, typeConverter); } @@ -961,4 +965,30 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto } } + + /** + * Serializable ObjectFactory for lazy resolution of a dependency. + */ + private class DependencyProvider extends DependencyObjectFactory implements Provider { + + public DependencyProvider(DependencyDescriptor descriptor, String beanName) { + super(descriptor, beanName); + } + + public Object get() throws BeansException { + return getObject(); + } + } + + + /** + * Separate inner class for avoiding a hard dependency on the javax.inject API. + */ + private class DependencyProviderFactory { + + public Object createDependencyProvider(DependencyDescriptor descriptor, String beanName) { + return new DependencyProvider(descriptor, beanName); + } + } + } diff --git a/org.springframework.beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java b/org.springframework.beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java index 4e21315ecdc..fba1691903b 100644 --- a/org.springframework.beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java +++ b/org.springframework.beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java @@ -573,6 +573,7 @@ public final class AutowiredAnnotationBeanPostProcessorTests { @Test public void testObjectFactoryQualifierInjection() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(bf); bf.addBeanPostProcessor(bpp); @@ -580,6 +581,7 @@ public final class AutowiredAnnotationBeanPostProcessorTests { RootBeanDefinition bd = new RootBeanDefinition(TestBean.class); bd.addQualifier(new AutowireCandidateQualifier(Qualifier.class, "testBean")); bf.registerBeanDefinition("testBean", bd); + bf.registerBeanDefinition("testBean2", new RootBeanDefinition(TestBean.class)); ObjectFactoryQualifierInjectionBean bean = (ObjectFactoryQualifierInjectionBean) bf.getBean("annotatedBean"); assertSame(bf.getBean("testBean"), bean.getTestBean()); @@ -1282,7 +1284,7 @@ public final class AutowiredAnnotationBeanPostProcessorTests { public static class ObjectFactoryQualifierInjectionBean { @Autowired - //@Qualifier("testBean") + @Qualifier("testBean") private ObjectFactory testBeanFactory; public TestBean getTestBean() { diff --git a/org.springframework.beans/src/test/java/org/springframework/beans/factory/annotation/InjectAnnotationBeanPostProcessorTests.java b/org.springframework.beans/src/test/java/org/springframework/beans/factory/annotation/InjectAnnotationBeanPostProcessorTests.java new file mode 100644 index 00000000000..0bf98607551 --- /dev/null +++ b/org.springframework.beans/src/test/java/org/springframework/beans/factory/annotation/InjectAnnotationBeanPostProcessorTests.java @@ -0,0 +1,867 @@ +/* + * 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.io.Serializable; +import java.util.List; +import java.util.Map; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Provider; + +import static org.junit.Assert.*; +import org.junit.Test; +import test.beans.ITestBean; +import test.beans.IndexedTestBean; +import test.beans.NestedTestBean; +import test.beans.TestBean; + +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.AutowireCandidateQualifier; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.GenericBeanDefinition; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.util.SerializationTestUtils; + +/** + * Unit tests for {@link org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor} + * processing the JSR-303 {@link javax.inject.Inject} annotation. + * + * @author Juergen Hoeller + * @since 3.0 + */ +public class InjectAnnotationBeanPostProcessorTests { + + @Test + public void testIncompleteBeanDefinition() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("testBean", new GenericBeanDefinition()); + try { + bf.getBean("testBean"); + } + catch (BeanCreationException ex) { + assertTrue(ex.getRootCause() instanceof IllegalStateException); + } + } + + @Test + public void testResourceInjection() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + RootBeanDefinition bd = new RootBeanDefinition(ResourceInjectionBean.class); + bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); + bf.registerBeanDefinition("annotatedBean", bd); + TestBean tb = new TestBean(); + bf.registerSingleton("testBean", tb); + + ResourceInjectionBean bean = (ResourceInjectionBean) bf.getBean("annotatedBean"); + assertSame(tb, bean.getTestBean()); + assertSame(tb, bean.getTestBean2()); + + bean = (ResourceInjectionBean) bf.getBean("annotatedBean"); + assertSame(tb, bean.getTestBean()); + assertSame(tb, bean.getTestBean2()); + } + + @Test + public void testExtendedResourceInjection() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.registerResolvableDependency(BeanFactory.class, bf); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + RootBeanDefinition bd = new RootBeanDefinition(TypedExtendedResourceInjectionBean.class); + bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); + bf.registerBeanDefinition("annotatedBean", bd); + TestBean tb = new TestBean(); + bf.registerSingleton("testBean", tb); + NestedTestBean ntb = new NestedTestBean(); + bf.registerSingleton("nestedTestBean", ntb); + + TypedExtendedResourceInjectionBean bean = (TypedExtendedResourceInjectionBean) bf.getBean("annotatedBean"); + assertSame(tb, bean.getTestBean()); + assertSame(tb, bean.getTestBean2()); + assertSame(tb, bean.getTestBean3()); + assertSame(tb, bean.getTestBean4()); + assertSame(ntb, bean.getNestedTestBean()); + assertSame(bf, bean.getBeanFactory()); + + bean = (TypedExtendedResourceInjectionBean) bf.getBean("annotatedBean"); + assertSame(tb, bean.getTestBean()); + assertSame(tb, bean.getTestBean2()); + assertSame(tb, bean.getTestBean3()); + assertSame(tb, bean.getTestBean4()); + assertSame(ntb, bean.getNestedTestBean()); + assertSame(bf, bean.getBeanFactory()); + } + + @Test + public void testExtendedResourceInjectionWithOverriding() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.registerResolvableDependency(BeanFactory.class, bf); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + RootBeanDefinition annotatedBd = new RootBeanDefinition(TypedExtendedResourceInjectionBean.class); + TestBean tb2 = new TestBean(); + annotatedBd.getPropertyValues().addPropertyValue("testBean2", tb2); + bf.registerBeanDefinition("annotatedBean", annotatedBd); + TestBean tb = new TestBean(); + bf.registerSingleton("testBean", tb); + NestedTestBean ntb = new NestedTestBean(); + bf.registerSingleton("nestedTestBean", ntb); + + TypedExtendedResourceInjectionBean bean = (TypedExtendedResourceInjectionBean) bf.getBean("annotatedBean"); + assertSame(tb, bean.getTestBean()); + assertSame(tb2, bean.getTestBean2()); + assertSame(tb, bean.getTestBean3()); + assertSame(tb, bean.getTestBean4()); + assertSame(ntb, bean.getNestedTestBean()); + assertSame(bf, bean.getBeanFactory()); + bf.destroySingletons(); + } + + @Test + public void testExtendedResourceInjectionWithAtRequired() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.registerResolvableDependency(BeanFactory.class, bf); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.addBeanPostProcessor(new RequiredAnnotationBeanPostProcessor()); + RootBeanDefinition bd = new RootBeanDefinition(TypedExtendedResourceInjectionBean.class); + bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); + bf.registerBeanDefinition("annotatedBean", bd); + TestBean tb = new TestBean(); + bf.registerSingleton("testBean", tb); + NestedTestBean ntb = new NestedTestBean(); + bf.registerSingleton("nestedTestBean", ntb); + + TypedExtendedResourceInjectionBean bean = (TypedExtendedResourceInjectionBean) bf.getBean("annotatedBean"); + assertSame(tb, bean.getTestBean()); + assertSame(tb, bean.getTestBean2()); + assertSame(tb, bean.getTestBean3()); + assertSame(tb, bean.getTestBean4()); + assertSame(ntb, bean.getNestedTestBean()); + assertSame(bf, bean.getBeanFactory()); + } + + @Test + public void testConstructorResourceInjection() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.registerResolvableDependency(BeanFactory.class, bf); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + RootBeanDefinition bd = new RootBeanDefinition(ConstructorResourceInjectionBean.class); + bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); + bf.registerBeanDefinition("annotatedBean", bd); + TestBean tb = new TestBean(); + bf.registerSingleton("testBean", tb); + NestedTestBean ntb = new NestedTestBean(); + bf.registerSingleton("nestedTestBean", ntb); + + ConstructorResourceInjectionBean bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean"); + assertSame(tb, bean.getTestBean()); + assertSame(tb, bean.getTestBean2()); + assertSame(tb, bean.getTestBean3()); + assertSame(tb, bean.getTestBean4()); + assertSame(ntb, bean.getNestedTestBean()); + assertSame(bf, bean.getBeanFactory()); + + bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean"); + assertSame(tb, bean.getTestBean()); + assertSame(tb, bean.getTestBean2()); + assertSame(tb, bean.getTestBean3()); + assertSame(tb, bean.getTestBean4()); + assertSame(ntb, bean.getNestedTestBean()); + assertSame(bf, bean.getBeanFactory()); + } + + @Test + public void testConstructorResourceInjectionWithMultipleCandidatesAsCollection() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition( + ConstructorsCollectionResourceInjectionBean.class)); + TestBean tb = new TestBean(); + bf.registerSingleton("testBean", tb); + NestedTestBean ntb1 = new NestedTestBean(); + bf.registerSingleton("nestedTestBean1", ntb1); + NestedTestBean ntb2 = new NestedTestBean(); + bf.registerSingleton("nestedTestBean2", ntb2); + + ConstructorsCollectionResourceInjectionBean bean = (ConstructorsCollectionResourceInjectionBean) bf.getBean("annotatedBean"); + assertNull(bean.getTestBean3()); + assertSame(tb, bean.getTestBean4()); + assertEquals(2, bean.getNestedTestBeans().size()); + assertSame(ntb1, bean.getNestedTestBeans().get(0)); + assertSame(ntb2, bean.getNestedTestBeans().get(1)); + bf.destroySingletons(); + } + + @Test + public void testConstructorResourceInjectionWithMultipleCandidatesAndFallback() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ConstructorsResourceInjectionBean.class)); + TestBean tb = new TestBean(); + bf.registerSingleton("testBean", tb); + + ConstructorsResourceInjectionBean bean = (ConstructorsResourceInjectionBean) bf.getBean("annotatedBean"); + assertSame(tb, bean.getTestBean3()); + assertNull(bean.getTestBean4()); + bf.destroySingletons(); + } + + @Test + public void testConstructorInjectionWithMap() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + RootBeanDefinition bd = new RootBeanDefinition(MapConstructorInjectionBean.class); + bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); + bf.registerBeanDefinition("annotatedBean", bd); + TestBean tb1 = new TestBean(); + TestBean tb2 = new TestBean(); + bf.registerSingleton("testBean1", tb1); + bf.registerSingleton("testBean2", tb1); + + MapConstructorInjectionBean bean = (MapConstructorInjectionBean) bf.getBean("annotatedBean"); + assertEquals(2, bean.getTestBeanMap().size()); + assertTrue(bean.getTestBeanMap().keySet().contains("testBean1")); + assertTrue(bean.getTestBeanMap().keySet().contains("testBean2")); + assertTrue(bean.getTestBeanMap().values().contains(tb1)); + assertTrue(bean.getTestBeanMap().values().contains(tb2)); + + bean = (MapConstructorInjectionBean) bf.getBean("annotatedBean"); + assertEquals(2, bean.getTestBeanMap().size()); + assertTrue(bean.getTestBeanMap().keySet().contains("testBean1")); + assertTrue(bean.getTestBeanMap().keySet().contains("testBean2")); + assertTrue(bean.getTestBeanMap().values().contains(tb1)); + assertTrue(bean.getTestBeanMap().values().contains(tb2)); + } + + @Test + public void testFieldInjectionWithMap() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + RootBeanDefinition bd = new RootBeanDefinition(MapFieldInjectionBean.class); + bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); + bf.registerBeanDefinition("annotatedBean", bd); + TestBean tb1 = new TestBean(); + TestBean tb2 = new TestBean(); + bf.registerSingleton("testBean1", tb1); + bf.registerSingleton("testBean2", tb1); + + MapFieldInjectionBean bean = (MapFieldInjectionBean) bf.getBean("annotatedBean"); + assertEquals(2, bean.getTestBeanMap().size()); + assertTrue(bean.getTestBeanMap().keySet().contains("testBean1")); + assertTrue(bean.getTestBeanMap().keySet().contains("testBean2")); + assertTrue(bean.getTestBeanMap().values().contains(tb1)); + assertTrue(bean.getTestBeanMap().values().contains(tb2)); + + bean = (MapFieldInjectionBean) bf.getBean("annotatedBean"); + assertEquals(2, bean.getTestBeanMap().size()); + assertTrue(bean.getTestBeanMap().keySet().contains("testBean1")); + assertTrue(bean.getTestBeanMap().keySet().contains("testBean2")); + assertTrue(bean.getTestBeanMap().values().contains(tb1)); + assertTrue(bean.getTestBeanMap().values().contains(tb2)); + } + + @Test + public void testMethodInjectionWithMap() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + RootBeanDefinition bd = new RootBeanDefinition(MapMethodInjectionBean.class); + bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); + bf.registerBeanDefinition("annotatedBean", bd); + TestBean tb = new TestBean(); + bf.registerSingleton("testBean", tb); + + MapMethodInjectionBean bean = (MapMethodInjectionBean) bf.getBean("annotatedBean"); + assertEquals(1, bean.getTestBeanMap().size()); + assertTrue(bean.getTestBeanMap().keySet().contains("testBean")); + assertTrue(bean.getTestBeanMap().values().contains(tb)); + assertSame(tb, bean.getTestBean()); + + bean = (MapMethodInjectionBean) bf.getBean("annotatedBean"); + assertEquals(1, bean.getTestBeanMap().size()); + assertTrue(bean.getTestBeanMap().keySet().contains("testBean")); + assertTrue(bean.getTestBeanMap().values().contains(tb)); + assertSame(tb, bean.getTestBean()); + } + + @Test + public void testMethodInjectionWithMapAndMultipleMatches() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class)); + bf.registerBeanDefinition("testBean1", new RootBeanDefinition(TestBean.class)); + bf.registerBeanDefinition("testBean2", new RootBeanDefinition(TestBean.class)); + + try { + bf.getBean("annotatedBean"); + fail("should have failed, more than one bean of type"); + } + catch (BeanCreationException e) { + // expected + } + bf.destroySingletons(); + } + + @Test + public void testMethodInjectionWithMapAndMultipleMatchesButOnlyOneAutowireCandidate() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class)); + bf.registerBeanDefinition("testBean1", new RootBeanDefinition(TestBean.class)); + RootBeanDefinition rbd2 = new RootBeanDefinition(TestBean.class); + rbd2.setAutowireCandidate(false); + bf.registerBeanDefinition("testBean2", rbd2); + + MapMethodInjectionBean bean = (MapMethodInjectionBean) bf.getBean("annotatedBean"); + TestBean tb = (TestBean) bf.getBean("testBean1"); + assertEquals(1, bean.getTestBeanMap().size()); + assertTrue(bean.getTestBeanMap().keySet().contains("testBean1")); + assertTrue(bean.getTestBeanMap().values().contains(tb)); + assertSame(tb, bean.getTestBean()); + bf.destroySingletons(); + } + + @Test + public void testObjectFactoryInjection() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryQualifierInjectionBean.class)); + RootBeanDefinition bd = new RootBeanDefinition(TestBean.class); + bd.addQualifier(new AutowireCandidateQualifier(Qualifier.class, "testBean")); + bf.registerBeanDefinition("testBean", bd); + bf.registerBeanDefinition("testBean2", new RootBeanDefinition(TestBean.class)); + + ObjectFactoryQualifierInjectionBean bean = (ObjectFactoryQualifierInjectionBean) bf.getBean("annotatedBean"); + assertSame(bf.getBean("testBean"), bean.getTestBean()); + bf.destroySingletons(); + } + + @Test + public void testObjectFactoryQualifierInjection() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryQualifierInjectionBean.class)); + RootBeanDefinition bd = new RootBeanDefinition(TestBean.class); + bd.addQualifier(new AutowireCandidateQualifier(Qualifier.class, "testBean")); + bf.registerBeanDefinition("testBean", bd); + + ObjectFactoryQualifierInjectionBean bean = (ObjectFactoryQualifierInjectionBean) bf.getBean("annotatedBean"); + assertSame(bf.getBean("testBean"), bean.getTestBean()); + bf.destroySingletons(); + } + + @Test + public void testObjectFactorySerialization() throws Exception { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryInjectionBean.class)); + bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class)); + bf.setSerializationId("test"); + + ObjectFactoryInjectionBean bean = (ObjectFactoryInjectionBean) bf.getBean("annotatedBean"); + assertSame(bf.getBean("testBean"), bean.getTestBean()); + bean = (ObjectFactoryInjectionBean) SerializationTestUtils.serializeAndDeserialize(bean); + assertSame(bf.getBean("testBean"), bean.getTestBean()); + bf.destroySingletons(); + } + + /** + * Verifies that a dependency on a {@link org.springframework.beans.factory.FactoryBean} can be autowired via + * {@link org.springframework.beans.factory.annotation.Autowired @Inject}, specifically addressing the JIRA issue + * raised in SPR-4040. + */ + @Test + public void testBeanAutowiredWithFactoryBean() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("factoryBeanDependentBean", new RootBeanDefinition(FactoryBeanDependentBean.class)); + bf.registerSingleton("stringFactoryBean", new StringFactoryBean()); + + final StringFactoryBean factoryBean = (StringFactoryBean) bf.getBean("&stringFactoryBean"); + final FactoryBeanDependentBean bean = (FactoryBeanDependentBean) bf.getBean("factoryBeanDependentBean"); + + assertNotNull("The singleton StringFactoryBean should have been registered.", factoryBean); + assertNotNull("The factoryBeanDependentBean should have been registered.", bean); + assertEquals("The FactoryBeanDependentBean should have been autowired 'by type' with the StringFactoryBean.", + factoryBean, bean.getFactoryBean()); + + bf.destroySingletons(); + } + + + public static class ResourceInjectionBean { + + @Inject + private TestBean testBean; + + private TestBean testBean2; + + + @Inject + public void setTestBean2(TestBean testBean2) { + if (this.testBean2 != null) { + throw new IllegalStateException("Already called"); + } + this.testBean2 = testBean2; + } + + public TestBean getTestBean() { + return this.testBean; + } + + public TestBean getTestBean2() { + return this.testBean2; + } + } + + + public static class ExtendedResourceInjectionBean extends ResourceInjectionBean { + + @Inject + protected ITestBean testBean3; + + private T nestedTestBean; + + private ITestBean testBean4; + + private BeanFactory beanFactory; + + public ExtendedResourceInjectionBean() { + } + + @Inject @Required + public void setTestBean2(TestBean testBean2) { + super.setTestBean2(testBean2); + } + + @Inject + private void inject(ITestBean testBean4, T nestedTestBean) { + this.testBean4 = testBean4; + this.nestedTestBean = nestedTestBean; + } + + @Inject + protected void initBeanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + public ITestBean getTestBean3() { + return this.testBean3; + } + + public ITestBean getTestBean4() { + return this.testBean4; + } + + public T getNestedTestBean() { + return this.nestedTestBean; + } + + public BeanFactory getBeanFactory() { + return this.beanFactory; + } + } + + + public static class TypedExtendedResourceInjectionBean extends ExtendedResourceInjectionBean { + + } + + + public static class OptionalResourceInjectionBean extends ResourceInjectionBean { + + @Inject + protected ITestBean testBean3; + + private IndexedTestBean indexedTestBean; + + private NestedTestBean[] nestedTestBeans; + + @Inject + public NestedTestBean[] nestedTestBeansField; + + private ITestBean testBean4; + + @Inject + public void setTestBean2(TestBean testBean2) { + super.setTestBean2(testBean2); + } + + @Inject + private void inject(ITestBean testBean4, NestedTestBean[] nestedTestBeans, IndexedTestBean indexedTestBean) { + this.testBean4 = testBean4; + this.indexedTestBean = indexedTestBean; + this.nestedTestBeans = nestedTestBeans; + } + + public ITestBean getTestBean3() { + return this.testBean3; + } + + public ITestBean getTestBean4() { + return this.testBean4; + } + + public IndexedTestBean getIndexedTestBean() { + return this.indexedTestBean; + } + + public NestedTestBean[] getNestedTestBeans() { + return this.nestedTestBeans; + } + } + + + public static class OptionalCollectionResourceInjectionBean extends ResourceInjectionBean { + + @Inject + protected ITestBean testBean3; + + private IndexedTestBean indexedTestBean; + + private List nestedTestBeans; + + public List nestedTestBeansSetter; + + @Inject + public List nestedTestBeansField; + + private ITestBean testBean4; + + @Inject + public void setTestBean2(TestBean testBean2) { + super.setTestBean2(testBean2); + } + + @Inject + private void inject(ITestBean testBean4, List nestedTestBeans, IndexedTestBean indexedTestBean) { + this.testBean4 = testBean4; + this.indexedTestBean = indexedTestBean; + this.nestedTestBeans = nestedTestBeans; + } + + @Inject + public void setNestedTestBeans(List nestedTestBeans) { + this.nestedTestBeansSetter = nestedTestBeans; + } + + public ITestBean getTestBean3() { + return this.testBean3; + } + + public ITestBean getTestBean4() { + return this.testBean4; + } + + public IndexedTestBean getIndexedTestBean() { + return this.indexedTestBean; + } + + public List getNestedTestBeans() { + return this.nestedTestBeans; + } + } + + + public static class ConstructorResourceInjectionBean extends ResourceInjectionBean { + + @Inject + protected ITestBean testBean3; + + private ITestBean testBean4; + + private NestedTestBean nestedTestBean; + + private ConfigurableListableBeanFactory beanFactory; + + + public ConstructorResourceInjectionBean() { + throw new UnsupportedOperationException(); + } + + public ConstructorResourceInjectionBean(ITestBean testBean3) { + throw new UnsupportedOperationException(); + } + + @Inject + public ConstructorResourceInjectionBean(ITestBean testBean4, NestedTestBean nestedTestBean, + ConfigurableListableBeanFactory beanFactory) { + this.testBean4 = testBean4; + this.nestedTestBean = nestedTestBean; + this.beanFactory = beanFactory; + } + + public ConstructorResourceInjectionBean(NestedTestBean nestedTestBean) { + throw new UnsupportedOperationException(); + } + + public ConstructorResourceInjectionBean(ITestBean testBean3, ITestBean testBean4, NestedTestBean nestedTestBean) { + throw new UnsupportedOperationException(); + } + + @Inject + public void setTestBean2(TestBean testBean2) { + super.setTestBean2(testBean2); + } + + public ITestBean getTestBean3() { + return this.testBean3; + } + + public ITestBean getTestBean4() { + return this.testBean4; + } + + public NestedTestBean getNestedTestBean() { + return this.nestedTestBean; + } + + public ConfigurableListableBeanFactory getBeanFactory() { + return this.beanFactory; + } + } + + + public static class ConstructorsResourceInjectionBean { + + protected ITestBean testBean3; + + private ITestBean testBean4; + + private NestedTestBean[] nestedTestBeans; + + public ConstructorsResourceInjectionBean() { + } + + @Inject + public ConstructorsResourceInjectionBean(ITestBean testBean3) { + this.testBean3 = testBean3; + } + + public ConstructorsResourceInjectionBean(ITestBean testBean4, NestedTestBean[] nestedTestBeans) { + this.testBean4 = testBean4; + this.nestedTestBeans = nestedTestBeans; + } + + public ConstructorsResourceInjectionBean(NestedTestBean nestedTestBean) { + throw new UnsupportedOperationException(); + } + + public ConstructorsResourceInjectionBean(ITestBean testBean3, ITestBean testBean4, NestedTestBean nestedTestBean) { + throw new UnsupportedOperationException(); + } + + public ITestBean getTestBean3() { + return this.testBean3; + } + + public ITestBean getTestBean4() { + return this.testBean4; + } + + public NestedTestBean[] getNestedTestBeans() { + return this.nestedTestBeans; + } + } + + + public static class ConstructorsCollectionResourceInjectionBean { + + protected ITestBean testBean3; + + private ITestBean testBean4; + + private List nestedTestBeans; + + public ConstructorsCollectionResourceInjectionBean() { + } + + public ConstructorsCollectionResourceInjectionBean(ITestBean testBean3) { + this.testBean3 = testBean3; + } + + @Inject + public ConstructorsCollectionResourceInjectionBean(ITestBean testBean4, List nestedTestBeans) { + this.testBean4 = testBean4; + this.nestedTestBeans = nestedTestBeans; + } + + public ConstructorsCollectionResourceInjectionBean(NestedTestBean nestedTestBean) { + throw new UnsupportedOperationException(); + } + + public ConstructorsCollectionResourceInjectionBean(ITestBean testBean3, ITestBean testBean4, + NestedTestBean nestedTestBean) { + throw new UnsupportedOperationException(); + } + + public ITestBean getTestBean3() { + return this.testBean3; + } + + public ITestBean getTestBean4() { + return this.testBean4; + } + + public List getNestedTestBeans() { + return this.nestedTestBeans; + } + } + + + public static class MapConstructorInjectionBean { + + private Map testBeanMap; + + @Inject + public MapConstructorInjectionBean(Map testBeanMap) { + this.testBeanMap = testBeanMap; + } + + public Map getTestBeanMap() { + return this.testBeanMap; + } + } + + + public static class MapFieldInjectionBean { + + @Inject + private Map testBeanMap; + + + public Map getTestBeanMap() { + return this.testBeanMap; + } + } + + + public static class MapMethodInjectionBean { + + private TestBean testBean; + + private Map testBeanMap; + + @Inject + public void setTestBeanMap(TestBean testBean, Map testBeanMap) { + this.testBean = testBean; + this.testBeanMap = testBeanMap; + } + + public TestBean getTestBean() { + return this.testBean; + } + + public Map getTestBeanMap() { + return this.testBeanMap; + } + } + + + public static class ObjectFactoryInjectionBean implements Serializable { + + @Inject + private Provider testBeanFactory; + + public TestBean getTestBean() { + return this.testBeanFactory.get(); + } + } + + + public static class ObjectFactoryQualifierInjectionBean { + + @Inject + @Named("testBean") + private Provider testBeanFactory; + + public TestBean getTestBean() { + return (TestBean) this.testBeanFactory.get(); + } + } + + + /** + * Bean with a dependency on a {@link org.springframework.beans.factory.FactoryBean}. + */ + private static class FactoryBeanDependentBean { + + @Inject + private FactoryBean factoryBean; + + public final FactoryBean getFactoryBean() { + return this.factoryBean; + } + } + + + public static class StringFactoryBean implements FactoryBean { + + public String getObject() throws Exception { + return ""; + } + + public Class getObjectType() { + return String.class; + } + + public boolean isSingleton() { + return true; + } + } + +} diff --git a/org.springframework.beans/template.mf b/org.springframework.beans/template.mf index b7ffe9ccd3a..3078108f9ce 100644 --- a/org.springframework.beans/template.mf +++ b/org.springframework.beans/template.mf @@ -4,6 +4,7 @@ Bundle-Vendor: SpringSource Bundle-ManifestVersion: 2 Import-Template: javax.el.*;version="[1.0.0, 2.0.0)";resolution:=optional, + javax.inject.*;version="[0.9.0, 2.0.0)";resolution:=optional, javax.xml.*;version="0";resolution:=optional, net.sf.cglib.*;version="[2.1.3, 2.2.0)";resolution:=optional, org.apache.commons.logging.*;version="[1.1.1, 2.0.0)", diff --git a/org.springframework.context/.classpath b/org.springframework.context/.classpath index 08d728bbd0a..769d9eed842 100644 --- a/org.springframework.context/.classpath +++ b/org.springframework.context/.classpath @@ -18,6 +18,7 @@ + diff --git a/org.springframework.context/context.iml b/org.springframework.context/context.iml index 6a929d18fa7..ca85d7c64bc 100644 --- a/org.springframework.context/context.iml +++ b/org.springframework.context/context.iml @@ -1,168 +1,169 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.context/ivy.xml b/org.springframework.context/ivy.xml index ee322654a0e..2d5a59eaa06 100644 --- a/org.springframework.context/ivy.xml +++ b/org.springframework.context/ivy.xml @@ -35,9 +35,10 @@ + - + @@ -45,7 +46,6 @@ - @@ -60,6 +60,7 @@ + diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java index 235e7668716..70c45036f05 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.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. @@ -39,6 +39,8 @@ import org.springframework.util.StringUtils; * themselves annotated with * {@link org.springframework.stereotype.Component @Component}. * + *

Also supports JSR-330's {@link javax.inject.Named} annotation, if available. + * *

If the annotation's value doesn't indicate a bean name, an appropriate * name will be built based on the short name of the class (with the first * letter lower-cased). For example: @@ -52,6 +54,7 @@ import org.springframework.util.StringUtils; * @see org.springframework.stereotype.Repository#value() * @see org.springframework.stereotype.Service#value() * @see org.springframework.stereotype.Controller#value() + * @see javax.inject.Named#value() */ public class AnnotationBeanNameGenerator implements BeanNameGenerator { @@ -107,7 +110,8 @@ public class AnnotationBeanNameGenerator implements BeanNameGenerator { Set metaAnnotationTypes, Map attributes) { boolean isStereotype = annotationType.equals(COMPONENT_ANNOTATION_CLASSNAME) || - (metaAnnotationTypes != null && metaAnnotationTypes.contains(COMPONENT_ANNOTATION_CLASSNAME)); + (metaAnnotationTypes != null && metaAnnotationTypes.contains(COMPONENT_ANNOTATION_CLASSNAME)) || + annotationType.equals("javax.inject.Named"); return (isStereotype && attributes != null && attributes.containsKey("value")); } 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 b15210e8e45..2428e935890 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 @@ -199,6 +199,8 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo for (String basePackage : basePackages) { Set candidates = findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) { + ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); + candidate.setScope(scopeMetadata.getScopeName()); String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); @@ -219,8 +221,7 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo } if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); - ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); - definitionHolder = applyScope(definitionHolder, scopeMetadata); + definitionHolder = applyScopedProxyMode(definitionHolder, scopeMetadata); beanDefinitions.add(definitionHolder); registerBeanDefinition(definitionHolder, this.registry); } @@ -301,19 +302,17 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo /** * Apply the specified scope to the given bean definition. - * @param definitionHolder the bean definition to configure - * @param scopeMetadata the corresponding scope metadata + * @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 applyScope(BeanDefinitionHolder definitionHolder, ScopeMetadata scopeMetadata) { - String scope = scopeMetadata.getScopeName(); - ScopedProxyMode scopedProxyMode = scopeMetadata.getScopedProxyMode(); - definitionHolder.getBeanDefinition().setScope(scope); + private BeanDefinitionHolder applyScopedProxyMode(BeanDefinitionHolder definition, ScopeMetadata metadata) { + ScopedProxyMode scopedProxyMode = metadata.getScopedProxyMode(); if (scopedProxyMode.equals(ScopedProxyMode.NO)) { - return definitionHolder; + return definition; } boolean proxyTargetClass = scopedProxyMode.equals(ScopedProxyMode.TARGET_CLASS); - return ScopedProxyCreator.createScopedProxy(definitionHolder, this.registry, proxyTargetClass); + return ScopedProxyCreator.createScopedProxy(definition, this.registry, proxyTargetClass); } 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 4578bab663d..b2bb113ad78 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,6 +17,7 @@ package org.springframework.context.annotation; import java.io.IOException; +import java.lang.annotation.Annotation; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; @@ -164,8 +165,20 @@ public class ClassPathScanningCandidateComponentProvider implements ResourceLoad * {@link Repository @Repository}, {@link Service @Service}, and * {@link Controller @Controller} stereotype annotations. */ + @SuppressWarnings("unchecked") protected void registerDefaultFilters() { this.includeFilters.add(new AnnotationTypeFilter(Component.class)); + this.includeFilters.add(new AnnotationTypeFilter(Scope.class)); + ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader(); + try { + this.includeFilters.add(new AnnotationTypeFilter( + ((Class) cl.loadClass("javax.inject.Named")))); + this.includeFilters.add(new AnnotationTypeFilter( + ((Class) cl.loadClass("javax.inject.Scope")))); + } + catch (ClassNotFoundException ex) { + // JSR-330 API not available - simply skip. + } } diff --git a/org.springframework.context/src/test/java/org/springframework/beans/factory/support/InjectAnnotationAutowireContextTests.java b/org.springframework.context/src/test/java/org/springframework/beans/factory/support/InjectAnnotationAutowireContextTests.java new file mode 100644 index 00000000000..92921dbfb19 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/beans/factory/support/InjectAnnotationAutowireContextTests.java @@ -0,0 +1,691 @@ +/* + * 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.support; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Qualifier; + +import static org.junit.Assert.*; +import org.junit.Test; + +import org.springframework.aop.scope.ScopedProxyUtils; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.UnsatisfiedDependencyException; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.ConstructorArgumentValues; +import org.springframework.context.annotation.AnnotationConfigUtils; +import org.springframework.context.support.GenericApplicationContext; + +/** + * Integration tests for handling JSR-303 {@link javax.inject.Qualifier} annotations. + * + * @author Juergen Hoeller + * @since 3.0 + */ +public class InjectAnnotationAutowireContextTests { + + private static final String JUERGEN = "juergen"; + + private static final String MARK = "mark"; + + + @Test + public void testAutowiredFieldWithSingleNonQualifiedCandidate() { + GenericApplicationContext context = new GenericApplicationContext(); + ConstructorArgumentValues cavs = new ConstructorArgumentValues(); + cavs.addGenericArgumentValue(JUERGEN); + RootBeanDefinition person = new RootBeanDefinition(Person.class, cavs, null); + context.registerBeanDefinition(JUERGEN, person); + context.registerBeanDefinition("autowired", + new RootBeanDefinition(QualifiedFieldTestBean.class)); + AnnotationConfigUtils.registerAnnotationConfigProcessors(context); + try { + context.refresh(); + fail("expected BeanCreationException"); + } + catch (BeanCreationException e) { + assertTrue(e.getRootCause() instanceof NoSuchBeanDefinitionException); + assertEquals("autowired", e.getBeanName()); + } + } + + @Test + public void testAutowiredMethodParameterWithSingleNonQualifiedCandidate() { + GenericApplicationContext context = new GenericApplicationContext(); + ConstructorArgumentValues cavs = new ConstructorArgumentValues(); + cavs.addGenericArgumentValue(JUERGEN); + RootBeanDefinition person = new RootBeanDefinition(Person.class, cavs, null); + context.registerBeanDefinition(JUERGEN, person); + context.registerBeanDefinition("autowired", + new RootBeanDefinition(QualifiedMethodParameterTestBean.class)); + AnnotationConfigUtils.registerAnnotationConfigProcessors(context); + try { + context.refresh(); + fail("expected BeanCreationException"); + } + catch (BeanCreationException e) { + assertTrue(e.getRootCause() instanceof NoSuchBeanDefinitionException); + assertEquals("autowired", e.getBeanName()); + } + } + + @Test + public void testAutowiredConstructorArgumentWithSingleNonQualifiedCandidate() { + GenericApplicationContext context = new GenericApplicationContext(); + ConstructorArgumentValues cavs = new ConstructorArgumentValues(); + cavs.addGenericArgumentValue(JUERGEN); + RootBeanDefinition person = new RootBeanDefinition(Person.class, cavs, null); + context.registerBeanDefinition(JUERGEN, person); + context.registerBeanDefinition("autowired", + new RootBeanDefinition(QualifiedConstructorArgumentTestBean.class)); + AnnotationConfigUtils.registerAnnotationConfigProcessors(context); + try { + context.refresh(); + fail("expected BeanCreationException"); + } + catch (BeanCreationException e) { + assertTrue(e instanceof UnsatisfiedDependencyException); + assertEquals("autowired", e.getBeanName()); + } + } + + @Test + public void testAutowiredFieldWithSingleQualifiedCandidate() { + GenericApplicationContext context = new GenericApplicationContext(); + ConstructorArgumentValues cavs = new ConstructorArgumentValues(); + cavs.addGenericArgumentValue(JUERGEN); + RootBeanDefinition person = new RootBeanDefinition(Person.class, cavs, null); + person.addQualifier(new AutowireCandidateQualifier(TestQualifier.class)); + context.registerBeanDefinition(JUERGEN, person); + context.registerBeanDefinition("autowired", new RootBeanDefinition(QualifiedFieldTestBean.class)); + AnnotationConfigUtils.registerAnnotationConfigProcessors(context); + context.refresh(); + QualifiedFieldTestBean bean = (QualifiedFieldTestBean) context.getBean("autowired"); + assertEquals(JUERGEN, bean.getPerson().getName()); + } + + @Test + public void testAutowiredMethodParameterWithSingleQualifiedCandidate() { + GenericApplicationContext context = new GenericApplicationContext(); + ConstructorArgumentValues cavs = new ConstructorArgumentValues(); + cavs.addGenericArgumentValue(JUERGEN); + RootBeanDefinition person = new RootBeanDefinition(Person.class, cavs, null); + person.addQualifier(new AutowireCandidateQualifier(TestQualifier.class)); + context.registerBeanDefinition(JUERGEN, person); + context.registerBeanDefinition("autowired", + new RootBeanDefinition(QualifiedMethodParameterTestBean.class)); + AnnotationConfigUtils.registerAnnotationConfigProcessors(context); + context.refresh(); + QualifiedMethodParameterTestBean bean = + (QualifiedMethodParameterTestBean) context.getBean("autowired"); + assertEquals(JUERGEN, bean.getPerson().getName()); + } + + @Test + public void testAutowiredMethodParameterWithStaticallyQualifiedCandidate() { + GenericApplicationContext context = new GenericApplicationContext(); + ConstructorArgumentValues cavs = new ConstructorArgumentValues(); + cavs.addGenericArgumentValue(JUERGEN); + RootBeanDefinition person = new RootBeanDefinition(QualifiedPerson.class, cavs, null); + context.registerBeanDefinition(JUERGEN, + ScopedProxyUtils.createScopedProxy(new BeanDefinitionHolder(person, JUERGEN), context, true).getBeanDefinition()); + context.registerBeanDefinition("autowired", + new RootBeanDefinition(QualifiedMethodParameterTestBean.class)); + AnnotationConfigUtils.registerAnnotationConfigProcessors(context); + context.refresh(); + QualifiedMethodParameterTestBean bean = + (QualifiedMethodParameterTestBean) context.getBean("autowired"); + assertEquals(JUERGEN, bean.getPerson().getName()); + } + + @Test + public void testAutowiredMethodParameterWithStaticallyQualifiedCandidateAmongOthers() { + GenericApplicationContext context = new GenericApplicationContext(); + ConstructorArgumentValues cavs = new ConstructorArgumentValues(); + cavs.addGenericArgumentValue(JUERGEN); + RootBeanDefinition person = new RootBeanDefinition(QualifiedPerson.class, cavs, null); + ConstructorArgumentValues cavs2 = new ConstructorArgumentValues(); + cavs2.addGenericArgumentValue(MARK); + RootBeanDefinition person2 = new RootBeanDefinition(Person.class, cavs2, null); + context.registerBeanDefinition(JUERGEN, person); + context.registerBeanDefinition(MARK, person2); + context.registerBeanDefinition("autowired", + new RootBeanDefinition(QualifiedMethodParameterTestBean.class)); + AnnotationConfigUtils.registerAnnotationConfigProcessors(context); + context.refresh(); + QualifiedMethodParameterTestBean bean = + (QualifiedMethodParameterTestBean) context.getBean("autowired"); + assertEquals(JUERGEN, bean.getPerson().getName()); + } + + @Test + public void testAutowiredConstructorArgumentWithSingleQualifiedCandidate() { + GenericApplicationContext context = new GenericApplicationContext(); + ConstructorArgumentValues cavs = new ConstructorArgumentValues(); + cavs.addGenericArgumentValue(JUERGEN); + RootBeanDefinition person = new RootBeanDefinition(Person.class, cavs, null); + person.addQualifier(new AutowireCandidateQualifier(TestQualifier.class)); + context.registerBeanDefinition(JUERGEN, person); + context.registerBeanDefinition("autowired", + new RootBeanDefinition(QualifiedConstructorArgumentTestBean.class)); + AnnotationConfigUtils.registerAnnotationConfigProcessors(context); + context.refresh(); + QualifiedConstructorArgumentTestBean bean = + (QualifiedConstructorArgumentTestBean) context.getBean("autowired"); + assertEquals(JUERGEN, bean.getPerson().getName()); + } + + @Test + public void testAutowiredFieldWithMultipleNonQualifiedCandidates() { + GenericApplicationContext context = new GenericApplicationContext(); + ConstructorArgumentValues cavs1 = new ConstructorArgumentValues(); + cavs1.addGenericArgumentValue(JUERGEN); + RootBeanDefinition person1 = new RootBeanDefinition(Person.class, cavs1, null); + ConstructorArgumentValues cavs2 = new ConstructorArgumentValues(); + cavs2.addGenericArgumentValue(MARK); + RootBeanDefinition person2 = new RootBeanDefinition(Person.class, cavs2, null); + context.registerBeanDefinition(JUERGEN, person1); + context.registerBeanDefinition(MARK, person2); + context.registerBeanDefinition("autowired", + new RootBeanDefinition(QualifiedFieldTestBean.class)); + AnnotationConfigUtils.registerAnnotationConfigProcessors(context); + try { + context.refresh(); + fail("expected BeanCreationException"); + } + catch (BeanCreationException e) { + assertTrue(e.getRootCause() instanceof NoSuchBeanDefinitionException); + assertEquals("autowired", e.getBeanName()); + } + } + + @Test + public void testAutowiredMethodParameterWithMultipleNonQualifiedCandidates() { + GenericApplicationContext context = new GenericApplicationContext(); + ConstructorArgumentValues cavs1 = new ConstructorArgumentValues(); + cavs1.addGenericArgumentValue(JUERGEN); + RootBeanDefinition person1 = new RootBeanDefinition(Person.class, cavs1, null); + ConstructorArgumentValues cavs2 = new ConstructorArgumentValues(); + cavs2.addGenericArgumentValue(MARK); + RootBeanDefinition person2 = new RootBeanDefinition(Person.class, cavs2, null); + context.registerBeanDefinition(JUERGEN, person1); + context.registerBeanDefinition(MARK, person2); + context.registerBeanDefinition("autowired", + new RootBeanDefinition(QualifiedMethodParameterTestBean.class)); + AnnotationConfigUtils.registerAnnotationConfigProcessors(context); + try { + context.refresh(); + fail("expected BeanCreationException"); + } + catch (BeanCreationException e) { + assertTrue(e.getRootCause() instanceof NoSuchBeanDefinitionException); + assertEquals("autowired", e.getBeanName()); + } + } + + @Test + public void testAutowiredConstructorArgumentWithMultipleNonQualifiedCandidates() { + GenericApplicationContext context = new GenericApplicationContext(); + ConstructorArgumentValues cavs1 = new ConstructorArgumentValues(); + cavs1.addGenericArgumentValue(JUERGEN); + RootBeanDefinition person1 = new RootBeanDefinition(Person.class, cavs1, null); + ConstructorArgumentValues cavs2 = new ConstructorArgumentValues(); + cavs2.addGenericArgumentValue(MARK); + RootBeanDefinition person2 = new RootBeanDefinition(Person.class, cavs2, null); + context.registerBeanDefinition(JUERGEN, person1); + context.registerBeanDefinition(MARK, person2); + context.registerBeanDefinition("autowired", + new RootBeanDefinition(QualifiedConstructorArgumentTestBean.class)); + AnnotationConfigUtils.registerAnnotationConfigProcessors(context); + try { + context.refresh(); + fail("expected BeanCreationException"); + } + catch (BeanCreationException e) { + assertTrue(e instanceof UnsatisfiedDependencyException); + assertEquals("autowired", e.getBeanName()); + } + } + + @Test + public void testAutowiredFieldResolvesQualifiedCandidate() { + GenericApplicationContext context = new GenericApplicationContext(); + ConstructorArgumentValues cavs1 = new ConstructorArgumentValues(); + cavs1.addGenericArgumentValue(JUERGEN); + RootBeanDefinition person1 = new RootBeanDefinition(Person.class, cavs1, null); + person1.addQualifier(new AutowireCandidateQualifier(TestQualifier.class)); + ConstructorArgumentValues cavs2 = new ConstructorArgumentValues(); + cavs2.addGenericArgumentValue(MARK); + RootBeanDefinition person2 = new RootBeanDefinition(Person.class, cavs2, null); + context.registerBeanDefinition(JUERGEN, person1); + context.registerBeanDefinition(MARK, person2); + context.registerBeanDefinition("autowired", + new RootBeanDefinition(QualifiedFieldTestBean.class)); + AnnotationConfigUtils.registerAnnotationConfigProcessors(context); + context.refresh(); + QualifiedFieldTestBean bean = (QualifiedFieldTestBean) context.getBean("autowired"); + assertEquals(JUERGEN, bean.getPerson().getName()); + } + + @Test + public void testAutowiredMethodParameterResolvesQualifiedCandidate() { + GenericApplicationContext context = new GenericApplicationContext(); + ConstructorArgumentValues cavs1 = new ConstructorArgumentValues(); + cavs1.addGenericArgumentValue(JUERGEN); + RootBeanDefinition person1 = new RootBeanDefinition(Person.class, cavs1, null); + person1.addQualifier(new AutowireCandidateQualifier(TestQualifier.class)); + ConstructorArgumentValues cavs2 = new ConstructorArgumentValues(); + cavs2.addGenericArgumentValue(MARK); + RootBeanDefinition person2 = new RootBeanDefinition(Person.class, cavs2, null); + context.registerBeanDefinition(JUERGEN, person1); + context.registerBeanDefinition(MARK, person2); + context.registerBeanDefinition("autowired", + new RootBeanDefinition(QualifiedMethodParameterTestBean.class)); + AnnotationConfigUtils.registerAnnotationConfigProcessors(context); + context.refresh(); + QualifiedMethodParameterTestBean bean = + (QualifiedMethodParameterTestBean) context.getBean("autowired"); + assertEquals(JUERGEN, bean.getPerson().getName()); + } + + @Test + public void testAutowiredConstructorArgumentResolvesQualifiedCandidate() { + GenericApplicationContext context = new GenericApplicationContext(); + ConstructorArgumentValues cavs1 = new ConstructorArgumentValues(); + cavs1.addGenericArgumentValue(JUERGEN); + RootBeanDefinition person1 = new RootBeanDefinition(Person.class, cavs1, null); + person1.addQualifier(new AutowireCandidateQualifier(TestQualifier.class)); + ConstructorArgumentValues cavs2 = new ConstructorArgumentValues(); + cavs2.addGenericArgumentValue(MARK); + RootBeanDefinition person2 = new RootBeanDefinition(Person.class, cavs2, null); + context.registerBeanDefinition(JUERGEN, person1); + context.registerBeanDefinition(MARK, person2); + context.registerBeanDefinition("autowired", + new RootBeanDefinition(QualifiedConstructorArgumentTestBean.class)); + AnnotationConfigUtils.registerAnnotationConfigProcessors(context); + context.refresh(); + QualifiedConstructorArgumentTestBean bean = + (QualifiedConstructorArgumentTestBean) context.getBean("autowired"); + assertEquals(JUERGEN, bean.getPerson().getName()); + } + + @Test + public void testAutowiredFieldResolvesQualifiedCandidateWithDefaultValueAndNoValueOnBeanDefinition() { + GenericApplicationContext context = new GenericApplicationContext(); + ConstructorArgumentValues cavs1 = new ConstructorArgumentValues(); + cavs1.addGenericArgumentValue(JUERGEN); + RootBeanDefinition person1 = new RootBeanDefinition(Person.class, cavs1, null); + // qualifier added, but includes no value + person1.addQualifier(new AutowireCandidateQualifier(TestQualifierWithDefaultValue.class)); + ConstructorArgumentValues cavs2 = new ConstructorArgumentValues(); + cavs2.addGenericArgumentValue(MARK); + RootBeanDefinition person2 = new RootBeanDefinition(Person.class, cavs2, null); + context.registerBeanDefinition(JUERGEN, person1); + context.registerBeanDefinition(MARK, person2); + context.registerBeanDefinition("autowired", + new RootBeanDefinition(QualifiedFieldWithDefaultValueTestBean.class)); + AnnotationConfigUtils.registerAnnotationConfigProcessors(context); + context.refresh(); + QualifiedFieldWithDefaultValueTestBean bean = + (QualifiedFieldWithDefaultValueTestBean) context.getBean("autowired"); + assertEquals(JUERGEN, bean.getPerson().getName()); + } + + @Test + public void testAutowiredFieldDoesNotResolveCandidateWithDefaultValueAndConflictingValueOnBeanDefinition() { + GenericApplicationContext context = new GenericApplicationContext(); + ConstructorArgumentValues cavs1 = new ConstructorArgumentValues(); + cavs1.addGenericArgumentValue(JUERGEN); + RootBeanDefinition person1 = new RootBeanDefinition(Person.class, cavs1, null); + // qualifier added, and non-default value specified + person1.addQualifier(new AutowireCandidateQualifier(TestQualifierWithDefaultValue.class, "not the default")); + ConstructorArgumentValues cavs2 = new ConstructorArgumentValues(); + cavs2.addGenericArgumentValue(MARK); + RootBeanDefinition person2 = new RootBeanDefinition(Person.class, cavs2, null); + context.registerBeanDefinition(JUERGEN, person1); + context.registerBeanDefinition(MARK, person2); + context.registerBeanDefinition("autowired", + new RootBeanDefinition(QualifiedFieldWithDefaultValueTestBean.class)); + AnnotationConfigUtils.registerAnnotationConfigProcessors(context); + try { + context.refresh(); + fail("expected BeanCreationException"); + } + catch (BeanCreationException e) { + assertTrue(e.getRootCause() instanceof NoSuchBeanDefinitionException); + assertEquals("autowired", e.getBeanName()); + } + } + + @Test + public void testAutowiredFieldResolvesWithDefaultValueAndExplicitDefaultValueOnBeanDefinition() { + GenericApplicationContext context = new GenericApplicationContext(); + ConstructorArgumentValues cavs1 = new ConstructorArgumentValues(); + cavs1.addGenericArgumentValue(JUERGEN); + RootBeanDefinition person1 = new RootBeanDefinition(Person.class, cavs1, null); + // qualifier added, and value matches the default + person1.addQualifier(new AutowireCandidateQualifier(TestQualifierWithDefaultValue.class, "default")); + ConstructorArgumentValues cavs2 = new ConstructorArgumentValues(); + cavs2.addGenericArgumentValue(MARK); + RootBeanDefinition person2 = new RootBeanDefinition(Person.class, cavs2, null); + context.registerBeanDefinition(JUERGEN, person1); + context.registerBeanDefinition(MARK, person2); + context.registerBeanDefinition("autowired", + new RootBeanDefinition(QualifiedFieldWithDefaultValueTestBean.class)); + AnnotationConfigUtils.registerAnnotationConfigProcessors(context); + context.refresh(); + QualifiedFieldWithDefaultValueTestBean bean = + (QualifiedFieldWithDefaultValueTestBean) context.getBean("autowired"); + assertEquals(JUERGEN, bean.getPerson().getName()); + } + + @Test + public void testAutowiredFieldResolvesWithMultipleQualifierValues() { + GenericApplicationContext context = new GenericApplicationContext(); + ConstructorArgumentValues cavs1 = new ConstructorArgumentValues(); + cavs1.addGenericArgumentValue(JUERGEN); + RootBeanDefinition person1 = new RootBeanDefinition(Person.class, cavs1, null); + AutowireCandidateQualifier qualifier = new AutowireCandidateQualifier(TestQualifierWithMultipleAttributes.class); + qualifier.setAttribute("number", 456); + person1.addQualifier(qualifier); + ConstructorArgumentValues cavs2 = new ConstructorArgumentValues(); + cavs2.addGenericArgumentValue(MARK); + RootBeanDefinition person2 = new RootBeanDefinition(Person.class, cavs2, null); + AutowireCandidateQualifier qualifier2 = new AutowireCandidateQualifier(TestQualifierWithMultipleAttributes.class); + qualifier2.setAttribute("number", 123); + person2.addQualifier(qualifier2); + context.registerBeanDefinition(JUERGEN, person1); + context.registerBeanDefinition(MARK, person2); + context.registerBeanDefinition("autowired", + new RootBeanDefinition(QualifiedFieldWithMultipleAttributesTestBean.class)); + AnnotationConfigUtils.registerAnnotationConfigProcessors(context); + context.refresh(); + QualifiedFieldWithMultipleAttributesTestBean bean = + (QualifiedFieldWithMultipleAttributesTestBean) context.getBean("autowired"); + assertEquals(MARK, bean.getPerson().getName()); + } + + @Test + public void testAutowiredFieldDoesNotResolveWithMultipleQualifierValuesAndConflictingDefaultValue() { + GenericApplicationContext context = new GenericApplicationContext(); + ConstructorArgumentValues cavs1 = new ConstructorArgumentValues(); + cavs1.addGenericArgumentValue(JUERGEN); + RootBeanDefinition person1 = new RootBeanDefinition(Person.class, cavs1, null); + AutowireCandidateQualifier qualifier = new AutowireCandidateQualifier(TestQualifierWithMultipleAttributes.class); + qualifier.setAttribute("number", 456); + person1.addQualifier(qualifier); + ConstructorArgumentValues cavs2 = new ConstructorArgumentValues(); + cavs2.addGenericArgumentValue(MARK); + RootBeanDefinition person2 = new RootBeanDefinition(Person.class, cavs2, null); + AutowireCandidateQualifier qualifier2 = new AutowireCandidateQualifier(TestQualifierWithMultipleAttributes.class); + qualifier2.setAttribute("number", 123); + qualifier2.setAttribute("value", "not the default"); + person2.addQualifier(qualifier2); + context.registerBeanDefinition(JUERGEN, person1); + context.registerBeanDefinition(MARK, person2); + context.registerBeanDefinition("autowired", + new RootBeanDefinition(QualifiedFieldWithMultipleAttributesTestBean.class)); + AnnotationConfigUtils.registerAnnotationConfigProcessors(context); + try { + context.refresh(); + fail("expected BeanCreationException"); + } + catch (BeanCreationException e) { + assertTrue(e.getRootCause() instanceof NoSuchBeanDefinitionException); + assertEquals("autowired", e.getBeanName()); + } + } + + @Test + public void testAutowiredFieldResolvesWithMultipleQualifierValuesAndExplicitDefaultValue() { + GenericApplicationContext context = new GenericApplicationContext(); + ConstructorArgumentValues cavs1 = new ConstructorArgumentValues(); + cavs1.addGenericArgumentValue(JUERGEN); + RootBeanDefinition person1 = new RootBeanDefinition(Person.class, cavs1, null); + AutowireCandidateQualifier qualifier = new AutowireCandidateQualifier(TestQualifierWithMultipleAttributes.class); + qualifier.setAttribute("number", 456); + person1.addQualifier(qualifier); + ConstructorArgumentValues cavs2 = new ConstructorArgumentValues(); + cavs2.addGenericArgumentValue(MARK); + RootBeanDefinition person2 = new RootBeanDefinition(Person.class, cavs2, null); + AutowireCandidateQualifier qualifier2 = new AutowireCandidateQualifier(TestQualifierWithMultipleAttributes.class); + qualifier2.setAttribute("number", 123); + qualifier2.setAttribute("value", "default"); + person2.addQualifier(qualifier2); + context.registerBeanDefinition(JUERGEN, person1); + context.registerBeanDefinition(MARK, person2); + context.registerBeanDefinition("autowired", + new RootBeanDefinition(QualifiedFieldWithMultipleAttributesTestBean.class)); + AnnotationConfigUtils.registerAnnotationConfigProcessors(context); + context.refresh(); + QualifiedFieldWithMultipleAttributesTestBean bean = + (QualifiedFieldWithMultipleAttributesTestBean) context.getBean("autowired"); + assertEquals(MARK, bean.getPerson().getName()); + } + + @Test + public void testAutowiredFieldDoesNotResolveWithMultipleQualifierValuesAndMultipleMatchingCandidates() { + GenericApplicationContext context = new GenericApplicationContext(); + ConstructorArgumentValues cavs1 = new ConstructorArgumentValues(); + cavs1.addGenericArgumentValue(JUERGEN); + RootBeanDefinition person1 = new RootBeanDefinition(Person.class, cavs1, null); + AutowireCandidateQualifier qualifier = new AutowireCandidateQualifier(TestQualifierWithMultipleAttributes.class); + qualifier.setAttribute("number", 123); + person1.addQualifier(qualifier); + ConstructorArgumentValues cavs2 = new ConstructorArgumentValues(); + cavs2.addGenericArgumentValue(MARK); + RootBeanDefinition person2 = new RootBeanDefinition(Person.class, cavs2, null); + AutowireCandidateQualifier qualifier2 = new AutowireCandidateQualifier(TestQualifierWithMultipleAttributes.class); + qualifier2.setAttribute("number", 123); + qualifier2.setAttribute("value", "default"); + person2.addQualifier(qualifier2); + context.registerBeanDefinition(JUERGEN, person1); + context.registerBeanDefinition(MARK, person2); + context.registerBeanDefinition("autowired", + new RootBeanDefinition(QualifiedFieldWithMultipleAttributesTestBean.class)); + AnnotationConfigUtils.registerAnnotationConfigProcessors(context); + try { + context.refresh(); + fail("expected BeanCreationException"); + } + catch (BeanCreationException e) { + assertTrue(e.getRootCause() instanceof NoSuchBeanDefinitionException); + assertEquals("autowired", e.getBeanName()); + } + } + + @Test + public void testAutowiredFieldDoesNotResolveWithBaseQualifierAndNonDefaultValueAndMultipleMatchingCandidates() { + GenericApplicationContext context = new GenericApplicationContext(); + ConstructorArgumentValues cavs1 = new ConstructorArgumentValues(); + cavs1.addGenericArgumentValue("the real juergen"); + RootBeanDefinition person1 = new RootBeanDefinition(Person.class, cavs1, null); + person1.addQualifier(new AutowireCandidateQualifier(Qualifier.class, "juergen")); + ConstructorArgumentValues cavs2 = new ConstructorArgumentValues(); + cavs2.addGenericArgumentValue("juergen imposter"); + RootBeanDefinition person2 = new RootBeanDefinition(Person.class, cavs2, null); + person2.addQualifier(new AutowireCandidateQualifier(Qualifier.class, "juergen")); + context.registerBeanDefinition("juergen1", person1); + context.registerBeanDefinition("juergen2", person2); + context.registerBeanDefinition("autowired", + new RootBeanDefinition(QualifiedConstructorArgumentWithBaseQualifierNonDefaultValueTestBean.class)); + AnnotationConfigUtils.registerAnnotationConfigProcessors(context); + try { + context.refresh(); + fail("expected BeanCreationException"); + } + catch (BeanCreationException e) { + assertTrue(e instanceof UnsatisfiedDependencyException); + assertEquals("autowired", e.getBeanName()); + } + } + + + private static class QualifiedFieldTestBean { + + @Inject + @TestQualifier + private Person person; + + public Person getPerson() { + return this.person; + } + } + + + private static class QualifiedMethodParameterTestBean { + + private Person person; + + @Inject + public void setPerson(@TestQualifier Person person) { + this.person = person; + } + + public Person getPerson() { + return this.person; + } + } + + + private static class QualifiedConstructorArgumentTestBean { + + private Person person; + + @Inject + public QualifiedConstructorArgumentTestBean(@TestQualifier Person person) { + this.person = person; + } + + public Person getPerson() { + return this.person; + } + + } + + + public static class QualifiedFieldWithDefaultValueTestBean { + + @Inject + @TestQualifierWithDefaultValue + private Person person; + + public Person getPerson() { + return this.person; + } + } + + + public static class QualifiedFieldWithMultipleAttributesTestBean { + + @Inject + @TestQualifierWithMultipleAttributes(number=123) + private Person person; + + public Person getPerson() { + return this.person; + } + } + + + private static class QualifiedFieldWithBaseQualifierDefaultValueTestBean { + + @Inject + private Person person; + + public Person getPerson() { + return this.person; + } + } + + + public static class QualifiedConstructorArgumentWithBaseQualifierNonDefaultValueTestBean { + + private Person person; + + @Inject + public QualifiedConstructorArgumentWithBaseQualifierNonDefaultValueTestBean( + @Named("juergen") Person person) { + this.person = person; + } + + public Person getPerson() { + return this.person; + } + } + + + private static class Person { + + private String name; + + public Person(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + } + + + @TestQualifier + private static class QualifiedPerson extends Person { + + public QualifiedPerson() { + super(null); + } + + public QualifiedPerson(String name) { + super(name); + } + } + + + @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + @Qualifier + public static @interface TestQualifier { + } + + + @Target(ElementType.FIELD) + @Retention(RetentionPolicy.RUNTIME) + @Qualifier + public static @interface TestQualifierWithDefaultValue { + + public abstract String value() default "default"; + } + + + @Target(ElementType.FIELD) + @Retention(RetentionPolicy.RUNTIME) + @Qualifier + public static @interface TestQualifierWithMultipleAttributes { + + public abstract String value() default "default"; + + public abstract int number(); + } + +} diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/AnnotationScopeMetadataResolverTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/AnnotationScopeMetadataResolverTests.java index 4cfe5ab2c47..7d31e003d02 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/AnnotationScopeMetadataResolverTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/AnnotationScopeMetadataResolverTests.java @@ -16,6 +16,12 @@ package org.springframework.context.annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Retention; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + import static org.junit.Assert.*; import org.junit.Before; import org.junit.Test; @@ -69,6 +75,15 @@ public final class AnnotationScopeMetadataResolverTests { assertEquals(ScopedProxyMode.TARGET_CLASS, scopeMetadata.getScopedProxyMode()); } + @Test + public void testCustomRequestScope() { + AnnotatedBeanDefinition bd = new AnnotatedGenericBeanDefinition(AnnotatedWithCustomRequestScope.class); + ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(bd); + assertNotNull("resolveScopeMetadata(..) must *never* return null.", scopeMetadata); + assertEquals("request", scopeMetadata.getScopeName()); + assertEquals(ScopedProxyMode.NO, scopeMetadata.getScopedProxyMode()); + } + @Test(expected=IllegalArgumentException.class) public void testCtorWithNullScopedProxyMode() { new AnnotationScopeMetadataResolver(null); @@ -94,4 +109,17 @@ public final class AnnotationScopeMetadataResolverTests { private static final class AnnotatedWithScopedProxy { } + + @CustomRequestScope + private static final class AnnotatedWithCustomRequestScope { + } + + + @Target({ElementType.TYPE, ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + @Scope("request") + public @interface CustomRequestScope { + + } + } diff --git a/org.springframework.context/template.mf b/org.springframework.context/template.mf index 67c9c5c0fb8..262398c5082 100644 --- a/org.springframework.context/template.mf +++ b/org.springframework.context/template.mf @@ -14,6 +14,7 @@ Import-Template: groovy.*;version="[1.5.0, 2.0.0)";resolution:=optional, javax.annotation.*;version="0";resolution:=optional, javax.ejb.*;version="[2.1.0, 4.0.0)";resolution:=optional, + javax.inject.*;version="[0.9.0, 2.0.0)";resolution:=optional, javax.interceptor.*;version="[3.0.0, 4.0.0)";resolution:=optional, javax.jms.*;version="[1.1.0, 2.0.0)";resolution:=optional, javax.management.*;version="0";resolution:=optional, diff --git a/org.springframework.integration-tests/.classpath b/org.springframework.integration-tests/.classpath index 0fd0bfab5a4..53bf684735c 100644 --- a/org.springframework.integration-tests/.classpath +++ b/org.springframework.integration-tests/.classpath @@ -25,6 +25,7 @@ + diff --git a/org.springframework.integration-tests/integration-tests.iml b/org.springframework.integration-tests/integration-tests.iml index a5713abde85..23fc185a62f 100644 --- a/org.springframework.integration-tests/integration-tests.iml +++ b/org.springframework.integration-tests/integration-tests.iml @@ -27,6 +27,7 @@ + diff --git a/org.springframework.integration-tests/ivy.xml b/org.springframework.integration-tests/ivy.xml index 3504365bdb8..7737084bc10 100644 --- a/org.springframework.integration-tests/ivy.xml +++ b/org.springframework.integration-tests/ivy.xml @@ -30,6 +30,7 @@ + diff --git a/org.springframework.integration-tests/src/test/java/org/springframework/context/annotation/jsr330/ClassPathBeanDefinitionScannerJsr330ScopeIntegrationTests.java b/org.springframework.integration-tests/src/test/java/org/springframework/context/annotation/jsr330/ClassPathBeanDefinitionScannerJsr330ScopeIntegrationTests.java new file mode 100644 index 00000000000..705229cedf3 --- /dev/null +++ b/org.springframework.integration-tests/src/test/java/org/springframework/context/annotation/jsr330/ClassPathBeanDefinitionScannerJsr330ScopeIntegrationTests.java @@ -0,0 +1,393 @@ +/* + * 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 java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import javax.inject.Named; +import javax.inject.Singleton; + +import org.junit.After; +import static org.junit.Assert.*; +import org.junit.Before; +import org.junit.Test; + +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; +import org.springframework.context.annotation.ScopeMetadata; +import org.springframework.context.annotation.ScopeMetadataResolver; +import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpSession; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.context.support.GenericWebApplicationContext; + +/** + * @author Mark Fisher + * @author Juergen Hoeller + * @author Chris Beams + */ +public class ClassPathBeanDefinitionScannerJsr330ScopeIntegrationTests { + + private static final String DEFAULT_NAME = "default"; + + private static final String MODIFIED_NAME = "modified"; + + private ServletRequestAttributes oldRequestAttributes; + + private ServletRequestAttributes newRequestAttributes; + + private ServletRequestAttributes oldRequestAttributesWithSession; + + private ServletRequestAttributes newRequestAttributesWithSession; + + + @Before + public void setUp() { + this.oldRequestAttributes = new ServletRequestAttributes(new MockHttpServletRequest()); + this.newRequestAttributes = new ServletRequestAttributes(new MockHttpServletRequest()); + + MockHttpServletRequest oldRequestWithSession = new MockHttpServletRequest(); + oldRequestWithSession.setSession(new MockHttpSession()); + this.oldRequestAttributesWithSession = new ServletRequestAttributes(oldRequestWithSession); + + MockHttpServletRequest newRequestWithSession = new MockHttpServletRequest(); + newRequestWithSession.setSession(new MockHttpSession()); + this.newRequestAttributesWithSession = new ServletRequestAttributes(newRequestWithSession); + } + + @After + public void tearDown() throws Exception { + RequestContextHolder.setRequestAttributes(null); + } + + + @Test + public void testPrototype() { + ApplicationContext context = createContext(ScopedProxyMode.NO); + ScopedTestBean bean = (ScopedTestBean) context.getBean("prototype"); + assertTrue(context.isPrototype("prototype")); + assertFalse(context.isSingleton("prototype")); + } + + @Test + public void testSingletonScopeWithNoProxy() { + RequestContextHolder.setRequestAttributes(oldRequestAttributes); + ApplicationContext context = createContext(ScopedProxyMode.NO); + ScopedTestBean bean = (ScopedTestBean) context.getBean("singleton"); + assertTrue(context.isSingleton("singleton")); + assertFalse(context.isPrototype("singleton")); + + // should not be a proxy + assertFalse(AopUtils.isAopProxy(bean)); + + assertEquals(DEFAULT_NAME, bean.getName()); + bean.setName(MODIFIED_NAME); + + RequestContextHolder.setRequestAttributes(newRequestAttributes); + // not a proxy so this should not have changed + assertEquals(MODIFIED_NAME, bean.getName()); + + // singleton bean, so name should be modified even after lookup + ScopedTestBean bean2 = (ScopedTestBean) context.getBean("singleton"); + assertEquals(MODIFIED_NAME, bean2.getName()); + } + + @Test + public void testSingletonScopeIgnoresProxyInterfaces() { + RequestContextHolder.setRequestAttributes(oldRequestAttributes); + ApplicationContext context = createContext(ScopedProxyMode.INTERFACES); + ScopedTestBean bean = (ScopedTestBean) context.getBean("singleton"); + + // should not be a proxy + assertFalse(AopUtils.isAopProxy(bean)); + + assertEquals(DEFAULT_NAME, bean.getName()); + bean.setName(MODIFIED_NAME); + + RequestContextHolder.setRequestAttributes(newRequestAttributes); + // not a proxy so this should not have changed + assertEquals(MODIFIED_NAME, bean.getName()); + + // singleton bean, so name should be modified even after lookup + ScopedTestBean bean2 = (ScopedTestBean) context.getBean("singleton"); + assertEquals(MODIFIED_NAME, bean2.getName()); + } + + @Test + public void testSingletonScopeIgnoresProxyTargetClass() { + RequestContextHolder.setRequestAttributes(oldRequestAttributes); + ApplicationContext context = createContext(ScopedProxyMode.TARGET_CLASS); + ScopedTestBean bean = (ScopedTestBean) context.getBean("singleton"); + + // should not be a proxy + assertFalse(AopUtils.isAopProxy(bean)); + + assertEquals(DEFAULT_NAME, bean.getName()); + bean.setName(MODIFIED_NAME); + + RequestContextHolder.setRequestAttributes(newRequestAttributes); + // not a proxy so this should not have changed + assertEquals(MODIFIED_NAME, bean.getName()); + + // singleton bean, so name should be modified even after lookup + ScopedTestBean bean2 = (ScopedTestBean) context.getBean("singleton"); + assertEquals(MODIFIED_NAME, bean2.getName()); + } + + @Test + public void testRequestScopeWithNoProxy() { + RequestContextHolder.setRequestAttributes(oldRequestAttributes); + ApplicationContext context = createContext(ScopedProxyMode.NO); + ScopedTestBean bean = (ScopedTestBean) context.getBean("request"); + + // should not be a proxy + assertFalse(AopUtils.isAopProxy(bean)); + + assertEquals(DEFAULT_NAME, bean.getName()); + bean.setName(MODIFIED_NAME); + + RequestContextHolder.setRequestAttributes(newRequestAttributes); + // not a proxy so this should not have changed + assertEquals(MODIFIED_NAME, bean.getName()); + + // but a newly retrieved bean should have the default name + ScopedTestBean bean2 = (ScopedTestBean) context.getBean("request"); + assertEquals(DEFAULT_NAME, bean2.getName()); + } + + @Test + public void testRequestScopeWithProxiedInterfaces() { + RequestContextHolder.setRequestAttributes(oldRequestAttributes); + ApplicationContext context = createContext(ScopedProxyMode.INTERFACES); + IScopedTestBean bean = (IScopedTestBean) context.getBean("request"); + + // should be dynamic proxy, implementing both interfaces + assertTrue(AopUtils.isJdkDynamicProxy(bean)); + assertTrue(bean instanceof AnotherScopeTestInterface); + + assertEquals(DEFAULT_NAME, bean.getName()); + bean.setName(MODIFIED_NAME); + + RequestContextHolder.setRequestAttributes(newRequestAttributes); + // this is a proxy so it should be reset to default + assertEquals(DEFAULT_NAME, bean.getName()); + + RequestContextHolder.setRequestAttributes(oldRequestAttributes); + assertEquals(MODIFIED_NAME, bean.getName()); + } + + @Test + public void testRequestScopeWithProxiedTargetClass() { + RequestContextHolder.setRequestAttributes(oldRequestAttributes); + ApplicationContext context = createContext(ScopedProxyMode.TARGET_CLASS); + IScopedTestBean bean = (IScopedTestBean) context.getBean("request"); + + // should be a class-based proxy + assertTrue(AopUtils.isCglibProxy(bean)); + assertTrue(bean instanceof RequestScopedTestBean); + + assertEquals(DEFAULT_NAME, bean.getName()); + bean.setName(MODIFIED_NAME); + + RequestContextHolder.setRequestAttributes(newRequestAttributes); + // this is a proxy so it should be reset to default + assertEquals(DEFAULT_NAME, bean.getName()); + + RequestContextHolder.setRequestAttributes(oldRequestAttributes); + assertEquals(MODIFIED_NAME, bean.getName()); + } + + @Test + public void testSessionScopeWithNoProxy() { + RequestContextHolder.setRequestAttributes(oldRequestAttributesWithSession); + ApplicationContext context = createContext(ScopedProxyMode.NO); + ScopedTestBean bean = (ScopedTestBean) context.getBean("session"); + + // should not be a proxy + assertFalse(AopUtils.isAopProxy(bean)); + + assertEquals(DEFAULT_NAME, bean.getName()); + bean.setName(MODIFIED_NAME); + + RequestContextHolder.setRequestAttributes(newRequestAttributesWithSession); + // not a proxy so this should not have changed + assertEquals(MODIFIED_NAME, bean.getName()); + + // but a newly retrieved bean should have the default name + ScopedTestBean bean2 = (ScopedTestBean) context.getBean("session"); + assertEquals(DEFAULT_NAME, bean2.getName()); + } + + @Test + public void testSessionScopeWithProxiedInterfaces() { + RequestContextHolder.setRequestAttributes(oldRequestAttributesWithSession); + ApplicationContext context = createContext(ScopedProxyMode.INTERFACES); + IScopedTestBean bean = (IScopedTestBean) context.getBean("session"); + + // should be dynamic proxy, implementing both interfaces + assertTrue(AopUtils.isJdkDynamicProxy(bean)); + assertTrue(bean instanceof AnotherScopeTestInterface); + + assertEquals(DEFAULT_NAME, bean.getName()); + bean.setName(MODIFIED_NAME); + + RequestContextHolder.setRequestAttributes(newRequestAttributesWithSession); + // this is a proxy so it should be reset to default + assertEquals(DEFAULT_NAME, bean.getName()); + bean.setName(MODIFIED_NAME); + + IScopedTestBean bean2 = (IScopedTestBean) context.getBean("session"); + assertEquals(MODIFIED_NAME, bean2.getName()); + bean2.setName(DEFAULT_NAME); + assertEquals(DEFAULT_NAME, bean.getName()); + + RequestContextHolder.setRequestAttributes(oldRequestAttributesWithSession); + assertEquals(MODIFIED_NAME, bean.getName()); + } + + @Test + public void testSessionScopeWithProxiedTargetClass() { + RequestContextHolder.setRequestAttributes(oldRequestAttributesWithSession); + ApplicationContext context = createContext(ScopedProxyMode.TARGET_CLASS); + IScopedTestBean bean = (IScopedTestBean) context.getBean("session"); + + // should be a class-based proxy + assertTrue(AopUtils.isCglibProxy(bean)); + assertTrue(bean instanceof ScopedTestBean); + assertTrue(bean instanceof SessionScopedTestBean); + + assertEquals(DEFAULT_NAME, bean.getName()); + bean.setName(MODIFIED_NAME); + + RequestContextHolder.setRequestAttributes(newRequestAttributesWithSession); + // this is a proxy so it should be reset to default + assertEquals(DEFAULT_NAME, bean.getName()); + bean.setName(MODIFIED_NAME); + + IScopedTestBean bean2 = (IScopedTestBean) context.getBean("session"); + assertEquals(MODIFIED_NAME, bean2.getName()); + bean2.setName(DEFAULT_NAME); + assertEquals(DEFAULT_NAME, bean.getName()); + + RequestContextHolder.setRequestAttributes(oldRequestAttributesWithSession); + assertEquals(MODIFIED_NAME, bean.getName()); + } + + + private ApplicationContext createContext(final ScopedProxyMode scopedProxyMode) { + GenericWebApplicationContext context = new GenericWebApplicationContext(); + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context); + scanner.setIncludeAnnotationConfig(false); + scanner.setScopeMetadataResolver(new ScopeMetadataResolver() { + public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) { + ScopeMetadata metadata = new ScopeMetadata(); + if (definition instanceof AnnotatedBeanDefinition) { + AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition; + for (String type : annDef.getMetadata().getAnnotationTypes()) { + if (type.equals(javax.inject.Singleton.class.getName())) { + metadata.setScopeName(BeanDefinition.SCOPE_SINGLETON); + break; + } + else if (annDef.getMetadata().getMetaAnnotationTypes(type).contains(javax.inject.Scope.class.getName())) { + metadata.setScopeName(type.substring(type.length() - 13, type.length() - 6).toLowerCase()); + metadata.setScopedProxyMode(scopedProxyMode); + break; + } + else if (type.startsWith("javax.inject")) { + metadata.setScopeName(BeanDefinition.SCOPE_PROTOTYPE); + } + } + } + return metadata; + } + }); + + // Scan twice in order to find errors in the bean definition compatibility check. + scanner.scan(getClass().getPackage().getName()); + scanner.scan(getClass().getPackage().getName()); + + context.registerAlias("classPathBeanDefinitionScannerJsr330ScopeIntegrationTests.SessionScopedTestBean", "session"); + context.refresh(); + return context; + } + + + public static interface IScopedTestBean { + + String getName(); + + void setName(String name); + } + + + public static abstract class ScopedTestBean implements IScopedTestBean { + + private String name = DEFAULT_NAME; + + public String getName() { return this.name; } + + public void setName(String name) { this.name = name; } + } + + + @Named("prototype") + public static class PrototypeScopedTestBean extends ScopedTestBean { + } + + + @Named("singleton") + @Singleton + public static class SingletonScopedTestBean extends ScopedTestBean { + } + + + public static interface AnotherScopeTestInterface { + } + + + @RequestScoped + @Named("request") + public static class RequestScopedTestBean extends ScopedTestBean implements AnotherScopeTestInterface { + } + + + @SessionScoped + public static class SessionScopedTestBean extends ScopedTestBean implements AnotherScopeTestInterface { + } + + + @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + @javax.inject.Scope + public static @interface RequestScoped { + } + + + @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + @javax.inject.Scope + public static @interface SessionScoped { + } + +} diff --git a/org.springframework.integration-tests/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerScopeIntegrationTests.java b/org.springframework.integration-tests/src/test/java/org/springframework/context/annotation/scope/ClassPathBeanDefinitionScannerScopeIntegrationTests.java similarity index 95% rename from org.springframework.integration-tests/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerScopeIntegrationTests.java rename to org.springframework.integration-tests/src/test/java/org/springframework/context/annotation/scope/ClassPathBeanDefinitionScannerScopeIntegrationTests.java index 0a120ce8327..8b48e58da82 100644 --- a/org.springframework.integration-tests/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerScopeIntegrationTests.java +++ b/org.springframework.integration-tests/src/test/java/org/springframework/context/annotation/scope/ClassPathBeanDefinitionScannerScopeIntegrationTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.context.annotation; +package org.springframework.context.annotation.scope; import org.junit.After; import static org.junit.Assert.*; @@ -26,6 +26,9 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; +import org.springframework.context.annotation.Scope; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpSession; @@ -279,15 +282,11 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests { private ApplicationContext createContext(ScopedProxyMode scopedProxyMode) { GenericWebApplicationContext context = new GenericWebApplicationContext(); - ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context, false); + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context); scanner.setIncludeAnnotationConfig(false); - scanner.addIncludeFilter(new AnnotationTypeFilter(ScopeTestComponent.class)); scanner.setBeanNameGenerator(new BeanNameGenerator() { public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) { - String beanClassName = ClassUtils.getShortName(definition.getBeanClassName()); - int begin = beanClassName.lastIndexOf('.') + 1; - int end = beanClassName.lastIndexOf("ScopedTestBean"); - return beanClassName.substring(begin, end).toLowerCase(); + return definition.getScope(); } }); scanner.setScopedProxyMode(scopedProxyMode); @@ -301,10 +300,6 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests { } - public static @interface ScopeTestComponent { - } - - public static interface IScopedTestBean { String getName(); @@ -323,8 +318,8 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests { } - @ScopeTestComponent - public static class SingletonScopedTestBean extends ScopedTestBean { + @Scope("singleton") + public static class SingletonScopedTestBean extends ScopedTestBean { } @@ -333,13 +328,11 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests { @Scope("request") - @ScopeTestComponent - public static class RequestScopedTestBean extends ScopedTestBean implements AnotherScopeTestInterface { + public static class RequestScopedTestBean extends ScopedTestBean implements AnotherScopeTestInterface { } @Scope("session") - @ScopeTestComponent public static class SessionScopedTestBean extends ScopedTestBean implements AnotherScopeTestInterface { }