Java Reference

Ready Processing

Introduction

The Ready Processing components allow you to perform application startup operations in a controlled manner. When certain defined conditions are met, your code is notified, and therefore able to perform any desired operations.

Overview

There are two ways this startup processing works.

In the simplest case, you can monitor when beans have been autowired, either after a particular phase, or when all autowiring is complete. This is handled through the CtxEventListener interface. Bean autowiring is handled by the KOS BeanContext.

In the more complex case, you can define when individual objects become ready (that is, fully functional), and objects can be notified when dependent objects are ready.

What is "Ready"

An object is "ready" when it is fully functional.

This is an important concept because some beans, like those that interact with external systems or devices, are not fully operational until these external components are available.

For example, we don’t want the UI to start pouring a drink before a connection to the dispenser is made.

What that in mind, ready processing gives us the opportunity to:

  • Define when an object becomes ready (you decide what that means for each particular object)

  • Issue callbacks to objects that have work to do once the objects it depends on are ready

API Components

In this section, we’ll learn about the four Ready Processing API components.

Annotations:

  • @WhenReady

Interfaces:

  • Ready

  • ReadyListener

Classes:

  • ReadyIndicator

@WhenReady (annotation)

This section describes the @WhenReady annotation, which is used to monitor when a field becomes ready. It takes one optional parameter.

API: @WhenReady annotation
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface WhenReady {

    String[] value() default {""};  // the ready group this object is part of
}

The class that uses this annotation must also implement ReadyListener in order to get the onReady() callback when all @WhenReady objects are ready.

This is only meaningful if the referenced object implements Ready, so that it has an indicator to attach to.

Here’s an example:

Example using the @WhenReady annotation
public class MyClass1 {

    // (1)
    @Autowired
    @WhenReady
    private MyClass2 myClass2;

    // (2)
    @Autowired
    @WhenReady("Group-A")
    private MyClass3 myClass3;

    // (3)
    @Autowired
    @WhenReady({"Group-A", "Group-B"})
    private MyClass4 myClass4;

    // . . .
}

As you can see in the above figure, all of the autowired fields are also annotated with @WhenReady.

Looking at the code:

  • (1) No "group" parameter is given. This indicates TBD.

  • (2) The group parameter is "Group-A". TBD

  • (3) Two groups are defined.

For this code to actually do something, this class must implement the ReadyListener interface, which we see in the next section.

ReadyListener (interface)

For the @WhenReady annotation to be of any value, the class must also implement the ReadyListener interface. This interface defines two callbacks that are made when a ready condition is met.

API: ReadyListener interface
public interface ReadyListener {

    void onReady();

    default void onGroupReady(String group) {
    }
}

The onReady() method (required) is called when all @WhenReady annotations in the object become ready. This will never be called more than once, and will never be called if any dependent object never becomes ready. This can be used in conjunction with onGroupReady() to know when all groups have been become ready.

The onGroupReady() method (optional) is called when all fields annotated with @WhenReady and a particular group become ready. The value of @WhenReady is a group name (default empty string) which allows for multiple callbacks to occur, once for each time all objects in a named group are ready. The order of callbacks is non-deterministic, and will occur as the dependent objects become ready and complete the groups. This can be overridden in order to perform a phased startup based on the availability of various combinations of beans. Multiple groups can be enumerated in each @WhenReady annotation.

Let’s add to the previous example to see the ReadyListener working in conjunction with @WhenReady:

Example using @WhenReady and ReadyListener
public class MyClass1 implements ReadyListener {

    @Autowired
    @WhenReady
    private MyClass2 myClass2;

    @Autowired
    @WhenReady("Group-A")
    private MyClass3 myClass3;

    @Override
    public void onGroupReady(String group) {
        switch (group) {
            case "Group-A":
                // We know myClass3 is now ready, so perform related work.
                break;
            default:
                // Group we're not interested in:
                break;
        }
    }

    @Override
    public void onReady() {
        // We now know that both myClass2 and myClass3 are ready.
    }
}

In the figure above, you see that both methods of the ReadyListener interface are implemented.

The onGroupReady() method is called every time a group of beans is ready.

The onReady() method is called one time, after all beans in the context are ready.

Ready (interface)

Continuing on with our example, how do MyClass2 and MyClass3 indicate if they’re ready or not?

This is accomplished by implementing the Ready interface. When a class implements the Ready interface, it is telling the rest of the system that there’s some work it must perform before it can be relied upon by other objects. Other objects can then query this object’s isReady() method to determine if it is indeed ready or not.

The Ready interface defines one required and two optional methods. The optional methods are almost never overridden.

API: Ready interface
public interface Ready {

    // Returns the readyIndicator object:
    ReadyIndicator getReady();

    // Returns true if the implementing object is ready:
    default boolean isReady() {
        return getReady().isReady();
    }

    // Indicates that the implementing object is ready:
    default void setReady() {
        getReady().setReady();
    }
}

The following is a partial example of a class implementing the Ready interface. The next section will finish this by bringing the ReadyIndicator into play.

