Created
December 12, 2013 11:07
-
-
Save bduisenov/7926391 to your computer and use it in GitHub Desktop.
Spring CDI @observes implementation (quick PoC)
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
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