Java Reference

Listeners

This application note explores how KOS objects can communicate with each other using KOS listeners.

Prerequisites

You should have completed the First One application note, as it builds the framework used in this and other app notes.

Overview

It’s common in software design for objects to communicate with each other. The most obvious way is through direct method calls. However, this approach can lead to an overly coupled solution that becomes difficult to maintain.

An alternative is to use messages. KOS provides an excellent mechanism to both send and receive messages using its ListenerList class.

lucid app note listeners
Figure 1. KOS acts as an intermediary between message senders and receivers

With this technique, senders are unaware of which objects are receiving, and receivers are unaware of which objects are sending. Producers simply say "I send this type of message", while consumers say "I want to receive this type of message". This loose coupling between objects leads to easier development and maintenance.

Message

A message is simply a unit of communication that a source sends to one or more recipients. A message may or may not contain a data payload. Messages are also referred to as events.

Steps

In this section, we’ll add code to our app note framework developed in the First One page.

We need to do three things:

  1. Define what message is to be sent and what (if any) payload does it have

  2. Define the receiver class or classes (the "listeners")

  3. Define the sender class

Source code for this and most other Java reference pages is available on GitHub.

1) Define messages

We are going to create two messages:

  • one without a payload : somethingHappened(), and

  • one with a payload : somethingHappenedWithData(SomeData someData).

Both are defined inside a single listener interface:

API: MessageListener interface
package com.example.listeners;

public interface MessageListener {

    void somethingHappened();

    void somethingHappenedWithData(SomeData someData);
}

Here’s our payload, which is a simple data bean:

SomeData payload bean
package com.example.listeners;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class SomeData {
    private int id;
    private String firstName;
    private String lastName;
}

2) Define the receivers

For our example, we are going to set up two receivers, Listener1 and Listener2. Here’s the first listener:

Listener1 class
package com.example.listeners;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Listener1 implements MessageListener { (1)

    @Override
    public void somethingHappened() { (2)
        log.info("> Listener1.somethingHappened()");
    }

    @Override
    public void somethingHappenedWithData(SomeData someData) { (3)
        log.info("> Listener1.somethingHappenedWithData()");
        log.info("> {}", someData);
    }
}
1 Implement the message listener interface
2 Override the first message
3 Override the second message

As you can see, we are simply logging information when those methods are executed.

The second listener is identical to the first except for a couple log statements:

Listener2 class
package com.example.listeners;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Listener2 implements MessageListener {

    @Override
    public void somethingHappened() {
        log.info("> Listener2.somethingHappened()");
    }

    @Override
    public void somethingHappenedWithData(SomeData someData) {
        log.info("> Listener2.somethingHappenedWithData()");
        log.info("> {}", someData);
    }
}

These two listeners will now get called whenever any sender sends those messages.

3) Define the sender

Next, we create a message sender that we name Speaker. Here it is:

Speaker class
package com.example.listeners;

import com.tccc.kos.commons.core.context.annotations.Autowired;
import com.tccc.kos.commons.util.ListenerList;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Speaker {

    @Autowired
    private ListenerList<MessageListener> listenerList; (1)

    public void saySomething() {
        log.info("> Speaker.saySomething()");
        listenerList.fireEvent(MessageListener::somethingHappened); (2)
    }

    public void saySomethingWithData() {
        log.info("> Speaker.saySomethingWithData()");

        SomeData someData = new SomeData(23, "Fred", "Flintstone");
        listenerList.fireEvent(listener -> listener.somethingHappenedWithData(someData)); (3)
    }

    public void saySomethingWithDataToSpecificClass() {
        log.info("> Speaker.saySomethingWithDataToSpecificClass()");

        SomeData someData1 = new SomeData(24, "Wilma", "Flintstone");
        listenerList.fireEvent(Listener1.class, listener -> listener.somethingHappenedWithData(someData1)); (4)

        SomeData someData2 = new SomeData(25, "Barney", "Rubble");
        listenerList.fireEvent(Listener2.class, listener -> listener.somethingHappenedWithData(someData2)); (5)
    }
}
1 Autowire a ListenerList indicating the type of message it will handle (KOS instantiates this object)
2 Send the message that contains no payload to all listeners
3 Send the message with given payload to all listeners
4 Send a message to only the Listener1 class
5 Send a message to only the Listener2 class

Prepare Code

In order to execute our code, we need to modify our KOS application by adding a ListenerModule class and making a tweak to MyKosApp.

ListenerModule class

The ListenerModule class initializes our application and then runs its code:

ListenerModule class
package com.example.listeners;

import com.example.Module;
import com.tccc.kos.commons.core.context.BeanContext;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ListenerModule implements Module {

    @Override
    public void init(BeanContext ctx) {
        log.info("> ListenerModule.init()");
        ctx.add(new Speaker()); (1)
        ctx.add(new Listener1());
        ctx.add(new Listener2());
    }

    @Override
    @SneakyThrows
    public void run() {
        log.info("> ListenerModule.run()");
        Speaker speaker = Objects.requireNonNull(ctx.getBean(Speaker.class)); (2)
        Thread.sleep(1000);
        speaker.saySomething(); (3)
        Thread.sleep(1000);
        speaker.saySomethingWithData(); (3)
        Thread.sleep(1000);
        speaker.saySomethingWithDataToSpecificClass(); (3)
    }
}
1 Create each of our objects and add them to the context
2 Grab a reference to the speaker class
3 Issue the three different calls to test our messaging

MyKosApp class

Change the one line in our KOS application class as indicated below:

MyKosApp class
package com.example;

public class MyKosApp extends SystemApplication<BaseAppConfig> {

    private Module module;

    public void initModule() {
        module = new ListenerModule(getCtx()); (1)
    }

    // . . .
}
1 Modify this line to instantiate the ListenerModule

Project structure

project structure
Figure 2. Project structure

Debug The Code

Debug this program in your IDE and monitor the log output.

Output logs

The application log output should look something like this (blank lines inserted for clarity):

ListernerModule output
> ====== BEGIN =========================================
> MyKosApp.load()
> ListenerModule.init()
> MyKosApp.start()
> ListenerModule.run()

> Speaker.saySomething() (1)
> Listener2.somethingHappened() (2)
> Listener1.somethingHappened()

> Speaker.saySomethingWithData() (3)
> Listener2.somethingHappenedWithData() (4)
> SomeData(id=23, firstName=Fred, lastName=Flintstone)
> Listener1.somethingHappenedWithData()
> SomeData(id=23, firstName=Fred, lastName=Flintstone)

> Speaker.saySomethingWithDataToSpecificClass() (5)
> Listener1.somethingHappenedWithData()
> SomeData(id=24, firstName=Wilma, lastName=Flintstone) (6)
> Listener2.somethingHappenedWithData()
> SomeData(id=25, firstName=Barney, lastName=Rubble)
> ====== END ===========================================
1 The Speaker class saySomething() method is executed, which fires the somethingHappended() event
2 Both listeners receive that message (note the receive order is not guaranteed)
3 The Speaker class saySomethingWithData() method is executed, which fires the somethingHappenedWithData() event
4 Both listeners receive that message and display the same data
5 The Speaker class now sends a message only to Listener1
6 The Speaker class now sends a message only to Listener2

Summary

In this application note we implemented code to see how the KOS system can assist with object-to-object communication using a loosely-coupled messaging technique.

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.