Avoiding raw types in Java message dispatcher

1.6k views Asked by At

Objective

I am trying to build a MessageDispatcher that converts messages from a 3rd party API in to user defined messages and then dispatches them to a user registered listener.

The user will be expected to:

  1. Define an interface for each type of user message.
  2. Register a listener with the message dispatcher for each message type.
  3. Pass raw/3rd party data in to the message dispatcher.
  4. Handle messages passed back to listeners.

Problem Description

Unfortunately I can't seem to avoid using a raw type to achieve my desired API. I read elsewhere that there are no exceptional cases for using Raw types and they only exist in the language for backwards compatibility.

Is there a way I can change the code below to work or do I need to re-design my API?

Interfaces

The MessageDispatcher implements the following interface:

public interface MessageDispatcher {

    // Register a listener for a given user defined message type.
    public <T> void registerListener(
        Class<T> messageClass, 
        MessageListener<T> listener);

    // Receive data in 3rd party format, convert and dispatch.
    public void onData(Data data);

}

The MessageListener interface is defined as:

public interface MessageListener<T> {

    public void onMessage(T message);   

}

An example user messages might look like this:

public interface MyMessage {

    public String getName();   

}

Registering Listeners

The user can register a listener as follows:

messageDispatcher.registerListener(MyMessage.class, 
    new MessageListener<MyMessage.class>() {
    @Override

   public void onMessage(MyMessage message) {
        System.out.println("Hello " + message.getName());
    }
}

A standard message dispatcher might implement the method like this:

private Map<Class<?>,MessageListener<?>> messageClassToListenerMap;

public <T> void registerListener(
    Class<T> messageClass, 
    MessageListener<T> listener) {

    messageClassToListenerMap.put(messageClass, listener);

    // SNIP: Process the messageClass and extract the information needed
    // for creating dynamic proxies elsewhere in a proxy factory.

}

Dispatching Messages

When a new message is received by the MessageDispatcher it creates a dynamic proxy for the object and dispatches it to an appropriate listener. But this is where my problem is:

public void onData(Data data) {

    // SNIP: Use proxy factory (not shown) to get message class and
    // dynamic proxy object appropriate to the 3rd party data.
    Class<?> messageClass;  // e.g. = MyMessage.class;
    Object dynamicProxy;    // e.g. = DynamicProxy for MyMessage.class;

    // TODO: How to I pick the appropriate MessageListener and dispatch the
    // dynamicProxy in a type safe way?  See below.

}

If I try and use the type I can't dispatch the data:

// Assuming a listener has been registered for the example:
MessageListener<?> listener = messageClassToListenerMap.get(messageClass);

listener.onMessage(dynamicProxy); // ERROR: can't accept Object.
listener.onMessage(messageClass.cast(dynamicProxy); // ERROR: Wrong capture.

It makes sense, because there's no way I can know what type of data my listener accepts and what type of data I am passing it.

But if I use raw types it works fine:

// Assuming a listener has been registered for the example:
MessageListener listener = messageClassToListenerMap.get(messageClass);  
listener.onMessage(dynamicProxy); // OK, provided I always pass the correct type of object.
3

There are 3 answers

1
newacct On BEST ANSWER

You don't need to use raw types -- just cast the wildcarded types into a type that does what you want. This kinda flies in the face of type safety. And it will give an unchecked cast warning, which you can ignore. But it proves that it's possible to not use raw types.

MessageListener<Object> listener = (MessageListener<Object>)messageClassToListenerMap.get(messageClass);

listener.onMessage(dynamicProxy);
3
kan On

You cannot use generics here, because exact type is known only at run-time, so you have to use unsafe cast. Raw types don't check types, hence it works. Generics works only in compile time, your dispatcher works in run time.

So, you should explicitly check it:

MessageListener listener = messageClassToListenerMap.get(messageClass);  
if(!messageClass.isAssignableFrom(dynamicProxy.getClass()))
  throw new Something();
listener.onMessage(dynamicProxy);

I think it's wrong design. I would recommend do something like that:

interface MyMessageListener
{
  void onMessageA(String name);
  void onMessageB(String otherParam);
}

when you could dispatch messages by the interface class and method name. (you could use interfaces with single method, but not so nice imho). Moreover, the Spring already has infrastructure for it: MethodInterceptor, RemoteExporter, RemoteInvocation and some related.

1
ledkk On

i have a trubble when i see you code , why do you use the proxy for you message class ... the message is just a java bean to sign a message event had happen , you just tell the listener that , there is a some type message had happen , you should do something to have do a listener's role ... this is a message's role .... i don't konw why there is a proxy for message , it so surprise......