A partial example using the Ready interface
public class MyClass2 implements Ready {

    @Override
    public ReadyIndicator getReady() {
        // Return readyIndicator (not defined yet)
    }
}

ReadyIndicator (class)

The ReadyIndicator class is used in conjunction with the Ready interface.

API: ReadyIndicator class
public class ReadyIndicator {

    // Methods:
    public void setReady();
    public boolean isReady();
}

Building on the partial example given in the figure above, we now have a more complete example:

A more complete example using the Ready interface
public class MyClass2 implements Ready {

    private final ReadyIndicator readyIndicator = new ReadyIndicator();

    @Override
    public ReadyIndicator getReady() {
        return readyIndicator;
    }
}

Example Code

This section gives a more complete example, which uses all API components associated with this topic.

MyClass1

This class depends on three objects that get inject (autowired): myClass2, myClass3, and myClass4.

When all objects (throughout the entire application) whose @WhenReady group is "Apple" have been injected, then onGroupReady() is called with the group parameter equal to "Apple". The myClass3 object then performs some work that relies on all objects in the apple group being ready.

Likewise, when all objects whose @WhenReady group is "Banana" have been injected, then onGroupReady() is called with the group parameter equal to "Banana". The myClass4 object then performs some work that relies on all objects in the banana group being ready.

The same applies when the @WhenReady group is "Cherry".

Finally, the onReady() method is called when all objects in the entire application has been instantiated, injected, and have indicated that they are ready.

MyClass1 class
public class MyClass1 implements ReadyListener {

    @Autowired
    @WhenReady
    private MyClass2 myClass2;

    @Autowired
    @WhenReady("Apple")
    private MyClass3 myClass3;

    @Autowired
    @WhenReady({"Banana", "Cherry"})
    private MyClass4 myClass4;

    //--- Implement the ReadyListner ---

    @Override
    public void onGroupReady(String group) {
        switch (group) {
            case "Apple":
                myClass3.doSomeAppleRelatedWork();
                break;
            case "Banana":
                myClass4.doSomeBananaRelatedWork();
                break;
            case "Cherry":
                myClass4.doSomeCherryRelatedWork();
                break;
            default:
                // Group we're not interested in:
                break;
        }
    }

    @Override
    public void onReady() {
        // We now know that myClass2, myClass3, and myClass4 are all ready.
        myClass2.doSomeWork();
        myClass3.doMoreWork();
        myClass4.doMoreWork();
    }
}

MyClass2

This object is ready when its autowiring is complete. Therefore, the onCtxAutowiringCompleted() method sets the "is ready" indicator. This also sets the myClass3 "is ready" indicator, demonstrating that one object can indicate that another object is ready.

MyClass2 class
public class MyClass2 implements CtxEventListner, Ready {

    private final ReadyIndicator readyIndicator = new ReadyIndicator();

    @Autowired
    private MyClass3 myClass3;

    //--- Implement the CtxEventListner ---

    @Override
    public void onCtxAutowiringCompleted() {
        readyIndicator.setReady();  // this object is ready when autowiring is finished
        myClass3.setReady();        // when this class is ready, so is myClass3
    }

    //--- Implement the ReadyListner ---

    @Override
    public ReadyIndicator getReady() {
        return readyIndicator;
    }
}

MyClass3

The MyClass3 object is told by the MyClass2 class when it’s ready, therefore it doesn’t need to do much.

MyClass3 class
public class MyClass3 implements Ready {

    private final ReadyIndicator readyIndicator = new ReadyIndicator();

    //--- Implement the ReadyListner ---

    @Override
    public ReadyIndicator getReady() {
        return readyIndicator;
    }
}

MyClass4

The MyClass4 class starts a thread (code not given) in its constructor. This hypothetical thread performs all of the startup that it needs, after which it marks the MyClass4 object ready.

MyClass4 class
public class MyClass4 implements Ready {

    private final ReadyIndicator readyIndicator = new ReadyIndicator();

    public class MyClass4() {
        // Start a thread that runs which will eventually make the indicator "ready".
    }

    //--- Implement the ReadyListner ---

    @Override
    public ReadyIndicator getReady() {
        return readyIndicator;
    }
}

Summary

This concludes the section on the KOS bean context. You should now have a good idea how to create some beans, wire them together, and perform actions when they are available and ready. If it’s still a bit fuzzy, we’d encourage to re-read these five pages, as seeing the information again after gaining some core understanding will help with the learning process.

If you’re ready to move ahead, take a look at the TBD section.

Previous
Next
On this page
Java Development
Seamlessly transition from Legacy+ systems to Freestyle microdosing and advanced distributed dispense systems.
UI Development
Using KOS SDKs, integrating Consumer and Non-consumer facing UIs becomes seamless, giving you less hassle and more time to create.
Video Library
Meet some of our development team, as they lead you through the tools, features, and tips and tricks of various KOS tools.
Resources
Familiarize yourself with KOS terminology, our reference materials, and explore additional resources that complement your KOS journey.
Copyright © 2024 TCCC. All rights reserved.