Skip to content

Instantly share code, notes, and snippets.

@bduisenov
Created December 12, 2013 11:07
Show Gist options
  • Save bduisenov/7926391 to your computer and use it in GitHub Desktop.
Save bduisenov/7926391 to your computer and use it in GitHub Desktop.
Spring CDI @observes implementation (quick PoC)
package kz.bas.app.server.core.integration.cdi;
import org.jboss.errai.common.client.api.annotations.Portable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import javax.enterprise.event.Observes;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* @author Babur Duisenov.
*/
@Component("observable")
final public class SpringCDIObservesListener implements ApplicationListener<ContextRefreshedEvent>, Observable {
private static final Logger logger = LoggerFactory.getLogger(SpringCDIObservesListener.class);
// wouldn't fire on context refresh
private static boolean initialized;
private static ApplicationContext applicationContext;
final private static Map<String, Map<Class, Method>> observerMatrix = new HashMap<String, Map<Class, Method>>();
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (!initialized) {
applicationContext = event.getApplicationContext();
discover();
initialized = true;
}
}
/**
* finds all spring beans having methods annotated with @Observes.
*/
private void discover() {
for (String beanName : applicationContext.getBeanDefinitionNames()) {
final Class<?> clazz = applicationContext.getType(beanName);
// skip spring framework beans from further processing
if (clazz.getName().startsWith("org.springframework")) continue;
ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Class<?>[] parameterType = method.getParameterTypes();
checkArgument(parameterType.length == 1,
"methods which use @Observes annotation should have one and only parameter arg");
// check that this is an errai portable object and assert that there is one and only annotation.
checkArgument(Arrays.asList(parameterType[0].getAnnotations()[0].annotationType()).contains(Portable.class),
"parameter types annotated with @Observes should be annotated with Errai's @Portable");
// in order to invoke observable methods using reflection, they have to be public
logger.warn("all discovered methods with @Observes annotation will become public");
method.setAccessible(true);
register(parameterType[0].getName(), clazz, method);
}
}, new ReflectionUtils.MethodFilter() {
public boolean matches(Method method) {
boolean exists = false;
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for (Annotation[] k : parameterAnnotations) {
if (exists) break;
for (Annotation v : k) {
// there should be only one method in class per observable object
exists = v.annotationType().equals(Observes.class);
break;
}
}
return exists;
}
});
}
logger.info("found {} beans with @Observes annotation", observerMatrix.size());
}
/**
* get the list of classes with methods on which should be intercepted
*
* @param type of event
* @return
*/
private Map<Class, Method> get(String type) {
return observerMatrix.get(type);
}
/**
* Note: there is no guarantee of the order!
*
* @param eventType
* @param clazz
* @param method
*/
private void register(String eventType, final Class clazz, final Method method) {
if (observerMatrix.containsKey(eventType)) {
Map<Class, Method> target = observerMatrix.get(eventType);
target.put(clazz, method);
} else {
observerMatrix.put(eventType, new HashMap<Class, Method>() {{
put(clazz, method);
}});
}
}
/**
* currently works without sessions and only with singletons
*
* @param event object to fire
*/
@Override
public void fire(Object event) {
if (event == null) return;
String type = event.getClass().getName();
// TODO: where this should be handled? here?
Map<Class, Method> classMethodMap = checkNotNull(get(type));
for (Map.Entry<Class, Method> entry : classMethodMap.entrySet()) {
Object bean = applicationContext.getBean(entry.getKey());
ReflectionUtils.invokeMethod(entry.getValue(), bean, new Object[]{ event });
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment