Issue
In Spring 4.2+, we can use @EventListener annotation with a "condition" expression.
In my scenario, I need to match the id of the event object with a regular expression that is configured in a .properties file.
However, it seems impossible to reference any bean's property or method from the condition's regular expression, as the root context seems to be the event object itself.
So far, I have an abstract class, that sets the event id pattern property based on the class name. The goal is to make the implementation of each Event Listener as clean and simple as possible.
@Service
@PropertySource(value = "classpath:subscriberEventMapping.properties")
public abstract class AbstractEventHandler implements IEventHandler {
private String eventIdPattern;
@Autowired
Environment env;
@Autowired(required = true)
public void configureEventIdPattern() {
String simpleClassName = this.getClass().getSimpleName();
String resolvedEventIdPattern = env.getProperty(
simpleClassName.substring(0,1).toLowerCase() +
simpleClassName.substring(1, simpleClassName.length()));
this.eventIdPattern = resolvedEventIdPattern == null ? ".*" : resolvedEventIdPattern;
}
public String getEventIdPattern() {
return eventIdPattern;
}
}
The properties file looks like this:
regExpEventHandler=^(901|909|998|1000)$
dummyEventHandler=^([1-9][0-9]{0,2}|1000)$
And then, I have a sample Event Listener that extends the above Abstract class:
@Service
public class RegExpEventHandler extends AbstractEventHandler {
@Log
private ILog logger;
@Override
@EventListener(condition = "#event.eventid matches @regExpEventHandler.getEventIdPattern()")
public void onEvent(Event event) {
logger.debug("RegExpEventHandler processing : {} with event pattern : {}", event, getEventIdPattern());
}
}
The problem is that the expression
"#event.eventid matches @regExpEventHandler.getEventIdPattern()"
does not work, because the bean "@regExpEventHandler" cannot be found in the context used by the @EventListener.
Is there a way to access methods or properties of an existing Spring Bean here? Any other better approach for this scenario ?
I know I can easily access STATIC constants or methods by using something like:
#event.eventid matches T(my.package.RegExpEventHandler.MY_CONSTANT)
But a String constant (static final) cannot be initialized from a properties file using a @Value expression.
Using NON-FINAL static constants can work, but then EACH Event Listener needs to add boiler-plate to initialize the static constant from a non-static variable using a @Value expression, which we want to avoid.
Thanks a lot in advance !
Solution
It works for me - I looked at the EventExpressionEvaluator
and saw that it added a bean resolver to the evaluation context...
public EvaluationContext createEvaluationContext(ApplicationEvent event, Class<?> targetClass,
Method method, Object[] args, BeanFactory beanFactory) {
Method targetMethod = getTargetMethod(targetClass, method);
EventExpressionRootObject root = new EventExpressionRootObject(event, args);
MethodBasedEvaluationContext evaluationContext = new MethodBasedEvaluationContext(
root, targetMethod, args, getParameterNameDiscoverer());
if (beanFactory != null) {
evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
}
return evaluationContext;
}
So I wrote a quick test...
@SpringBootApplication
public class So43225913Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(So43225913Application.class, args);
context.publishEvent("foo");
}
@EventListener(condition = "@bar.accept(event)")
public void listen(Object event) {
System.out.println("handler:" + event);
}
@Bean
public Bar bar() {
return new Bar();
}
public static class Bar {
public boolean accept(Object o) {
System.out.println("bar:" + o);
return true;
}
}
}
and it works just fine...
bar:org.springframework.context.PayloadApplicationEvent[...
handler:foo
(This was with 4.3.7; boot 1.5.2).
Answered By - Gary Russell