Over a million developers have joined DZone.

Dynamic subtyping in Java

· Java Zone

What every Java engineer should know about microservices: Reactive Microservices Architecture.  Brought to you in partnership with Lightbend.

Subtyping Issues in Java

Behavior customization is a common requirement for large software systems. A software product cannot fully satisfy requirements of all customers; there are always differences that must be implemented, which are specific to each customer. Thus, software products must be flexible enough to allow these customizations in behavior even without changes in product's source code.

In Java-based software products, a solution for such problems is usually based on a principle called subtyping. Subtyping can be defined on an example of two types A and B: if type B is a subtype of type A, then all code that operates correctly on objects of type A will operate correctly on objects of type B. Naturally, a software product behavior can be customized by replacing some default class A with custom class B where B is a subtype of A.

If subtyping is based on an interface, then implementations of the interface can be independent of each other, which decreases complexity of the system and benefits modularity. At the same time such independence limits code reusability. A custom implementation can use Delegation pattern to call methods, but it is impossible to override a method in the base class.

Another approach for subtyping is direct subclassing of the implementation class. Subclassing allows method overriding in the base class and also introduces design-time dependencies on that class. These dependencies limit subclass reusability.

As you can see standard Java subtyping techniques limit reusability of either a base class or a subclass.

Dynamic subtyping

The approach described below combines the advantages of standard Java subtyping techniques. It allows creation of a new class, which can override methods in an original class, but at the same time, is dependent only on the interface of the original.

Let's start with the following example:
 
Suppose we have an interface Action as described below and its implementation in form of class ActionImpl.

//This is an interface of Action
public interface Action{
void doAction1();
void doAction2();
void doAll();
}

//This is a default implementation of the interface Action
class ActionImpl implements Action{
public void doAction1(){ ... }
public void doAction2(){ ... }
public void doAll(){
doAction1();
doAction2();
}
}

Class ActionImpl implements all methods of the interface and is used as a default implementation.
Now let's suppose we want to override method doAction2 in the ActionImpl in such a way that the new class won't depend on ActionImpl.
Let's create a new class ActionExtension as the following:

//This is a custom implementation that overrides one method.
abstract class ActionExtension extends ImplementationOf<Action> implements Action{
public void doAction2(){

Super.doAction2();
}
}
This class extends a simple base class which looks like this:

//This is  base class for dynamic subclasses
public abstract class ImplementationOf<T>{
/**
* A field for calling super class methods.
* Should be used only in expressions where super keyword can be used.
* */
protected final T Super = null;
}
Note that ActionExtension class is an abstract class, so we don't have to define all methods, just those that we want to override in ActionImpl.
That's all we need to do at design time. Now let's look how it works at run-time.

At the point of code where instance of Action is expected we need to create a new class:

//Fragment of code that creates dynamic subclass of ActionImpl
Class dynamicSubclass = DynamicClassExtender.extend(ActionImpl.class, Action.class, ActionExtension.class);
The created class is a subclass of ActionImpl and contains all methods copied from ActionExtension class and modified in such a way that all calls to methods of Super field are directed to ActionImpl class. This class can be used to create an instance which can be used instead of instance of ActionImpl class:

Action action = (Action) dynamicSubclass.newInstance();
In the real product the code above should be integrated into a dependency injection framework of your choice, and actual mapping of extension classes to implementations should be configurable.

Dynamic class extender

From the previous chapter we saw that all the complexities of dynamic subclassing are hidden in DynamicClassExtender class, which uses a byte code manipulation library to create a direct subclass of ActionImpl from ActionExtension class. In order to see what kind of byte code manipulation is involved let's start with ActionExtender class decompiled using standard javap utility:

public abstract class ActionExtension extends ImplementationOf implements Action{
public ActionExtension();
Code:
0: aload_0
1: invokespecial #10; //Method ImplementationOf."<init>":()V
4: return

public void doAction2();
Code:
0: aload_0
1: getfield #17; //Field Super:Ljava/lang/Object;
4: checkcast #5; //class Action
7: invokeinterface #21, 1; //InterfaceMethodAction.doAction2:()V
12: return

}
Now if we manually create a class that extends ActionImpl and overrides doAction2 method and then decompile it with javap we'll see the following byte code:

public class ActionExtension$ActionImpl extends ActionImpl{
public ActionExtension$ActionImpl();
Code:
0: aload_0
1: invokespecial #8; //Method ActionImpl."<init>":()V
4: return

public void doAction2();
Code:
0: aload_0
1: invokespecial #15; //Method ActionImpl.doAction2:()V
4: return

}
After comparing byte codes from both classes we can figure out a list of manipulations that DynamicClassExtender must apply on ActionExtension class in order to create a dynamic subclass of ActionImpl:
  • change class name to ActionExtension$ActionImpl
  • change access attribute to public, remove abstract attribute
  • change super class name to ActionImpl
  • replace references to ImplementationOf with references to ActionImpl
  • replace method invocations on Super field (instructions 1,4,7 in ActionExtension.doAction2) with method invocations in super class (instruction 1 in ActionExtension$ActionImpl.doAction2)
You can find a proof-of-concept implementation of DynamicClassExtender in the zip file attached to this article. The source code is distributed under the terms of BSD License.

Conclusion

The dynamic approach for subtyping, presented above, may help to create customizable Java applications without sharing implementation source code and without sacrificing code reusability.

Microservices for Java, explained. Revitalize your legacy systems (and your career) with Reactive Microservices Architecture, a free O'Reilly book. Brought to you in partnership with Lightbend.

Topics:

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}