Skip to content

Instantly share code, notes, and snippets.

@raulraja
Created August 20, 2011 19:59
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save raulraja/1159583 to your computer and use it in GitHub Desktop.
Save raulraja/1159583 to your computer and use it in GitHub Desktop.
Simple Event Broadcast with JAVA / Spring AOP / Annotations
/*
* Copyright (C) 2011 47 Degrees, LLC
* http://47deg.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fortysevendeg.commons.spring.web;
import org.springframework.aop.framework.Advised;
import org.springframework.context.ApplicationContext;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ContextUtils {
/**
*
* @param applicationContext
* @param annotation
* @return a map of services and their methods annotated with annotation
*/
public static Map<String, List<Method>> findServicesWithMethodAnnotation(ApplicationContext applicationContext, Class<? extends Annotation> annotation) {
Map<String, List<Method>> beanMap = new HashMap<String, List<Method>>();
String[] allBeanNames = applicationContext.getBeanDefinitionNames();
if (allBeanNames != null) {
for (String beanName : allBeanNames) {
Object listener = applicationContext.getBean(beanName);
Class<?> listenerType = listener.getClass();
if (Advised.class.isAssignableFrom(listenerType)) {
listenerType = ((Advised) listener).getTargetSource().getTargetClass();
}
Method[] methods = listenerType.getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(annotation)) {
List<Method> methodList = beanMap.get(beanName);
if (methodList == null) {
methodList = new ArrayList<Method>();
beanMap.put(beanName, methodList);
}
methodList.add(method);
}
}
}
}
return beanMap;
}
}
/*
* Copyright (C) 2011 47 Degrees, LLC
* http://47deg.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fortysevendeg.commons.services.events;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({METHOD})
@Retention(RUNTIME)
public @interface EventListener {
String[] value();
}
/*
* Copyright (C) 2011 47 Degrees, LLC
* http://47deg.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fortysevendeg.commons.services.events;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({METHOD})
@Retention(RUNTIME)
public @interface EventPublisher {
String[] value();
}
/*
* Copyright (C) 2011 47 Degrees, LLC
* http://47deg.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fortysevendeg.commons.services.events;
public interface EventService {
/**
* Broadcast an event and its context
* @param event the event
* @param args the arguments
*/
void publish(String event, Object... args);
/**
* Subscribes this objects as listener for a given event
* @param event
* @param listeners the service names
*/
void subscribe(String event, String... listeners);
/**
* UnSubscribes this objects as listener for a given event
* @param event
* @param listeners the service names
*/
void unSubscribe(String event, String... listeners);
}
/*
* Copyright (C) 2011 47 Degrees, LLC
* http://47deg.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fortysevendeg.commons.services.events.impl;
import com.fortysevendeg.commons.services.events.EventListener;
import com.fortysevendeg.commons.services.events.EventPublisher;
import com.fortysevendeg.commons.services.events.EventService;
import com.fortysevendeg.commons.spring.web.ContextUtils;
import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.aop.framework.Advised;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Service;
import java.lang.reflect.Method;
import java.util.*;
@Service
@Aspect
public class EventPublisherImpl implements EventService, ApplicationListener<ContextRefreshedEvent>, ApplicationContextAware {
private final static Logger logger = Logger.getLogger(EventPublisherImpl.class);
private ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
private Map<String, List<EventSubscriber>> eventListeners = new TreeMap<String, List<EventSubscriber>>();
/**
* Handle an application event.
*
* @param contextRefreshedEvent the event to respond to
*/
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
eventListeners.clear();
Map<String, List<Method>> methodMap = ContextUtils.findServicesWithMethodAnnotation(applicationContext, EventListener.class);
for (Map.Entry<String, List<Method>> entry : methodMap.entrySet()) {
for (Method method : entry.getValue()) {
String[] events = method.getAnnotation(EventListener.class).value();
for (String event : events) {
subscribe(event, entry.getKey());
}
}
}
}
/**
* Subscribes this objects as listener for a given event
*
* @param event
* @param listenerNames
*/
@Override
public synchronized void subscribe(String event, String... listenerNames) {
if (logger.isDebugEnabled()) logger.debug(String.format("Subscribing %s to %s", Arrays.toString(listenerNames), event));
List<EventSubscriber> subscribers = new ArrayList<EventSubscriber>();
for (String listenerName : listenerNames) {
Object bean = applicationContext.getBean(listenerName);
Class<?> listenerType = bean.getClass();
if (Advised.class.isAssignableFrom(listenerType)) {
listenerType = ((Advised) bean).getTargetSource().getTargetClass();
}
Method[] methods = listenerType.getMethods();
List<Method> subscribedMethods = new ArrayList<Method>();
for (Method method : methods) {
if (method.isAnnotationPresent(EventListener.class)) {
if (Arrays.asList(method.getAnnotation(EventListener.class).value()).contains(event)) {
subscribedMethods.add(method);
}
}
}
EventSubscriber eventSubscriber = new EventSubscriber(listenerName, subscribedMethods);
subscribers.add(eventSubscriber);
}
getListenersForEvent(event, true).addAll(subscribers);
}
/**
* UnSubscribes this objects as listener for a given event
*
* @param event
* @param listenerNames
*/
@Override
public void unSubscribe(String event, String... listenerNames) {
if (logger.isDebugEnabled()) logger.debug(String.format("UnSubscribing %s to %s", Arrays.toString(listenerNames), event));
getListenersForEvent(event, false).removeAll(Arrays.asList(listenerNames));
}
private List<EventSubscriber> getListenersForEvent(String value, boolean create) {
List<EventSubscriber> listeners = eventListeners.get(value);
if (listeners == null && create) {
listeners = new ArrayList<EventSubscriber>();
eventListeners.put(value, listeners);
}
return listeners;
}
/**
* Broadcast an
*
* @param event
* @param args
*/
@Override
public void publish(String event, Object... args) {
if (logger.isDebugEnabled()) logger.debug(String.format("Publishing %s", event));
List<EventSubscriber> listenersForEvent = getListenersForEvent(event, false);
if (listenersForEvent != null) {
for (EventSubscriber listener : listenersForEvent) {
Object bean = applicationContext.getBean(listener.getBeanName());
for (Method method : listener.getMethods()) {
try {
method.invoke(bean, args);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
}
}
/**
* Intercepts methods that declare @EventPublisher and broadcasts the return value to all listeners
*
* @param pjp proceeding join point
* @return the intercepted method returned object
* @throws Throwable in case something goes wrong in the actual method call
*/
@Around(value = "@annotation(com.fortysevendeg.commons.services.events.EventPublisher) && @annotation(eventPublisher)")
public Object handleBroadcast(ProceedingJoinPoint pjp, EventPublisher eventPublisher) throws Throwable {
Object retVal = pjp.proceed();
String[] events = eventPublisher.value();
for (String event : events) {
if (logger.isDebugEnabled()) logger.debug(String.format("Intercepted %s", Arrays.toString(events)));
publish(event, retVal);
}
return retVal;
}
}
/*
* Copyright (C) 2011 47 Degrees, LLC
* http://47deg.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fortysevendeg.commons.services.events.impl;
import java.lang.reflect.Method;
import java.util.List;
public class EventSubscriber {
private String beanName;
private List<Method> methods;
public EventSubscriber(String beanName, List<Method> methods) {
this.beanName = beanName;
this.methods = methods;
}
public String getBeanName() {
return beanName;
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
public List<Method> getMethods() {
return methods;
}
public void setMethods(List<Method> methods) {
this.methods = methods;
}
}
/*
* Copyright (C) 2011 47 Degrees, LLC
* http://47deg.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fortysevendeg.commons.services.events.samples;
public interface SampleAnnotations {
Object broadcastEvent();
void receiveEvent(Object arg);
}
/*
* Copyright (C) 2011 47 Degrees, LLC
* http://47deg.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fortysevendeg.commons.services.events.samples;
import com.fortysevendeg.commons.services.events.EventListener;
import com.fortysevendeg.commons.services.events.EventPublisher;
import org.springframework.stereotype.Service;
@Service
public class SampleAnnotationsImpl implements SampleAnnotations {
public static final String TEST_EVENT_ANNOTATIONS = "TEST_EVENT_ANNOTATIONS";
@Override
@EventPublisher(TEST_EVENT_ANNOTATIONS)
public Object broadcastEvent() {
return "any object may be returned";
}
@Override
@EventListener(TEST_EVENT_ANNOTATIONS)
public void receiveEvent(Object arg) {
// This will receive the event and the context as args. Notice for simplicity is all in the same service
// but any service may broadcast and other services subscribed to the broadcasted event would be notified
// without the need of declaring dependencies between services.
// This is specially handy when otherwise service wiring may result in cyclical dependencies or a service may
// not know which services should handle a broadcast and everything is determined at runtime via annotated
// event handlers.
}
}
/*
* Copyright (C) 2011 47 Degrees, LLC
* http://47deg.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fortysevendeg.commons.services.events.samples;
public interface SampleProgrammatically {
void broadcastEvent();
void receiveEvent(String arg1, String arg2);
}
/*
* Copyright (C) 2011 47 Degrees, LLC
* http://47deg.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fortysevendeg.commons.services.events.samples;
import com.fortysevendeg.commons.services.events.EventListener;
import com.fortysevendeg.commons.services.events.EventService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class SampleProgrammaticallyImpl implements SampleProgrammatically {
public static final String TEST_EVENT = "TEST_EVENT_PROG";
@Autowired
private EventService eventService;
@Override
public void broadcastEvent() {
eventService.publish(TEST_EVENT, "x", "z");
}
@Override
@EventListener(TEST_EVENT)
public void receiveEvent(String arg1, String arg2) {
//do something with the event. This method is invoked with reflection and no dependencies are necessary
//between the broadcaster and the receiver
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment