Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to guarantee the order in which @PostConstruct methods are invoked?

I have a system which is using Spring for dependency injection. I use annotation-based autowiring. The beans are discovered by component scanning, i.e. my context XML contains this:

<context:component-scan base-package="org.example"/>

I have created a noddy example below to illustrate my problem.

There is a Zoo which is a container for Animal objects. The developer of Zoo does not know which Animal objects will be contained whilst he is developing Zoo; the set of concrete Animal objects instantiated by Spring is known at compile-time, but there are various build profiles resulting in various sets of Animals, and the code for Zoo must not change under these circumstances.

The purpose of Zoo is to allow other parts of the system (illustrated here as ZooPatron) to access the set of Animal objects at runtime, without needing to depend explicitly on certain Animals.

Actually, the concrete Animal classes will all be contributed by various Maven artifacts. I want to be able to assemble a distribution of my project by simply depending on the various artifacts containing these concrete Animals, and have everything autowire correctly at compile-time.

I have attempted to solve this problem (unsuccessfully) by having the individual Animals depend upon the Zoo, in order that they can call a registration method on the Zoo during @PostConstruct. This avoids the Zoo depending explicitly on an explicit list of Animals.

The problem with this approach is that the customers of Zoo wish to interact with it only when all the Animals have registered. There is a finite set of Animals which is known at compile-time, and the registration all happens during the Spring wiring phase of my lifecycle, so a subscription model should be unneccesary (i.e. I don't wish to add Animals to the Zoo at runtime).

Unfortunately, all the customers of Zoo simply depend upon Zoo. This is exactly the same relationship which the Animals have with Zoo. Therefore, the @PostConstruct methods of the Animals and ZooPatron are called in an arbitrary sequence. This is illustrated with the example code below - at the time @PostConstruct is invoked on ZooPatron, no Animals have registered, it is some milliseconds later when they all register.

So there are two types of dependency here, which I am struggling to express in Spring. The customers of Zoo only want to use it once all the Animals are in it. (perhaps "Ark" would have been a better example...)

My question is basically: what is the best way to solve this problem?

@Component
public class Zoo {

    private Set<Animal> animals = new HashSet<Animal>();

    public void register(Animal animal) {
        animals.add(animal);
    }

    public Collection<Animal> getAnimals() {
        return animals;
    }

}

public abstract class Animal {

    @Autowired
    private Zoo zoo;

    @SuppressWarnings("unused")
    @PostConstruct
    private void init() {
        zoo.register(this);
    }

    @Component
    public static class Giraffe extends Animal {
    }

    @Component
    public static class Monkey extends Animal {
    }

    @Component
    public static class Lion extends Animal {
    }

    @Component
    public static class Tiger extends Animal {
    }

}

public class ZooPatron {

    public ZooPatron(Zoo zoo) {
        System.out.println("There are " + zoo.getAnimals().size()
                             + " different animals.");
    }

}

@Component
public class Test {

    @Autowired
    private Zoo zoo;

    @SuppressWarnings("unused")
    @PostConstruct
    private void init() {
        new Thread(new Runnable() {
            private static final int ITERATIONS = 10;
            private static final int DELAY = 5;
            @Override
            public void run() {
                for (int i = 0; i<ITERATIONS; i++) {
                    new ZooPatron(zoo);
                    try {
                        Thread.sleep(DELAY);
                    } catch (InterruptedException e) {
                        // nop
                    }
                }
            }
        }).start();     
    }

}

public class Main {

    public static void main(String... args) {
        new ClassPathXmlApplicationContext("/context.xml");
    }

}

Output:

There are 0 different animals.
There are 3 different animals.
There are 4 different animals.
There are 4 different animals.
... etc

Explanation of accepted solution

Basically the answer is: no, you cannot guarantee the order of @PostConstruct calls without either going "outside" Spring or modifying its behaviour.

The real problem here was not that I wanted to sequence the @PostConstruct invocations, that was merely a symptom of the dependencies being expressed incorrectly.

If the consumers of Zoo depend upon him, and Zoo in turn depends upon Animals, everything works correctly. My mistake was that I didn't want Zoo to depend upon an explicit list of Animal subclasses, and therefore introduced this registration method. As pointed out in the answers, mixing a self-registration mechanism with dependency injection will never work without unnecessary complexity.

The answer is to declare that Zoo is dependent upon a collection of Animals, then allow Spring to populate the collection through auto-wiring.

Thus, there is no hard list of collection members, they are discovered by Spring, but the dependencies are correctly expressed and therefore the @PostConstruct methods happen in the sequence I want.

Thanks for the excellent answers.

like image 416
wool.in.silver Avatar asked Aug 15 '11 15:08

wool.in.silver


4 Answers

You might instead have the Set of Animals @Injected into the Zoo.

@Component
public class Zoo {

    @Inject
    private Set<Animal> animals = new HashSet<Animal>();

    // ...
}

Then Zoo's @PostConstruct should only be called once all the Animals are injected. The only gotcha is that there must be at least one Animal in the system, but it doesn't sound like that should be an issue.

like image 96
Brian Kent Avatar answered Oct 11 '22 13:10

Brian Kent


I don't think there is a way to ensure @PostConstruct order without introducing dependencies.

I think you're looking for trouble trying to mix injection or self registration. To some extent, @PostConstruct call order should not matter - if it does, it might not be the right tool for the job.

A couple ideas for your example

  • try to have an @Autowired on Zoo#animals: no need for self-registration by animals, also the zoo is aware of the animals but not the reverse, which feels cleaner
  • keep the register, but let external actors do the registration (someone is putting the animals in the zoo, right? - they're not showing up at the entrance all by themselves)
  • if you need to insert new animals at any time, but don't want manual insertion, do a more dynamic accessor on zoo: don't store the list of animals, but use the spring context to get all existing instances of the interface.

I don't think there is a 'right' answer, it all depends on your use case.

like image 42
ptyx Avatar answered Oct 11 '22 14:10

ptyx


Reframe your problem so that it doesn't rely on invocation order.

like image 41
user207421 Avatar answered Oct 11 '22 12:10

user207421


The best way, IMO, is to avoid doing too much work during the construction of the object graph (just as in Java, you avoid doing too much work in the constructor), and to avoid calling methods from dependencies when you're not sure they're fully initialized yet.

If you just remove the @PostConstruct annotation from the Test#init() method, and simply invoke it from your main method, after the context has been created, you won't have this problem anymore.

like image 35
JB Nizet Avatar answered Oct 11 '22 14:10

JB Nizet