From 001d0e734c3641b37de9cc38c526a41f9af8ed48 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 4 Jun 2014 14:55:43 +0200 Subject: [PATCH] Support for @Order at the bean declaration level This commit introduces OrderProvider and OrderProviderComparator, two interfaces designed to externalize how a collection of element is sorted according to their order value. FactoryAwareOrderProvider is an OrderProvider implementation that knows about the objects to order and the corresponding BeanFactory instance. This allows to retrieve additional metadata about the actual instances to sort, such as its factory method. A @Bean method can now holds an additional @Order to define the order value that this bean should have when injected as part of a collection or array. Issue: SPR-11310 --- .../support/DefaultDependencyComparator.java | 58 ++++++ .../support/DefaultListableBeanFactory.java | 32 ++- .../support/FactoryAwareOrderProvider.java | 84 ++++++++ ...wiredAnnotationBeanPostProcessorTests.java | 45 +++- .../support/DependencyComparatorTests.java | 68 ++++++ .../FactoryAwareOrderProviderTests.java | 139 +++++++++++++ .../annotation/AnnotationConfigUtils.java | 3 +- .../annotation/spr11310/Spr11310Tests.java | 93 +++++++++ .../DefaultOrderProviderComparator.java | 70 +++++++ .../core/annotation/OrderProvider.java | 36 ++++ .../annotation/OrderProviderComparator.java | 49 +++++ .../DefaultOrderProviderComparatorTests.java | 193 ++++++++++++++++++ 12 files changed, 860 insertions(+), 10 deletions(-) create mode 100644 spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultDependencyComparator.java create mode 100644 spring-beans/src/main/java/org/springframework/beans/factory/support/FactoryAwareOrderProvider.java create mode 100644 spring-beans/src/test/java/org/springframework/beans/factory/support/DependencyComparatorTests.java create mode 100644 spring-beans/src/test/java/org/springframework/beans/factory/support/FactoryAwareOrderProviderTests.java create mode 100644 spring-context/src/test/java/org/springframework/context/annotation/spr11310/Spr11310Tests.java create mode 100644 spring-core/src/main/java/org/springframework/core/annotation/DefaultOrderProviderComparator.java create mode 100644 spring-core/src/main/java/org/springframework/core/annotation/OrderProvider.java create mode 100644 spring-core/src/main/java/org/springframework/core/annotation/OrderProviderComparator.java create mode 100644 spring-core/src/test/java/org/springframework/core/annotation/DefaultOrderProviderComparatorTests.java diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultDependencyComparator.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultDependencyComparator.java new file mode 100644 index 0000000000..ee9e2ad09b --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultDependencyComparator.java @@ -0,0 +1,58 @@ +/* + * Copyright 2002-2014 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.util.Comparator; + +import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.core.annotation.DefaultOrderProviderComparator; + +/** + * The default {@link Comparator} to use to order dependencies. Extend + * from {@link DefaultOrderProviderComparator} so that the bean factory + * has the ability to provide an {@link org.springframework.core.annotation.OrderProvider} + * that is aware of more bean metadata, if any. + * + * @author Stephane Nicoll + * @since 4.1 + * @see org.springframework.core.annotation.OrderProviderComparator + * @see org.springframework.core.annotation.OrderProvider + * @see DefaultListableBeanFactory#setDependencyComparator(java.util.Comparator) + */ +public class DefaultDependencyComparator extends DefaultOrderProviderComparator implements Comparator { + + /** + * Shared default instance of DefaultDependencyComparator. + */ + public static final DefaultDependencyComparator INSTANCE = new DefaultDependencyComparator(); + + private final Comparator comparator; + + public DefaultDependencyComparator(Comparator comparator) { + this.comparator = comparator; + } + + public DefaultDependencyComparator() { + this(AnnotationAwareOrderComparator.INSTANCE); + } + + @Override + public int compare(Object o1, Object o2) { + return comparator.compare(o1, o2); + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index d7110db594..b43c0f87f7 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -32,6 +32,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.IdentityHashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -60,6 +61,7 @@ import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.OrderProviderComparator; import org.springframework.core.annotation.OrderUtils; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -895,7 +897,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter()); Object result = converter.convertIfNecessary(matchingBeans.values(), type); if (this.dependencyComparator != null && result instanceof Object[]) { - Arrays.sort((Object[]) result, this.dependencyComparator); + sortArray((Object[]) result, matchingBeans); } return result; } @@ -922,7 +924,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter()); Object result = converter.convertIfNecessary(matchingBeans.values(), type); if (this.dependencyComparator != null && result instanceof List) { - Collections.sort((List) result, this.dependencyComparator); + sortList((List) result, matchingBeans); } return result; } @@ -983,6 +985,32 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto } } + private void sortArray(Object[] items, Map matchingBeans) { + if (this.dependencyComparator instanceof OrderProviderComparator) { + ((OrderProviderComparator) this.dependencyComparator) + .sortArray(items, createFactoryAwareOrderProvider(matchingBeans)); + } else { + Arrays.sort(items, this.dependencyComparator); + } + } + + private void sortList(List items, Map matchingBeans) { + if (this.dependencyComparator instanceof OrderProviderComparator) { + ((OrderProviderComparator) this.dependencyComparator) + .sortList(items, createFactoryAwareOrderProvider(matchingBeans)); + } else { + Collections.sort(items, this.dependencyComparator); + } + } + + private FactoryAwareOrderProvider createFactoryAwareOrderProvider(Map beans) { + IdentityHashMap instancesToBeanNames = new IdentityHashMap(); + for (Map.Entry entry : beans.entrySet()) { + instancesToBeanNames.put(entry.getValue(), entry.getKey()); + } + return new FactoryAwareOrderProvider(instancesToBeanNames, this); + } + /** * Find bean instances that match the required type. * Called during autowiring for the specified bean. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/FactoryAwareOrderProvider.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/FactoryAwareOrderProvider.java new file mode 100644 index 0000000000..600603c089 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/FactoryAwareOrderProvider.java @@ -0,0 +1,84 @@ +/* + * Copyright 2002-2014 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.reflect.Method; +import java.util.Map; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.Order; +import org.springframework.core.annotation.OrderProvider; + +/** + * An {@link OrderProvider} implementation that is aware of the + * bean metadata of the instances to sort. + * + *

Lookup for the method factory of an instance to sort, if + * any and retrieve the {@link Order} value defined on it. This + * essentially allows for the following construct: + * + *

+ * @Configuration
+ * public class AppConfig {
+ *
+ *     @Bean
+ *     @Order(5)
+ *     public MyService myService() {
+ *         return new MyService();
+ *     }
+ * }
+ * + * @author Stephane Nicoll + * @since 4.1 + */ +public class FactoryAwareOrderProvider implements OrderProvider { + + private final Map instancesToBeanNames; + + private final ConfigurableListableBeanFactory beanFactory; + + public FactoryAwareOrderProvider(Map instancesToBeanNames, + ConfigurableListableBeanFactory beanFactory) { + this.instancesToBeanNames = instancesToBeanNames; + this.beanFactory = beanFactory; + } + + @Override + public Integer getOrder(Object obj) { + Method factoryMethod = getFactoryMethod(instancesToBeanNames.get(obj)); + if (factoryMethod != null) { + Order order = AnnotationUtils.getAnnotation(factoryMethod, Order.class); + if (order != null) { + return order.value(); + } + } + return null; + } + + private Method getFactoryMethod(String beanName) { + if (beanName != null && beanFactory.containsBeanDefinition(beanName)) { + BeanDefinition bd = beanFactory.getMergedBeanDefinition(beanName); + if (bd instanceof RootBeanDefinition) { + return ((RootBeanDefinition) bd).getResolvedFactoryMethod(); + } + } + return null; + } + +} diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java index 8953633dff..b63e1e499a 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java @@ -38,11 +38,11 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.TypedStringValue; import org.springframework.beans.factory.support.AutowireCandidateQualifier; +import org.springframework.beans.factory.support.DefaultDependencyComparator; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.core.Ordered; -import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.annotation.Order; import org.springframework.tests.sample.beans.ITestBean; import org.springframework.tests.sample.beans.IndexedTestBean; @@ -51,12 +51,15 @@ import org.springframework.tests.sample.beans.TestBean; import org.springframework.util.SerializationTestUtils; import static org.junit.Assert.*; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.*; /** * @author Juergen Hoeller * @author Mark Fisher * @author Sam Brannen * @author Chris Beams + * @author Stephane Nicoll */ public class AutowiredAnnotationBeanPostProcessorTests { @@ -351,7 +354,7 @@ public class AutowiredAnnotationBeanPostProcessorTests { @Test public void testOrderedResourceInjection() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - bf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); + bf.setDependencyComparator(DefaultDependencyComparator.INSTANCE); AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(bf); bf.addBeanPostProcessor(bpp); @@ -382,10 +385,38 @@ public class AutowiredAnnotationBeanPostProcessorTests { bf.destroySingletons(); } + @Test + public void testOrderedResourceInjectionDetectsFactoryAwareComparator() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + DefaultDependencyComparator comparator = mock(DefaultDependencyComparator.class); + bf.setDependencyComparator(comparator); + + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(OptionalResourceInjectionBean.class)); + TestBean tb = new TestBean(); + bf.registerSingleton("testBean", tb); + IndexedTestBean itb = new IndexedTestBean(); + bf.registerSingleton("indexedTestBean", itb); + final OrderedNestedTestBean ntb1 = new OrderedNestedTestBean(); + ntb1.setOrder(2); + bf.registerSingleton("nestedTestBean1", ntb1); + final OrderedNestedTestBean ntb2 = new OrderedNestedTestBean(); + ntb2.setOrder(1); + bf.registerSingleton("nestedTestBean2", ntb2); + + OptionalResourceInjectionBean bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean"); + verify(comparator, never()).compare(any(), any()); + verify(comparator, never()).sortList(any(),any()); + verify(comparator, times(2)).sortArray(any(),any()); + bf.destroySingletons(); + } + @Test public void testAnnotationOrderedResourceInjection() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - bf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); + bf.setDependencyComparator(DefaultDependencyComparator.INSTANCE); AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(bf); bf.addBeanPostProcessor(bpp); @@ -417,7 +448,7 @@ public class AutowiredAnnotationBeanPostProcessorTests { @Test public void testOrderedCollectionResourceInjection() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - bf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); + bf.setDependencyComparator(DefaultDependencyComparator.INSTANCE); AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(bf); bf.addBeanPostProcessor(bpp); @@ -458,7 +489,7 @@ public class AutowiredAnnotationBeanPostProcessorTests { @Test public void testAnnotationOrderedCollectionResourceInjection() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - bf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); + bf.setDependencyComparator(DefaultDependencyComparator.INSTANCE); AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(bf); bf.addBeanPostProcessor(bpp); @@ -576,7 +607,7 @@ public class AutowiredAnnotationBeanPostProcessorTests { @Test public void testConstructorResourceInjectionWithMultipleOrderedCandidates() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - bf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); + bf.setDependencyComparator(DefaultDependencyComparator.INSTANCE); AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(bf); bf.addBeanPostProcessor(bpp); @@ -600,7 +631,7 @@ public class AutowiredAnnotationBeanPostProcessorTests { @Test public void testConstructorResourceInjectionWithMultipleCandidatesAsOrderedCollection() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - bf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); + bf.setDependencyComparator(DefaultDependencyComparator.INSTANCE); AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(bf); bf.addBeanPostProcessor(bpp); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/DependencyComparatorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/DependencyComparatorTests.java new file mode 100644 index 0000000000..e9ea18af93 --- /dev/null +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/DependencyComparatorTests.java @@ -0,0 +1,68 @@ +/* + * Copyright 2002-2014 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 static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.junit.Test; + +import org.springframework.core.Ordered; + +/** + * + * @author Stephane Nicoll + */ +public class DependencyComparatorTests { + + private final DefaultDependencyComparator comparator = new DefaultDependencyComparator(); + + @Test + public void plainComparator() { + List items = new ArrayList(); + C c = new C(5); + C c2 = new C(-5); + items.add(c); + items.add(c2); + Collections.sort(items, comparator); + assertOrder(items, c2, c); + } + + private void assertOrder(List actual, Object... expected) { + for (int i = 0; i < actual.size(); i++) { + assertSame("Wrong instance at index '" + i + "'", expected[i], actual.get(i)); + } + assertEquals("Wrong number of items", expected.length, actual.size()); + } + + private static class C implements Ordered { + private final int order; + + private C(int order) { + this.order = order; + } + + @Override + public int getOrder() { + return order; + } + } + +} diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/FactoryAwareOrderProviderTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/FactoryAwareOrderProviderTests.java new file mode 100644 index 0000000000..2b969ce9cc --- /dev/null +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/FactoryAwareOrderProviderTests.java @@ -0,0 +1,139 @@ +/* + * Copyright 2002-2014 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 static org.junit.Assert.*; +import static org.mockito.BDDMockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import java.lang.reflect.Method; +import java.util.HashMap; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.core.annotation.Order; +import org.springframework.util.ReflectionUtils; + +/** + * + * @author Stephane Nicoll + */ +public class FactoryAwareOrderProviderTests { + + @Rule + public final TestName name = new TestName(); + + @Mock + private ConfigurableListableBeanFactory beanFactory; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void noBeanName() { + FactoryAwareOrderProvider orderProvider = createOrderProvider(new HashMap()); + assertNull(orderProvider.getOrder(25)); + } + + @Test + public void beanNameNotRegistered() { + HashMap beans = new HashMap<>(); + beans.put(25, "myBean"); + given(beanFactory.containsBeanDefinition("myBean")).willReturn(false); + FactoryAwareOrderProvider orderProvider = createOrderProvider(beans); + assertNull(orderProvider.getOrder(25)); + verify(beanFactory).containsBeanDefinition("myBean"); + } + + + @Test + public void beanNameNoRootBeanDefinition() { + HashMap beans = new HashMap<>(); + beans.put(25, "myBean"); + given(beanFactory.containsBeanDefinition("myBean")).willReturn(true); + given(beanFactory.getMergedBeanDefinition("myBean")).willReturn(mock(BeanDefinition.class)); + FactoryAwareOrderProvider orderProvider = createOrderProvider(beans); + assertNull(orderProvider.getOrder(25)); + verify(beanFactory).containsBeanDefinition("myBean"); + verify(beanFactory).getMergedBeanDefinition("myBean"); + } + + @Test + public void beanNameNoFactory() { + HashMap beans = new HashMap<>(); + beans.put(25, "myBean"); + RootBeanDefinition rbd = mock(RootBeanDefinition.class); + given(rbd.getResolvedFactoryMethod()).willReturn(null); + + given(beanFactory.containsBeanDefinition("myBean")).willReturn(true); + given(beanFactory.getMergedBeanDefinition("myBean")).willReturn(rbd); + FactoryAwareOrderProvider orderProvider = createOrderProvider(beans); + assertNull(orderProvider.getOrder(25)); + verify(beanFactory).containsBeanDefinition("myBean"); + verify(beanFactory).getMergedBeanDefinition("myBean"); + } + + @Test + public void beanNameFactoryNoOrderValue() { + HashMap beans = new HashMap<>(); + beans.put(25, "myBean"); + + Method m = ReflectionUtils.findMethod(getClass(), name.getMethodName()); + RootBeanDefinition rbd = mock(RootBeanDefinition.class); + given(rbd.getResolvedFactoryMethod()).willReturn(m); + + given(beanFactory.containsBeanDefinition("myBean")).willReturn(true); + given(beanFactory.getMergedBeanDefinition("myBean")).willReturn(rbd); + FactoryAwareOrderProvider orderProvider = createOrderProvider(beans); + assertNull(orderProvider.getOrder(25)); + verify(beanFactory).containsBeanDefinition("myBean"); + verify(beanFactory).getMergedBeanDefinition("myBean"); + } + + @Test + @Order(500) + public void beanNameFactoryOrderValue() { + HashMap beans = new HashMap<>(); + beans.put(25, "myBean"); + + Method m = ReflectionUtils.findMethod(getClass(), name.getMethodName()); + RootBeanDefinition rbd = mock(RootBeanDefinition.class); + given(rbd.getResolvedFactoryMethod()).willReturn(m); + + given(beanFactory.containsBeanDefinition("myBean")).willReturn(true); + given(beanFactory.getMergedBeanDefinition("myBean")).willReturn(rbd); + FactoryAwareOrderProvider orderProvider = createOrderProvider(beans); + assertEquals(Integer.valueOf(500), orderProvider.getOrder(25)); + verify(beanFactory).containsBeanDefinition("myBean"); + verify(beanFactory).getMergedBeanDefinition("myBean"); + } + + private FactoryAwareOrderProvider createOrderProvider(HashMap beans) { + return new FactoryAwareOrderProvider(beans, beanFactory); + } + +} diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java index e7d6d8f8ff..7bd49f97ca 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java @@ -28,6 +28,7 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.DefaultDependencyComparator; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.support.GenericApplicationContext; @@ -243,7 +244,7 @@ public class AnnotationConfigUtils { DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry); if (beanFactory != null) { if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) { - beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); + beanFactory.setDependencyComparator(DefaultDependencyComparator.INSTANCE); } if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) { beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver()); diff --git a/spring-context/src/test/java/org/springframework/context/annotation/spr11310/Spr11310Tests.java b/spring-context/src/test/java/org/springframework/context/annotation/spr11310/Spr11310Tests.java new file mode 100644 index 0000000000..d4c21d7e32 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/context/annotation/spr11310/Spr11310Tests.java @@ -0,0 +1,93 @@ +/* + * Copyright 2002-2014 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.spr11310; + +import static org.junit.Assert.*; + +import java.util.List; + +import org.junit.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; + +/** + * + * @author Stephane Nicoll + */ +public class Spr11310Tests { + + @Test + public void orderedList() { + ApplicationContext context = new AnnotationConfigApplicationContext(Config.class); + StringHolder holder = context.getBean(StringHolder.class); + assertEquals("second", holder.itemsList.get(0)); + assertEquals("first", holder.itemsList.get(1)); + assertEquals("unknownOrder", holder.itemsList.get(2)); + } + + @Test + public void orderedArray() { + ApplicationContext context = new AnnotationConfigApplicationContext(Config.class); + StringHolder holder = context.getBean(StringHolder.class); + assertEquals("second", holder.itemsArray[0]); + assertEquals("first", holder.itemsArray[1]); + assertEquals("unknownOrder", holder.itemsArray[2]); + } + + + @Configuration + static class Config { + + @Bean + @Order(50) + public String first() { + return "first"; + } + + @Bean + public String unknownOrder() { + return "unknownOrder"; + } + + @Bean + @Order(5) + public String second() { + return "second"; + } + + @Bean + public StringHolder stringHolder() { + return new StringHolder(); + } + + } + + + private static class StringHolder { + @Autowired + private List itemsList; + + @Autowired + private String[] itemsArray; + + } +} diff --git a/spring-core/src/main/java/org/springframework/core/annotation/DefaultOrderProviderComparator.java b/spring-core/src/main/java/org/springframework/core/annotation/DefaultOrderProviderComparator.java new file mode 100644 index 0000000000..b9dc2ad9a1 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/annotation/DefaultOrderProviderComparator.java @@ -0,0 +1,70 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.core.annotation; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * A default {@link OrderProviderComparator} implementation that uses the + * value provided by the {@link OrderProvider} and fallbacks to + * {@link AnnotationAwareOrderComparator} if none is set. + * + *

