Monday, December 26, 2011

Annotated callback methods in custom class

We're implementing a simple message service framework to demonstrate how we can implement a framework that uses annotations, like JAX-WS. Using annotations minimizes boilerplate code, and promotes high cohesion of classes. It also promotes decoupling, because it prevents forcing the use of framework classes on clients as much as possible.

The messaging framework has one interface with a register method, which is used to register custom objects that handle incoming messages. The framework does not know what the type is of this object. It uses the reflection API to check for annotated methods, which tell the messaging framework to call these methods when a message has to be handled.

The message service framework supports two types of messages, which we define in as an enum type in MessageType.java:

package com.javaeenotes;


public enum MessageType {
    NORMAL,
    URGENT
}

The interface of the message service framework has a method to register custom client objects that will handle incoming messages. The second method of the interface is only included, so we can send messages through this framework. The interface of the message service framework is defined in MessageService.java:

package com.javaeenotes;


public interface MessageService {
    void registerMessageHandler(Object object);

    void sendMessage(String message, MessageType messageType)
        throws Exception;
}

Now, we define our annotation. This annotation can be used by the client application to mark methods that will handle incoming messages generated by the message service framework. It also has a message type enum parameter, so the framework will only call this method if the type of the message matches the specified type parameter. The annotation is defined in MessageMethod.java:

package com.javaeenotes;


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


// Only usable on methods.
@Target(ElementType.METHOD)
// Annotation must be available during runtime.
@Retention(RetentionPolicy.RUNTIME)
public @interface MessageMethod {
    MessageType messageType() default MessageType.NORMAL;
}

A client object will use the annotation to mark callback methods for specified message types. The example client handler has one method for both message types. An example client message handler class for this demo is defined as ExampleMessageHandler.java:

package com.javaeenotes;


public class ExampleMessageHandler {
    @MessageMethod(messageType = MessageType.NORMAL)
    public void handleNormalMessage(String message) {
        System.out.print("NORMAL MESSAGE: " + message + "\n");
    }


    @MessageMethod(messageType = MessageType.URGENT)
    public void handleUrgentMessage(String message) {
        System.out.println("URGENT MESSAGE: " + message + "\n");
    }
}

The most important class of this tutorial is: MessageServiceImpl.java, which you can find below. This is the implementation of our message service framework interface. This class is responsible for determining callback methods provided by the client, and call them when messages arrive.

package com.javaeenotes;


import java.lang.reflect.Method;


public class MessageServiceImpl implements MessageService {
    private Object messageHandler;


    @Override
    public void registerMessageHandler(Object messageHandlerObject) {
        messageHandler = messageHandlerObject;
    }


    @Override
    public void sendMessage(String message, MessageType messageType)
            throws Exception {
        
        for (Method method : messageHandler.getClass().getMethods()) {
            if (method.isAnnotationPresent(MessageMethod.class)) {
                MessageMethod messageMethod
                    = method.getAnnotation(MessageMethod.class);

                if (messageMethod.messageType() == MessageType.NORMAL
                        && messageType == MessageType.NORMAL) {

                    method.invoke(messageHandler, message);
                } else if (messageMethod.messageType()
                        == MessageType.URGENT
                        && messageType == MessageType.URGENT) {

                    method.invoke(messageHandler, message);
                }
            }
        }
    }
}

Now, we only need a Main class to get this demonstration working. The class will create the example client object, and register it in the framework. Then, it will create two example messages, and pass them to the framework, which then will call the annotated callback methods of the example client object. The Main class is defined in: Main.java.

package com.javaeenotes;


public class Main {
    public static void main(String[] args) {
        // Create message service and register message handler.
        MessageService messageService = new MessageServiceImpl();
        messageService.registerMessageHandler(
                new ExampleMessageHandler());

        try {
            // Sending test messages through the message service.
            messageService.sendMessage("This is a normal message.",
                    MessageType.NORMAL);

            messageService.sendMessage("This is an urgent message!",
                    MessageType.URGENT);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

If we run the Main class, we should get the following output:

NORMAL MESSAGE: This is a normal message.
URGENT MESSAGE: This is an urgent message!

1 comment:

  1. Nice explanation.These methods give nice idea for us, Thanks for sharing your thoughts.
    Ecommerce developer

    ReplyDelete