Changed handler inheritance, added unit tests and docu

This commit is contained in:
benni 2013-01-18 12:09:59 +01:00
parent 1ed1bb29a8
commit d08470c339
15 changed files with 258 additions and 29 deletions

View File

@ -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!

View File

@ -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())

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -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){
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));
// 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);
}
}
return handlers;
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 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 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()

View File

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

View File

@ -15,7 +15,8 @@ import org.junit.runners.Suite;
MessagePublicationTest.class,
FilterTest.class,
MetadataReaderTest.class,
ListenerSubscriptionTest.class
ListenerSubscriptionTest.class,
MethodDispatchTest.class
})
public class AllTests {
}

View File

@ -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) {
}

View 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);
}
}

View File

@ -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();

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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())){

View File

@ -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
View 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.