Plugin Your Own Components with Java 8

by Per Minborg

on February 3, 2016

Components are Good!


Spire and Duke is plugging in...
A couple of years ago when I worked in the telecom industry, I spent time cleaning up Java code that mixed the concept of abstraction (interfaces) and implementation (concrete classes implementing one or several interfaces).

Another related problem I have come across several times, is the use of Singletons, used to hold various implementations. To be fair, I have created more than a couple of those singletons myself, but nowadays, I almost never use them.

In this post, I will show why and how to avoid them and at the same time gain some additional advantages like separation of interfaces and implementations using Components and a ComponentHandler.

The ComponentHandler that is shown in the examples a bit further down is derived from the open-source project Speedment that I am contributing to. Speedment is great if you want to write database applications in Java 8 using standard Java 8 Streams. Read more about Speedment here.

Why Are Singletons Bad?

The purpose of a singleton is to guarantee that just a single instance of a class or interface is present at any single time. This is of course not a bad thing in it self. On the contrary, there are many situations when we want this property, for example for logging or for other system components like queues or factories. However, the way people chose to enforce the singleton property is more often than not, simply bad.

In my previous post Enforcing Java Singletons is Very Hard, I presented a number of Singleton Idioms that can be used to enforce the singleton property and in the same post, I showed that it is hard to really enforce the singleton patterns (albeit "cheating" with reflections) and also talked about some other disadvantages of singletons.

Here is one of the idioms from the post, called the Enum Singleton Idiom which I first learned about in Joshua Bloch's book "Effective Java" many years ago.

public enum FooEnumSingleton {

    INSTANCE;

    public static FooEnumSingleton getInstance() { return INSTANCE; }

    public void bar() { }

}

This provides a relatively solid and simple singleton, but there are some drawbacks. Because all enums inherit from the built-in Java class Enum, we can not inherit from other base classes. Furthermore, we can not override methods when we want to test our singleton in JUnit tests, for example if we want to "harnes" our class, call some methods and then inspect its state or functionality. Additionally, there is no support or control over the singletons Lifecycle Management. When we first reference the singleton above, the class loader will create and initialize it, pretty much outside our control. If another programmer calls our singleton, it will load no matter what. Now, if our singleton depends on other singletons, which each depends on yet another singleton and so on, our code will likely break. Perhaps not in the first version where things were easy, but it is just a matter of time until it undoubtedly will fail.

I will talk more about lifecycles in the next section.

Component Lifecycles

The Lifecycle of a component or class can be certain stages the component will pass during the time it exists. For example, a component may be created, added, started, stopped and and then eventually, removed from a system. Using the right tools, a component can easily be setup to react to its own lifecycle events.

The Component Handler

Often, it is very convenient to setup some kind of Component Handler that keeps track of all our singleton components. This way, we can have them all collected at one place and treat them in a consistent way. The Component Handler can also be made to automatically call the component's lifecycle methods whenever they are added, changed or removed from the Component Handler.

Now, do not fall for the temptation to make the Component Handler a singleton under the excuse that "now it is just one singleton". Admittedly, it is better to have just one singleton than having 100 singletons, but it is generally even better to just let our Component Handler be just a normal object and keep track of the Component Handler instance in our code. Especially if we are deploying our application in a web server, where there might be many instances of the "Singletons" under different class loaders.

The Class Mapper

The classical (apologies for the bad word-joke) "Class Mapper" is a good starting point when we want to implement a Component Handler. Take a look at this example:

public interface ClassMapper {

/**
* Puts a new mapping and returns the previous mapping for the given class,
* or null if no mapping existed.
*
* @param <K> key class
* @param <V> item type
* @param keyClass to use
* @param item to add
* @return the previous mapping that existed, or null
* if no mapping existed
*/
<K, V extends K> K put(Class<K> keyClass, V item);

/**
* Returns the mapping for the given class, or null if no mapping exists
*
* @param <K> The class type
* @param clazz the class to use
* @return the mapping for the given class, or null if no mapping exists
*/
public <K> K get(Class<K> clazz);

}

and a corresponding (almost trivial) implementation:

public class ClassMapperImpl implements ClassMapper {

private final Map<Class<?>, Object> map;

public ClassMapperImpl() {
this.map = new ConcurrentHashMap<>();
}

@Override
public <K, V extends K> K put(Class<K> keyClass, V item) {
requireNonNull(keyClass);
requireNonNull(item);
@SuppressWarnings("unchecked")
// We know that it is a safe cast
// because V extends K
final K result = (K) map.put(keyClass, item);
return result;
}

@Override
public <K> K get(Class<K> clazz) {
@SuppressWarnings("unchecked")
// We know that it is a safe cast
// because the previous put()
// guarantees this
final K result = (K) map.get(clazz);
return result;
}

}

The good thing with this is that now we have a small rudimentary Component Handler. Suppose that we have an interface like this:

public interface JokeComponent {

String nextJoke();

}
The nextJoke() method is supposed to return a funny joke, and we want a pluggable concept so we are planning to implement several versions of the JokeComponent. We start by constructing a very simple JokeComponent that just returns a single joke regardless how many times we call the nextJoke() method:

public class StaticJokeComponent implements JokeComponent {

@Override
public String nextJoke() {
return "I went to buy some camouflage trousers the other day,"
+ " but I couldn’t find any.";
}

}
Now we can test our Component Handler as shown in the following program:

public class Main {

public static void main(String[] args) {
final ClassMapper componentHandler = new ClassMapperImpl();
setupComponents(componentHandler);

crankOutTenJokes(componentHandler);
}

private static void setupComponents(ClassMapper componentHandler) {
// Plug in the selected basic JokeGenerator
componentHandler.put(JokeComponent.class, new StaticJokeComponent());
}

private static void crankOutTenJokes(ClassMapper componentHandler) {
// Get the current JokeGenerator that is plugged into the componentHandler
JokeComponent jokeGenerator = componentHandler.get(JokeComponent.class);
// Tell the world who is making the jokes
System.out.println(jokeGenerator.getClass().getSimpleName() + " says:");
// print ten of its joke
IntStream.rangeClosed(1, 10)
.mapToObj(i -> jokeGenerator.nextJoke())
.forEach(System.out::println);
}

}

This will produce the following jokes:

StaticJokeComponent says:
I went to buy some camouflage trousers the other day, but I couldn’t find any.
I went to buy some camouflage trousers the other day, but I couldn’t find any.
I went to buy some camouflage trousers the other day, but I couldn’t find any.
I went to buy some camouflage trousers the other day, but I couldn’t find any.
I went to buy some camouflage trousers the other day, but I couldn’t find any.
I went to buy some camouflage trousers the other day, but I couldn’t find any.
I went to buy some camouflage trousers the other day, but I couldn’t find any.
I went to buy some camouflage trousers the other day, but I couldn’t find any.
I went to buy some camouflage trousers the other day, but I couldn’t find any.
I went to buy some camouflage trousers the other day, but I couldn’t find any.
Even though the joke might be a bit fun the first or second time, we start to lose interest a bit further down the list... Time for improvement by writing a new JokeComponent that doesn't tell the same joke all the time.

public class RandomJokeComponent implements JokeComponent {

private final Random random = new Random();
private final List<String> jokes = Arrays.asList(
"What's blue and doesn't weigh much? Light blue.",
"What do you get when you cross a joke with a rhetorical question?",
"What do you call a fish with no eyes? A fsh.",
"Two men walk into a bar... the third one ducks.",
"A farmer rounded up her 196 cows and got 200 of them."
);

@Override
public String nextJoke() {
return jokes.get(random.nextInt(jokes.size()));
}

}
Now we can plug in this JokeComponent instead and see what happens. Modify the code so it looks like this instead:

    private static void setupComponents(ClassMapper componentHandler) {
// Plug in the selected basic JokeGenerator
// Use the RandomJokeGenerator this time instead ---+
// V
componentHandler.put(JokeComponent.class, new RandomJokeComponent());
}
Now we get another output:
What's blue and doesn't weigh much? Light blue.
Two men walk into a bar... the third one ducks.
A farmer rounded up her 196 cows and got 200 of them.
What do you call a fish with no eyes? A fsh.
What do you get when you cross a joke with a rhetorical question?
A farmer rounded up her 196 cows and got 200 of them.
What do you get when you cross a joke with a rhetorical question?
What do you call a fish with no eyes? A fsh.
What's blue and doesn't weigh much? Light blue.
What do you call a fish with no eyes? A fsh.
This is a small step forward for the humor society, but we could as well write yet another implementation of the JokeComponent that, whenever nextJoke() is called, goes out on the internet and retrieves a funny story from some site. The user code outside the component would remain exactly the same.

Note that the Component Handler can handle any number of interfaces and the interfaces can look completely different and contain different methods, so you could set it up to also handle RidleComponent and perhaps also BlooperComponent that could, for instance, return URL:s to funny films on the net.

The ComponentHandler with Lifecycles


It is fairly easy to expand the Component Handler so it can handle Component Lifecycles. Let us define a simple Lifecycle Component like this.

public interface Component {

void added();

void removed();
}
So, every time we add a Component to the Component Handler, we want the Component Handler to automatically invoke the Component::add method and every time it is removed we want it to call the Component::removed method. That way, we know that all component in the Component Handler will be initialized (by the added() method) properly and also that they will clean up stuff (if needed by the removed() method) when they are removed from the Component Handler. Evidently, every component that we want to add to the Component Handler must implement the Component interface. First, we improve the ClassMapper interface so it takes a generic parameter for the values to extend.

public interface ClassMapper<T> {

<K extends T, V extends K> K put(Class<K> keyClass, V item);

public <K extends T> K get(Class<K> clazz);

}

Then we could write a ComponentHandler as depicted here under:

public class ComponentHandler implements ClassMapper<Component> {

private final Map<Class<? extends Component>, Component> map;

public ComponentHandler() {
this.map = new ConcurrentHashMap<>();
}

@Override
public <K extends Component, V extends K> K put(Class<K> keyClass, V item) {
requireNonNull(keyClass);
requireNonNull(item);
item.added();
@SuppressWarnings("unchecked")
// We know that it is a safe cast
// because V extends K
final K result = (K) map.put(keyClass, item);
if (result != null) {
result.removed();
}
return result;
}

@Override
public <K extends Component> K get(Class<K> clazz) {
@SuppressWarnings("unchecked")
// We know that it is a safe cast
// because the previous put()
// guarantees this
final K result = (K) map.get(clazz);
return result;
}

}

I have made some simplifications in the code, but the general outline becomes obvious. Whenever we add a Component, its added() method will be called and whenever we replace a component with a new one, the old's removed() method is called.

In the example below, we are to make a Queue for Strings that is a pluggable Component in the new ComponentHandler we just wrote. We could define a StringQueueComponent as a Component like this:

public interface StringQueueComponent extends Component {

boolean offer(String msg);

String poll();
}
We could start with a very straightforward implementation using one of the existing Queue implementation already in the Java framework.

public class MemoryStringQueueComponent implements StringQueueComponent {

private final Queue<String> queue = new ConcurrentLinkedQueue<>();

@Override
public boolean offer(String msg) {
return queue.offer(msg);
}

@Override
public String poll() {
return queue.poll();
}

@Override
public void added() {
System.out.println(getClass().getSimpleName() + " added");
}

@Override
public void removed() {
queue.clear();
System.out.println(getClass().getSimpleName() + " removed");
}

}
and then we may come up with an alternative Queue that places the strings on a file as shown in this outline (I have not shown the actual code for file handling because it is quit bulky, but the principle is obvious).

public class FileStringQueueComponent implements StringQueueComponent {

// File variable goes here

@Override
public boolean offer(String msg) {
// Append the string to the file
}

@Override
public String poll() {
// Check if the file has grown
// if yes, read the next line and return it
// if no, return null
}

@Override
public void added() {
// Open the file and trunkate it if contains an old queue.
// Set a pointer to the current file location
}

@Override
public void removed() {
// Close the file and remove it from the file system
}

}
So now, when we put the new ComponentHandler to use as depicted below:

public class Main {

public static void main(String[] args) {
ComponentHandler componentHandler = new ComponentHandler();
setupComponents(componentHandler);
///
putStuffInTheStingQueue(componentHandler);
printStuffInTheStringQueue(componentHandler);

// Select a new queue component (content of the old queue is lost)
componentHandler.put(StringQueueComponent.class, new FileStringQueueComponent());

putStuffInTheStingQueue(componentHandler);
printStuffInTheStringQueue(componentHandler);

}

private static void putStuffInTheStingQueue(ComponentHandler componentHandler) {
StringQueueComponent queue = componentHandler.get(StringQueueComponent.class);
queue.offer("A");
queue.offer("B");
queue.offer("C");
}

private static void printStuffInTheStringQueue(ComponentHandler componentHandler) {
StringQueueComponent queue = componentHandler.get(StringQueueComponent.class);
System.out.println("Stuff in " + queue.getClass().getSimpleName());
String s;
while ((s = queue.poll()) != null) {
System.out.println(s);
}
}

private static void setupComponents(ComponentHandler componentHandler) {
componentHandler.put(StringQueueComponent.class, new MemoryStringQueueComponent());
}

}
it will produce the following output (given a real implementation of the FileStringQueueComponent):

MemoryStringQueueComponent added
Stuff in MemoryStringQueueComponent
A
B
C
FileStringQueueComponent added
MemoryStringQueueComponent removed
Stuff in FileStringQueueComponent
A
B
C

Conclusion


By using a Component Framework, we gain many advantages over singletons :

  • We can control the lifecycle of our components.
  • The ability to test our components becomes much better.
  • We get a clear separation between interfaces an implementations.
  • We collect all our component in one place and access them in a standardized way.
  • We can ensure that our component is not called before it is initialized.
  • Our code will be less likely to fail in a multi-classloader scenario like a web server.


If you want to take a look at a real existing component handler, have a look at Speedment's  component handler on GitHub. For performance reasons, this component handler is more optimized so that we can obtain components without the overhead of looking them up in a Map.


About

Per Minborg

Per Minborg is a Palo Alto based developer and architect, currently serving as CTO at Speedment, Inc. He is a regular speaker at various conferences e.g. JavaOne, DevNexus, Jdays, JUGs and Meetups. Per has 15+ US patent applications and invention disclosures. He is a JavaOne alumni and co-author of the publication “Modern Java”.