Changed handler inheritance, added unit tests and docu
This commit is contained in:
parent
1ed1bb29a8
commit
d08470c339
19
README.md
19
README.md
@ -9,7 +9,7 @@ Read this documentation to get an overview of its features and how cool this mes
|
||||
You can also check out the <a href="http://codeblock.engio.net/?p=37" target="_blank">performance comparison</a>
|
||||
which also contains a partial list of the features of the compared implementations.
|
||||
|
||||
The current version is 1.1.0 and it is available from the Maven Central Repository. See the release notes for more details.
|
||||
The current version is 1.1.1 and it is available from the Maven Central Repository. See the release notes for more details.
|
||||
|
||||
Table of contents:
|
||||
+ [Features](#features)
|
||||
@ -60,8 +60,8 @@ Listener definition (in any bean):
|
||||
// do something
|
||||
}
|
||||
|
||||
// this handler will be invoked asynchronously
|
||||
@Listener(dispatch = Mode.Asynchronous)
|
||||
// this handler will be invoked concurrently
|
||||
@Listener(delivery = Mode.Concurrent)
|
||||
public void handleSubTestEvent(SubTestEvent event) {
|
||||
// do something more expensive here
|
||||
}
|
||||
@ -69,13 +69,13 @@ Listener definition (in any bean):
|
||||
// this handler will receive messages of type SubTestEvent
|
||||
// or any of its sub types that passe the given filter(s)
|
||||
@Listener(priority = 10,
|
||||
dispatch = Mode.Synchronous,
|
||||
delivery = Mode.Sequential,
|
||||
filters = {@Filter(Filters.SpecialEvent.class)})
|
||||
public void handleFiltered(SubTestEvent event) {
|
||||
//do something special here
|
||||
}
|
||||
|
||||
@Listener(dispatch = Mode.Synchronous, rejectSubtypes = true)
|
||||
@Listener(delivery = Mode.Sequential, rejectSubtypes = true)
|
||||
@Enveloped(messages = {TestEvent.class, TestEvent2.class})
|
||||
public void handleVariousEvents(MessageEnvelope envelope) {
|
||||
// the envelope will contain either an instance of TestEvent or TestEvent2
|
||||
@ -135,6 +135,15 @@ Of course you can always clone the repository and build from source
|
||||
|
||||
<h2>Release Notes</h2>
|
||||
|
||||
<h3>1.1.1</h3>
|
||||
|
||||
+ Introduced new property to @Listener annotation that allows to activate/deactivate any message handler
|
||||
+ Full support of proxies created by cglib
|
||||
+ Message handler inheritance changed! See wiki page about handler definition for more details.
|
||||
+ Changed @Listener property "dispatch" to "delivery" and renamed the associated enumeration values to
|
||||
more precisely indicate their meaning
|
||||
+ Added more unit tests
|
||||
|
||||
<h3>1.1.0</h3>
|
||||
|
||||
First stable release!
|
||||
|
@ -29,6 +29,15 @@ public class ReflectionUtils {
|
||||
return methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses the class hierarchy upwards, starting at the given subclass, looking
|
||||
* for an override of the given methods -> finds the bottom most override of the given
|
||||
* method if any exists
|
||||
*
|
||||
* @param overridingMethod
|
||||
* @param subclass
|
||||
* @return
|
||||
*/
|
||||
public static Method getOverridingMethod(Method overridingMethod, Class subclass) {
|
||||
Class current = subclass;
|
||||
while(!current.equals(overridingMethod.getDeclaringClass())){
|
||||
@ -67,7 +76,7 @@ public class ReflectionUtils {
|
||||
}
|
||||
|
||||
private static boolean isOverriddenBy(Method superclassMethod, Method subclassMethod) {
|
||||
// if the declaring classes are the same or the subclass method is not defined in the subbclass
|
||||
// if the declaring classes are the same or the subclass method is not defined in the subclass
|
||||
// hierarchy of the given superclass method or the method names are not the same then
|
||||
// subclassMethod does not override superclassMethod
|
||||
if (superclassMethod.getDeclaringClass().equals(subclassMethod.getDeclaringClass())
|
||||
|
@ -17,10 +17,12 @@ public @interface Listener {
|
||||
|
||||
Filter[] filters() default {}; // no filters by default
|
||||
|
||||
Mode dispatch() default Mode.Synchronous;
|
||||
Mode delivery() default Mode.Sequential;
|
||||
|
||||
int priority() default 0;
|
||||
|
||||
boolean rejectSubtypes() default false;
|
||||
|
||||
boolean enabled() default true;
|
||||
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ public class MessageHandlerMetadata {
|
||||
this.handler = handler;
|
||||
this.filter = filter;
|
||||
this.listenerConfig = listenerConfig;
|
||||
this.isAsynchronous = listenerConfig.dispatch().equals(Mode.Asynchronous);
|
||||
this.isAsynchronous = listenerConfig.delivery().equals(Mode.Concurrent);
|
||||
this.envelope = handler.getAnnotation(Enveloped.class);
|
||||
this.acceptsSubtypes = !listenerConfig.rejectSubtypes();
|
||||
if(this.envelope != null){
|
||||
@ -86,4 +86,7 @@ public class MessageHandlerMetadata {
|
||||
}
|
||||
|
||||
|
||||
public boolean isEnabled() {
|
||||
return listenerConfig.enabled();
|
||||
}
|
||||
}
|
||||
|
@ -59,19 +59,31 @@ public class MetadataReader {
|
||||
// get all listeners defined by the given class (includes
|
||||
// listeners defined in super classes)
|
||||
public List<MessageHandlerMetadata> getMessageHandlers(Class<?> target) {
|
||||
// get all handlers (this will include overridden handlers)
|
||||
List<Method> allMethods = ReflectionUtils.getMethods(AllMessageHandlers, target);
|
||||
List<MessageHandlerMetadata> handlers = new LinkedList<MessageHandlerMetadata>();
|
||||
for(Method handler : allMethods){
|
||||
// get all handlers (this will include all (inherited) methods directly annotated using @Listener)
|
||||
List<Method> allHandlers = ReflectionUtils.getMethods(AllMessageHandlers, target);
|
||||
// retain only those that are at the bottom of their respective class hierarchy (deepest overriding method)
|
||||
List<Method> bottomMostHandlers = new LinkedList<Method>();
|
||||
for(Method handler : allHandlers){
|
||||
if(!ReflectionUtils.containsOverridingMethod(allHandlers, handler)){
|
||||
bottomMostHandlers.add(handler);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
List<MessageHandlerMetadata> filteredHandlers = new LinkedList<MessageHandlerMetadata>();
|
||||
// for each handler there will be no overriding method that specifies @Listener annotation
|
||||
// but an overriding method does inherit the listener configuration of the overwritten method
|
||||
for(Method handler : bottomMostHandlers){
|
||||
Listener listener = handler.getAnnotation(Listener.class);
|
||||
if(!listener.enabled() || !isValidMessageHandler(handler)) continue; // disabled or invalid listeners are ignored
|
||||
Method overriddenHandler = ReflectionUtils.getOverridingMethod(handler, target);
|
||||
if(overriddenHandler == null && isValidMessageHandler(handler)){
|
||||
// add the handler only if it has not been overridden because
|
||||
// either the override in the subclass deactivates the handler (by not specifying the @Listener)
|
||||
// or the handler defined in the subclass is part of the list and will be processed itself
|
||||
handlers.add(getHandlerMetadata(handler));
|
||||
// if a handler is overwritten it inherits the configuration of its parent method
|
||||
MessageHandlerMetadata handlerMetadata = new MessageHandlerMetadata(overriddenHandler == null ? handler : overriddenHandler,
|
||||
getFilter(listener), listener);
|
||||
filteredHandlers.add(handlerMetadata);
|
||||
|
||||
}
|
||||
}
|
||||
return handlers;
|
||||
return filteredHandlers;
|
||||
}
|
||||
|
||||
|
||||
@ -82,6 +94,9 @@ public class MetadataReader {
|
||||
|
||||
|
||||
private boolean isValidMessageHandler(Method handler) {
|
||||
if(handler == null || handler.getAnnotation(Listener.class) == null){
|
||||
return false;
|
||||
}
|
||||
if (handler.getParameterTypes().length != 1) {
|
||||
// a messageHandler only defines one parameter (the message)
|
||||
System.out.println("Found no or more than one parameter in messageHandler [" + handler.getName()
|
||||
|
@ -8,5 +8,5 @@ package net.engio.mbassy.listener;
|
||||
* To change this template use File | Settings | File Templates.
|
||||
*/
|
||||
public enum Mode {
|
||||
Synchronous,Asynchronous
|
||||
Sequential, Concurrent
|
||||
}
|
||||
|
@ -15,7 +15,8 @@ import org.junit.runners.Suite;
|
||||
MessagePublicationTest.class,
|
||||
FilterTest.class,
|
||||
MetadataReaderTest.class,
|
||||
ListenerSubscriptionTest.class
|
||||
ListenerSubscriptionTest.class,
|
||||
MethodDispatchTest.class
|
||||
})
|
||||
public class AllTests {
|
||||
}
|
||||
|
@ -131,7 +131,14 @@ public class MetadataReaderTest extends UnitTest {
|
||||
}
|
||||
|
||||
// the same handlers as its super class
|
||||
public class EventListener2 extends EventListener1 {}
|
||||
public class EventListener2 extends EventListener1 {
|
||||
|
||||
// redefine handler implementation (not configuration)
|
||||
public void handleString(String s) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class EventListener3 extends EventListener2 {
|
||||
|
||||
@ -141,7 +148,7 @@ public class MetadataReaderTest extends UnitTest {
|
||||
|
||||
}
|
||||
|
||||
// remove this handler
|
||||
@Listener(enabled = false)
|
||||
public void handleString(String s) {
|
||||
|
||||
}
|
||||
|
55
src/test/java/net/engio/mbassy/MethodDispatchTest.java
Normal file
55
src/test/java/net/engio/mbassy/MethodDispatchTest.java
Normal file
@ -0,0 +1,55 @@
|
||||
package net.engio.mbassy;
|
||||
|
||||
import net.engio.mbassy.common.UnitTest;
|
||||
import net.engio.mbassy.listener.Listener;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Very simple test to verify dispatch to correct message handler
|
||||
*
|
||||
* @author bennidi
|
||||
* Date: 1/17/13
|
||||
*/
|
||||
public class MethodDispatchTest extends UnitTest{
|
||||
|
||||
private boolean listener1Called = false;
|
||||
private boolean listener2Called = false;
|
||||
|
||||
|
||||
|
||||
// a simple event listener
|
||||
public class EventListener1 {
|
||||
|
||||
@Listener
|
||||
public void handleString(String s) {
|
||||
listener1Called = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// the same handlers as its super class
|
||||
public class EventListener2 extends EventListener1 {
|
||||
|
||||
// redefine handler implementation (not configuration)
|
||||
public void handleString(String s) {
|
||||
listener2Called = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDispatch1(){
|
||||
MBassador bus = new MBassador(BusConfiguration.Default());
|
||||
EventListener2 listener2 = new EventListener2();
|
||||
bus.subscribe(listener2);
|
||||
bus.post("jfndf").now();
|
||||
assertTrue(listener2Called);
|
||||
assertFalse(listener1Called);
|
||||
|
||||
EventListener1 listener1 = new EventListener1();
|
||||
bus.subscribe(listener1);
|
||||
bus.post("jfndf").now();
|
||||
assertTrue(listener1Called);
|
||||
}
|
||||
|
||||
}
|
@ -20,7 +20,7 @@ public class EventingTestBean {
|
||||
}
|
||||
|
||||
// this handler will be invoked asynchronously
|
||||
@Listener(priority = 0, dispatch = Mode.Asynchronous)
|
||||
@Listener(priority = 0, delivery = Mode.Concurrent)
|
||||
public void handleSubTestEvent(SubTestEvent event) {
|
||||
event.counter.incrementAndGet();
|
||||
}
|
||||
@ -29,7 +29,7 @@ public class EventingTestBean {
|
||||
// or any subtabe and that passes the given filter
|
||||
@Listener(
|
||||
priority = 10,
|
||||
dispatch = Mode.Synchronous,
|
||||
delivery = Mode.Sequential,
|
||||
filters = {@Filter(Filters.RejectAll.class), @Filter(Filters.AllowAll.class)})
|
||||
public void handleFiltered(SubTestEvent event) {
|
||||
event.counter.incrementAndGet();
|
||||
|
@ -11,7 +11,7 @@ import net.engio.mbassy.listener.Mode;
|
||||
public class EventingTestBean2 extends EventingTestBean{
|
||||
|
||||
// redefine the configuration for this handler
|
||||
@Listener(dispatch = Mode.Synchronous)
|
||||
@Listener(delivery = Mode.Sequential)
|
||||
public void handleSubTestEvent(SubTestEvent event) {
|
||||
super.handleSubTestEvent(event);
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ public class EventingTestBean3 extends EventingTestBean2{
|
||||
|
||||
|
||||
// this handler will be invoked asynchronously
|
||||
@Listener(priority = 0, dispatch = Mode.Synchronous)
|
||||
@Listener(priority = 0, delivery = Mode.Sequential)
|
||||
public void handleSubTestEventAgain(SubTestEvent event) {
|
||||
event.counter.incrementAndGet();
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ import net.engio.mbassy.subscription.MessageEnvelope;
|
||||
public class MultiEventHandler {
|
||||
|
||||
|
||||
@Listener(dispatch = Mode.Synchronous)
|
||||
@Listener(delivery = Mode.Sequential)
|
||||
@Enveloped(messages = {TestEvent.class, TestEvent2.class})
|
||||
public void handleEvents(MessageEnvelope envelope) {
|
||||
if(TestEvent.class.isAssignableFrom(envelope.getMessage().getClass())){
|
||||
@ -31,7 +31,7 @@ public class MultiEventHandler {
|
||||
}
|
||||
}
|
||||
|
||||
@Listener(dispatch = Mode.Synchronous, filters = @Filter(Filters.RejectSubtypes.class))
|
||||
@Listener(delivery = Mode.Sequential, filters = @Filter(Filters.RejectSubtypes.class))
|
||||
@Enveloped(messages = {TestEvent.class, TestEvent2.class})
|
||||
public void handleSuperTypeEvents(MessageEnvelope envelope) {
|
||||
if(TestEvent.class.isAssignableFrom(envelope.getMessage().getClass())){
|
||||
|
@ -2,6 +2,7 @@ package net.engio.mbassy.listeners;
|
||||
|
||||
import net.engio.mbassy.events.SubTestEvent;
|
||||
import net.engio.mbassy.events.TestEvent;
|
||||
import net.engio.mbassy.listener.Listener;
|
||||
|
||||
/**
|
||||
* This bean overrides all the handlers defined in its superclass. Since it does not specify any annotations
|
||||
@ -14,16 +15,19 @@ public class NonListeningBean extends EventingTestBean{
|
||||
|
||||
|
||||
@Override
|
||||
@Listener(enabled = false)
|
||||
public void handleTestEvent(TestEvent event) {
|
||||
event.counter.incrementAndGet(); // should never be called
|
||||
}
|
||||
|
||||
@Override
|
||||
@Listener(enabled = false)
|
||||
public void handleSubTestEvent(SubTestEvent event) {
|
||||
event.counter.incrementAndGet(); // should never be called
|
||||
}
|
||||
|
||||
@Override
|
||||
@Listener(enabled = false)
|
||||
public void handleFiltered(SubTestEvent event) {
|
||||
event.counter.incrementAndGet(); // should never be called
|
||||
}
|
||||
|
124
wiki-listener-def.md
Normal file
124
wiki-listener-def.md
Normal file
@ -0,0 +1,124 @@
|
||||
Listener Definition
|
||||
===================
|
||||
|
||||
MBassador allows a variety of message handler configurations that will affect how a message
|
||||
is delivered to a specific listener. There are properties to control the handling of subclasses
|
||||
of the specified message (the method parameter), the execution order of handlers for the same message,
|
||||
filters, delivery modes etc.
|
||||
|
||||
<h2>Message handler properties</h2>
|
||||
|
||||
<table>
|
||||
<tr> <td>Property</td> <td>Meaning</td> <td>Default</td> </tr>
|
||||
|
||||
<tr>
|
||||
<td>delivery</td>
|
||||
<td>Message delivery can either run sequentially(i.e. one listener at a time) or concurrently
|
||||
(i.e. multiple threads are used to deliver the same message to different listeners).
|
||||
Note:The number of parallel threads is configurable per instance using the BusConfiguration</td>
|
||||
<td>Sequential</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>priority</td>
|
||||
<td>The priority is used to determine the order in which a message is delivered to
|
||||
different message handlers that consume the same message type. Higher priority means
|
||||
higher precedence in message delivery.</td>
|
||||
<td>0</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>rejectSubtypes</td>
|
||||
<td>The primary message type consumed by a message handler is determined by the type of
|
||||
its parameter.Polymorphism does allow any sub type of that message type to be delivered
|
||||
to the handler as well, which is the default behaviour of any message handler.
|
||||
The handler can be configured to not receiving any sub types by specifying thus using this
|
||||
property.
|
||||
</td>
|
||||
<td>false</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>enabled</td>
|
||||
<td>A handler can be explicitly disabled to not take part in message delivery.
|
||||
</td>
|
||||
<td>true</td>
|
||||
</tr>
|
||||
|
||||
|
||||
</table>
|
||||
|
||||
<h2>Message handler definition</h2>
|
||||
|
||||
The standard message handler definition looks like the following.It will
|
||||
receive all messages of type TestEvent or any subtype sequentially.
|
||||
|
||||
// every message of type TestEvent or any subtype will be delivered
|
||||
// to this handler
|
||||
@Listener
|
||||
public void handleTestEvent(TestEvent event) {
|
||||
// do something
|
||||
}
|
||||
|
||||
|
||||
|
||||
This handler will receive all messages of type SubTestEvent or any subtype concurrently
|
||||
|
||||
// this handler will be invoked concurrently
|
||||
@Listener(delivery = Mode.Concurrent)
|
||||
public void handleSubTestEvent(SubTestEvent event) {
|
||||
// do something more expensive here
|
||||
}
|
||||
|
||||
This handler will receive all messages of type SubTestEvent or any subtype sequentially,
|
||||
given that they pass the specified filters. This handler will be invoked before the formerly
|
||||
defined one, since it specifies a higher priority
|
||||
|
||||
// this handler will receive messages of type SubTestEvent
|
||||
// or any of its sub types that passe the given filter(s)
|
||||
@Listener(priority = 10,
|
||||
dispatch = Mode.Synchronous,
|
||||
filters = {@Filter(Filters.SpecialEvent.class)})
|
||||
public void handleFiltered(SubTestEvent event) {
|
||||
//do something special here
|
||||
}
|
||||
|
||||
|
||||
@Listener(dispatch = Mode.Synchronous, rejectSubtypes = true)
|
||||
@Enveloped(messages = {TestEvent.class, TestEvent2.class})
|
||||
public void handleVariousEvents(MessageEnvelope envelope) {
|
||||
// the envelope will contain either an instance of TestEvent or TestEvent2
|
||||
// if rejectSubtypes were set to 'false' (default) also subtypes of TestEvent or TestEvent2 would be allowed
|
||||
}
|
||||
|
||||
|
||||
|
||||
<h2>Enveloped message handlers</h2>
|
||||
|
||||
Since one parameter (the message) does not offer a great deal of flexibility if different types
|
||||
of messages should be consumed, there exists the possibility to wrap a message inside an envelope.
|
||||
An enveloped message handler specifies the message type it consumes by using the @Enveloped annotation
|
||||
in addition to the @Listener annotation. All configurations of @Listener apply to each of the specified
|
||||
message types.
|
||||
|
||||
@Listener(dispatch = Mode.Synchronous, rejectSubtypes = true)
|
||||
@Enveloped(messages = {TestEvent.class, TestEvent2.class})
|
||||
public void handleVariousEvents(MessageEnvelope envelope) {
|
||||
// the envelope will contain either an instance of TestEvent or TestEvent2
|
||||
// if rejectSubtypes were set to 'false' (default) also subtypes of TestEvent or TestEvent2 would be allowed
|
||||
}
|
||||
|
||||
|
||||
<h2>Inheritance</h2>
|
||||
|
||||
Message handler inheritance corresponds to inheritance of methods as defined in the Java language itself.
|
||||
A subclass of any class that defines message handlers will inherit these handler and their configuration.
|
||||
It is possible to change (override) the configuration simply by overriding the super class' method and
|
||||
specifying a different configuration. This way, it is also possible to deactivate a message handler of
|
||||
a super class by using the "enabled" property on the overridden method.
|
||||
If a class overrides a method that is configured as a message handler in one of its super classes
|
||||
it is still considered a message handler but of course the implementation of the overriding class
|
||||
will be used.
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user