-
-
Save Xanaxiel/a1e7ec870d8ac05998219da82cfb1c9f to your computer and use it in GitHub Desktop.
Simple Event Broadcast with JAVA / Spring AOP / Annotations
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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(); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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(); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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. | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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