Skip to content
Advertisement

CDI events fire() create new observers instances

I’m writing a JavaFX application (JavaSE) with CDI (Weld). I’m trying to use CDI Events to replace the awful event system of JavaFX (JavaFX Properties).

I was able to let CDI create my JavaFX controllers properly. It means that when a FXML is loaded, the controller is created by CDI (using FXLoader.setControllerFactory). Unfortunately as soon as I added an @Observes in one controller, CDI started to create multiple instances this controller. that’s crazy.

It appears that I just don’t understand the proper semantic of fire(). Since when fire() will create observers ?! May be this have something to do with @Dependent scope.

I made an interceptor to send an event when some property change somewhere (in my case it is a background task that update a property Progress). (Don’t pay attention to ObservableSourceLiterral and @ObservableSource, it’s just to not fire events to everybody)

@ObservableProperty
@Interceptor
public class CDIPropertyWatcher {
    @Inject
    private Event<PropertyChangedEvent> listeners;

    @AroundInvoke
    public Object onPropertyCalled(InvocationContext ctx) throws Exception {
        String method = ctx.getMethod()
                .getName();
        if (method.startsWith("set")) {
            Object result = ctx.proceed();
            String propertyName = method.substring(3, 4)
                    .toLowerCase() + method.substring(4);
            listeners.select(new ObservableSourceLiterral(ctx.getTarget().getClass().getSuperclass()))
                    .fire(new PropertyChangedEvent(ctx.getTarget(), propertyName));

            return result;
        } else {
            return ctx.proceed();
        }
    }
}

Here is the setter in my Task class:

@ObservableProperty
public void setProgress(double progress) {
      this.progress = progress;
}

This is a method of my controller responsible to receive the event:

public void onProgressTaskChanged(@Observes @ObservableSource(Task.class) PropertyChangedEvent evt)
{
        Double progress = evt.getSource(Task.class).getProgress();
        System.out.println("onProgressTaskChanged "+progress+" "+this);
        if (progressBar!=null)
        {
            Platform.runLater(() -> progressBar.setProgress(progress));
        }
}

I expect that when I fire an event, it is received by the actual existing observers, and not create new ones.

Advertisement

Answer

So the trick here is indeed the @Dependent scope of a bean with observer.

According to specification, there will always be new instance created to receive the notification and destroyed afterwards. Relevant parts of spec are 5.5.6 Invocation of observer methods and 6.4.2 Destruction of objects with scope @Dependent.

To explain why it behaves like this – for other (normal) scopes, you always have 0 or 1 instance of that given bean within a context and notification can easily pick the existing one or create new and store it in the context. Whereas with @Dependent you can have 0 to n instances which would lead to multiple notifications per event fired if this behaved differently.

Try using some normal scope (application/request/session/conversation) instead and you should get the behaviour you expect.

User contributions licensed under: CC BY-SA
7 People found this is helpful
Advertisement