Avoiding Raw Types in Java Message Manager

goal

I am trying to create a MessageDispatcher that converts messages from a third-party API into user messages and then sends them to a user registered as a user.

The user will expect:

  • Define an interface for each type of user message.
  • Register a listener with a message manager for each type of message.
  • Transfer raw / third-party data to the message manager.
  • Process messages sent to listeners.

Description of the problem

Unfortunately, I cannot avoid using a raw type to achieve my desired API. I read elsewhere that there are no exceptional cases of using Raw types, and they exist only in the language for backward compatibility.

Is there a way to change the code below to work or do I need to reverse engineer my API?

Interfaces

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 message might look like this:

 public interface MyMessage { public String getName(); } 

Register listeners

A 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 manager can implement this method:

 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. } 

Sending messages

When a new message receives a MessageDispatcher, it creates a dynamic proxy for the object and sends it to the appropriate listener. But this is my problem:

 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; // eg = MyMessage.class; Object dynamicProxy; // eg = 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 to use a type, I cannot send 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. 

This makes sense because I just can't figure out what types of data my listener accepts and what data types I pass to him.

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. 
+4
source share
3 answers

You do not need to use raw types - just enter wildcard types in the type that does what you want. This species flies in the face of type safety. And this will give a warning that you can ignore. But this proves that it is impossible to use raw types.

 MessageListener<Object> listener = (MessageListener<Object>)messageClassToListenerMap.get(messageClass); listener.onMessage(dynamicProxy); 
+1
source

You cannot use generics here because the exact type is known only at runtime, so you need to use insecure listing. Raw types do not check types, therefore, this works. Generics only works at compile time, your dispatcher works at runtime.

So you should explicitly check this:

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

I think this is the wrong design. I would recommend doing something like this:

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

when you can send messages by interface class and method name. (you can use interfaces with one method, but not so nice imho). Moreover, Spring already has an infrastructure for it: MethodInterceptor , RemoteExporter , RemoteInvocation and some related ones.

+1
source

I have trubble, when I see your code, why are you using a proxy for your message class ... a message is just a java bean to sign a message event happened, just tell the listener that there is a message of a certain type, you have to do that to have the role of a listener ... this is the role of the message .... I do not understand why there is a proxy for the message, it is so amazing ......

0
source

Source: https://habr.com/ru/post/1390253/


All Articles