Errai: The browser as a platform

Friday, April 27, 2012

Errai IOC: DI in the Browser (Part 2)

In the last post, we got our feet a little bit wet by exploring the basics of Errai's dependency injection framework, which we call Errai IOC. In this post, we're going to continue the dive by taking a look at producers and bean life-cycles.

Statically wiring up your application is one thing. But what if you want to create managed beans dynamically at runtime? Or what if you want to control what beans get injected based on the runtime environment?

Look no further.

Dependent Scoped Beans

Out of the box, Errai supports two basic scopes: the singleton scope, and the dependent scope. For the sake of consistency, CDI's @ApplicationScope is aliased to the singleton scope within the context of the client application.

Most developers should be familiar with the concept of a singleton, and should easily come to understand what it's use on a bean implies; scoping a bean to a singleton scope will result in there being exactly one instance of that bean created during the lifecycle of the application. 

The dependent scope is a little bit different. It is a scope which is dependent on the scope, and by extension, the lifecycle of the bean which it is injected to. But it also has the property of producing a new instance of the bean for every different injection context.

When it comes to developing UI applications in the browser around beans, this becomes a fairly important -- and natural -- way to accomplish component reuse.

For example, we might wish to create an application component which is newly instantiated every time a user performs a certain action -- like a dialog box popping up.

@Dependent
public class MyDialogBox {
  @Inject SomeBean bean;
  @PostConstruct
  private void init() {
    // do stuff
  }
  
  public void show() {
    // do stuff to show dialog box.
  }


  public void hide() {
    // do stuff to hide dialog box.
  }
 
  @PreDestroy
  private void destroyBean() {
    hide();
  }
}

So we create @Dependent scoped bean. Now, unlike the singleton scope, this bean will not be instantiated by the container unless it is injected into another bean, or it is explicitly created from an instance factory or the bean manager.

Let's take a look at how we might go about that.

@Singleton
public class MySingletonBean() {
  @Inject Instance<MyDialogBox> dialogBoxInstance;

  // ... assume we've done some UiBinder stuff here ...
  @UiHandler
  void onOpenDialogClick(ClickHandler handler) {
    MyDialogBox myDialogBox = dialogBoxInstance.get();  
    myDialogBox.show();
  }
}

In this example we have injected an instance of javax.enterprise.inject.Instance parameterized with the bean type we'd like it to provide.  In this case, since MyDialogBox is dependent scoped, every single time dialogBoxInstance.get() is called, a new instance of the bean, fully wired will be created.

And just as it's been created by the bean manager, it can also be destroyed by the bean manager. Resources that the bean obtains from the container, can automatically be de-allocated cleanly through explicitly garbaging the bean.

Errai IOC actually provides it's own mechanism which is different from CDI for doing this, to make interaction with the BeanManager for ad-hoc disposal easier in client code. It is the org.jboss.errai.ioc.client.api.Disposer interface. It works pretty much the same as Instance, in that you inject it, parameterized with the bean it represents:

@Singleton
public class MySingletonBean() {
  // .. //
  @Inject Disposer<MyDialogBox> dialogBoxDisposer;
  void closeDialogBox(MyDialogBox dialogBox) {
    dialogBoxDisposer.dispose(dialogBox);
  }
}

If you refer back to our implementation of MyDialogBox, you will notice that we have indicated a @PreDestroy method, which is to be called by the bean manager when the bean is destroyed.  In that case, we just had it call its own hide() method to make the point. But the effects of disposing of a bean are far more than just calling that method. Any other beans that were created as a result of constructing that bean will also be destroyed implicitly. And any observer methods, bus service endpoints, and such will automatically be deregistered. Thus, using the bean manager to create and destroy your beans, gives you the advantage of having a standard way to manage and dispose of resources as build out your application.

Producers

This is cool, but what happens when we want to dynamically choose what instances to return to created beans at runtime? Enter the producer.

Producers produce beans. Could that be put more concisely? They essentially allow you to offload control of bean instance creation from the bean manager itself, into logic you so choose. Let's take a look at a basic example:

@Singleton
public class MyProducerBean() {

  @Produces @Singleton
  private NotificationHandler produceNotificationHandler() {
    if (Browser.supportsWebkitNotification()) {
      return new HTML5WebkitNotificationHandler();
    }
    else {
      return new SimpleNotificationHandler();
    }
  }
}

This is a contrived example. Don't go looking for any class called Browser.  But contrived or not, it's probably the sort of thing you'd find yourself wanting to do in the world of web applications.

Here, we've created a producer method by annotating it with the @javax.enterprise.inject.Produces annotation. But we've also annotated the method with the @Singleton annotation. This tells the container that it should call the method exactly once at runtime and cache the instance result in the bean manager, and return that same instance to all injection points. Not specifying a scope, implies the producer is dependent-scoped, as we talked about in our previous section.

In this example, we are checking to see if our browser support WebKit desktop notifications. If it does, we return an instance of the HTML5WebkitNotificationHandler which implements the NotificationHandler interface and obviously knows how to produce desktop notifications. In the alternative, we return a SimpleNotificationHandler. Perhaps it provides window alerts. I'll let you speculate on what the fallback functionality is here.

The bottom line is that other parts of the application which want to provide notifications now only need to @Inject NotificationHandler into their beans, providing a good separation of concerns.  The components need only refer to the interface without knowledge of the actual implementation. That's the power of the DI pattern.

So that's the basics of producers.

In the next installment of this series, we'll look at bean alternatives and qualifiers.

No comments:

Post a Comment