3

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 !

Maikel Nait
  • 251
  • 3
  • 18
  • Why would `regExpEventHandler` be a bean? It is a property not a bean... – M. Deinum Apr 05 '17 at 08:53
  • Not sure if I follow... my understanding is that within Spring EL expressions, beans can be referenced using @. I also tried something simpler like #event.eventid matches ${regExpEventHandler} , which should get the corresponding value from the properties file, but the expression fails as well – Maikel Nait Apr 05 '17 at 08:58
  • Yes beans but you are trying to reference a property as a bean that isn't going to work. – M. Deinum Apr 05 '17 at 09:03
  • ah you are referencing yourself. For starters your `@Autowired` should be a `@PostConstruct` I would say. – M. Deinum Apr 05 '17 at 09:08
  • 1
    Trying other approaches, like "#event.eventid matches $regExpEventHandler", hoping that the corresponding property from the .properties file will be fetched, yields the error : org.springframework.expression.spel.SpelEvaluationException: EL1008E:(pos 23): Property or field '$regExpEventHandler' cannot be found on object of type 'org.springframework.context.event.EventExpressionRootObject' - maybe not public? I'm trying similar approaches but to no avail so far... – Maikel Nait Apr 05 '17 at 09:09
  • Instead of `@beanName` try `#this`. – M. Deinum Apr 05 '17 at 09:11
  • Thanks for your help so far, M. Deinum... at the end, what I just need is how to code the event listener condition expression: @EventListener(condition = "#event.eventid matches [HOW TO REFERENCE MY BEAN PROPERTY OR ENVIRONMENT PROPERTY FROM .PROPERTIES FILE HERE]") – Maikel Nait Apr 05 '17 at 09:13
  • If I use something like @EventListener(condition = "#event.eventid matches #this.getEventIdPattern()") the error is: org.springframework.expression.spel.SpelEvaluationException: EL1004E:(pos 29): Method call: Method getEventIdPattern() cannot be found on org.springframework.context.event.EventExpressionRootObject type. It looks like the EventListener somehow can only see the Event in the current Context – Maikel Nait Apr 05 '17 at 09:16
  • `#this` is an `EventExpressionObject` which is a simple wrapper containing the event and arguments - see my answer below. `@bean` works fine for me. – Gary Russell Apr 05 '17 at 13:03

1 Answers1

4

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).

Gary Russell
  • 166,535
  • 14
  • 146
  • 179
  • Thanks a lot Gary for your help... We are using Spring 4.2.6, and I just found in this version, the EventExpressionEvaluator DOES NOT have a bean resolver... hence my headaches ... I'm pretty sure switching to 4.3.7 may just solve the problem... thanks a lot again ! – Maikel Nait Apr 06 '17 at 01:21
  • And working perfectly after switching to 4.3.7 ... Many thanks ! – Maikel Nait Apr 07 '17 at 06:26