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; + } + } + +}