Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Instantiate beans in order in Spring?

Is it possible to set order of instantiation in Spring?

I don't want to use @DependsOn and I don't want to use Ordered interface. I just need an order of instantiation.

The following usage of @Order annotation does not work:

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;

/**
 * Order does not work here
 */
public class OrderingOfInstantiation {

   public static class MyBean1 {{
      System.out.println(getClass().getSimpleName());
   }}

   public static class MyBean2 {{
      System.out.println(getClass().getSimpleName());
   }}

   @Configuration
   public static class Config {

      @Bean
      @Order(2)
      public MyBean1 bean1() {
         return new MyBean1();
      }

      @Bean
      @Order(1)
      public MyBean2 bean2() {
         return new MyBean2();
      }

   }

   public static void main(String[] args) {
      new AnnotationConfigApplicationContext(Config.class);
   }

}

Beans are still instantiated in lexicographic order.

Why it does not work here?

Can I rely of lexicographic order anyway?

UPDATE

I would like any solution allowing to provide order of creation.

The goal is to populate collections at config level in correct order. Depends on -- does not match the task. Any "explanations" on why Spring does not like to have ordered instantiations -- also does not match the task.

Order means order :)

like image 323
Dims Avatar asked Mar 23 '16 19:03

Dims


Video Answer


2 Answers

From @Order javadoc

NOTE: Annotation-based ordering is supported for specific kinds of components only — for example, for annotation-based AspectJ aspects. Ordering strategies within the Spring container, on the other hand, are typically based on the Ordered interface in order to allow for programmatically configurable ordering of each instance.

So I guess Spring just doesn't follow @Order() when creating beans.

But if you just want to populate collections, maybe this is good enough for you:

import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import java.lang.annotation.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@Configuration
public class OrderingOfInstantiation {

    public static void main(String[] args) {
        new AnnotationConfigApplicationContext(OrderingOfInstantiation.class);
    }

    @Component
    @CollectionOrder(collection = "myBeans", order = 1)
    public static class MyBean1 {{
        System.out.println(getClass().getSimpleName());
    }}

    @Component
    @CollectionOrder(collection = "myBeans", order = 2)
    public static class MyBean2 {{
        System.out.println(getClass().getSimpleName());
    }}

    @Configuration
    public static class CollectionsConfig {

        @Bean
        List<Object> myBeans() {
            return new ArrayList<>();
        }
    }

    // PopulateConfig will populate all collections beans
    @Configuration
    public static class PopulateConfig implements ApplicationContextAware {

        @SuppressWarnings("unchecked")
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            Multimap<String, Object> beansMap = MultimapBuilder.hashKeys().arrayListValues().build();

            // get all beans
            applicationContext.getBeansWithAnnotation(CollectionOrder.class)
                    .values().stream()

                    // get CollectionOrder annotation
                    .map(bean -> Pair.of(bean, bean.getClass().getAnnotation(CollectionOrder.class)))

                    // sort by order
                    .sorted((p1, p2) -> p1.getRight().order() - p2.getRight().order())

                    // add to multimap
                    .forEach(pair -> beansMap.put(pair.getRight().collection(), pair.getLeft()));

            // and add beans to collections
            beansMap.asMap().entrySet().forEach(entry -> {
                Collection collection = applicationContext.getBean(entry.getKey(), Collection.class);
                collection.addAll(entry.getValue());

                // debug
                System.out.println(entry.getKey() + ":");
                collection.stream()
                        .map(bean -> bean.getClass().getSimpleName())
                        .forEach(System.out::println);
            });
        }

    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE})
    @Documented
    public @interface CollectionOrder {
        int order() default 0;

        String collection();
    }
}

It won't change instantiation order, but you'll get ordered collections.

like image 74
Yoav Aharoni Avatar answered Oct 17 '22 12:10

Yoav Aharoni


If you want to make sure that a specific bean is created before another bean you can use the @DependsOn annotation.

@Configuration
public class Configuration {

   @Bean 
   public Foo foo() {
   ...
   }

   @Bean
   @DependsOn("foo")
   public Bar bar() {
   ...
   }
}

Keep in mind that this does not set the order, it only guarantees that the bean "foo" is created before "bar". JavaDoc for @DependsOn

like image 32
Richard Avatar answered Oct 17 '22 14:10

Richard