Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

Eclipse4/RCP/Dependency Injection

< Eclipse4‎ | RCP
Revision as of 07:27, 12 April 2011 by Briandealwis.gmail.com (Talk | contribs) (Using the E4AP DI Framework)

With 10 years of experience with the Eclipse platform, a number of problems have surfaced.

  1. Code frequently uses global singleton accessors (e.g., Platform, PlatformUI) or required navigating a deep chain of dependencies (e.g., obtaining an IStatusLineManager). Singleton services is particularly problematic for app servers like RAP/Riena.
  2. This tightly couples consumers of "things" to their producer/provider, inhibits reuse, etc
  3. Mechanisms are not very dynamic — plugins react to context changes drastically rather than incrementally (just close the affected controls)
  4. IEvaluationContext has global state that gets swapped according to context change (such as the focus control)
  5. Current solutions are not multi-threadable - they assume evaluation occurs in the UI thread
  6. Scaling granularity
    • Current solutions don't scale down to services with brief lifetimes
    • Current solutions may not scale up to very large numbers of services
  7. Currently don't track service consumers to notify when services come/go
  8. Client code needs to know the internals of the Eclipse code base
  9. No support for services lookup that are composed out of other services on the fly

Dependency Injection (DI) is one approach to circumvent these problems: rather than require client code to know how to access a service, the client instead describes the service required, and the platform is responsible for configuring the object with an appropriate service. DI shields the client code from knowing the provenance of the injected objects.

  • DI is not new. Pointers to other projects and books (e.g., Dhanji Prasanna's Dependency Injection)
  • Different approaches: separate configuration, or annotating POJOs with injection points.

The Eclipse 4 Application Platform provides a JSR 330-compatible dependency injection (DI) framework, similar to Spring or Guice. Instead of PlatformUI.getWorkbench().getHelpSystem(), E4AP plugins can have the HelpSystem directly injected into an object.


Using the E4AP DI Framework

FIXME incorporate questions and comments from http://www.toedter.com/blog/?p=194

The E4AP DI Framework supports three types of injection:

  1. Constructor injection: values are matched to constructor arguments
  2. Method injection: values are injected into methods annotated with@Inject and that have satisfying arguments
  3. Field injection: values are injected into fields annotated with @Inject and that have a satisfying type

FIXME: Are methods and fields examined down the class chain? What happens to shadowed methods or fields?

FIXME: Are Annotations on interface methods are

FIXME: does the injector support @Singleton

With DI, code no longer requires long chains to access services, like Platform.getDefault().getThis().getThat() or getSite().getThis().getThat(). If the code requires the selection service, it simply declares a field like:

@Inject protected ESelectionService ess; @Inject protected EKeybindingService ekb;

E4AP's injection framework also supports other injection types, such as to retrieve a preference value:

@Inject @Preference(node="my.plugin.id", value="dateFormat") protected String dateFormat;

and the field is updated as the preference changes too.

  • E4AP's injector uses annotations
    • JSR 330 annotations:
      • value injection: @Inject, @Named
      • lifecycle injection: @PostConstruct, @PreDestroy
    • E4AP-specific annotations: @Preference, @Event, @UIEvent, @Active, @CanExecute, @Execute, @Persist, @Focus, @GroupUpdates
  • Order of injection classes: Constructor, @Injected variables, @Injected methods, @PostConstruct
    • no guarantees of ordering within injection classes; methods requiring values from injected variables should either be called from @PostConstruct or be injected with both
    • @Optional and null values for services
  • List of available services
    • E4AP services
    • OSGi services
  • plugins: org.eclipse.e4.core.di, org.eclipse.e4.core.di.extensions, and org.eclipse.e4.ui.di
  • re-injection: as the backing context changes, changed values will be re-injected

Considerations

  • thread-safety: method injection may happen on any thread
  • final fields can only be supported using constructor injection
  • if several injectable fields required, then avoid using several single setter-style methods and use a single method with multiple arguments

In e4 injected values are dynamic; values that changed in the context get propagated into injected fields/methods. So, if OSGi strategy worked properly, the changes in the service implementations would be propagated. Also, we have the "@Optional" annotation which allows values not currently in the context to be injected as "null" and re-injected later when the values are added to the context.

Extending the DI Framework

ExtendedObjectSupplier LookupContextStrategy


  • Accessing the IEclipseContext; see Eclipse4/RCP/Contexts
    • Adding, setting, or modifying variables
      • Why is modify different from set

Advantages / Disadvantages

DI provides a number of advantages:

  • Clients are able to write POJOs and list the services they need. The DI framework provides
  • Useful for testing: the assumptions are placed in the DI container rather than in the client code

DI has some disadvantages too:

  • Concerns about discoverability of services - cannot use code completion to find out what is available.


Advanced Topics

you can have a factory classes by using IBinding:

InjectorFactory.getDefault().addBinding(MyPart.class).implementedBy(MyFactory.class)

or using ContextFunctions:

public class MyFactory extends ContextFunction {

public Object compute(IEclipseContext context) {
 MPart result = ContextInjectionFactory.make(MyPart.class, context);
 doWhatever(result);
 return result;

}

Back to the top