This essentially means that the value of the {@link OrderProvider} + * takes precedence over the behavior of {@link AnnotationAwareOrderComparator} + * + * @author Stephane Nicoll + * @since 4.1 + */ +public class DefaultOrderProviderComparator implements OrderProviderComparator { + + /** + * Shared default instance of DefaultOrderProviderComparator. + */ + public static final DefaultOrderProviderComparator INSTANCE = new DefaultOrderProviderComparator(); + + @Override + public void sortList(List items, OrderProvider orderProvider) { + Collections.sort(items, new OrderProviderAwareComparator(orderProvider)); + } + + @Override + public void sortArray(Object[] items, OrderProvider orderProvider) { + Arrays.sort(items, new OrderProviderAwareComparator(orderProvider)); + } + + + private static class OrderProviderAwareComparator extends AnnotationAwareOrderComparator { + + private final OrderProvider orderProvider; + + private OrderProviderAwareComparator(OrderProvider orderProvider) { + this.orderProvider = orderProvider; + } + + @Override + protected int getOrder(Object obj) { + Integer order = this.orderProvider.getOrder(obj); + if (order != null) { + return order; + } + return super.getOrder(obj); + } + } + +} diff --git a/spring-core/src/main/java/org/springframework/core/annotation/OrderProvider.java b/spring-core/src/main/java/org/springframework/core/annotation/OrderProvider.java new file mode 100644 index 0000000000..58c9e05ab5 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/annotation/OrderProvider.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.core.annotation; + +/** + * Strategy interface to provide the order value of a given instance. + * + * @author Stephane Nicoll + * @since 4.1 + * @see OrderProviderComparator + */ +public interface OrderProvider { + + /** + * Return the order value of the specified object or {@code null} if + * it cannot be retrieved. + * @param obj the object to handle + * @return the order value for that object or {@code null} if none is found + */ + Integer getOrder(Object obj); + +} diff --git a/spring-core/src/main/java/org/springframework/core/annotation/OrderProviderComparator.java b/spring-core/src/main/java/org/springframework/core/annotation/OrderProviderComparator.java new file mode 100644 index 0000000000..37c8394f4b --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/annotation/OrderProviderComparator.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.core.annotation; + +import java.util.List; + +/** + * Sort a collection of element according to an {@link OrderProvider}. + * + * @author Stephane Nicoll + * @since 4.1 + */ +public interface OrderProviderComparator { + + /** + * Sort the specified list of items according to their order value, + * using the specified {@link OrderProvider} to retrieve an order + * if necessary. + * @param items the items to sort + * @param orderProvider the order provider to use + * @see java.util.Collections#sort(java.util.List, java.util.Comparator) + */ + void sortList(List items, OrderProvider orderProvider); + + /** + * Sort the specified array of items according to their order value, + * using the specified {@link OrderProvider} to retrieve an order + * if necessary. + * @param items the items to sort + * @param orderProvider the order provider to use + * @see java.util.Arrays#sort(Object[], java.util.Comparator) + */ + void sortArray(Object[] items, OrderProvider orderProvider); + +} diff --git a/spring-core/src/test/java/org/springframework/core/annotation/DefaultOrderProviderComparatorTests.java b/spring-core/src/test/java/org/springframework/core/annotation/DefaultOrderProviderComparatorTests.java new file mode 100644 index 0000000000..950de24126 --- /dev/null +++ b/spring-core/src/test/java/org/springframework/core/annotation/DefaultOrderProviderComparatorTests.java @@ -0,0 +1,193 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.core.annotation; + +import static org.junit.Assert.*; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +import org.springframework.core.Ordered; + +/** + * + * @author Stephane Nicoll + */ +public class DefaultOrderProviderComparatorTests { + + private final DefaultOrderProviderComparator comparator = new DefaultOrderProviderComparator(); + + @Test + public void listNoFactoryMethod() { + A a = new A(); + C c = new C(-50); + B b = new B(); + + List items = Arrays.asList(a, c, b); + comparator.sortList(items, new OrderProvider() { + @Override + public Integer getOrder(Object obj) { + return null; + } + }); + assertOrder(items, c, a, b); + } + + @Test + public void listFactoryMethod() { + A a = new A(); + C c = new C(3); + B b = new B(); + + List items = Arrays.asList(a, c, b); + comparator.sortList(items, new OrderProvider() { + @Override + public Integer getOrder(Object obj) { + if (obj == a) { + return 4; + } + if (obj == b) { + return 2; + } + return null; + } + }); + assertOrder(items, b, c, a); + } + + @Test + public void listFactoryMethodOverridesStaticOrder() { + A a = new A(); + C c = new C(5); + C c2 = new C(-5); + + + List items = Arrays.asList(a, c, c2); + comparator.sortList(items, new OrderProvider() { + @Override + public Integer getOrder(Object obj) { + if (obj == a) { + return 4; + } + if (obj == c2) { + return 2; + } + return null; + } + }); + assertOrder(items, c2, a, c); + } + + @Test + public void arrayNoFactoryMethod() { + A a = new A(); + C c = new C(-50); + B b = new B(); + + Object[] items = new Object[] {a, c, b}; + comparator.sortArray(items, new OrderProvider() { + @Override + public Integer getOrder(Object obj) { + return null; + } + }); + assertOrder(items, c, a, b); + } + + @Test + public void arrayFactoryMethod() { + A a = new A(); + C c = new C(3); + B b = new B(); + + Object[] items = new Object[] {a, c, b}; + comparator.sortArray(items, new OrderProvider() { + @Override + public Integer getOrder(Object obj) { + if (obj == a) { + return 4; + } + if (obj == b) { + return 2; + } + return null; + } + }); + assertOrder(items, b, c, a); + } + + @Test + public void arrayFactoryMethodOverridesStaticOrder() { + A a = new A(); + C c = new C(5); + C c2 = new C(-5); + + Object[] items = new Object[] {a, c, c2}; + comparator.sortArray(items, new OrderProvider() { + @Override + public Integer getOrder(Object obj) { + if (obj == a) { + return 4; + } + if (obj == c2) { + return 2; + } + return null; + } + }); + assertOrder(items, c2, a, c); + } + + private void assertOrder(List actual, Object... expected) { + for (int i = 0; i < actual.size(); i++) { + assertSame("Wrong instance at index '" + i + "'", expected[i], actual.get(i)); + } + assertEquals("Wrong number of items", expected.length, actual.size()); + } + + private void assertOrder(Object[] actual, Object... expected) { + for (int i = 0; i < actual.length; i++) { + assertSame("Wrong instance at index '" + i + "'", expected[i], actual[i]); + } + assertEquals("Wrong number of items", expected.length, expected.length); + } + + + @Order(1) + private static class A { + } + + @Order(2) + private static class B { + } + + private static class C implements Ordered { + private final int order; + + private C(int order) { + this.order = order; + } + + @Override + public int getOrder() { + return order; + } + } + +}